using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
public int Id { get; set; }
public Bar Bar { get; set; } = new Bar();
[JsonConverter(typeof(JsonSingleOrEmptyArrayConverter<Dictionary<string, int>>))]
public Dictionary<string, int> Details { get; private set; } = new Dictionary<string, int>();
public override string ToString()
return $"Id: {Id}, Bar: {Bar.A} {Bar.B}, Details count: {Details.Count}";
public int A { get; set; }
public int B { get; set; }
public class JsonSingleOrEmptyArrayConverter<T> : JsonConverter where T : class
public override bool CanConvert(Type objectType)
return typeof(T).IsAssignableFrom(objectType);
public override bool CanWrite { get { return false; } }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
var contract = serializer.ContractResolver.ResolveContract(objectType);
if (!(contract is Newtonsoft.Json.Serialization.JsonObjectContract || contract is Newtonsoft.Json.Serialization.JsonDictionaryContract))
throw new JsonSerializationException(string.Format("Unsupported objectType {0} at {1}.", objectType, reader.Path));
switch (reader.SkipComments().TokenType)
case JsonToken.StartArray:
switch (reader.TokenType)
throw new JsonSerializationException(string.Format("Too many objects at path {0}.", reader.Path));
existingValue = existingValue ?? contract.DefaultCreator();
serializer.Populate(reader, existingValue);
throw new JsonSerializationException(string.Format("Unclosed array at path {0}.", reader.Path));
case JsonToken.StartObject:
existingValue = existingValue ?? contract.DefaultCreator();
serializer.Populate(reader, existingValue);
throw new InvalidOperationException("Unexpected token type " + reader.TokenType.ToString());
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
throw new NotImplementedException();
public static partial class JsonExtensions
public static JsonReader SkipComments(this JsonReader reader)
while (reader.TokenType == JsonToken.Comment && reader.Read())
public static void Test()
'bar': { 'A': 5, 'B': 6 }
'bar': { 'A': 7, 'B': 8 }
var foo1 = JsonConvert.DeserializeObject<Foo>(goodJson);
Console.WriteLine($"goodJson: {foo1}");
Assert.AreEqual(4, foo1.Details.Count);
var foo2 = JsonConvert.DeserializeObject<Foo>(badJson);
Console.WriteLine($"badJson: {foo2}");
Assert.AreEqual(0, foo2.Details.Count);
public static void Main()
Console.WriteLine("Environment version: {0} ({1}), {2}", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , Environment.Version, Environment.OSVersion);
Console.WriteLine("{0} version: {1}", typeof(JsonSerializer).Namespace, typeof(JsonSerializer).Assembly.FullName);
Console.WriteLine("Failed with unhandled exception: ");