using System.Collections;
using System.Collections.Generic;
using System.Text.Json.Serialization;
public record Person(string FirstName, string LastName);
public sealed class PersonConverter : DefaultConverterFactory<Person>
record PersonDTO(string FirstName, string LastName, string Name);
protected override Person Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions modifiedOptions)
var dto = JsonSerializer.Deserialize<PersonDTO>(ref reader, modifiedOptions);
var oldNames = dto?.Name?.Split(' ', StringSplitOptions.RemoveEmptyEntries) ?? Enumerable.Empty<string>();
return new Person(dto.FirstName ?? oldNames.FirstOrDefault(), dto.LastName ?? oldNames.LastOrDefault());
public abstract class DefaultConverterFactory<T> : JsonConverterFactory
class DefaultConverter : JsonConverter<T>
readonly JsonSerializerOptions modifiedOptions;
readonly DefaultConverterFactory<T> factory;
public DefaultConverter(JsonSerializerOptions options, DefaultConverterFactory<T> factory)
this.modifiedOptions = options.CopyAndRemoveConverter(factory.GetType());
Console.WriteLine(string.Join(", ", new object [] { $" {options.Converters.Count} converters: " }.Concat(options.Converters)));
throw new ApplicationException("Throwing an exception where the inner converter is manufactured:");
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
Console.WriteLine(string.Join(", ", new object [] { $" {options.Converters.Count} converters: " }.Concat(options.Converters)));
factory.Write(writer, value, modifiedOptions);
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
factory.Read(ref reader, typeToConvert, modifiedOptions);
protected virtual T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions modifiedOptions)
=> (T)JsonSerializer.Deserialize(ref reader, typeToConvert, modifiedOptions);
protected virtual void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions modifiedOptions)
=> JsonSerializer.Serialize(writer, value, modifiedOptions);
public override bool CanConvert(Type typeToConvert) => typeof(T) == typeToConvert;
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => new DefaultConverter(options, this);
public static class JsonSerializerExtensions
public static JsonSerializerOptions CopyAndRemoveConverter(this JsonSerializerOptions options, Type converterType)
var copy = new JsonSerializerOptions(options);
for (var i = copy.Converters.Count - 1; i >= 0; i--)
if (copy.Converters[i].GetType() == converterType)
copy.Converters.RemoveAt(i);
public static void Test()
foreach (var json in GetJson())
var options = new JsonSerializerOptions
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
Converters = { new PersonConverter() },
var person = JsonSerializer.Deserialize<List<Person>>(json, options);
var json2 = JsonSerializer.Serialize(person, options);
person.ForEach(p => Console.WriteLine(" " + p));
Console.WriteLine(json2);
var person2 = JsonSerializer.Deserialize<List<Person>>(json2, options);
Assert.That(person.SequenceEqual(person2));
static IEnumerable<string> GetJson() =>
@"[{""lastName"":""LastName"",""firstName"":""FirstName""}]",
@"[{""name"":""Full Name""}]",
@"[{""name"":""Full Name""},{""name"":""Full Name""},{""lastName"":""LastName"",""firstName"":""FirstName""}]",
public static void Main()
Console.WriteLine("Environment version: {0} ({1})", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , GetNetCoreVersion());
Console.WriteLine("System.Text.Json version: " + 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];