using System; using System.Collections.Generic; using System.Reflection; using Newtonsoft.Json; #if NET6_0_OR_GREATER using System.Diagnostics.CodeAnalysis; #endif namespace MLEM.Data.Json { /// /// A that doesn't actually serialize the object, but instead serializes the name given to it by the underlying . /// Optionally, the name of a can be passed to this converter when used in the by passing the arguments for the constructor as . /// /// The type of the object to convert public class StaticJsonConverter : JsonConverter { private readonly Dictionary entries; private readonly Dictionary inverse; private readonly bool throwOnRead; /// /// Creates a new static json converter using the given underlying . /// /// The dictionary to use /// Whether to throw a in if a key is missing, or throw a if a stored json value is not a string. If this is , returns instead. public StaticJsonConverter(Dictionary entries, bool throwOnRead = false) { this.entries = entries; this.inverse = StaticJsonConverter.CreateInverse(entries); this.throwOnRead = throwOnRead; } /// /// Creates a new static json converter by finding the underlying from the given type and member name /// /// The type that the dictionary is declared in /// The name of the dictionary itself public StaticJsonConverter( #if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)] #endif Type type, string memberName) : this(StaticJsonConverter.GetEntries(type, memberName)) {} /// /// Creates a new static json converter by finding the underlying from the given type and member name /// /// The type that the dictionary is declared in /// The name of the dictionary itself /// Whether to throw a in if a key is missing, or throw a if a stored json value is not a string. If this is , returns instead. public StaticJsonConverter( #if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)] #endif Type type, string memberName, bool throwOnRead) : this(StaticJsonConverter.GetEntries(type, memberName), throwOnRead) {} /// Writes the JSON representation of the object. /// The to write to. /// The value. /// The calling serializer. public override void WriteJson(JsonWriter writer, T value, JsonSerializer serializer) { if (value == null) { writer.WriteNull(); return; } if (!this.inverse.TryGetValue(value, out var key)) throw new KeyNotFoundException($"Cannot write {value} that is not a registered entry"); writer.WriteValue(key); } /// Reads the JSON representation of the object. /// The to read from. /// Type of the object. /// The existing value of object being read. If there is no existing value then null will be used. /// The existing value has a value. /// The calling serializer. /// The object value. public override T ReadJson(JsonReader reader, Type objectType, T existingValue, bool hasExistingValue, JsonSerializer serializer) { if (reader.Value == null) return default; if (reader.TokenType != JsonToken.String) { if (this.throwOnRead) throw new JsonSerializationException($"Expected a string value for {reader.Value}, got a {reader.TokenType}"); return default; } if (!this.entries.TryGetValue((string) reader.Value, out var ret) && this.throwOnRead) throw new KeyNotFoundException($"Could not find registered entry for {reader.Value}"); return ret; } private static Dictionary GetEntries( #if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)] #endif Type type, string memberName) { const BindingFlags flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; var value = type.GetProperty(memberName, flags)?.GetValue(null) ?? type.GetField(memberName, flags)?.GetValue(null); if (value == null) throw new ArgumentException($"There is no property or field value for name {memberName}", nameof(memberName)); return value as Dictionary ?? throw new InvalidCastException($"{value} is not of expected type {typeof(T)}"); } private static Dictionary CreateInverse(Dictionary entries) { var ret = new Dictionary(); foreach (var entry in entries) { if (ret.ContainsKey(entry.Value)) throw new ArgumentException($"Cannot create a static json converter with duplicate value {entry.Value}"); ret.Add(entry.Value, entry.Key); } return ret; } } }