From 64de115c595bc39f7dc5396daef058c15bbb944a Mon Sep 17 00:00:00 2001 From: Jerry Maine Date: Wed, 27 May 2020 21:59:57 -0500 Subject: [PATCH] Support for overriding enum field name in JsonConverterEnum (#31081) --- .../System.Text.Json/ref/System.Text.Json.cs | 6 +++ .../src/System.Text.Json.csproj | 3 +- .../JsonStringEnumMemberAttribute.cs | 28 ++++++++++ .../Converters/Value/EnumConverter.cs | 52 ++++++++++++++++--- .../tests/Serialization/EnumConverterTests.cs | 27 ++++++++++ 5 files changed, 108 insertions(+), 8 deletions(-) create mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonStringEnumMemberAttribute.cs diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.cs index 4aa8377c12746c..f311bd6e3f2915 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -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() { } diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj index 7fcf718583ae6b..00be0dcafac57a 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -1,4 +1,4 @@ - + true $(NetCoreAppCurrent);netstandard2.0;netcoreapp3.0;net461;$(NetFrameworkCurrent) @@ -62,6 +62,7 @@ + diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonStringEnumMemberAttribute.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonStringEnumMemberAttribute.cs new file mode 100644 index 00000000000000..87bf51f6332e8a --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonStringEnumMemberAttribute.cs @@ -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 +{ + /// + /// Specifies the enum member that is present in the JSON when serializing and deserializing. + /// This overrides any naming policy specified by . + /// + [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] + public sealed class JsonStringEnumMemberAttribute : JsonAttribute + { + /// + /// Initializes a new instance of with the specified enum member name. + /// + /// The name of the enum member. + public JsonStringEnumMemberAttribute(string name) + { + Name = name; + } + + /// + /// The name of the enum member. + /// + public string Name { get; } + } +} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs index 5d0fb439c04f4c..56ae32dacdfab2 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs @@ -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 { @@ -19,6 +22,9 @@ internal class EnumConverter : JsonConverter private readonly EnumConverterOptions _converterOptions; private readonly JsonNamingPolicy _namingPolicy; private readonly ConcurrentDictionary? _nameCache; + private readonly ConcurrentDictionary? _enumToStringCache; + private readonly ConcurrentDictionary, T>? _stringToEnumCache; + public override bool CanConvert(Type type) { @@ -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(false); + + if (attribute != null) { + _enumToStringCache ??= new ConcurrentDictionary(); + _stringToEnumCache ??= new ConcurrentDictionary, 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)) { @@ -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)) @@ -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)) { diff --git a/src/libraries/System.Text.Json/tests/Serialization/EnumConverterTests.cs b/src/libraries/System.Text.Json/tests/Serialization/EnumConverterTests.cs index 3c15d9d0869dd8..41e318e981f437 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/EnumConverterTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/EnumConverterTests.cs @@ -185,5 +185,32 @@ public void EnumWithConverterAttribute() obj = JsonSerializer.Deserialize("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(serializedString); + Assert.Equal(e, obj); + + obj = JsonSerializer.Deserialize(serializedNumber); + Assert.Equal(e, obj); + } } }