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 class TolerantObjectCollectionConverter<TItem> : JsonConverter
public override bool CanConvert(Type objectType)
return !objectType.IsArray && objectType != typeof(string) && typeof(ICollection<TItem>).IsAssignableFrom(objectType);
public override bool CanWrite { get { return 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)
var contract = serializer.ContractResolver.ResolveContract(objectType) as JsonArrayContract;
if (contract == null || contract.IsMultidimensionalArray || objectType.IsArray)
throw new JsonSerializationException(string.Format("Invalid array contract for {0}", objectType));
var tokenType = reader.SkipComments().TokenType;
if (tokenType == JsonToken.Null)
if (tokenType != JsonToken.StartArray)
throw new JsonSerializationException(string.Format("Expected {0}, encountered {1} at path {2}", JsonToken.StartArray, reader.TokenType, reader.Path));
var collection = existingValue as ICollection<TItem> ?? (ICollection<TItem>)contract.DefaultCreator();
switch (reader.TokenType)
case JsonToken.StartObject:
collection.Add(serializer.Deserialize<TItem>(reader));
throw new JsonSerializationException("Unclosed array at path: " + reader.Path);
public static partial class JsonExtensions
public static JsonReader SkipComments(this JsonReader reader)
while (reader.TokenType == JsonToken.Comment && reader.Read())
[JsonProperty("total_events")]
public int TotalEvents { get; set; }
[JsonProperty("json.appID")]
[JsonConverter(typeof(TolerantObjectCollectionConverter<JsonAppID>))]
public List<JsonAppID> AppIds { get; set; }
[JsonProperty("unique_field_count")]
public int UniqueFieldCount { get; set; }
[JsonProperty(PropertyName = "count")]
public int Count { get; set; }
[JsonProperty(PropertyName = "term")]
public string Term { get; set; }
public static void Test()
foreach (var json in GetJson())
Console.WriteLine("\nTests passed.");
static void Test(string json)
Console.WriteLine("\nTesting JSON: ");
var result = JsonConvert.DeserializeObject<RootObject>(json);
var json2 = JsonConvert.SerializeObject(result, Formatting.Indented);
Console.WriteLine("Re-serialized result: ");
Console.WriteLine(json2);
Assert.IsTrue(JToken.Parse(json).SelectTokens("json.appID[*]").Where(t => t.Type == JTokenType.Object || t.Type == JTokenType.Null)
.SequenceEqual(JToken.Parse(json2).SelectTokens("json.appID[*]").Where(t => t.Type == JTokenType.Object || t.Type == JTokenType.Null)));
static IEnumerable<string> GetJson()
""total_events"": 3551574,
[] /* <----- I need to ignore this empty array */
""unique_field_count"": 2
""total_events"": 3551574,
""json.appID"": /* Some Comment */ [
[], /* <----- I need to ignore this empty array */
""unique_field_count"": 2
public static void Main()
Console.WriteLine("Environment version: " + Environment.Version);
Console.WriteLine("Json.NET version: " + typeof(JsonSerializer).Assembly.FullName);
Console.WriteLine("Failed with unhandled exception: ");
public class AssertionFailedException : System.Exception
public AssertionFailedException() : base() { }
public AssertionFailedException(string s) : base(s) { }
public static class Assert
public static void IsTrue(bool value)
public static void IsTrue(bool value, string message)
throw new AssertionFailedException(message ?? "failed");