using System.Collections;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
public class ObjectWithResultArrayConverter<TModel> : JsonConverter<TModel> where TModel : class, new()
protected virtual string HeadersName => "header";
protected virtual string ResultsName => "results";
public override TModel? ReadJson(JsonReader reader, Type objectType, TModel? existingValue, bool hasExistingValue, JsonSerializer serializer)
if (!(serializer.ContractResolver.ResolveContract(objectType) is JsonObjectContract contract))
throw new NotImplementedException("Contract is not a JsonObjectContract");
var resultsProperty = contract.Properties.GetClosestMatchProperty(ResultsName);
if (resultsProperty == null)
throw new NotImplementedException($"No results property {ResultsName} found.");
if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
var jObj = JObject.Load(reader);
var headers = jObj.GetValue(HeadersName, StringComparison.OrdinalIgnoreCase).RemoveFromLowestPossibleParent() as JArray;
var results = jObj.GetValue(ResultsName, StringComparison.OrdinalIgnoreCase).RemoveFromLowestPossibleParent() as JArray;
var value = existingValue ?? (TModel?)contract.DefaultCreator?.Invoke() ?? new TModel();
serializer.Populate(jObj.CreateReader(), value);
if (headers != null && results != null)
var resultsJArray = new JArray(results.Children<JArray>().Select(innerArray => new JObject(headers.Zip(innerArray, (h, i) => new JProperty((string)h!, i)))));
var resultsValue = serializer.Deserialize(resultsJArray.CreateReader(), resultsProperty.PropertyType);
resultsProperty.ValueProvider!.SetValue(value, resultsValue);
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, TModel? value, JsonSerializer serializer) => throw new NotImplementedException();
public static partial class JsonExtensions
public static JsonReader MoveToContentAndAssert(this JsonReader reader)
ArgumentNullException.ThrowIfNull(reader);
if (reader.TokenType == JsonToken.None)
while (reader.TokenType == JsonToken.Comment)
public static JsonReader ReadAndAssert(this JsonReader reader)
ArgumentNullException.ThrowIfNull(reader);
throw new JsonReaderException("Unexpected end of JSON stream.");
public static TJToken? RemoveFromLowestPossibleParent<TJToken>(this TJToken? node) where TJToken : JToken
var property = node.Parent as JProperty;
if (toRemove.Parent != null)
public string? RootEntity { get; set; }
public int Count { get; set; }
public List<Item> Results { get; set; } = new();
public record Item(string Name, string Email, string Function);
public static void Test()
var jsonString = GetJson();
var settings = new JsonSerializerSettings
Converters = { new ObjectWithResultArrayConverter<Model>() },
ContractResolver = new CamelCasePropertyNamesContractResolver(),
var model = JsonConvert.DeserializeObject<Model>(jsonString, settings);
var json2 = JsonConvert.SerializeObject(model, Formatting.Indented, settings);
Console.WriteLine("Deserialized and re-serialized {0}:", model);
Console.WriteLine(json2);
Assert.AreEqual(JToken.Parse(jsonString).SelectToken("results")?.Count(), JToken.Parse(json2).SelectToken("results")?.Count());
static string GetJson() =>
"rootEntity": "function",
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: ");