11using System . Text . Json ;
2+ using System . Text . Json . Nodes ;
23using System . Text . Json . Serialization ;
34
45namespace Limekuma . Utils ;
@@ -83,31 +84,24 @@ private class UnionJsonConverterInner<TA, TB> : JsonConverter<Union<TA, TB>>
8384 public override Union < TA , TB > Read ( ref Utf8JsonReader reader , Type typeToConvert ,
8485 JsonSerializerOptions options )
8586 {
86- try
87- {
88- TA ? valueA = JsonSerializer . Deserialize < TA > ( ref reader , options ) ;
89- return new ( valueA ) ;
90- }
91- catch ( JsonException )
92- {
93- }
94- catch ( InvalidOperationException )
87+ using JsonDocument doc = JsonDocument . ParseValue ( ref reader ) ;
88+ JsonElement element = doc . RootElement ;
89+ if ( element . ValueKind is JsonValueKind . Null )
9590 {
91+ return new ( null ) ;
9692 }
9793
98- try
94+ bool preferA = ShouldChooseA ( typeof ( TA ) , typeof ( TB ) , element . ValueKind ) ;
95+ if ( preferA )
9996 {
100- TB ? valueB = JsonSerializer . Deserialize < TB > ( ref reader , options ) ;
101- return new ( valueB ) ;
97+ TA ? a = element . Deserialize < TA > ( options ) ;
98+ return new ( a ) ;
10299 }
103- catch ( JsonException )
100+ else
104101 {
102+ TB ? b = element . Deserialize < TB > ( options ) ;
103+ return new ( b ) ;
105104 }
106- catch ( InvalidOperationException )
107- {
108- }
109-
110- return new ( null ) ;
111105 }
112106
113107 public override void Write ( Utf8JsonWriter writer , Union < TA , TB > value , JsonSerializerOptions options )
@@ -120,5 +114,123 @@ public override void Write(Utf8JsonWriter writer, Union<TA, TB> value, JsonSeria
120114
121115 JsonSerializer . Serialize ( writer , value . Value , options ) ;
122116 }
117+
118+ private static bool ShouldChooseA ( Type ta , Type tb , JsonValueKind kind )
119+ {
120+ ta = UnwrapNullable ( ta ) ;
121+ tb = UnwrapNullable ( tb ) ;
122+ switch ( kind )
123+ {
124+ case JsonValueKind . String :
125+ {
126+ bool taMatch = IsStringLike ( ta ) ;
127+ bool tbMatch = IsStringLike ( tb ) ;
128+ return taMatch switch
129+ {
130+ true when ! tbMatch => true ,
131+ false when tbMatch => false ,
132+ _ => true
133+ } ;
134+ }
135+ case JsonValueKind . Number :
136+ {
137+ bool taMatch = IsNumericLike ( ta ) || ta . IsEnum ;
138+ bool tbMatch = IsNumericLike ( tb ) || tb . IsEnum ;
139+ return taMatch switch
140+ {
141+ true when ! tbMatch => true ,
142+ false when tbMatch => false ,
143+ _ => true
144+ } ;
145+ }
146+ case JsonValueKind . True :
147+ case JsonValueKind . False :
148+ {
149+ bool taMatch = IsBooleanLike ( ta ) ;
150+ bool tbMatch = IsBooleanLike ( tb ) ;
151+ return taMatch switch
152+ {
153+ true when ! tbMatch => true ,
154+ false when tbMatch => false ,
155+ _ => true
156+ } ;
157+ }
158+ case JsonValueKind . Array :
159+ {
160+ bool taMatch = IsArrayLike ( ta ) ;
161+ bool tbMatch = IsArrayLike ( tb ) ;
162+ if ( taMatch && ! tbMatch ) return true ;
163+ if ( ! taMatch && tbMatch ) return false ;
164+ return true ;
165+ }
166+ case JsonValueKind . Object :
167+ {
168+ bool taMatch = IsObjectLike ( ta ) ;
169+ bool tbMatch = IsObjectLike ( tb ) ;
170+ if ( taMatch && ! tbMatch ) return true ;
171+ if ( ! taMatch && tbMatch ) return false ;
172+ return true ;
173+ }
174+ case JsonValueKind . Undefined :
175+ case JsonValueKind . Null :
176+ default :
177+ return true ;
178+ }
179+ }
180+
181+ private static Type UnwrapNullable ( Type t ) => Nullable . GetUnderlyingType ( t ) ?? t ;
182+
183+ private static bool IsStringLike ( Type t )
184+ {
185+ if ( t == typeof ( string ) ) return true ;
186+ if ( t == typeof ( Guid ) ) return true ;
187+ if ( t == typeof ( DateTime ) ) return true ;
188+ if ( t == typeof ( DateTimeOffset ) ) return true ;
189+ if ( t . FullName is "System.DateOnly" ) return true ;
190+ if ( t . FullName is "System.TimeOnly" ) return true ;
191+ if ( t == typeof ( Uri ) ) return true ;
192+ return false ;
193+ }
194+
195+ private static bool IsNumericLike ( Type t )
196+ {
197+ TypeCode code = Type . GetTypeCode ( t ) ;
198+ return code switch
199+ {
200+ TypeCode . Byte or TypeCode . SByte or TypeCode . Int16 or TypeCode . UInt16 or TypeCode . Int32 or TypeCode . UInt32 or TypeCode . Int64 or TypeCode . UInt64 or TypeCode . Single or TypeCode . Double or TypeCode . Decimal => true ,
201+ _ => false ,
202+ } ;
203+ }
204+
205+ private static bool IsBooleanLike ( Type t ) => t == typeof ( bool ) ;
206+
207+ private static bool IsArrayLike ( Type t )
208+ {
209+ if ( t == typeof ( string ) ) return false ;
210+ if ( t . IsArray ) return true ;
211+ if ( typeof ( System . Collections . IDictionary ) . IsAssignableFrom ( t ) ) return false ;
212+ foreach ( Type i in t . GetInterfaces ( ) )
213+ {
214+ switch ( i . IsGenericType )
215+ {
216+ case true when i . GetGenericTypeDefinition ( ) == typeof ( IDictionary < , > ) :
217+ return false ;
218+ case true when i . GetGenericTypeDefinition ( ) == typeof ( IEnumerable < > ) :
219+ return true ;
220+ }
221+ }
222+
223+ return typeof ( System . Collections . IEnumerable ) . IsAssignableFrom ( t ) &&
224+ ! typeof ( System . Collections . IDictionary ) . IsAssignableFrom ( t ) ;
225+ }
226+
227+ private static bool IsObjectLike ( Type t )
228+ {
229+ if ( IsStringLike ( t ) ) return false ;
230+ if ( IsNumericLike ( t ) || t . IsEnum ) return false ;
231+ if ( IsBooleanLike ( t ) ) return false ;
232+ if ( IsArrayLike ( t ) ) return false ;
233+ return true ;
234+ }
123235 }
124- }
236+ }
0 commit comments