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

JsonPolymorphic Deserialization Fails When TypeDiscriminatorPropertyName Not First Property #78447

Closed
troysteinbauer opened this issue Nov 16, 2022 · 3 comments

Comments

@troysteinbauer
Copy link

troysteinbauer commented Nov 16, 2022

Description

I am attempting to deserialize external json from a service which is structured in a way that I can take advantage of JsonPolymorphic attribute, however it appears that it needs the type discriminator as the first property in the json object. I don't have control over this external source of json to adjust it, and many libraries treat json objects as an unordered collection.

Is there a work around? I assume this is for performance to only look ahead for the first property, but I believe it is common to not have control over the ordering of the json. I couldn't find any documentation about this limitation either.

Reproduction Steps

using System.Text.Json;
using System.Text.Json.Serialization;

public class PolymorphicJsonExample
{
    private static readonly JsonSerializerOptions JsonSerializerOptions = new JsonSerializerOptions(JsonSerializerOptions.Default);
    [JsonPolymorphic(TypeDiscriminatorPropertyName = "Type")]
    [JsonDerivedType(typeof(TypeA), "a")]
    [JsonDerivedType(typeof(TypeB), "b")]
    private abstract record Base(string Type);
    private sealed record TypeA(string Type, int ValueA) : Base(Type);
    private sealed record TypeB(string Type, bool ValueB) : Base(Type);

    [Test]
    public void FromJsonA()
    {
        const string jsonA = """{"Type":"a","ValueA":1}""";
        var valueA = JsonSerializer.Deserialize<Base>(jsonA, JsonSerializerOptions);
        Assert.That(valueA, Is.TypeOf<TypeA>());
    }

    [Test]
    public void FromJsonA_Revered()
    {
        const string jsonA = """{"ValueA":1,"Type":"a"}""";
        var valueA = JsonSerializer.Deserialize<Base>(jsonA, JsonSerializerOptions);
        Assert.That(valueA, Is.TypeOf<TypeA>());
    }
}

Expected behavior

No exceptions thrown during test PolymorphicJsonExample.FromJsonA_Reverse() and it successfully passes.

Actual behavior

Exception thrown:

  Failed FromJsonA_Revered [< 1 ms]
  Error Message:
   System.NotSupportedException : Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with 'JsonConstructorAttribute' is not supported. Type 'Tape.Net.Tests.PolymorphicJsonExampleUpper+Base'. Path: $ | LineNumber: 0 | BytePositionInLine: 10.
  ----> System.NotSupportedException : Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with 'JsonConstructorAttribute' is not supported. Type 'Tape.Net.Tests.PolymorphicJsonExampleUpper+Base'.
  Stack Trace:
     at System.Text.Json.ThrowHelper.ThrowNotSupportedException(ReadStack& state, Utf8JsonReader& reader, NotSupportedException ex)
   at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 utf8Json, JsonTypeInfo jsonTypeInfo, Nullable`1 actualByteCount)
   at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 json, JsonTypeInfo jsonTypeInfo)
   at System.Text.Json.JsonSerializer.Deserialize[TValue](String json, JsonSerializerOptions options)
   at Tape.Net.Tests.PolymorphicJsonExampleUpper.FromJsonA_Revered() in PolymorphicJsonExampleUpper.cs:line 29
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)
--NotSupportedException

Regression?

No response

Known Workarounds

No response

Configuration

  • dotnet 7.0.100
  • System.Text.Json 7.0.0

Other information

No response

@ghost ghost added the untriaged New issue has not been triaged by the area owner label Nov 16, 2022
@ghost
Copy link

ghost commented Nov 16, 2022

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

I am attempting to deserialize external json from a service which is structured in a way that I can take advantage of JsonPolymorphic attribute, however it appears that it needs the type discriminator as the first property in the json object. I don't have control over this external source of json to adjust it, and many libraries treat json objects as an unordered collection.

Is there a work around? I assume this is for performance to only look ahead for the first property, but I believe it is common to not have control over the ordering of the json. I couldn't find any documentation about this limitation either.

Reproduction Steps

using System.Text.Json;
using System.Text.Json.Serialization;

public class PolymorphicJsonExampleUpper
{
    private static readonly JsonSerializerOptions JsonSerializerOptions = new JsonSerializerOptions(JsonSerializerOptions.Default);
    [JsonPolymorphic(TypeDiscriminatorPropertyName = "Type")]
    [JsonDerivedType(typeof(TypeA), "a")]
    [JsonDerivedType(typeof(TypeB), "b")]
    private abstract record Base(string Type);
    private sealed record TypeA(string Type, int ValueA) : Base(Type);

    [Test]
    public void FromJsonA()
    {
        const string jsonA = """{"Type":"a","ValueA":1}""";
        var valueA = JsonSerializer.Deserialize<Base>(jsonA, JsonSerializerOptions);
        Assert.That(valueA, Is.TypeOf<TypeA>());
    }

    [Test]
    public void FromJsonA_Revered()
    {
        const string jsonA = """{"ValueA":1,"Type":"a"}""";
        var valueA = JsonSerializer.Deserialize<Base>(jsonA, JsonSerializerOptions);
        Assert.That(valueA, Is.TypeOf<TypeA>());
    }
}

Expected behavior

No exceptions thrown during test PolymorphicJsonExample.FromJsonA_Reverse() and it successfully passes.

Actual behavior

Exception thrown:

  Failed FromJsonA_Revered [< 1 ms]
  Error Message:
   System.NotSupportedException : Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with 'JsonConstructorAttribute' is not supported. Type 'Tape.Net.Tests.PolymorphicJsonExampleUpper+Base'. Path: $ | LineNumber: 0 | BytePositionInLine: 10.
  ----> System.NotSupportedException : Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with 'JsonConstructorAttribute' is not supported. Type 'Tape.Net.Tests.PolymorphicJsonExampleUpper+Base'.
  Stack Trace:
     at System.Text.Json.ThrowHelper.ThrowNotSupportedException(ReadStack& state, Utf8JsonReader& reader, NotSupportedException ex)
   at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 utf8Json, JsonTypeInfo jsonTypeInfo, Nullable`1 actualByteCount)
   at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 json, JsonTypeInfo jsonTypeInfo)
   at System.Text.Json.JsonSerializer.Deserialize[TValue](String json, JsonSerializerOptions options)
   at Tape.Net.Tests.PolymorphicJsonExampleUpper.FromJsonA_Revered() in PolymorphicJsonExampleUpper.cs:line 29
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)
--NotSupportedException

Regression?

No response

Known Workarounds

No response

Configuration

  • dotnet 7.0.100
  • System.Text.Json 7.0.0

Other information

No response

Author: troysteinbauer
Assignees: -
Labels:

area-System.Text.Json, untriaged

Milestone: -

@Symbai
Copy link

Symbai commented Nov 16, 2022

Dupe of #73693 and #72604

@eiriktsarpalis
Copy link
Member

Duplicate of #72604.

@ghost ghost removed the untriaged New issue has not been triaged by the area owner label Nov 16, 2022
@ghost ghost locked as resolved and limited conversation to collaborators Dec 16, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants