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

Properties defined in inherited interface sometimes fail to serialize #56204

Closed
insolublemaize opened this issue Jul 22, 2021 · 5 comments
Closed
Labels
untriaged New issue has not been triaged by the area owner

Comments

@insolublemaize
Copy link

Describe the bug

Suppose you have ICar which inherits from IVehicle. When ICar is used as the return type from a controller action, it is serialized correctly.

But if a class CarWrapper has a property of type ICar, when CarWrapper is used as the return type from a controller action, the properties defined in IVehicle fail to serialize.

To Reproduce

An example can be found here https://github.com/insolublemaize/aspnetcore-serialization-bug. Run the project and go to /vehicle/GetCar and /vehicle/GetCarWrapper to see the difference between the serialized car objects.

The relevant section of code is:

public interface IVehicle
{
    public int Speed { get; set; }
}

public interface ICar : IVehicle
{
    public int NumberOfWheels { get; set; }
}

public class Car : ICar
{
    public int NumberOfWheels { get; set; }
    public int Speed { get; set; }
}

public class CarWrapper
{
    public ICar Car { get; set; }
}
/* Returns
{
    "numberOfWheels": 0,
    "speed": 0
}
*/
[HttpGet(nameof(GetCar))]
public ICar GetCar()
{
    return new Car();
}

/* Returns (notice that "speed" is missing)
{
    "car": 
    {
        "numberOfWheels": 0
    }
}
*/
[HttpGet(nameof(GetCarWrapper))]
public CarWrapper GetCarWrapper()
{
    return new CarWrapper
    {
        Car = new Car()
    };
}

Further technical details

  • ASP.NET Core version: 5.0.8
  • IDE: Microsoft Visual Studio Enterprise 2019 Version 16.10.1
  • dotnet --info:
.NET SDK (reflecting any global.json):
 Version:   6.0.100-preview.6.21355.2
 Commit:    7f8e0d76c0

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.19042
 OS Platform: Windows
 RID:         win10-x64
 Base Path:   C:\Program Files\dotnet\sdk\6.0.100-preview.6.21355.2\

Host (useful for support):
  Version: 6.0.0-preview.6.21352.12
  Commit:  770d630b28

.NET SDKs installed:
  1.0.4 [C:\Program Files\dotnet\sdk]
  2.1.701 [C:\Program Files\dotnet\sdk]
  3.1.411 [C:\Program Files\dotnet\sdk]
  5.0.104 [C:\Program Files\dotnet\sdk]
  5.0.301 [C:\Program Files\dotnet\sdk]
  5.0.302 [C:\Program Files\dotnet\sdk]
  6.0.100-preview.6.21355.2 [C:\Program Files\dotnet\sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.All 2.1.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.1.28 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.App 2.1.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 2.1.28 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 3.1.16 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 3.1.17 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 5.0.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 5.0.8 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 6.0.0-preview.6.21355.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 1.0.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 1.1.2 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.1.12 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.1.28 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 3.1.16 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 3.1.17 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 5.0.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 5.0.8 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 6.0.0-preview.6.21352.12 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.WindowsDesktop.App 3.1.16 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 3.1.17 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 5.0.7 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 5.0.8 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 6.0.0-preview.6.21353.1 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
@davidfowl
Copy link
Member

davidfowl commented Jul 23, 2021

This is due to the JSON serializer not supporting polymorphic serialization. This is supported in .NET 6 with additional configuration #30083 (comment).

cc @eiriktsarpalis

@davidfowl davidfowl transferred this issue from dotnet/aspnetcore Jul 23, 2021
@dotnet-issue-labeler
Copy link

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

@dotnet-issue-labeler dotnet-issue-labeler bot added the untriaged New issue has not been triaged by the area owner label Jul 23, 2021
@eiriktsarpalis
Copy link
Member

eiriktsarpalis commented Jul 23, 2021

Note that polymorphism support has been cut from .NET 6 and the feature will likely be delivered in .NET 7. In the meantime, using a custom converter that implements polymorphism is the only workaround.

Duplicate of #30083.

@davidfowl
Copy link
Member

In the meantime, using a custom converter that implements polymorphism is the only workaround.

Do we have a sample that we can document and point people at?

@eiriktsarpalis
Copy link
Member

There are a few workarounds floating around in the wild, although they typically come with too many gotchas and/or security issues for us to give an implicit seal of approval. The simplest way to do polymorphism drawing from the sample above is probably the following:

public class VehicleConverter : JsonConverter<IVehicle>
{
    public override IVehicle Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        => throw new NotSupportedException("Polymorphic deserialization not supported in this model.");

    public override void Write(Utf8JsonWriter writer, IVehicle vehicle, JsonSerializerOptions options)
    {
        switch(vehicle)
        {
            case ICar car:
                writer.WriteStartObject();
                writer.WriteNumber(nameof(ICar.NumberOfWheels), car.NumberOfWheels);
                writer.WriteNumber(nameof(ICar.Speed), car.Speed);
                writer.WriteEndObject();
                break;

            case IScooter scooter:
                writer.WriteStartObject();
                writer.WriteString(nameof(IScooter.Color), scooter.Color);
                writer.WriteNumber(nameof(IScooter.Speed), scooter.Speed);
                writer.WriteEndObject();
                break;

            default:
                writer.WriteStartObject();
                writer.WriteNumber(nameof(IVehicle.Speed), vehicle.Speed);
                writer.WriteEndObject();
                break;
        };
    }
}

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

No branches or pull requests

3 participants