You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
132 lines
4.3 KiB
132 lines
4.3 KiB
using System.Diagnostics; |
|
using System.Diagnostics.CodeAnalysis; |
|
using System.Reflection; |
|
using System.Runtime.Serialization; |
|
using System.Text.Json; |
|
using System.Text.Json.Serialization; |
|
|
|
namespace StabilityMatrix.Core.Converters.Json; |
|
|
|
public class DefaultUnknownEnumConverter< |
|
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T |
|
> : JsonConverter<T> |
|
where T : Enum |
|
{ |
|
/// <summary> |
|
/// Lazy initialization for <see cref="EnumMemberValues"/>. |
|
/// </summary> |
|
private readonly Lazy<Dictionary<string, T>> _enumMemberValuesLazy = |
|
new( |
|
() => |
|
typeof(T) |
|
.GetFields() |
|
.Where(field => field.IsStatic) |
|
.Select( |
|
field => |
|
new |
|
{ |
|
FieldName = field.Name, |
|
FieldValue = (T)field.GetValue(null)!, |
|
EnumMemberValue = field |
|
.GetCustomAttributes<EnumMemberAttribute>(false) |
|
.FirstOrDefault() |
|
?.Value?.ToString() |
|
} |
|
) |
|
.ToDictionary(x => x.EnumMemberValue ?? x.FieldName, x => x.FieldValue) |
|
); |
|
|
|
/// <summary> |
|
/// Gets a dictionary of enum member values, keyed by the EnumMember attribute value, or the field name if no EnumMember attribute is present. |
|
/// </summary> |
|
private Dictionary<string, T> EnumMemberValues => _enumMemberValuesLazy.Value; |
|
|
|
/// <summary> |
|
/// Lazy initialization for <see cref="EnumMemberNames"/>. |
|
/// </summary> |
|
private readonly Lazy<Dictionary<T, string>> _enumMemberNamesLazy; |
|
|
|
/// <summary> |
|
/// Gets a dictionary of enum member names, keyed by the enum member value. |
|
/// </summary> |
|
private Dictionary<T, string> EnumMemberNames => _enumMemberNamesLazy.Value; |
|
|
|
/// <summary> |
|
/// Gets the value of the "Unknown" enum member, or the 0 value if no "Unknown" member is present. |
|
/// </summary> |
|
private T UnknownValue => |
|
EnumMemberValues.TryGetValue("Unknown", out var res) ? res : (T)Enum.ToObject(typeof(T), 0); |
|
|
|
/// <inheritdoc /> |
|
public override bool HandleNull => true; |
|
|
|
public DefaultUnknownEnumConverter() |
|
{ |
|
_enumMemberNamesLazy = new Lazy<Dictionary<T, string>>( |
|
() => EnumMemberValues.ToDictionary(x => x.Value, x => x.Key) |
|
); |
|
} |
|
|
|
/// <inheritdoc /> |
|
public override T Read( |
|
ref Utf8JsonReader reader, |
|
Type typeToConvert, |
|
JsonSerializerOptions options |
|
) |
|
{ |
|
if (reader.TokenType is not (JsonTokenType.String or JsonTokenType.PropertyName)) |
|
{ |
|
throw new JsonException("Expected String or PropertyName token"); |
|
} |
|
|
|
if (reader.GetString() is { } readerString) |
|
{ |
|
// First try get exact match |
|
if (EnumMemberValues.TryGetValue(readerString, out var enumMemberValue)) |
|
{ |
|
return enumMemberValue; |
|
} |
|
|
|
// Otherwise try get case-insensitive match |
|
if ( |
|
EnumMemberValues.Keys.FirstOrDefault( |
|
key => key.Equals(readerString, StringComparison.OrdinalIgnoreCase) |
|
) is |
|
{ } enumMemberName |
|
) |
|
{ |
|
return EnumMemberValues[enumMemberName]; |
|
} |
|
|
|
Debug.WriteLine($"Unknown enum member value for {typeToConvert}: {readerString}"); |
|
} |
|
|
|
return UnknownValue; |
|
} |
|
|
|
/// <inheritdoc /> |
|
public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options) |
|
{ |
|
if (value == null) |
|
{ |
|
writer.WriteNullValue(); |
|
return; |
|
} |
|
|
|
writer.WriteStringValue(EnumMemberNames[value]); |
|
} |
|
|
|
/// <inheritdoc /> |
|
public override T ReadAsPropertyName( |
|
ref Utf8JsonReader reader, |
|
Type typeToConvert, |
|
JsonSerializerOptions options |
|
) => Read(ref reader, typeToConvert, options); |
|
|
|
/// <inheritdoc /> |
|
public override void WriteAsPropertyName( |
|
Utf8JsonWriter writer, |
|
T? value, |
|
JsonSerializerOptions options |
|
) => Write(writer, value, options); |
|
}
|
|
|