using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
public class FaactRecordDifferences
public string Id { get; set; }
public int Year { get; set; }
[JsonConverter(typeof(FieldDifferenceListConverter))]
public List<FieldDifference> Fields { get; set; }
public class FieldDifference
public string FieldName { get; set; }
public string FieldValue { get; set; }
public Type type { get; set; }
public class FieldDifferenceListConverter : JsonConverter<List<FieldDifference>>
public override void WriteJson(JsonWriter writer, List<FieldDifference> value, JsonSerializer serializer)
writer.WriteStartObject();
foreach (var field in value)
writer.WritePropertyName(field.FieldName);
if (field.type == typeof(string))
writer.WriteValue(field.FieldValue);
writer.WriteRawValue(field.FieldValue);
public override List<FieldDifference> ReadJson(JsonReader reader, Type objectType, List<FieldDifference> existingValue, bool hasExistingValue, JsonSerializer serializer)
var list = existingValue ?? (List<FieldDifference>)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
switch (reader.MoveToContentAndAssert().TokenType)
case JsonToken.StartObject:
while (reader.ReadToContentAndAssert().TokenType != JsonToken.EndObject)
var name = reader.TokenType == JsonToken.PropertyName ? (string)reader.Value : throw new JsonSerializationException($"Expected PropertyName but got {reader.TokenType}");
FieldDifference field = reader.ReadToContentAndAssert().TokenType switch
JsonToken.String => new FieldDifference { FieldName = name, FieldValue = (string)reader.Value, type = typeof(string) },
JsonToken.Integer => new FieldDifference { FieldName = name, FieldValue = Convert.ToString(reader.Value, CultureInfo.InvariantCulture), type = typeof(int)},
JsonToken.Float => new FieldDifference { FieldName = name, FieldValue = Convert.ToString(reader.Value, CultureInfo.InvariantCulture), type = typeof(double)},
JsonToken.Boolean => new FieldDifference { FieldName = name, FieldValue = JsonConvert.ToString((bool)reader.Value), type = typeof(bool)},
JsonToken.Null => new FieldDifference { FieldName = name, FieldValue = null, type = typeof(string)},
JsonToken.Date => new FieldDifference { FieldName = name, FieldValue = JsonConvert.SerializeObject(reader.Value), type = reader.ValueType},
_ => new FieldDifference { FieldName = name, FieldValue = reader.ReadOuterJson(Formatting.None, DateParseHandling.None), type = typeof(object) },
throw new JsonSerializationException($"Unknown token {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)
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 string ReadOuterJson(this JsonReader reader, Formatting formatting = Formatting.None, DateParseHandling? dateParseHandling = null, FloatParseHandling? floatParseHandling = null)
if (reader.TokenType == JsonToken.Null)
var oldDateParseHandling = reader.DateParseHandling;
var oldFloatParseHandling = reader.FloatParseHandling;
if (dateParseHandling != null)
reader.DateParseHandling = dateParseHandling.Value;
if (floatParseHandling != null)
reader.FloatParseHandling = floatParseHandling.Value;
using (var sw = new StringWriter(CultureInfo.InvariantCulture))
using (var jsonWriter = new JsonTextWriter(sw) { Formatting = formatting })
jsonWriter.WriteToken(reader);
reader.DateParseHandling = oldDateParseHandling;
reader.FloatParseHandling = oldFloatParseHandling;
public static void Test()
var model = new FaactRecordDifferences
new() { FieldName = "SomeFirstField", FieldValue = "1200.21", type = typeof(double) },
new() { FieldName = "AnotherTestField", FieldValue = "SomeStringValue", type = typeof(string) },
new() { FieldName = "SomeFirstField", FieldValue = "6120.40", type = typeof(double) },
new() { FieldName = "SomeBoolField", FieldValue = "true", type = typeof(bool) },
new() { FieldName = "SomeDateTimeField", FieldValue = JsonConvert.ToString(new DateTime(2023, 1, 1)), type = typeof(DateTime) },
new() { FieldName = "SomeIntField", FieldValue = "11011", type = typeof(int) },
var json = JsonConvert.SerializeObject(model, Formatting.Indented);
var model2 = JsonConvert.DeserializeObject<FaactRecordDifferences>(json);
var json2 = JsonConvert.SerializeObject(model2, Formatting.Indented);
Console.WriteLine("Deserialized and re-serialized {0}:", model2);
Console.WriteLine(json2);
Assert.That(JToken.DeepEquals(JToken.Parse(json), JToken.Parse(json2)));
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: ");