Skip to content

Commit e83ab8a

Browse files
committed
Added more options for... options on JsonStringEnumMemberConverter.
1 parent 0a94b24 commit e83ab8a

File tree

10 files changed

+475
-74
lines changed

10 files changed

+475
-74
lines changed

ClassLibraries/Macross.Json.Extensions/CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@
1111
`JsonStringEnumMemberConverter`. See the project [README](./README.md) for
1212
details on its usage.
1313

14+
* Added a constructor on `JsonStringEnumMemberConverter` which accepts
15+
`JsonStringEnumMemberConverterOptions options` & `params Type[]
16+
targetEnumTypes` parameters for specifying the options to be used to
17+
serialize/deserialize the specific target enum types.
18+
19+
* Added `JsonStringEnumMemberConverterOptionsAttribute` which can be used to
20+
decorate an enum type with the options to use when serializing/deserializing
21+
its values.
22+
1423
## 2.0.0
1524

1625
* `JsonMicrosoftDateTimeConverter` & `JsonMicrosoftDateTimeOffsetConverter` now

ClassLibraries/Macross.Json.Extensions/Code/Macross.Json.Extensions.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
<GenerateAssemblyVersionAttribute>false</GenerateAssemblyVersionAttribute>
2525
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
2626
<VersionPrefix>2.1.0</VersionPrefix>
27-
<VersionSuffix>beta1</VersionSuffix>
27+
<VersionSuffix>beta2</VersionSuffix>
2828
<FileVersion>$(VersionPrefix)</FileVersion>
2929
<PackageProjectUrl>https://github.com/Macross-Software/core/tree/develop/ClassLibraries/Macross.Json.Extensions</PackageProjectUrl>
3030
</PropertyGroup>
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
using System;
22
using System.Reflection;
3+
using System.Runtime.CompilerServices;
34
using System.Runtime.InteropServices;
45

56
[assembly: ComVisible(false)]
67
[assembly: CLSCompliant(false)]
78

89
[assembly: Guid("46de8812-8b05-40b9-8012-7702f9f31506")]
910

10-
[assembly: AssemblyVersion("2.1.0.21073")]
11+
[assembly: InternalsVisibleTo("Macross.Json.Extensions.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010051b7a480b13cecfa44862449486c6884bd6168c325445a0848f48deca9643657c5ae85df3cf6ffdb24d5bd3e9b71dc074ca602544b83511fbce1f83f1d06bb8b7b564414c9d8c719e4e39b95643dfc8e9ce997b5e2a1542a8ff6379186f87b8b695fee82c506170c4fb8ffcbf2e68f4b5d270083f8909c67916500608ce747e9")]
12+
13+
[assembly: AssemblyVersion("2.1.0.21074")]

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

Lines changed: 94 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Reflection;
1+
using System.Collections.Generic;
2+
using System.Reflection;
23
using System.Runtime.Serialization;
34

45
namespace System.Text.Json.Serialization
@@ -8,15 +9,13 @@ namespace System.Text.Json.Serialization
89
/// </summary>
910
public class JsonStringEnumMemberConverter : JsonConverterFactory
1011
{
11-
private readonly JsonNamingPolicy? _NamingPolicy;
12-
private readonly bool _AllowIntegerValues;
13-
private readonly ulong? _DeserializationFailureFallbackValue;
12+
private readonly HashSet<Type>? _EnumTypes;
13+
private readonly JsonStringEnumMemberConverterOptions? _Options;
1414

1515
/// <summary>
1616
/// Initializes a new instance of the <see cref="JsonStringEnumMemberConverter"/> class.
1717
/// </summary>
1818
public JsonStringEnumMemberConverter()
19-
: this(namingPolicy: null, allowIntegerValues: true)
2019
{
2120
}
2221

@@ -30,46 +29,91 @@ public JsonStringEnumMemberConverter()
3029
/// True to allow undefined enum values. When true, if an enum value isn't
3130
/// defined it will output as a number rather than a string.
3231
/// </param>
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)
32+
public JsonStringEnumMemberConverter(JsonNamingPolicy? namingPolicy = null, bool allowIntegerValues = true)
33+
: this(new JsonStringEnumMemberConverterOptions { NamingPolicy = namingPolicy, AllowIntegerValues = allowIntegerValues })
3934
{
40-
_NamingPolicy = namingPolicy;
41-
_AllowIntegerValues = allowIntegerValues;
42-
_DeserializationFailureFallbackValue = deserializationFailureFallbackValue;
4335
}
4436

