Skip to content

Commit 4b9becc

Browse files
committed
Added deserializationFailureFallbackValue option on JsonStringEnumMemberConverter.
1 parent a953634 commit 4b9becc

File tree

5 files changed

+85
-14
lines changed

5 files changed

+85
-14
lines changed

ClassLibraries/Macross.Json.Extensions/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
`JsonStringEnumMemberConverter`.
88
([#17](https://github.com/Macross-Software/core/pull/17))
99

10+
* Added the `deserializationFailureFallbackValue` option on
11+
`JsonStringEnumMemberConverter`. See the project [README](./README.md) for
12+
details on its usage.
13+
1014
## 2.0.0
1115

1216
* `JsonMicrosoftDateTimeConverter` & `JsonMicrosoftDateTimeOffsetConverter` now

ClassLibraries/Macross.Json.Extensions/Code/System.Text.Json.Serialization/JsonStringEnumMemberConverter.cs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ public class JsonStringEnumMemberConverter : JsonConverterFactory
1010
{
1111
private readonly JsonNamingPolicy? _NamingPolicy;
1212
private readonly bool _AllowIntegerValues;
13+
private readonly ulong? _DeserializationFailureFallbackValue;
1314

1415
/// <summary>
1516
/// Initializes a new instance of the <see cref="JsonStringEnumMemberConverter"/> class.
@@ -29,10 +30,16 @@ public JsonStringEnumMemberConverter()
2930
/// True to allow undefined enum values. When true, if an enum value isn't
3031
/// defined it will output as a number rather than a string.
3132
/// </param>
32-
public JsonStringEnumMemberConverter(JsonNamingPolicy? namingPolicy = null, bool allowIntegerValues = true)
33+
/// <param name="deserializationFailureFallbackValue">
34+
/// Optional default value to use when a json string does not match
35+
/// anything defined on the target enum. If not specified a <see
36+
/// cref="JsonException"/> is thrown for all failures.
37+
/// </param>
38+
public JsonStringEnumMemberConverter(JsonNamingPolicy? namingPolicy = null, bool allowIntegerValues = true, ulong? deserializationFailureFallbackValue = null)
3339
{
3440
_NamingPolicy = namingPolicy;
3541
_AllowIntegerValues = allowIntegerValues;
42+
_DeserializationFailureFallbackValue = deserializationFailureFallbackValue;
3643
}
3744

3845
/// <inheritdoc/>
@@ -55,13 +62,13 @@ public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializer
5562
typeof(NullableEnumMemberConverter<>).MakeGenericType(UnderlyingType),
5663
BindingFlags.Instance | BindingFlags.Public,
5764
binder: null,
58-
args: new object?[] { _NamingPolicy, _AllowIntegerValues },
65+
args: new object?[] { _NamingPolicy, _AllowIntegerValues, _DeserializationFailureFallbackValue },
5966
culture: null)
6067
: (JsonConverter)Activator.CreateInstance(
6168
typeof(EnumMemberConverter<>).MakeGenericType(typeToConvert),
6269
BindingFlags.Instance | BindingFlags.Public,
6370
binder: null,
64-
args: new object?[] { _NamingPolicy, _AllowIntegerValues },
71+
args: new object?[] { _NamingPolicy, _AllowIntegerValues, _DeserializationFailureFallbackValue },
6572
culture: null);
6673
}
6774

@@ -79,9 +86,9 @@ private class EnumMemberConverter<TEnum> : JsonConverter<TEnum>
7986
{
8087
private readonly JsonStringEnumMemberConverterHelper<TEnum> _JsonStringEnumMemberConverterHelper;
8188

82-
public EnumMemberConverter(JsonNamingPolicy? namingPolicy, bool allowIntegerValues)
89+
public EnumMemberConverter(JsonNamingPolicy? namingPolicy, bool allowIntegerValues, ulong? deserializationFailureFallbackValue)
8390
{
84-
_JsonStringEnumMemberConverterHelper = new JsonStringEnumMemberConverterHelper<TEnum>(namingPolicy, allowIntegerValues);
91+
_JsonStringEnumMemberConverterHelper = new JsonStringEnumMemberConverterHelper<TEnum>(namingPolicy, allowIntegerValues, deserializationFailureFallbackValue);
8592
}
8693

8794
public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
@@ -98,9 +105,9 @@ private class NullableEnumMemberConverter<TEnum> : JsonConverter<TEnum?>
98105
{
99106
private readonly JsonStringEnumMemberConverterHelper<TEnum> _JsonStringEnumMemberConverterHelper;
100107

101-
public NullableEnumMemberConverter(JsonNamingPolicy? namingPolicy, bool allowIntegerValues)
108+
public NullableEnumMemberConverter(JsonNamingPolicy? namingPolicy, bool allowIntegerValues, ulong? deserializationFailureFallbackValue)
102109
{
103-
_JsonStringEnumMemberConverterHelper = new JsonStringEnumMemberConverterHelper<TEnum>(namingPolicy, allowIntegerValues);
110+
_JsonStringEnumMemberConverterHelper = new JsonStringEnumMemberConverterHelper<TEnum>(namingPolicy, allowIntegerValues, deserializationFailureFallbackValue);
104111
}
105112

106113
public override TEnum? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)

ClassLibraries/Macross.Json.Extensions/Code/System.Text.Json.Serialization/JsonStringEnumMemberConverterHelper.cs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Collections.Generic;
22
using System.Reflection;
3+
using System.Runtime.CompilerServices;
34
using System.Runtime.Serialization;
45
using System.Globalization;
56

@@ -33,13 +34,14 @@ public EnumInfo(string name, TEnum enumValue, ulong rawValue)
3334
#endif
3435

3536
private readonly bool _AllowIntegerValues;
37+
private readonly TEnum? _DeserializationFailureFallbackValue;
3638
private readonly Type _EnumType;
3739
private readonly TypeCode _EnumTypeCode;
3840
private readonly bool _IsFlags;
3941
private readonly Dictionary<TEnum, EnumInfo> _RawToTransformed;
4042
private readonly Dictionary<string, EnumInfo> _TransformedToRaw;
4143

42-
public JsonStringEnumMemberConverterHelper(JsonNamingPolicy? namingPolicy, bool allowIntegerValues)
44+
public JsonStringEnumMemberConverterHelper(JsonNamingPolicy? namingPolicy, bool allowIntegerValues, ulong? deserializationFailureFallbackValue)
4345
{
4446
_AllowIntegerValues = allowIntegerValues;
4547
_EnumType = typeof(TEnum);
@@ -70,6 +72,9 @@ public JsonStringEnumMemberConverterHelper(JsonNamingPolicy? namingPolicy, bool
7072
if (enumValue is not TEnum typedValue)
7173
throw new NotSupportedException();
7274

75+
if (deserializationFailureFallbackValue.HasValue && rawValue == deserializationFailureFallbackValue)
76+
_DeserializationFailureFallbackValue = typedValue;
77+
7378
_RawToTransformed[typedValue] = new EnumInfo(transformedName, typedValue, rawValue);
7479
_TransformedToRaw[transformedName] = new EnumInfo(name, typedValue, rawValue);
7580
}
@@ -118,7 +123,7 @@ public TEnum Read(ref Utf8JsonReader reader)
118123
}
119124

120125
if (!matched)
121-
throw ThrowHelper.GenerateJsonException_DeserializeUnableToConvertValue(_EnumType, flagValue);
126+
return ReturnDefaultValueOrThrowJsonException(flagValue);
122127
}
123128
}
124129

