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 ArrayObjectConverter : JsonConverter
public override bool CanConvert(Type t) => t.GetListItemType() != null;
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) => throw new NotImplementedException();
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
System.Diagnostics.Debug.Assert(objectType.GetListItemType() != null);
if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
IList value = existingValue as IList ?? (IList)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator!();
if (reader.TokenType == JsonToken.StartArray)
serializer.Populate(reader, value);
else if (reader.TokenType == JsonToken.StartObject)
var itemType = objectType.GetListItemType().ThrowOnNull();
while (reader.ReadToContentAndAssert().TokenType != JsonToken.EndObject)
reader.AssertTokenType(JsonToken.PropertyName).ReadToContentAndAssert();
value.Add(serializer.Deserialize(reader, itemType));
throw new JsonSerializationException(string.Format("Unknown token type {0}", reader.TokenType));
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.");
public static Type? GetListItemType(this Type type)
if (type.IsPrimitive || type.IsArray || type == typeof(string))
var genType = type.GetGenericTypeDefinition();
if (genType == typeof(List<>))
return type.GetGenericArguments()[0];
public static partial class ObjectExtensions
public static T ThrowOnNull<T>(this T? value) where T : class => value ?? throw new ArgumentNullException(nameof(value));
public List<T> SomeArr { get; set; } = new ();
public string? prop1 { get; set; }
public static void Test()
static void TestPrimitiveList()
foreach (var json in GetPrimitiveJson())
var settings = new JsonSerializerSettings
Converters = { new ArrayObjectConverter() },
var model = JsonConvert.DeserializeObject<Model<string>>(json, settings);
Console.WriteLine(JsonConvert.SerializeObject(model, settings));
Assert.AreEqual(4, model?.SomeArr?.Count);
static void TestObjectList()
foreach (var json in GetObjectJson())
var settings = new JsonSerializerSettings
Converters = { new ArrayObjectConverter() },
var model = JsonConvert.DeserializeObject<Model<Item>>(json, settings);
Console.WriteLine(JsonConvert.SerializeObject(model, settings));
Assert.AreEqual(2, model?.SomeArr?.Count);
static IEnumerable<string> GetObjectJson() => new []
@"{""SomeArr"" : /* Comment */ [ /* Comment */ { ""prop1"":""value1"" }, { ""prop1"":""value1"" }]}",
/* Comment */ ""item1"": /* Comment */{ /* Comment */""prop1"":""value1"" },
""item2"": { /* Comment */""prop1"":""value1"" /* Comment */ }
static IEnumerable<string> GetPrimitiveJson() => new []
@"{""SomeArr"": /* Comment */ [""a"", ""b"", null, ""c""]}",
/* Comment */ ""item1"": ""a"", /* Comment */
/* Comment */ ""item2"": ""b"",
/* Comment */ ""item3"": null,
/* Comment */ ""item4"": ""c"",
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: ");