2021-03-13 03:15:39 +01:00
using System ;
using System.Collections.Generic ;
using System.Reflection ;
using Newtonsoft.Json ;
2022-10-31 18:33:53 +01:00
#if NET6_0_OR_GREATER
using System.Diagnostics.CodeAnalysis ;
#endif
2021-03-13 03:15:39 +01:00
namespace MLEM.Data.Json {
/// <summary>
2022-10-31 18:33:53 +01:00
/// A <see cref="JsonConverter{T}"/> that doesn't actually serialize the object, but instead serializes the name given to it by the underlying <see cref="Dictionary{T,T}"/>.
2021-03-13 03:20:38 +01:00
/// Optionally, the name of a <see cref="Dictionary{TKey,TValue}"/> can be passed to this converter when used in the <see cref="JsonConverterAttribute"/> by passing the arguments for the <see cref="StaticJsonConverter{T}(Type,string)"/> constructor as <see cref="JsonConverterAttribute.ConverterParameters"/>.
2021-03-13 03:15:39 +01:00
/// </summary>
/// <typeparam name="T">The type of the object to convert</typeparam>
public class StaticJsonConverter < T > : JsonConverter < T > {
private readonly Dictionary < string , T > entries ;
private readonly Dictionary < T , string > inverse ;
2024-07-19 21:10:30 +02:00
private readonly bool throwOnRead ;
2021-03-13 03:15:39 +01:00
/// <summary>
/// Creates a new static json converter using the given underlying <see cref="Dictionary{T,T}"/>.
/// </summary>
/// <param name="entries">The dictionary to use</param>
2024-07-19 21:10:30 +02:00
/// <param name="throwOnRead">Whether to throw a <see cref="KeyNotFoundException"/> in <see cref="ReadJson"/> if a key is missing, or throw a <see cref="JsonSerializationException"/> if a stored json value is not a string. If this is <see langword="false"/>, <see cref="ReadJson"/> returns <see langword="default"/> instead.</param>
public StaticJsonConverter ( Dictionary < string , T > entries , bool throwOnRead = false ) {
2021-03-13 03:15:39 +01:00
this . entries = entries ;
2024-07-19 21:10:30 +02:00
this . inverse = StaticJsonConverter < T > . CreateInverse ( entries ) ;
this . throwOnRead = throwOnRead ;
2021-03-13 03:15:39 +01:00
}
/// <summary>
/// Creates a new static json converter by finding the underlying <see cref="Dictionary{TKey,TValue}"/> from the given type and member name
/// </summary>
2021-03-13 03:20:38 +01:00
/// <param name="type">The type that the dictionary is declared in</param>
2021-03-13 03:15:39 +01:00
/// <param name="memberName">The name of the dictionary itself</param>
2022-10-31 18:33:53 +01:00
public StaticJsonConverter (
2022-12-13 13:11:36 +01:00
#if NET6_0_OR_GREATER
2022-10-31 18:33:53 +01:00
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)]
2022-12-13 13:11:36 +01:00
#endif
2022-10-31 18:33:53 +01:00
Type type , string memberName ) : this ( StaticJsonConverter < T > . GetEntries ( type , memberName ) ) { }
2021-03-13 03:15:39 +01:00
2024-07-19 21:10:30 +02:00
/// <summary>
/// Creates a new static json converter by finding the underlying <see cref="Dictionary{TKey,TValue}"/> from the given type and member name
/// </summary>
/// <param name="type">The type that the dictionary is declared in</param>
/// <param name="memberName">The name of the dictionary itself</param>
/// <param name="throwOnRead">Whether to throw a <see cref="KeyNotFoundException"/> in <see cref="ReadJson"/> if a key is missing, or throw a <see cref="JsonSerializationException"/> if a stored json value is not a string. If this is <see langword="false"/>, <see cref="ReadJson"/> returns <see langword="default"/> instead.</param>
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 < T > . GetEntries ( type , memberName ) , throwOnRead ) { }
2021-11-22 19:25:18 +01:00
/// <summary>Writes the JSON representation of the object.</summary>
/// <param name="writer">The <see cref="T:Newtonsoft.Json.JsonWriter" /> to write to.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param>
2021-03-13 03:15:39 +01:00
public override void WriteJson ( JsonWriter writer , T value , JsonSerializer serializer ) {
2024-07-19 21:35:47 +02:00
if ( value = = null ) {
writer . WriteNull ( ) ;
return ;
}
2021-03-13 03:15:39 +01:00
if ( ! this . inverse . TryGetValue ( value , out var key ) )
2024-07-19 21:35:47 +02:00
throw new KeyNotFoundException ( $"Cannot write {value} that is not a registered entry" ) ;
2021-03-13 03:15:39 +01:00
writer . WriteValue ( key ) ;
}
2021-11-22 19:25:18 +01:00
/// <summary>Reads the JSON representation of the object.</summary>
/// <param name="reader">The <see cref="T:Newtonsoft.Json.JsonReader" /> to read from.</param>
/// <param name="objectType">Type of the object.</param>
/// <param name="existingValue">The existing value of object being read. If there is no existing value then <c>null</c> will be used.</param>
/// <param name="hasExistingValue">The existing value has a value.</param>
/// <param name="serializer">The calling serializer.</param>
/// <returns>The object value.</returns>
2021-03-13 03:15:39 +01:00
public override T ReadJson ( JsonReader reader , Type objectType , T existingValue , bool hasExistingValue , JsonSerializer serializer ) {
2024-07-19 21:10:30 +02:00
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}" ) ;
2021-03-13 03:15:39 +01:00
return default ;
2024-07-19 21:10:30 +02:00
}
if ( ! this . entries . TryGetValue ( ( string ) reader . Value , out var ret ) & & this . throwOnRead )
throw new KeyNotFoundException ( $"Could not find registered entry for {reader.Value}" ) ;
2021-03-13 03:15:39 +01:00
return ret ;
}
2022-10-31 18:33:53 +01:00
private static Dictionary < string , T > GetEntries (
2022-12-13 13:11:36 +01:00
#if NET6_0_OR_GREATER
2022-10-31 18:33:53 +01:00
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)]
2022-12-13 13:11:36 +01:00
#endif
2022-10-31 18:33:53 +01:00
Type type , string memberName ) {
2021-03-13 03:15:39 +01:00
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 < string , T > ? ? throw new InvalidCastException ( $"{value} is not of expected type {typeof(T)}" ) ;
}
2024-07-19 21:10:30 +02:00
private static Dictionary < T , string > CreateInverse ( Dictionary < string , T > entries ) {
var ret = new Dictionary < T , string > ( ) ;
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 ;
}
2021-03-13 03:15:39 +01:00
}
2022-06-17 18:23:47 +02:00
}