@@ -139,11 +144,18 @@ public TEnum Read(ref Utf8JsonReader reader)
139144
}
140145
}
141146

142-
throw ThrowHelper.GenerateJsonException_DeserializeUnableToConvertValue(_EnumType, enumString);
147+
return ReturnDefaultValueOrThrowJsonException(enumString);
143148
}
144149

145150
if (token != JsonTokenType.Number || !_AllowIntegerValues)
151+
{
152+
if (_DeserializationFailureFallbackValue.HasValue)
153+
{
154+
reader.Skip();
155+
return _DeserializationFailureFallbackValue.Value;
156+
}
146157
throw ThrowHelper.GenerateJsonException_DeserializeUnableToConvertValue(_EnumType);
158+
}
147159

148160
switch (_EnumTypeCode)
149161
{
@@ -292,5 +304,9 @@ private ulong GetEnumValue(object value)
292304
_ => throw new NotSupportedException($"Enum '{value}' of {_EnumTypeCode} type is not supported."),
293305
};
294306
}
307+
308+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
309+
private TEnum ReturnDefaultValueOrThrowJsonException(string propertyValue)
310+
=> _DeserializationFailureFallbackValue ?? throw ThrowHelper.GenerateJsonException_DeserializeUnableToConvertValue(_EnumType, propertyValue);
295311
}
296312
}

