Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

internal sealed class EnumConverter<T>.Read() #63893

Closed
bobbyangers opened this issue Jan 17, 2022 · 5 comments
Closed

internal sealed class EnumConverter<T>.Read() #63893

bobbyangers opened this issue Jan 17, 2022 · 5 comments
Labels
area-System.Text.Json untriaged New issue has not been triaged by the area owner

Comments

@bobbyangers
Copy link

Description

The Read method should consider the _namingPolicy when reading a value the source string; as per the Write

internal sealed class EnumConverter<T>.Read()
{
   /* ...*/
 public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            JsonTokenType token = reader.TokenType;

            if (token == JsonTokenType.String)
            {
                if (!_converterOptions.HasFlag(EnumConverterOptions.AllowStrings))
                {
                    ThrowHelper.ThrowJsonException();
                    return default;
                }

                return ReadAsPropertyNameCore(ref reader, typeToConvert, options);
            }

Reproduction Steps

        [Test]
        public void When_reading_from_a_special_enum()
        {
             var query = from field in typeToConvert.GetFields(BindingFlags.Public | BindingFlags.Static)
                    let attr = field.GetCustomAttribute<EnumMemberAttribute>()
                    where attr != null
                    select (field.Name, attr.Value);
            var dictionary = query.ToDictionary(p => p.Item1, p => p.Item2);
 
            var json_settings = new JsonSerializerOptions
            {
                PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
                Converters = { new JsonStringEnumConverter(new DictionaryLookupNamingPolicy(dictionary, JsonNamingPolicy.CamelCase), allowIntegerValues) },
            };

            var data = @"{""value"": ""F""}";

            var result = JsonSerializer.Deserialize<DemoSpecial>(data, json_settings);

            ShowResult(result);
            result!.Value.Should().Be(SpecialEnum.First);

        }

        public class DemoSpecial
        {
            public SpecialEnum Value { get; set; }
        }

        public enum SpecialEnum
        {
            Unknown,
            [EnumMember(Value = "F")]
            First,
            [EnumMember(Value = "S")]
            Second,
            [EnumMember(Value = "T")]
            Third
        }

    public class JsonNamingPolicyDecorator : JsonNamingPolicy
    {
        readonly JsonNamingPolicy underlyingNamingPolicy;

        public JsonNamingPolicyDecorator(JsonNamingPolicy underlyingNamingPolicy) => this.underlyingNamingPolicy = underlyingNamingPolicy;

        public override string ConvertName(string name) => underlyingNamingPolicy == null ? name : underlyingNamingPolicy.ConvertName(name);
    }

    internal class DictionaryLookupNamingPolicy : JsonNamingPolicyDecorator
    {
        readonly Dictionary<string, string> dictionary;

        public DictionaryLookupNamingPolicy(Dictionary<string, string> dictionary, JsonNamingPolicy underlyingNamingPolicy) : base(underlyingNamingPolicy) => this.dictionary = dictionary ?? throw new ArgumentNullException(nameof(dictionary));

