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 KeyValueListAsObjectConverter<TValue> : JsonConverter<List<KeyValuePair<string, TValue>>>
public override List<KeyValuePair<string, TValue>> ReadJson(JsonReader reader, Type objectType, List<KeyValuePair<string, TValue>> existingValue, bool hasExistingValue, JsonSerializer serializer)
if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
reader.AssertTokenType(JsonToken.StartObject);
var list = existingValue ?? (List<KeyValuePair<string, TValue>>)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
while (reader.ReadToContentAndAssert().TokenType != JsonToken.EndObject)
var name = (string)reader.AssertTokenType(JsonToken.PropertyName).Value;
var value = serializer.Deserialize<TValue>(reader.ReadToContentAndAssert());
list.Add(new KeyValuePair<string, TValue>(name, value));
public override void WriteJson(JsonWriter writer, List<KeyValuePair<string, TValue>> value, JsonSerializer serializer)
writer.WriteStartObject();
foreach (var pair in value)
writer.WritePropertyName(pair.Key);
serializer.Serialize(writer, pair.Value);
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.");
[JsonConverter(typeof(KeyValueListAsObjectConverter<string>))]
public List<KeyValuePair<string, string>> data { get; } = new ();
public List<KeyValuePair<string, string>> data { get; } = new ();
public static void Test()
var jsonString = GetJson();
var model = JsonConvert.DeserializeObject<Model>(jsonString);
var json2 = JsonConvert.SerializeObject(model, Formatting.Indented);
Console.WriteLine("Re-serialized {0}:", model);
Console.WriteLine(json2);
Assert.AreEqual(jsonString.Replace(" ", "").Trim(), json2.Replace(" ", "").Trim());
var model2 = new Model2();
model2.data.AddRange(model.data);
var jsonAlternate = JsonConvert.SerializeObject(model2, Formatting.Indented);
Console.WriteLine("JSON in an equivalent format:");
Console.WriteLine(jsonAlternate);
public static void Main()
Console.WriteLine("Environment version: {0} ({1})", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , GetNetCoreVersion());
Console.WriteLine("{0} version: {1}", typeof(JsonSerializer).Assembly.GetName().Name, typeof(JsonSerializer).Assembly.FullName);
Console.WriteLine("Failed with unhandled exception: ");
public static string GetNetCoreVersion()
var assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly;
var assemblyPath = assembly.Location.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries);
int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App");
if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2)
return assemblyPath[netCoreAppIndex + 1];