Skip to content

Commit

Permalink
Support for overriding enum field name in JsonConverterEnum (dotnet#3…
Browse files Browse the repository at this point in the history
  • Loading branch information
jmaine committed May 28, 2020
1 parent 344085b commit 64de115
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 8 deletions.
6 changes: 6 additions & 0 deletions src/libraries/System.Text.Json/ref/System.Text.Json.cs
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,12 @@ public JsonStringEnumConverter(System.Text.Json.JsonNamingPolicy? namingPolicy =
public override bool CanConvert(System.Type typeToConvert) { throw null; }
public override System.Text.Json.Serialization.JsonConverter CreateConverter(System.Type typeToConvert, System.Text.Json.JsonSerializerOptions options) { throw null; }
}
[System.AttributeUsageAttribute(System.AttributeTargets.Field, AllowMultiple=false)]
public sealed partial class JsonStringEnumMemberAttribute : System.Text.Json.Serialization.JsonAttribute
{
public JsonStringEnumMemberAttribute(string name) { }
public string Name { get { throw null; } }
}
public sealed partial class ReferenceHandling
{
internal ReferenceHandling() { }
Expand Down
3 changes: 2 additions & 1 deletion src/libraries/System.Text.Json/src/System.Text.Json.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<TargetFrameworks>$(NetCoreAppCurrent);netstandard2.0;netcoreapp3.0;net461;$(NetFrameworkCurrent)</TargetFrameworks>
Expand Down Expand Up @@ -62,6 +62,7 @@
<Compile Include="System\Text\Json\Serialization\Attributes\JsonIgnoreAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\Attributes\JsonIncludeAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\Attributes\JsonPropertyNameAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\Attributes\JsonStringEnumMemberAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\ClassType.cs" />
<Compile Include="System\Text\Json\Serialization\ConverterList.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Collection\ArrayConverter.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace System.Text.Json.Serialization
{
/// <summary>
/// Specifies the enum member that is present in the JSON when serializing and deserializing.
/// This overrides any naming policy specified by <see cref="JsonNamingPolicy"/>.
/// </summary>
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
public sealed class JsonStringEnumMemberAttribute : JsonAttribute
{
/// <summary>
/// Initializes a new instance of <see cref="JsonStringEnumMemberAttribute"/> with the specified enum member name.
/// </summary>
/// <param name="name">The name of the enum member.</param>
public JsonStringEnumMemberAttribute(string name)
{
Name = name;
}

/// <summary>
/// The name of the enum member.
/// </summary>
public string Name { get; }
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
using System.Runtime.Serialization;
using System.Linq;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Concurrent;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Reflection;

namespace System.Text.Json.Serialization.Converters
{
Expand All @@ -19,6 +22,9 @@ internal class EnumConverter<T> : JsonConverter<T>
private readonly EnumConverterOptions _converterOptions;
private readonly JsonNamingPolicy _namingPolicy;
private readonly ConcurrentDictionary<string, string>? _nameCache;
private readonly ConcurrentDictionary<T, string>? _enumToStringCache;
private readonly ConcurrentDictionary<ValueTuple<string?>, T>? _stringToEnumCache;


public override bool CanConvert(Type type)
{
Expand All @@ -42,13 +48,29 @@ public EnumConverter(EnumConverterOptions options, JsonNamingPolicy? namingPolic
namingPolicy = JsonNamingPolicy.Default;
}
_namingPolicy = namingPolicy;

if (_converterOptions.HasFlag(EnumConverterOptions.AllowStrings)) {
var enumType = typeof(T);
var fields = enumType.GetFields();
foreach (var field in fields) {
var attribute = field.GetCustomAttribute<JsonStringEnumMemberAttribute>(false);

if (attribute != null) {
_enumToStringCache ??= new ConcurrentDictionary<T, string>();
_stringToEnumCache ??= new ConcurrentDictionary<ValueTuple<string?>, T>();
var enumValue = (T) Enum.Parse(enumType, field.Name);
_enumToStringCache[enumValue] = attribute.Name;
_stringToEnumCache[ValueTuple.Create(attribute.Name)] = enumValue;
}
}
}
}

public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
JsonTokenType token = reader.TokenType;

if (token == JsonTokenType.String)
if (token == JsonTokenType.String || token == JsonTokenType.Null)
{
if (!_converterOptions.HasFlag(EnumConverterOptions.AllowStrings))
{
Expand All @@ -58,13 +80,19 @@ public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerial

// Try parsing case sensitive first
string? enumString = reader.GetString();
if (!Enum.TryParse(enumString, out T value)
&& !Enum.TryParse(enumString, ignoreCase: true, out value))
if (_stringToEnumCache != null &&_stringToEnumCache.TryGetValue(ValueTuple.Create(enumString), out T value)) {
return value;
}
else
{
ThrowHelper.ThrowJsonException();
return default;
if (!Enum.TryParse(enumString, out value)
&& !Enum.TryParse(enumString, ignoreCase: true, out value))
{
ThrowHelper.ThrowJsonException();
return default;
}
return value;
}
return value;
}

if (token != JsonTokenType.Number || !_converterOptions.HasFlag(EnumConverterOptions.AllowNumbers))
Expand Down Expand Up @@ -157,6 +185,16 @@ public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions
// If strings are allowed, attempt to write it out as a string value
if (_converterOptions.HasFlag(EnumConverterOptions.AllowStrings))
{
if (_enumToStringCache != null &&_enumToStringCache.TryGetValue(value, out string? stringValue)) {
if (stringValue != null) {
writer.WriteStringValue(stringValue);
}
else {
writer.WriteNullValue();
}
return;
}

string original = value.ToString();
if (_nameCache != null && _nameCache.TryGetValue(original, out string? transformed))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,5 +185,32 @@ public void EnumWithConverterAttribute()
obj = JsonSerializer.Deserialize<MyCustomEnum>("2");
Assert.Equal(MyCustomEnum.Second, obj);
}

[JsonConverter(typeof(JsonStringEnumConverter))]
public enum MyCustomJsonStringEnumMemberEnum
{
[System.Text.Json.Serialization.JsonStringEnumMemberAttribute("one_")]
One,
[System.Text.Json.Serialization.JsonStringEnumMemberAttribute("two_")]
Two,
[System.Text.Json.Serialization.JsonStringEnumMemberAttribute(null)]
Null
}
[InlineData("One", "\"one_\"", "0")]
[InlineData("Two", "\"two_\"", "1")]
[InlineData("Null", "null", "2")]
[Theory]
public void EnumWithJsonStringEnumMemberAttribute(string enumString, string serializedString, string serializedNumber)
{
MyCustomJsonStringEnumMemberEnum e = (MyCustomJsonStringEnumMemberEnum) Enum.Parse(typeof(MyCustomJsonStringEnumMemberEnum), enumString);
string json = JsonSerializer.Serialize(e);
Assert.Equal(serializedString, json);

MyCustomJsonStringEnumMemberEnum obj = JsonSerializer.Deserialize<MyCustomJsonStringEnumMemberEnum>(serializedString);
Assert.Equal(e, obj);

obj = JsonSerializer.Deserialize<MyCustomJsonStringEnumMemberEnum>(serializedNumber);
Assert.Equal(e, obj);
}
}
}

0 comments on commit 64de115

Please sign in to comment.