using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Collections.ObjectModel;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
public class NoDuplicateKeysDictionaryConverter<TValue> : NoDuplicateKeysDictionaryConverter<Dictionary<string, TValue>, TValue>
public class NoDuplicateKeysDictionaryConverter<TDictionary, TValue> : JsonConverter<TDictionary> where TDictionary : IDictionary<string, TValue>
public override TDictionary ReadJson(JsonReader reader, Type objectType, TDictionary existingValue, bool hasExistingValue, JsonSerializer serializer)
if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
return typeof(TDictionary).IsValueType && Nullable.GetUnderlyingType(typeof(TDictionary)) == null ? throw new JsonSerializationException("null value") : default;
reader.AssertTokenType(JsonToken.StartObject);
var dictionary = existingValue ?? (TDictionary)serializer.ContractResolver.ResolveContract(typeof(TDictionary)).DefaultCreator();
while (reader.ReadToContentAndAssert().TokenType != JsonToken.EndObject)
var key = (string)reader.AssertTokenType(JsonToken.PropertyName).Value;
var value = serializer.Deserialize<TValue>(reader.ReadToContentAndAssert());
dictionary.Add(key, value);
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, TDictionary value, JsonSerializer serializer) => throw new NotImplementedException();
public static partial class JsonExtensions
public static JsonReader AssertTokenType(this JsonReader reader, JsonToken tokenType) =>
reader.TokenType == tokenType ? reader : throw new JsonSerializationException(string.Format("Unexpected token {0}, expected {1}", reader.TokenType, tokenType));
public static JsonReader ReadToContentAndAssert(this JsonReader reader) =>
reader.ReadAndAssert().MoveToContentAndAssert();
public static JsonReader MoveToContentAndAssert(this JsonReader reader)
throw new ArgumentNullException();
if (reader.TokenType == JsonToken.None)
while (reader.TokenType == JsonToken.Comment)
public static JsonReader ReadAndAssert(this JsonReader reader)
throw new ArgumentNullException();
throw new JsonReaderException("Unexpected end of JSON stream.");
[Newtonsoft.Json.JsonConverter(typeof(NoDuplicateKeysDictionaryConverter<string>))]
public Dictionary<string, string> Attributes { get; set; }
public static void Test()
var json = @"{""Attributes"":{""a"":""1"",""a"":""2""}}";
var ex = Assert.Throws<ArgumentException>(() => JsonConvert.DeserializeObject<Model>(json));
Console.WriteLine("Exception successfully thrown: \"{0}: {1}\"", ex.GetType().FullName, ex.Message);
var json2 = @"{""Attributes"":{""a"":""1"",""b"":""2""}}";
var model = JsonConvert.DeserializeObject<Model>(json2);
Assert.AreEqual(2, model.Attributes.Count);
var json2back = JsonConvert.SerializeObject(model);
Assert.IsTrue(JToken.DeepEquals(JToken.Parse(json2), JToken.Parse(json2back)));
static string GetJson() => @"
public static void Main()
Console.WriteLine("Environment version: {0} ({1})", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , Environment.Version);
Console.WriteLine("{0} version: {1}", typeof(JsonSerializer).Assembly.GetName().Name, typeof(JsonSerializer).Assembly.FullName);
Console.WriteLine("Failed with unhandled exception: ");