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 {
///
/// The entries, or registry, that this static json converter uses.
///
protected readonly Dictionary Entries;
///
/// The inverse of this static json converter's , mapping the values to their respective keys.
/// Note that this dictionary is only created once and is not automatically updated should the be modified.
///
protected readonly Dictionary Inverse;
///
/// 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.
///
protected readonly bool ThrowOnRead;
///
/// Creates a new static json converter using the given underlying .
///
/// The entries, or registry, that this static json converter uses.
/// 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;
}
///
/// Returns the entries, or registry, that this static json converter should use, using reflection to find the passed in the passed .
///
/// The type to find the entries in.
/// The name of the field or property that the entries are in within the given .
/// The found property or field's value.
/// Thrown if there is no property or field with the in the given .
/// Thrown if the value of the is not of the expected type.
protected 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)}");
}
///
/// Creates the inverse version of the passed , mapping the values to their keys, and throws an exception if there are any duplicate values.
///
/// The entries to create an inverse of.
/// The inverse of the given , mapping the values to their keys.
/// Thrown if the contains duplicate values, making it impossible to create a valid inverse.
protected 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;
}
}
}