1
- using System . Text . Json ;
1
+ using System . Diagnostics ;
2
+ using System . Diagnostics . CodeAnalysis ;
3
+ using System . Reflection ;
4
+ using System . Runtime . Serialization ;
5
+ using System . Text . Json ;
2
6
using System . Text . Json . Serialization ;
3
- using StabilityMatrix . Core . Extensions ;
4
7
5
8
namespace StabilityMatrix . Core . Converters . Json ;
6
9
7
- public class DefaultUnknownEnumConverter < T > : JsonConverter < T >
10
+ public class DefaultUnknownEnumConverter <
11
+ [ DynamicallyAccessedMembers ( DynamicallyAccessedMemberTypes . PublicFields ) ] T
12
+ > : JsonConverter < T >
8
13
where T : Enum
9
14
{
15
+ /// <summary>
16
+ /// Lazy initialization for <see cref="EnumMemberValues"/>.
17
+ /// </summary>
18
+ private readonly Lazy < Dictionary < string , T > > _enumMemberValuesLazy =
19
+ new (
20
+ ( ) =>
21
+ typeof ( T )
22
+ . GetFields ( )
23
+ . Where ( field => field . IsStatic )
24
+ . Select (
25
+ field =>
26
+ new
27
+ {
28
+ FieldName = field . Name ,
29
+ FieldValue = ( T ) field . GetValue ( null ) ! ,
30
+ EnumMemberValue = field
31
+ . GetCustomAttributes < EnumMemberAttribute > ( false )
32
+ . FirstOrDefault ( )
33
+ ? . Value ? . ToString ( )
34
+ }
35
+ )
36
+ . ToDictionary ( x => x . EnumMemberValue ?? x . FieldName , x => x . FieldValue )
37
+ ) ;
38
+
39
+ /// <summary>
40
+ /// Gets a dictionary of enum member values, keyed by the EnumMember attribute value, or the field name if no EnumMember attribute is present.
41
+ /// </summary>
42
+ private Dictionary < string , T > EnumMemberValues => _enumMemberValuesLazy . Value ;
43
+
44
+ /// <summary>
45
+ /// Lazy initialization for <see cref="EnumMemberNames"/>.
46
+ /// </summary>
47
+ private readonly Lazy < Dictionary < T , string > > _enumMemberNamesLazy ;
48
+
49
+ /// <summary>
50
+ /// Gets a dictionary of enum member names, keyed by the enum member value.
51
+ /// </summary>
52
+ private Dictionary < T , string > EnumMemberNames => _enumMemberNamesLazy . Value ;
53
+
54
+ /// <summary>
55
+ /// Gets the value of the "Unknown" enum member, or the 0 value if no "Unknown" member is present.
56
+ /// </summary>
57
+ private T UnknownValue =>
58
+ EnumMemberValues . TryGetValue ( "Unknown" , out var res ) ? res : ( T ) Enum . ToObject ( typeof ( T ) , 0 ) ;
59
+
60
+ /// <inheritdoc />
61
+ public override bool HandleNull => true ;
62
+
63
+ public DefaultUnknownEnumConverter ( )
64
+ {
65
+ _enumMemberNamesLazy = new Lazy < Dictionary < T , string > > (
66
+ ( ) => EnumMemberValues . ToDictionary ( x => x . Value , x => x . Key )
67
+ ) ;
68
+ }
69
+
70
+ /// <inheritdoc />
10
71
public override T Read (
11
72
ref Utf8JsonReader reader ,
12
73
Type typeToConvert ,
13
74
JsonSerializerOptions options
14
75
)
15
76
{
16
- if ( reader . TokenType != JsonTokenType . String )
77
+ if ( reader . TokenType is not ( JsonTokenType . String or JsonTokenType . PropertyName ) )
17
78
{
18
- throw new JsonException ( ) ;
79
+ throw new JsonException ( "Expected String or PropertyName token" ) ;
19
80
}
20
81
21
- var enumText = reader . GetString ( ) ? . Replace ( " " , "_" ) ;
22
- if ( Enum . TryParse ( typeof ( T ) , enumText , true , out var result ) )
82
+ if ( reader . GetString ( ) is { } readerString )
23
83
{
24
- return ( T ) result ! ;
25
- }
84
+ // First try get exact match
85
+ if ( EnumMemberValues . TryGetValue ( readerString , out var enumMemberValue ) )
86
+ {
87
+ return enumMemberValue ;
88
+ }
26
89
27
- // Unknown value handling
28
- if ( Enum . TryParse ( typeof ( T ) , "Unknown" , true , out var unknownResult ) )
29
- {
30
- return ( T ) unknownResult ! ;
90
+ // Otherwise try get case-insensitive match
91
+ if (
92
+ EnumMemberValues . Keys . FirstOrDefault (
93
+ key => key . Equals ( readerString , StringComparison . OrdinalIgnoreCase )
94
+ ) is
95
+ { } enumMemberName
96
+ )
97
+ {
98
+ return EnumMemberValues [ enumMemberName ] ;
99
+ }
100
+
101
+ Debug . WriteLine ( $ "Unknown enum member value for { typeToConvert } : { readerString } ") ;
31
102
}
32
103
33
- throw new JsonException ( $ "Unable to parse ' { enumText } ' to enum ' { typeof ( T ) } '." ) ;
104
+ return UnknownValue ;
34
105
}
35
106
107
+ /// <inheritdoc />
36
108
public override void Write ( Utf8JsonWriter writer , T ? value , JsonSerializerOptions options )
37
109
{
38
110
if ( value == null )
@@ -41,49 +113,20 @@ public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOption
41
113
return ;
42
114
}
43
115
44
- writer . WriteStringValue ( value . GetStringValue ( ) . Replace ( "_" , " " ) ) ;
116
+ writer . WriteStringValue ( EnumMemberNames [ value ] ) ;
45
117
}
46
118
47
119
/// <inheritdoc />
48
120
public override T ReadAsPropertyName (
49
121
ref Utf8JsonReader reader ,
50
122
Type typeToConvert ,
51
123
JsonSerializerOptions options
52
- )
53
- {
54
- if ( reader . TokenType != JsonTokenType . PropertyName )
55
- {
56
- throw new JsonException ( ) ;
57
- }
58
-
59
- var enumText = reader . GetString ( ) ? . Replace ( " " , "_" ) ;
60
- if ( Enum . TryParse ( typeof ( T ) , enumText , true , out var result ) )
61
- {
62
- return ( T ) result ! ;
63
- }
64
-
65
- // Unknown value handling
66
- if ( Enum . TryParse ( typeof ( T ) , "Unknown" , true , out var unknownResult ) )
67
- {
68
- return ( T ) unknownResult ! ;
69
- }
70
-
71
- throw new JsonException ( $ "Unable to parse '{ enumText } ' to enum '{ typeof ( T ) } '.") ;
72
- }
124
+ ) => Read ( ref reader , typeToConvert , options ) ;
73
125
74
126
/// <inheritdoc />
75
127
public override void WriteAsPropertyName (
76
128
Utf8JsonWriter writer ,
77
129
T ? value ,
78
130
JsonSerializerOptions options
79
- )
80
- {
81
- if ( value == null )
82
- {
83
- writer . WriteNullValue ( ) ;
84
- return ;
85
- }
86
-
87
- writer . WritePropertyName ( value . GetStringValue ( ) . Replace ( "_" , " " ) ) ;
88
- }
131
+ ) => Write ( writer , value , options ) ;
89
132
}
0 commit comments