using System.Collections;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using System.Runtime.Serialization;
public List<ElementData> Elements { get; set; }
[JsonProperty("type", Required = Required.Always)]
[JsonConverter(typeof(StringEnumConverter))]
public ElementType ElementType { get; set; }
public string SomeValue { get; set; }
public class ElementDataJsonConverter : JsonConverter<ElementData>
public override ElementData ReadJson(JsonReader reader, Type objectType, ElementData existingValue, bool hasExistingValue, JsonSerializer serializer)
var element = hasExistingValue ? existingValue : new ElementData();
serializer.Populate(reader, element);
public override void WriteJson(JsonWriter writer, ElementData value, JsonSerializer serializer)
JsonSerializer.Create().Serialize(writer, value);
[EnumMember(Value = "type1")] Type1,
[EnumMember(Value = "type2")] Type2,
public class CustomElementJsonConverter : JsonConverter<Step>
private static readonly Dictionary<string, JsonConverter> _converters = new();
static CustomElementJsonConverter()
foreach (var elementType in Enum.GetValues(typeof(ElementType)).Cast<ElementType>())
string elemTypeJsonName = elementType.ToString().ToLowerInvariant();
_converters[elemTypeJsonName] = new ElementDataJsonConverter();
public override Step ReadJson(JsonReader reader, Type objectType, Step existingValue, bool hasExistingValue, JsonSerializer serializer)
if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
var step = hasExistingValue ? existingValue : new Step();
step.Elements ??= new ();
step.Elements.AddRange(reader.EnumerateArray().Select(jElement =>
var elementType = jElement["type"].Value<string>().ToLowerInvariant();
var convertor = _converters[elementType];
using var subReader = jElement.CreateReader();
return (ElementData)convertor.ReadJson(subReader.MoveToContentAndAssert(), typeof(ElementData), null, serializer);
public override void WriteJson(JsonWriter writer, Step value, JsonSerializer serializer)
serializer.Serialize(writer, value.Elements);
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 IEnumerable<JToken> EnumerateArray(this JsonReader reader)
reader.MoveToContentAndAssert().AssertTokenType(JsonToken.StartArray);
while (reader.ReadAndAssert().TokenType != JsonToken.EndArray)
yield return JToken.Load(reader);
public static void Test()
new () { SomeValue = "some value" },
new () { ElementType = ElementType.Type2, SomeValue = "some other value" },
var settings = new JsonSerializerSettings
Converters = { new CustomElementJsonConverter() },
var json = JsonConvert.SerializeObject(step, Formatting.Indented, settings);
var step2 = JsonConvert.DeserializeObject<Step>(json, settings);
Assert.AreEqual(step.Elements.Count, step2.Elements.Count);
var json2 = JsonConvert.SerializeObject(step2, Formatting.Indented, settings);
Assert.AreEqual(json, 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: ");