ClassLibraries/Macross.Json.Extensions/README.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ For a list of changes see: [CHANGELOG](./CHANGELOG.md)
1515
[JsonStringEnumMemberConverter](./Code/System.Text.Json.Serialization/JsonStringEnumMemberConverter.cs)
1616
is similar to the official
1717
[JsonStringEnumConverter](https://docs.microsoft.com/en-us/dotnet/api/system.text.json.serialization.jsonstringenumconverter)
18-
but it adds three features and fixes one bug.
18+
but it adds a few features and bug fixes.
1919

2020
* [EnumMemberAttribute](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.serialization.enummemberattribute)
2121
Support
@@ -134,6 +134,33 @@ but it adds three features and fixes one bug.
134134
}
135135
```
136136

137+
* Deserialization Failure Fallback Value
138+
139+
If a json value is received that cannot be converted into something defined
140+
on the target enum the default behavior is to throw a `JsonException`. If
141+
you would prefer to have a default definition returned instead, the
142+
`deserializationFailureFallbackValue` option is provided.
143+
144+
```csharp
145+
public enum MyEnum
146+
{
147+
Unknown = 0,
148+
149+
[EnumMember(Value = "value1")]
150+
ValidValue = 1
151+
}
152+
153+
[TestMethod]
154+
public void DeserializationWithFallbackTest()
155+
{
156+
JsonSerializerOptions Options = new JsonSerializerOptions();
157+
Options.Converters.Add(new JsonStringEnumMemberConverter(deserializationFailureFallbackValue: 0));
158+
159+
MyEnum parsedValue = JsonSerializer.Deserialize<MyEnum>(@"""value99""", Options);
160+
Assert.AreEqual(MyEnum.Unknown, parsedValue);
161+
}
162+
```
163+
137164
## TimeSpans
138165

139166
Blog:

ClassLibraries/Macross.Json.Extensions/Test/JsonStringEnumMemberConverterTests.cs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,28 @@ public void EnumMemberDeserializationTest()
3838

3939
[ExpectedException(typeof(JsonException))]
4040
[TestMethod]
41-
public void EnumMemberInvalidTypeDeserializationTest() => JsonSerializer.Deserialize<FlagDefinitions>(@"null");
41+
[DataRow("null")]
42+
[DataRow(@"""invalid_value""")]
43+
public void EnumMemberInvalidDeserializationTest(string json) => JsonSerializer.Deserialize<FlagDefinitions>(json);
4244

43-
[ExpectedException(typeof(JsonException))]
4445
[TestMethod]
45-
public void EnumMemberInvalidValueDeserializationTest() => JsonSerializer.Deserialize<FlagDefinitions>(@"""invalid_value""");
46+
public void EnumMemberInvalidDeserializationWithFallbackTest()
47+
{
48+
JsonSerializerOptions Options = new JsonSerializerOptions();
49+
Options.Converters.Add(new JsonStringEnumMemberConverter(deserializationFailureFallbackValue: (ulong)DayOfWeek.Friday));
50+
51+
DayOfWeek dayOfWeek = JsonSerializer.Deserialize<DayOfWeek>(@"""invalid_value""", Options);
52+
Assert.AreEqual(DayOfWeek.Friday, dayOfWeek);
53+
54+
DayOfWeek[]? days = JsonSerializer.Deserialize<DayOfWeek[]>(@"[{}, ""Saturday""]", Options);
55+
CollectionAssert.AreEqual(new DayOfWeek[] { DayOfWeek.Friday, DayOfWeek.Saturday }, days);
56+
57+
Options = new JsonSerializerOptions();
58+
Options.Converters.Add(new JsonStringEnumMemberConverter(deserializationFailureFallbackValue: (ulong)FlagDefinitions.None));
59+
60+
FlagDefinitions Value = JsonSerializer.Deserialize<FlagDefinitions>(@"""invalid_value""", Options);
61+
Assert.AreEqual(FlagDefinitions.None, Value);
62+
}
4663

4764
[ExpectedException(typeof(JsonException))]
4865
[TestMethod]

0 commit comments

Comments
 (0)