using System; using System.Linq; using System.Reflection; using System.Text.Json; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using JsonSerializer = System.Text.Json.JsonSerializer; namespace LCU.Library.Internal.DocumentModels.Converter; internal static class PolymorphicJsonConverter { public const string TypeProperty = "_t"; public static Exception NotSupported() => new NotSupportedException($"{typeof(T).Name} must specify known deserialization types"); public static bool IsSerializationSupportedFor(Type actualType) => typeof(T).GetCustomAttribute() is { } knownTypes && knownTypes.Subclasses.Contains(actualType); public static Type DeserializationTypeFor(string actualType) => typeof(T) .GetCustomAttribute() .Subclasses .Single(t => t.Name == actualType); } public class SystemPolymorphicJsonConverter : System.Text.Json.Serialization.JsonConverter { public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var document = JsonDocument.ParseValue(ref reader); var type = document.RootElement.GetProperty(PolymorphicJsonConverter.TypeProperty).GetString(); var deserializationType = PolymorphicJsonConverter.DeserializationTypeFor(type); return (T)document.Deserialize(deserializationType, options); } public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) { var actualType = value.GetType(); if (!PolymorphicJsonConverter.IsSerializationSupportedFor(actualType)) { throw PolymorphicJsonConverter.NotSupported(); } writer.WriteStartObject(); writer.WriteString(PolymorphicJsonConverter.TypeProperty, actualType.Name); var actualValue = JsonSerializer.Serialize(value, actualType, options); writer.WriteRawValue(actualValue.Substring(1, actualValue.Length - 2), true); writer.WriteEndObject(); } } public class NewtonsoftPolymorphicJsonConverter : Newtonsoft.Json.JsonConverter { public override bool CanRead => _canRead; private bool _canRead = true; public override T ReadJson(JsonReader reader, Type objectType, T existingValue, bool hasExistingValue, Newtonsoft.Json.JsonSerializer serializer) { var document = JObject.Load(reader); var type = document.GetValue(PolymorphicJsonConverter.TypeProperty)!.Value(); var deserializationType = PolymorphicJsonConverter.DeserializationTypeFor(type); T result; lock (this) { _canRead = false; result = (T)JsonConvert.DeserializeObject(document.ToString(), deserializationType); _canRead = true; } return result; } public override bool CanWrite => _canWrite; private bool _canWrite = true; public override void WriteJson(JsonWriter writer, T value, Newtonsoft.Json.JsonSerializer serializer) { var actualType = value.GetType(); if (!PolymorphicJsonConverter.IsSerializationSupportedFor(actualType)) { throw PolymorphicJsonConverter.NotSupported(); } writer.WriteStartObject(); writer.WritePropertyName(PolymorphicJsonConverter.TypeProperty); writer.WriteValue(actualType.Name); JObject jValue; lock (this) { _canWrite = false; jValue = JObject.FromObject(value); _canWrite = true; } foreach (var property in jValue.Properties()) { property.WriteTo(writer); } writer.WriteEndObject(); } }