Skip to content

Commit 83b8aa2

Browse files
committed
refactor(UnionJsonConverter): Simplify JSON deserialization logic and error handling
1 parent a78219c commit 83b8aa2

File tree

1 file changed

+131
-19
lines changed

1 file changed

+131
-19
lines changed

src/Utils/Union.cs

Lines changed: 131 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Text.Json;
2+
using System.Text.Json.Nodes;
23
using System.Text.Json.Serialization;
34

45
namespace 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

Comments
 (0)