37+
/// <summary>
38+
/// Initializes a new instance of the <see cref="JsonStringEnumMemberConverter"/> class.
39+
/// </summary>
40+
/// <param name="options"><see cref="JsonStringEnumMemberConverterOptions"/>.</param>
41+
/// <param name="targetEnumTypes">Optional list of supported enum types to be converted. Specify <see langword="null"/> or empty to convert all enums.</param>
42+
public JsonStringEnumMemberConverter(JsonStringEnumMemberConverterOptions options, params Type[] targetEnumTypes)
43+
{
44+
_Options = options ?? throw new ArgumentNullException(nameof(options));
45+
46+
if (targetEnumTypes != null && targetEnumTypes.Length > 0)
47+
{
48+
#if NETSTANDARD2_0
49+
_EnumTypes = new HashSet<Type>();
50+
#else
51+
_EnumTypes = new HashSet<Type>(targetEnumTypes.Length);
52+
#endif
53+
foreach (Type enumType in targetEnumTypes)
54+
{
55+
if (enumType.IsEnum)
56+
{
57+
_EnumTypes.Add(enumType);
58+
_EnumTypes.Add(typeof(Nullable<>).MakeGenericType(enumType));
59+
continue;
60+
}
61+
62+
if (enumType.IsGenericType)
63+
{
64+
(bool IsNullableEnum, Type? UnderlyingType) = TestNullableEnum(enumType);
65+
if (IsNullableEnum)
66+
{
67+
_EnumTypes.Add(UnderlyingType!);
68+
_EnumTypes.Add(enumType);
69+
continue;
70+
}
71+
}
72+
73+
throw new NotSupportedException($"Type {enumType} is not supported by JsonStringEnumMemberConverter. Only enum types can be converted.");
74+
}
75+
}
76+
}
77+
78+
#pragma warning disable CA1062 // Validate arguments of public methods
4579
/// <inheritdoc/>
4680
public override bool CanConvert(Type typeToConvert)
4781
{
4882
// Don't perform a typeToConvert == null check for performance. Trust our callers will be nice.
49-
#pragma warning disable CA1062 // Validate arguments of public methods
50-
return typeToConvert.IsEnum
83+
return _EnumTypes != null
84+
? _EnumTypes.Contains(typeToConvert)
85+
: typeToConvert.IsEnum
5186
|| (typeToConvert.IsGenericType && TestNullableEnum(typeToConvert).IsNullableEnum);
52-
#pragma warning restore CA1062 // Validate arguments of public methods
5387
}
88+
#pragma warning restore CA1062 // Validate arguments of public methods
5489

5590
/// <inheritdoc/>
5691
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
5792
{
5893
(bool IsNullableEnum, Type? UnderlyingType) = TestNullableEnum(typeToConvert);
5994

60-
return IsNullableEnum
61-
? (JsonConverter)Activator.CreateInstance(
62-
typeof(NullableEnumMemberConverter<>).MakeGenericType(UnderlyingType),
63-
BindingFlags.Instance | BindingFlags.Public,
64-
binder: null,
65-
args: new object?[] { _NamingPolicy, _AllowIntegerValues, _DeserializationFailureFallbackValue },
66-
culture: null)
67-
: (JsonConverter)Activator.CreateInstance(
68-
typeof(EnumMemberConverter<>).MakeGenericType(typeToConvert),
69-
BindingFlags.Instance | BindingFlags.Public,
70-
binder: null,
71-
args: new object?[] { _NamingPolicy, _AllowIntegerValues, _DeserializationFailureFallbackValue },
72-
culture: null);
95+
try
96+
{
97+
return IsNullableEnum
98+
? (JsonConverter)Activator.CreateInstance(
99+
typeof(NullableEnumMemberConverter<>).MakeGenericType(UnderlyingType),
100+
BindingFlags.Instance | BindingFlags.Public,
101+
binder: null,
102+
args: new object?[] { _Options },
103+
culture: null)
104+
: (JsonConverter)Activator.CreateInstance(
105+
typeof(EnumMemberConverter<>).MakeGenericType(typeToConvert),
106+
BindingFlags.Instance | BindingFlags.Public,
107+
binder: null,
108+
args: new object?[] { _Options },
109+
culture: null);
110+
}
111+
catch (TargetInvocationException targetInvocationEx)
112+
{
113+
if (targetInvocationEx.InnerException != null)
114+
throw targetInvocationEx.InnerException;
115+
throw;
116+
}
73117
}
74118

