mirror of
https://gitlab.com/walljm/dynamicbible.git
synced 2025-07-22 23:09:49 -04:00
268 lines
8.8 KiB
C#
268 lines
8.8 KiB
C#
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>(T obj)
|
|
{
|
|
return JsonSerializer.Serialize(obj, jsonOptions);
|
|
}
|
|
|
|
public static T? Deserialize<T>(string obj)
|
|
{
|
|
return JsonSerializer.Deserialize<T>(obj, jsonOptions);
|
|
}
|
|
}
|
|
|
|
public class DictionaryJsonConverterFactoryBuilder
|
|
{
|
|
private readonly Dictionary<Type, Delegate> _parsers = new ();
|
|
private readonly Dictionary<Type, Delegate> _serializers = new ();
|
|
|
|
public DictionaryJsonConverterFactoryBuilder AddParser<T>(Converter<string, T> parser)
|
|
{
|
|
_parsers.Add(typeof(T), parser);
|
|
return this;
|
|
}
|
|
|
|
public DictionaryJsonConverterFactoryBuilder AddSerializer<T>(Converter<T, string> serializer)
|
|
{
|
|
_serializers.Add(typeof(T), serializer);
|
|
return this;
|
|
}
|
|
|
|
public DictionaryJsonConverterFactoryBuilder Add<T>(
|
|
Converter<string, T> parser, Converter<T, string> serializer)
|
|
{
|
|
return AddParser(parser).AddSerializer(serializer);
|
|
}
|
|
|
|
public DictionaryJsonConverterFactoryBuilder SetParser<T>(Converter<string, T> parser)
|
|
{
|
|
_parsers[typeof(T)] = parser;
|
|
return this;
|
|
}
|
|
|
|
public DictionaryJsonConverterFactoryBuilder SetSerializer<T>(Converter<T, string> serializer)
|
|
{
|
|
_serializers[typeof(T)] = serializer;
|
|
return this;
|
|
}
|
|
|
|
public DictionaryJsonConverterFactoryBuilder Set<T>(
|
|
Converter<string, T> parser, Converter<T, string> 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<Type, Type> s_converterTypes = new Dictionary<Type, Type>
|
|
{
|
|
[typeof(Dictionary<,>)] = typeof(DictionaryJsonConverter<,>),
|
|
}.ToImmutableDictionary();
|
|
|
|
public static readonly ImmutableArray<Type> SupportedDictionaryTypes = s_converterTypes.Keys.ToImmutableArray();
|
|
public static readonly DictionaryJsonConverterFactory Default =
|
|
new DictionaryJsonConverterFactoryBuilder().AddDefaults().Build();
|
|
|
|
private readonly ImmutableDictionary<Type, Delegate> _parsers;
|
|
private readonly ImmutableDictionary<Type, Delegate> _serializers;
|
|
private readonly Func<Type, JsonConverter> _valueFactory;
|
|
private readonly ConcurrentDictionary<Type, JsonConverter> _jsonConverterCache = new ();
|
|
|
|
internal DictionaryJsonConverterFactory(
|
|
ImmutableDictionary<Type, Delegate> parsers,
|
|
ImmutableDictionary<Type, Delegate> 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<T, string> MakeClassSerializer<T>() where T : class
|
|
{
|
|
return item => item?.ToString() ?? string.Empty;
|
|
}
|
|
|
|
private static Converter<T, string> MakeStructSerializer<T>() 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<TKey, TValue> : JsonConverter<Dictionary<TKey, TValue>?> where TKey : notnull
|
|
{
|
|
public static Dictionary<TKey, TValue>? Read(
|
|
ref Utf8JsonReader reader,
|
|
Converter<string?, TKey> 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<TKey, TValue>();
|
|
|
|
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<TValue>(ref reader, options);
|
|
result.Add(key, value!);
|
|
}
|
|
}
|
|
|
|
private readonly Converter<string?, TKey> _keyParser;
|
|
private readonly Converter<TKey, string> _keySerializer;
|
|
|
|
public DictionaryJsonConverter(
|
|
Converter<string?, TKey> keyParser,
|
|
Converter<TKey, string> keySerializer
|
|
)
|
|
{
|
|
_keyParser = keyParser;
|
|
_keySerializer = keySerializer;
|
|
}
|
|
|
|
public override Dictionary<TKey, TValue>? Read(
|
|
ref Utf8JsonReader reader,
|
|
Type typeToConvert,
|
|
JsonSerializerOptions options
|
|
)
|
|
{
|
|
return Read(ref reader, _keyParser, options);
|
|
}
|
|
|
|
public override void Write(
|
|
Utf8JsonWriter writer,
|
|
Dictionary<TKey, TValue>? 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();
|
|
}
|
|
}
|
|
} |