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

System.Text.Json unable to deserialize an object that implement IEnumerable but is itself not a list #81084

Closed
RyanGaudion opened this issue Jan 24, 2023 · 6 comments
Labels
area-System.Text.Json needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration
Milestone

Comments

@RyanGaudion
Copy link

Description

JsonSerializer.Deserialize is unable to deserialize a class that contains it's own properties will also implementing IEnumerable

Reproduction Steps

Please see the test code here:

    public class UnitTest1
    {
        [Fact]
        public void Test1()
        {
            string validJson = @"
{
    ""List1"": [{""Test"": ""a""}],
    ""List2"": [{""Test"": ""a""}]
}";

            MyClass obj = JsonSerializer.Deserialize<MyClass>(validJson);

        }
    }
    class MyClass //: IEnumerable<TestClass>
    {
        public List<TestClass> List1 { get; set; } = new List<TestClass>();
        public List<TestClass> List2 { get; set; } = new List<TestClass>();

        public IEnumerator<TestClass> GetEnumerator()
        {
            return List1.Concat(List2).GetEnumerator();
        }
        //IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    }

    class TestClass
    {
        public string Test { get; set; }
    }

This code passes, but when uncommenting out the 2 comments in MyClass - the tests then fail.

Expected behavior

I expected adding an interface and method to an object to not effect it's ability to be deserialized

Actual behavior

The deserialization fails with this error System.Text.Json.JsonException : The JSON value could not be converted to MyClass. Path: $ | LineNumber: 1 | BytePositionInLine: 1.

Regression?

No response

Known Workarounds

No response

Configuration

No response

Other information

No response

@ghost ghost added the untriaged New issue has not been triaged by the area owner label Jan 24, 2023
@ghost
Copy link

ghost commented Jan 24, 2023

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

Issue Details

Description

JsonSerializer.Deserialize is unable to deserialize a class that contains it's own properties will also implementing IEnumerable

Reproduction Steps

Please see the test code here:

    public class UnitTest1
    {
        [Fact]
        public void Test1()
        {
            string validJson = @"
{
    ""List1"": [{""Test"": ""a""}],
    ""List2"": [{""Test"": ""a""}]
}";

            MyClass obj = JsonSerializer.Deserialize<MyClass>(validJson);

        }
    }
    class MyClass //: IEnumerable<TestClass>
    {
        public List<TestClass> List1 { get; set; } = new List<TestClass>();
        public List<TestClass> List2 { get; set; } = new List<TestClass>();

        public IEnumerator<TestClass> GetEnumerator()
        {
            return List1.Concat(List2).GetEnumerator();
        }
        //IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    }

    class TestClass
    {
        public string Test { get; set; }
    }

This code passes, but when uncommenting out the 2 comments in MyClass - the tests then fail.

Expected behavior

I expected adding an interface and method to an object to not effect it's ability to be deserialized

Actual behavior

The deserialization fails with this error System.Text.Json.JsonException : The JSON value could not be converted to MyClass. Path: $ | LineNumber: 1 | BytePositionInLine: 1.

Regression?

No response

Known Workarounds

No response

Configuration

No response

Other information

No response

Author: RyanGaudion
Assignees: -
Labels:

area-System.Text.Json, untriaged

Milestone: -

@krwq
Copy link
Member

krwq commented Jan 24, 2023

You'll need to implement CustomConverter for your type.

The reason this is not serializable is following:

@krwq krwq added the needs-author-action An issue or pull request that requires more info or actions from the author. label Jan 24, 2023
@ghost
Copy link

ghost commented Jan 24, 2023

This issue has been marked needs-author-action and may be missing some important information.

@ghost ghost removed the untriaged New issue has not been triaged by the area owner label Jan 24, 2023
@krwq krwq added this to the Future milestone Jan 24, 2023
@krwq
Copy link
Member

krwq commented Jan 24, 2023

Marking as needs-author-action because given current information we should close this issue as duplicate. Please close or add further details (bot will automatically close this issue if there is no response)

@RyanGaudion
Copy link
Author

@krwq thank you for the reference to #63791. This looks like it'll resolve the issue. Is there any workaround to get an implementation like #63791 working with a custom converter? If so, what would this converter look like?

@ghost ghost added needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration and removed needs-author-action An issue or pull request that requires more info or actions from the author. labels Jan 25, 2023
@krwq
Copy link
Member

krwq commented Jan 25, 2023

Implementation could look something like this (done very minimal testing on that, just showing idea):

class MyClassConverter : JsonConverter<MyClass>
{
    public override MyClass? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        MyClass obj = new();

        while (reader.TokenType != JsonTokenType.EndObject)
        {
            if (reader.TokenType == JsonTokenType.PropertyName)
            {
                string? propertyName = reader.GetString();
                reader.Read();
                List<TestClass>? list = JsonSerializer.Deserialize<List<TestClass>>(ref reader, options);
                if (propertyName == nameof(MyClass.List1))
                {
                    obj.List1 = list;
                }
                else if (propertyName == nameof(MyClass.List2))
                {
                    obj.List2 = list;
                }
                else
                {
                    // we ignore unknown properties by default
                }
            }

            reader.Read();
        }

        return obj;
    }

    public override void Write(Utf8JsonWriter writer, MyClass value, JsonSerializerOptions options)
    {
        writer.WriteStartObject();
        writer.WritePropertyName(nameof(MyClass.List1));
        JsonSerializer.Serialize(writer, value.List1, options);
        writer.WritePropertyName(nameof(MyClass.List2));
        JsonSerializer.Serialize(writer, value.List2, options);
        writer.WriteEndObject();
    }
}

then you add that to options which you pass to Serialize/Deserialize:

JsonSerializerOptions options = new()
{
    Converters = { new MyClassConverter() }
};

@krwq krwq closed this as completed Jan 25, 2023
@ghost ghost locked as resolved and limited conversation to collaborators Feb 24, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Text.Json needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration
Projects
None yet
Development

No branches or pull requests

2 participants