        public override string ConvertName(string name) => dictionary.TryGetValue(name, out var value) ? value : base.ConvertName(name);

    }

Expected behavior

I have a DictionaryLookupNamingPolicy that extract the [EnumMember(Value="xx"] from the Enum.

When writing values out, all good.

        public class DemoSpecial
        {
            public SpecialEnum Value { get; set; }
        }

        public enum SpecialEnum
        {
            Unknown,
            [EnumMember(Value = "F")]
            First,
            [EnumMember(Value = "S")]
            Second,
            [EnumMember(Value = "T")]
            Third
        }

When reading value from { "value": "F" } it would expect to return a DemoSpecial.

Actual behavior

Currently, it fails:

The JSON value could not be converted to <namespace>.SpecialEnum. Path: $.value

Regression?

No response

Known Workarounds

Writing my own EnumConverter and override the the Read method

Configuration

No response

Other information

No response

@dotnet-issue-labeler dotnet-issue-labeler bot added area-System.Text.Json untriaged New issue has not been triaged by the area owner labels Jan 17, 2022
@ghost
Copy link

ghost commented Jan 17, 2022

Tagging subscribers to this area: @dotnet/area-system-text-json
See info in area-owners.md if you want to be subscribed.

Issue Details

Description

The Read method should consider the _namingPolicy when reading a value the source string; as per the Write

internal sealed class EnumConverter<T>.Read()
{
   /* ...*/
 public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            JsonTokenType token = reader.TokenType;

            if (token == JsonTokenType.String)
            {
                if (!_converterOptions.HasFlag(EnumConverterOptions.AllowStrings))
                {
                    ThrowHelper.ThrowJsonException();
                    return default;
                }

                return ReadAsPropertyNameCore(ref reader, typeToConvert, options);
            }

Reproduction Steps

        [Test]
        public void When_reading_from_a_special_enum()
        {
             var query = from field in typeToConvert.GetFields(BindingFlags.Public | BindingFlags.Static)
                    let attr = field.GetCustomAttribute<EnumMemberAttribute>()
                    where attr != null
                    select (field.Name, attr.Value);
            var dictionary = query.ToDictionary(p => p.Item1, p => p.Item2);
 
            var json_settings = new JsonSerializerOptions
            {
                PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
                Converters = { new JsonStringEnumConverter(new DictionaryLookupNamingPolicy(dictionary, JsonNamingPolicy.CamelCase), allowIntegerValues) },
            };

            var data = @"{""value"": ""F""}";

            var result = JsonSerializer.Deserialize<DemoSpecial>(data, json_settings);

            ShowResult(result);
            result!.Value.Should().Be(SpecialEnum.First);

        }

        public class DemoSpecial
        {
            public SpecialEnum Value { get; set; }
        }

        public enum SpecialEnum
        {
            Unknown,
            [EnumMember(Value = "F")]
            First,
            [EnumMember(Value = "S")]
            Second,
            [EnumMember(Value = "T")]
            Third
        }

    public class JsonNamingPolicyDecorator : JsonNamingPolicy
    {
        readonly JsonNamingPolicy underlyingNamingPolicy;

        public JsonNamingPolicyDecorator(JsonNamingPolicy underlyingNamingPolicy) => this.underlyingNamingPolicy = underlyingNamingPolicy;

        public override string ConvertName(string name) => underlyingNamingPolicy == null ? name : underlyingNamingPolicy.ConvertName(name);
    }

    internal class DictionaryLookupNamingPolicy : JsonNamingPolicyDecorator
    {
        readonly Dictionary<string, string> dictionary;

        public DictionaryLookupNamingPolicy(Dictionary<string, string> dictionary, JsonNamingPolicy underlyingNamingPolicy) : base(underlyingNamingPolicy) => this.dictionary = dictionary ?? throw new ArgumentNullException(nameof(dictionary));

        public override string ConvertName(string name) => dictionary.TryGetValue(name, out var value) ? value : base.ConvertName(name);

    }

Expected behavior

I have a DictionaryLookupNamingPolicy that extract the [EnumMember(Value="xx"] from the Enum.

When writing values out, all good.

        public class DemoSpecial
        {
            public SpecialEnum Value { get; set; }
        }

        public enum SpecialEnum
        {
            Unknown,
            [EnumMember(Value = "F")]
            First,
            [EnumMember(Value = "S")]
            Second,
            [EnumMember(Value = "T")]
            Third
        }

When reading value from { "value": "F" } it would expect to return a DemoSpecial.

Actual behavior

Currently, it fails:

The JSON value could not be converted to <namespace>.SpecialEnum. Path: $.value

Regression?

No response

Known Workarounds

Writing my own EnumConverter and override the the Read method

Configuration

No response

Other information

No response

Author: bobbyangers
Assignees: -
Labels:

area-System.Text.Json, untriaged

Milestone: -

@bobbyangers
Copy link
Author

bobbyangers commented Jan 17, 2022

In this function, should try and lookup the get the value from any _namingPolicy if not null

        internal override T ReadAsPropertyNameCore(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            string? enumString = reader.GetString();

            // Try parsing case sensitive first
            if (!Enum.TryParse(enumString, out T value)
    =>>            && !Enum.TryParse(enumString, ignoreCase: true, out value))
            {
                ThrowHelper.ThrowJsonException();
            }

            return value;
        }

if fact this line "cheats" into incorrectly accepted "camelCasePolicy";

@bobbyangers
Copy link
Author

bobbyangers commented Jan 17, 2022

This seems to do the trick...

public class EnumConverter<T> : JsonConverter<T>
{
   /* ... */
internal T ReadAsPropertyNameCore(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        string enumString = reader.GetString();

        /// adding this line seems to do the trick
        enumString = _namingPolicy?.ConvertName(enumString!);

        // Try parsing case sensitive first
        if (!Enum.TryParse(enumString, out T value)
            && !Enum.TryParse(enumString, ignoreCase: true, out value))
        {
            ThrowJsonException();
        }

        return value;
    }

@huoyaoyuan
Copy link
Member

Duplicate of #31619

@huoyaoyuan huoyaoyuan marked this as a duplicate of #31619 Jan 18, 2022
@layomia
Copy link
Contributor

layomia commented Jan 20, 2022

Duplicate of #31619

Closing as dup.

@layomia layomia closed this as completed Jan 20, 2022
@ghost ghost locked as resolved and limited conversation to collaborators Feb 19, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Text.Json untriaged New issue has not been triaged by the area owner
Projects
None yet
Development

No branches or pull requests

3 participants