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

JsonSerializer.Serialize an anonymous type, with duplicate nested names throws an unexpected exception #81550

Open
Tracked by #71944
dbaghdanov opened this issue Feb 2, 2023 · 2 comments
Labels
area-System.Text.Json enhancement Product code improvement that does NOT require public API changes/additions
Milestone

Comments

@dbaghdanov
Copy link

Description

Upgrading my project from .netcore 3.1 to .net6 I came across one of my tests failing due to a change in how JsonSerializer.Serialize will serialize an anonymous type.

Reproduction Steps

var obj = new 
{
  input = new {
    foo = "foo"
  },
  INPUT = new {
    foo = "foo"
  }
};

var json = JsonSerializer.Serialize(obj); // throws an exception

Expected behavior

In .netcore 3.1 the above would have returned:

{"input":{"foo":"bar"},"INPUT":{"foo":"bar"}}

Actual behavior

In .net5+ this exception is thrown instead:

Unhandled exception. System.InvalidOperationException: Members 'input' and 'INPUT' on type '<>f__AnonymousType2`2[<>f__AnonymousType1`1[System.String],<>f__AnonymousType1`1[System.String]]' cannot both bind with parameter 'input' in the deserialization constructor.
   at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_MultiplePropertiesBindToConstructorParameters(Type parentType, String parameterName, String firstMatchName, String secondMatchName)
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo.InitializeConstructorParameters(JsonParameterInfoValues[] jsonParameters, Boolean sourceGenMode)
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo..ctor(Type type, JsonConverter converter, Type runtimeType, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializerOptions.<InitializeForReflectionSerializer>g__CreateJsonTypeInfo|112_0(Type type, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializerOptions.GetClassFromContextOrCreate(Type type)
   at System.Text.Json.JsonSerializerOptions.GetOrAddClass(Type type)
   at System.Text.Json.JsonSerializer.GetTypeInfo(JsonSerializerOptions options, Type runtimeType)
   at System.Text.Json.JsonSerializer.Serialize[TValue](TValue value, JsonSerializerOptions options)

Unhandled exception. System.InvalidOperationException: Members 'input' and 'INPUT' on type '<>f__AnonymousType22[<>f__AnonymousType11[System.String],<>f__AnonymousType1`1[System.String]]' cannot both bind with parameter 'input' in the deserialization constructor.

Regression?

Worked as I expected in .netcore 3.1 but not in .net5+

Known Workarounds

If I were to create a class to represent my anonymous type instead, then I do receive the expected JSON string.

public sealed class Obj
{
    public Input input {get; set;}
    public Input INPUT {get; set;}
}

public sealed class Input
{
    public string foo {get;set;}	
}

And instantiate similar to my anonymous type:

var obj = new Obj()
{
    input = new Input()
    {
        foo="bar"
    }, 
    INPUT = new Input()
    {
        foo="bar"
    }
};

var json = JsonSerializer.Serialize(obj);

I do get my expected JSON string:

{"input":{"foo":"bar"},"INPUT":{"foo":"bar"}}

Configuration

$ dotnet --info
.NET SDK (reflecting any global.json):
 Version:   6.0.405
 Commit:    27ab36058b

Runtime Environment:
 OS Name:     debian
 OS Version:  11
 OS Platform: Linux
 RID:         debian.11-x64
 Base Path:   /usr/share/dotnet/sdk/6.0.405/

global.json file:
  /workspaces/bipsli/global.json

Host:
  Version:      6.0.13
  Architecture: x64
  Commit:       1af80ba017

.NET SDKs installed:
  6.0.405 [/usr/share/dotnet/sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.App 6.0.13 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 6.0.13 [/usr/share/dotnet/shared/Microsoft.NETCore.App]

Other information

I posted this same question to StackOverflow where it was suggested that I open an issue here.
https://stackoverflow.com/questions/75317065/exception-serializing-anonymous-type-to-json-when-nested-property-names-are-the

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

ghost commented Feb 2, 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

Upgrading my project from .netcore 3.1 to .net6 I came across one of my tests failing due to a change in how JsonSerializer.Serialize will serialize an anonymous type.

Reproduction Steps

var obj = new 
{
  input = new {
    foo = "foo"
  },
  INPUT = new {
    foo = "foo"
  }
};

var json = JsonSerializer.Serialize(obj); // throws an exception

Expected behavior

In .netcore 3.1 the above would have returned:

{"input":{"foo":"bar"},"INPUT":{"foo":"bar"}}

Actual behavior

In .net5+ this exception is thrown instead:

Unhandled exception. System.InvalidOperationException: Members 'input' and 'INPUT' on type '<>f__AnonymousType2`2[<>f__AnonymousType1`1[System.String],<>f__AnonymousType1`1[System.String]]' cannot both bind with parameter 'input' in the deserialization constructor.
   at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_MultiplePropertiesBindToConstructorParameters(Type parentType, String parameterName, String firstMatchName, String secondMatchName)
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo.InitializeConstructorParameters(JsonParameterInfoValues[] jsonParameters, Boolean sourceGenMode)
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo..ctor(Type type, JsonConverter converter, Type runtimeType, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializerOptions.<InitializeForReflectionSerializer>g__CreateJsonTypeInfo|112_0(Type type, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializerOptions.GetClassFromContextOrCreate(Type type)
   at System.Text.Json.JsonSerializerOptions.GetOrAddClass(Type type)
   at System.Text.Json.JsonSerializer.GetTypeInfo(JsonSerializerOptions options, Type runtimeType)
   at System.Text.Json.JsonSerializer.Serialize[TValue](TValue value, JsonSerializerOptions options)

Unhandled exception. System.InvalidOperationException: Members 'input' and 'INPUT' on type '<>f__AnonymousType22[<>f__AnonymousType11[System.String],<>f__AnonymousType1`1[System.String]]' cannot both bind with parameter 'input' in the deserialization constructor.

Regression?

Worked as I expected in .netcore 3.1 but not in .net5+

Known Workarounds

If I were to create a class to represent my anonymous type instead, then I do receive the expected JSON string.

public sealed class Obj
{
    public Input input {get; set;}
    public Input INPUT {get; set;}
}

public sealed class Input
{
    public string foo {get;set;}	
}

And instantiate similar to my anonymous type:

var obj = new Obj()
{
    input = new Input()
    {
        foo="bar"
    }, 
    INPUT = new Input()
    {
        foo="bar"
    }
};

var json = JsonSerializer.Serialize(obj);

I do get my expected JSON string:

{"input":{"foo":"bar"},"INPUT":{"foo":"bar"}}

Configuration

$ dotnet --info
.NET SDK (reflecting any global.json):
 Version:   6.0.405
 Commit:    27ab36058b

Runtime Environment:
 OS Name:     debian
 OS Version:  11
 OS Platform: Linux
 RID:         debian.11-x64
 Base Path:   /usr/share/dotnet/sdk/6.0.405/

global.json file:
  /workspaces/bipsli/global.json

Host:
  Version:      6.0.13
  Architecture: x64
  Commit:       1af80ba017

.NET SDKs installed:
  6.0.405 [/usr/share/dotnet/sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.App 6.0.13 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 6.0.13 [/usr/share/dotnet/shared/Microsoft.NETCore.App]

Other information

I posted this same question to StackOverflow where it was suggested that I open an issue here.
https://stackoverflow.com/questions/75317065/exception-serializing-anonymous-type-to-json-when-nested-property-names-are-the

Author: dbaghdanov
Assignees: -
Labels:

area-System.Text.Json

Milestone: -

@eiriktsarpalis
Copy link
Member

eiriktsarpalis commented Feb 3, 2023

I can reproduce. This appears to have been caused by the introduction of constructor deserialization support. Constructor deserialization will attempt to bind constructor parameters to properties using case insensitive comparison, so if the type uses two properties with different casing it will fail at that binding phase. We should consider relaxing this restriction in line with work in #71944.

In the meantime, it should be possible to work around your issue using classes instead of anonymous types when you serialize.

@eiriktsarpalis eiriktsarpalis added this to the Future milestone Feb 3, 2023
@ghost ghost removed the untriaged New issue has not been triaged by the area owner label Feb 3, 2023
@eiriktsarpalis eiriktsarpalis added enhancement Product code improvement that does NOT require public API changes/additions untriaged New issue has not been triaged by the area owner labels Feb 3, 2023
@ghost ghost removed the untriaged New issue has not been triaged by the area owner label Feb 3, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-System.Text.Json enhancement Product code improvement that does NOT require public API changes/additions
Projects
None yet
Development

No branches or pull requests

2 participants