using System.Collections.Generic;
using System.Text.Json.Serialization;
public static void Main()
var @class = new MyClass();
@class.RegularData["World"] = "Hello";
@class.ExtData = new Dictionary<string, object> { ["Hello"] = "World" };
var settings = new JsonSerializerOptions();
settings.Converters.Add(new JsonDictionaryObjectToConcreteTypeConverter());
string json = JsonSerializer.Serialize(@class, settings);
public Dictionary<string, object> RegularData { get; set; } = new Dictionary<string, object>();
public Dictionary<string, object> ExtData { get; set; }
public class JsonDictionaryObjectToConcreteTypeConverter : JsonConverter<Dictionary<string, object?>>
public static Dictionary<string, object?> ReadValue(ref Utf8JsonReader reader, Type? typeToConvert, JsonSerializerOptions options)
if (reader.TokenType != JsonTokenType.StartObject)
throw new JsonException($"The Json does not provide a 'StartObject' Token! Recognized token was '{reader.TokenType}'.");
var dictionary = new Dictionary<string, object?>();
if (reader.TokenType == JsonTokenType.EndObject)
if (reader.TokenType != JsonTokenType.PropertyName)
throw new JsonException($"A Property needs to start with a 'PropertyName' Token! Recognized token was '{reader.TokenType}'.");
var propertyName = reader.GetString();
if (string.IsNullOrWhiteSpace(propertyName))
throw new JsonException("The PropertyName is empty!");
dictionary.Add(propertyName, ExtractValue(ref reader, options));
public static void WriteValue(Utf8JsonWriter writer, IDictionary<string, object?> values, JsonSerializerOptions options)
string GetPropertyName(string propName) => options.PropertyNamingPolicy is not null ? options.PropertyNamingPolicy.ConvertName(propName) : propName;
writer.WriteStartObject();
foreach (var kv in values)
writer.WritePropertyName(GetPropertyName(kv.Key));
var valueAsString = kv.Value?.ToString();
else if (long.TryParse(valueAsString, out var l))
writer.WriteNumberValue(l);
else if (decimal.TryParse(valueAsString, out var d))
writer.WriteNumberValue(d);
else if (bool.TryParse(valueAsString, out var b))
writer.WriteBooleanValue(b);
JsonSerializer.Serialize(writer, value, value.GetType());
public override Dictionary<string, object?> Read(ref Utf8JsonReader reader, Type? typeToConvert, JsonSerializerOptions options)
=> ReadValue(ref reader, typeToConvert, options);
public override void Write(Utf8JsonWriter writer, Dictionary<string, object?> values, JsonSerializerOptions options)
=> WriteValue(writer, values, options);
private static object? ExtractValue(ref Utf8JsonReader reader, JsonSerializerOptions options)
switch (reader.TokenType)
case JsonTokenType.String:
if (reader.TryGetDateTime(out var date))
return reader.GetString();
case JsonTokenType.False:
case JsonTokenType.Number:
if (reader.TryGetInt64(out var result))
return reader.GetDecimal();
case JsonTokenType.StartObject:
return ReadValue(ref reader, null, options);
case JsonTokenType.StartArray:
return ReadArray(reader, options);
throw new JsonException($"Json Token '{reader.TokenType}' is not supported!");
private static object ReadArray(Utf8JsonReader reader, JsonSerializerOptions options)
var list = new List<object?>();
while (reader.Read() && reader.TokenType != JsonTokenType.EndArray)
list.Add(ExtractValue(ref reader, options));