1+ using System . Reflection ;
2+ using System . Runtime . Serialization ;
3+ using System . Text . Json ;
4+ using System . Text . Json . Serialization ;
5+ using FruityFoundation . Base . Structures ;
6+
7+ namespace WebApi . Util ;
8+
9+ /// <summary>
10+ /// A serializable and deserializable value for an enum value
11+ /// </summary>
12+ [ AttributeUsage ( AttributeTargets . Field , AllowMultiple = false , Inherited = true ) ]
13+ public class JsonEnumValueAttribute : Attribute
14+ {
15+ /// <summary>
16+ /// The string value.
17+ /// </summary>
18+ public string Value { get ; }
19+ /// <summary>
20+ /// The comparison mode to use if performing string comparisons against <see cref="Value"/>.
21+ /// </summary>
22+ public StringComparison ComparisonMode { get ; }
23+
24+ /// <summary>
25+ /// A serializable and deserializable value for an enum value
26+ /// </summary>
27+ /// <param name="value">The string value of this member</param>
28+ /// <param name="comparisonMode">The comparison mode to use.</param>
29+ public JsonEnumValueAttribute ( string value , StringComparison comparisonMode = StringComparison . OrdinalIgnoreCase )
30+ {
31+ Value = value ;
32+ ComparisonMode = comparisonMode ;
33+ }
34+ }
35+
36+ /// <summary>
37+ /// Serialize and deserialize enums using string members
38+ /// </summary>
39+ public class JsonEnumConverter < T > : JsonConverter < T > where T : struct , Enum
40+ {
41+ /// <inheritdoc />
42+ public override T Read ( ref Utf8JsonReader reader , Type typeToConvert , JsonSerializerOptions options )
43+ {
44+ var jsonValue = reader . GetString ( ) ;
45+
46+ if ( jsonValue is null )
47+ throw new SerializationException ( "value was null" ) ;
48+
49+ if ( ! FindEnumByJsonValue ( jsonValue ) . Try ( out var enumValue ) )
50+ throw new SerializationException ( $ "Unable to map value to type ({ typeof ( T ) . FullName } ): { jsonValue } ") ;
51+
52+ return enumValue ;
53+ }
54+
55+ /// <inheritdoc />
56+ public override void Write ( Utf8JsonWriter writer , T value , JsonSerializerOptions options )
57+ {
58+ writer . WriteStringValue ( GetJsonValueForEnum ( value ) ) ;
59+ }
60+
61+ /// <inheritdoc />
62+ public override bool CanConvert ( Type typeToConvert ) =>
63+ typeToConvert . IsEnum ;
64+
65+ private static string GetJsonValueForEnum ( T enumValue ) =>
66+ FindJsonValueForEnum ( enumValue )
67+ . Map ( x => x . Value )
68+ . OrThrow ( $ "Missing { nameof ( JsonEnumValueAttribute ) } on member: { enumValue . GetType ( ) . FullName } .{ enumValue . ToString ( "G" ) } ") ;
69+
70+ private static Maybe < JsonEnumValueAttribute > FindJsonValueForEnum ( T enumValue )
71+ {
72+ var enumName = Enum . GetName ( enumValue . GetType ( ) , enumValue ) ?? throw new ApplicationException ( $ "Unable to find enum member { enumValue } on type { enumValue . GetType ( ) . FullName } ") ;
73+
74+ return enumValue . GetType ( )
75+ . GetMember ( enumName )
76+ . Select ( x => x . GetCustomAttribute < JsonEnumValueAttribute > ( inherit : true ) )
77+ . Where ( x => x is not null )
78+ . Cast < JsonEnumValueAttribute > ( )
79+ . FirstOrEmpty ( ) ;
80+ }
81+
82+ private static Maybe < T > FindEnumByJsonValue ( string input ) =>
83+ Enum . GetValues < T > ( )
84+ . Select ( x => ( EnumValue : x , JsonValue : FindJsonValueForEnum ( x ) ) )
85+ . FirstOrEmpty ( x => x . JsonValue . Try ( out var jsonValue ) && jsonValue . Value . Equals ( input , jsonValue . ComparisonMode ) )
86+ . Map ( x => x . EnumValue ) ;
87+ }
0 commit comments