using System.Collections.Concurrent; using System.Collections.Immutable; using System.Numerics; using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; using DynamicBible.DataPreparation.Models; namespace DynamicBible.DataPreparation; public class JSON { static JSON() { jsonOptions = new () { AllowTrailingCommas = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, IgnoreReadOnlyProperties = false, Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, }; jsonOptions.Converters.Add(DictionaryJsonConverterFactory.Default); } private static readonly JsonSerializerOptions jsonOptions; public static string Serialize(T obj) { return JsonSerializer.Serialize(obj, jsonOptions); } public static T? Deserialize(string obj) { return JsonSerializer.Deserialize(obj, jsonOptions); } } public class DictionaryJsonConverterFactoryBuilder { private readonly Dictionary _parsers = new (); private readonly Dictionary _serializers = new (); public DictionaryJsonConverterFactoryBuilder AddParser(Converter parser) { _parsers.Add(typeof(T), parser); return this; } public DictionaryJsonConverterFactoryBuilder AddSerializer(Converter serializer) { _serializers.Add(typeof(T), serializer); return this; } public DictionaryJsonConverterFactoryBuilder Add( Converter parser, Converter serializer) { return AddParser(parser).AddSerializer(serializer); } public DictionaryJsonConverterFactoryBuilder SetParser(Converter parser) { _parsers[typeof(T)] = parser; return this; } public DictionaryJsonConverterFactoryBuilder SetSerializer(Converter serializer) { _serializers[typeof(T)] = serializer; return this; } public DictionaryJsonConverterFactoryBuilder Set( Converter parser, Converter serializer) { return SetParser(parser).SetSerializer(serializer); } public DictionaryJsonConverterFactoryBuilder AddDefaults() { return this .AddParser(sbyte.Parse) .AddParser(short.Parse) .AddParser(int.Parse) .AddParser(long.Parse) .AddParser(byte.Parse) .AddParser(ushort.Parse) .AddParser(uint.Parse) .AddParser(ulong.Parse) .AddParser(BigInteger.Parse) .AddParser(float.Parse) .AddParser(double.Parse) .AddParser(decimal.Parse) .AddParser(Guid.Parse); } public DictionaryJsonConverterFactory Build() { return new DictionaryJsonConverterFactory( _parsers.ToImmutableDictionary(), _serializers.ToImmutableDictionary()); } } public sealed class DictionaryJsonConverterFactory : JsonConverterFactory { private static readonly ImmutableDictionary s_converterTypes = new Dictionary { [typeof(Dictionary<,>)] = typeof(DictionaryJsonConverter<,>), }.ToImmutableDictionary(); public static readonly ImmutableArray SupportedDictionaryTypes = s_converterTypes.Keys.ToImmutableArray(); public static readonly DictionaryJsonConverterFactory Default = new DictionaryJsonConverterFactoryBuilder().AddDefaults().Build(); private readonly ImmutableDictionary _parsers; private readonly ImmutableDictionary _serializers; private readonly Func _valueFactory; private readonly ConcurrentDictionary _jsonConverterCache = new (); internal DictionaryJsonConverterFactory( ImmutableDictionary parsers, ImmutableDictionary serializers) { _parsers = parsers; _serializers = serializers; _valueFactory = CreateConverter; } public override bool CanConvert(Type typeToConvert) { return typeToConvert.IsGenericType && s_converterTypes.ContainsKey(typeToConvert.GetGenericTypeDefinition()) && _parsers.ContainsKey(typeToConvert.GenericTypeArguments[0]); } public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) { return _jsonConverterCache.GetOrAdd(typeToConvert, _valueFactory); } private static Converter MakeClassSerializer() where T : class { return item => item?.ToString() ?? string.Empty; } private static Converter MakeStructSerializer() where T : struct { return item => item.ToString() ?? string.Empty; } private JsonConverter CreateConverter(Type typeToConvert) { var keyType = typeToConvert.GenericTypeArguments[0]; var valueType = typeToConvert.GenericTypeArguments[1]; var keyParser = _parsers[keyType]; if (!_serializers.TryGetValue(keyType, out var keySerializer)) { var methodName = keyType.IsValueType ? nameof(MakeStructSerializer) : nameof(MakeClassSerializer); var obj = GetType() .GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic)? .MakeGenericMethod(keyType) .Invoke(null, null) ?? throw new NullReferenceException("Failed to invoke serializer maker"); keySerializer = (Delegate)obj; } var converterType = s_converterTypes[typeToConvert.GetGenericTypeDefinition()]; var result = Activator.CreateInstance( converterType.MakeGenericType(typeToConvert.GenericTypeArguments), keyParser, keySerializer) ?? throw new NullReferenceException("Failed to create converter"); return (JsonConverter)result; } } public class DictionaryJsonConverter : JsonConverter?> where TKey : notnull { public static Dictionary? Read( ref Utf8JsonReader reader, Converter keyParser, JsonSerializerOptions options ) { if (reader.TokenType == JsonTokenType.Null) { return null; } if (reader.TokenType != JsonTokenType.StartObject) { throw new JsonException("Dictionary must be JSON object."); } var result = new Dictionary(); while (true) { if (!reader.Read()) { throw new JsonException("Incomplete JSON object"); } if (reader.TokenType == JsonTokenType.EndObject) { return result; } var key = keyParser(reader.GetString()); if (!reader.Read()) { throw new JsonException("Incomplete JSON object"); } var value = JsonSerializer.Deserialize(ref reader, options); result.Add(key, value!); } } private readonly Converter _keyParser; private readonly Converter _keySerializer; public DictionaryJsonConverter( Converter keyParser, Converter keySerializer ) { _keyParser = keyParser; _keySerializer = keySerializer; } public override Dictionary? Read( ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options ) { return Read(ref reader, _keyParser, options); } public override void Write( Utf8JsonWriter writer, Dictionary? value, JsonSerializerOptions options ) { if (value is null) { writer.WriteNullValue(); } else { writer.WriteStartObject(); foreach (var pair in value) { writer.WritePropertyName(_keySerializer(pair.Key)); JsonSerializer.Serialize(writer, pair.Value, options); } writer.WriteEndObject(); } } }