75119
private static (bool IsNullableEnum, Type? UnderlyingType) TestNullableEnum(Type typeToConvert)
@@ -79,16 +123,32 @@ private static (bool IsNullableEnum, Type? UnderlyingType) TestNullableEnum(Type
79123
return (UnderlyingType?.IsEnum ?? false, UnderlyingType);
80124
}
81125

126+
internal static ulong GetEnumValue(TypeCode enumTypeCode, object value)
127+
{
128+
return enumTypeCode switch
129+
{
130+
TypeCode.Int32 => (ulong)(int)value,
131+
TypeCode.Int64 => (ulong)(long)value,
132+
TypeCode.Int16 => (ulong)(short)value,
133+
TypeCode.Byte => (byte)value,
134+
TypeCode.UInt32 => (uint)value,
135+
TypeCode.UInt64 => (ulong)value,
136+
TypeCode.UInt16 => (ushort)value,
137+
TypeCode.SByte => (ulong)(sbyte)value,
138+
_ => throw new NotSupportedException($"Enum '{value}' of {enumTypeCode} type is not supported."),
139+
};
140+
}
141+
82142
#pragma warning disable CA1812 // Remove class never instantiated
83143
private class EnumMemberConverter<TEnum> : JsonConverter<TEnum>
84144
where TEnum : struct, Enum
85145
#pragma warning restore CA1812 // Remove class never instantiated
86146
{
87147
private readonly JsonStringEnumMemberConverterHelper<TEnum> _JsonStringEnumMemberConverterHelper;
88148

89-
public EnumMemberConverter(JsonNamingPolicy? namingPolicy, bool allowIntegerValues, ulong? deserializationFailureFallbackValue)
149+
public EnumMemberConverter(JsonStringEnumMemberConverterOptions? options)
90150
{
91-
_JsonStringEnumMemberConverterHelper = new JsonStringEnumMemberConverterHelper<TEnum>(namingPolicy, allowIntegerValues, deserializationFailureFallbackValue);
151+
_JsonStringEnumMemberConverterHelper = new JsonStringEnumMemberConverterHelper<TEnum>(options);
92152
}
93153

94154
public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
@@ -105,9 +165,9 @@ private class NullableEnumMemberConverter<TEnum> : JsonConverter<TEnum?>
105165
{
106166
private readonly JsonStringEnumMemberConverterHelper<TEnum> _JsonStringEnumMemberConverterHelper;
107167

108-
public NullableEnumMemberConverter(JsonNamingPolicy? namingPolicy, bool allowIntegerValues, ulong? deserializationFailureFallbackValue)
168+
public NullableEnumMemberConverter(JsonStringEnumMemberConverterOptions? options)
109169
{
110-
_JsonStringEnumMemberConverterHelper = new JsonStringEnumMemberConverterHelper<TEnum>(namingPolicy, allowIntegerValues, deserializationFailureFallbackValue);
170+
_JsonStringEnumMemberConverterHelper = new JsonStringEnumMemberConverterHelper<TEnum>(options);
111171
}
112172

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

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

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,20 @@ public EnumInfo(string name, TEnum enumValue, ulong rawValue)
4141
private readonly Dictionary<TEnum, EnumInfo> _RawToTransformed;
4242
private readonly Dictionary<string, EnumInfo> _TransformedToRaw;
4343

44-
public JsonStringEnumMemberConverterHelper(JsonNamingPolicy? namingPolicy, bool allowIntegerValues, ulong? deserializationFailureFallbackValue)
44+
public JsonStringEnumMemberConverterHelper(JsonStringEnumMemberConverterOptions? options)
4545
{
46-
_AllowIntegerValues = allowIntegerValues;
4746
_EnumType = typeof(TEnum);
47+
48+
JsonStringEnumMemberConverterOptions? computedOptions
49+
= _EnumType.GetCustomAttribute<JsonStringEnumMemberConverterOptionsAttribute>(false)?.Options
50+
?? options;
51+
52+
_AllowIntegerValues = computedOptions?.AllowIntegerValues ?? true;
4853
_EnumTypeCode = Type.GetTypeCode(_EnumType);
4954
_IsFlags = _EnumType.IsDefined(typeof(FlagsAttribute), true);
5055

56+
ulong? deserializationFailureFallbackValue = computedOptions?.ConvertedDeserializationFailureFallbackValue;
57+
5158
string[] builtInNames = _EnumType.GetEnumNames();
5259
Array builtInValues = _EnumType.GetEnumValues();
5360

@@ -59,14 +66,14 @@ public JsonStringEnumMemberConverterHelper(JsonNamingPolicy? namingPolicy, bool
5966
Enum? enumValue = (Enum?)builtInValues.GetValue(i);
6067
if (enumValue == null)
6168
continue;
62-
ulong rawValue = GetEnumValue(enumValue);
69+
ulong rawValue = JsonStringEnumMemberConverter.GetEnumValue(_EnumTypeCode, enumValue);
6370

6471
string name = builtInNames[i];
6572
FieldInfo field = _EnumType.GetField(name, EnumBindings)!;
6673

6774
string transformedName = field.GetCustomAttribute<EnumMemberAttribute>(true)?.Value ??
6875
field.GetCustomAttribute<JsonPropertyNameAttribute>(true)?.Name ??
69-
namingPolicy?.ConvertName(name) ??
76+
computedOptions?.NamingPolicy?.ConvertName(name) ??
7077
name;
7178

7279
if (enumValue is not TEnum typedValue)
@@ -78,6 +85,9 @@ public JsonStringEnumMemberConverterHelper(JsonNamingPolicy? namingPolicy, bool
7885
_RawToTransformed[typedValue] = new EnumInfo(transformedName, typedValue, rawValue);
7986
_TransformedToRaw[transformedName] = new EnumInfo(name, typedValue, rawValue);
8087
}
88+
89+
if (deserializationFailureFallbackValue.HasValue && !_DeserializationFailureFallbackValue.HasValue)
90+
throw new JsonException($"JsonStringEnumMemberConverter could not find a definition on Enum type {_EnumType} matching deserializationFailureFallbackValue '{deserializationFailureFallbackValue}'.");
8191
}
8292

8393
public TEnum Read(ref Utf8JsonReader reader)
@@ -220,7 +230,7 @@ public void Write(Utf8JsonWriter writer, TEnum value)
220230
return;
221231
}
222232

223-
ulong rawValue = GetEnumValue(value);
233+
ulong rawValue = JsonStringEnumMemberConverter.GetEnumValue(_EnumTypeCode, value);
224234

225235
if (_IsFlags)
226236
{
@@ -289,22 +299,6 @@ public void Write(Utf8JsonWriter writer, TEnum value)
289299
}
290300
}
291301

292-
private ulong GetEnumValue(object value)
293-
{
294-
return _EnumTypeCode switch
295-
{
296-
TypeCode.Int32 => (ulong)(int)value,
297-
TypeCode.Int64 => (ulong)(long)value,
298-
TypeCode.Int16 => (ulong)(short)value,
299-
TypeCode.Byte => (byte)value,
300-
TypeCode.UInt32 => (uint)value,
301-
TypeCode.UInt64 => (ulong)value,
302-
TypeCode.UInt16 => (ushort)value,
303-
TypeCode.SByte => (ulong)(sbyte)value,
304-
_ => throw new NotSupportedException($"Enum '{value}' of {_EnumTypeCode} type is not supported."),
305-
};
306-
}
307-
308302
[MethodImpl(MethodImplOptions.AggressiveInlining)]
309303
private TEnum ReturnDefaultValueOrThrowJsonException(string propertyValue)
310304
=> _DeserializationFailureFallbackValue ?? throw ThrowHelper.GenerateJsonException_DeserializeUnableToConvertValue(_EnumType, propertyValue);

0 commit comments

Comments
 (0)