dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.26k stars 4.73k forks source link

System.Text.Json.JsonSerializer doesn't serialize properties from derived classes #31742

Closed mauricio-bv closed 4 years ago

mauricio-bv commented 4 years ago

I am trying to use the System.Text.Json serialization libray, but I can't get it is serialize it as I used to with Newtonson. Properties in derived classes or Interfaces are not serializing. This issue has also been asked and explained in the link below.

Link

Is there any serialization option I should set for proper serialization?

bartonjs commented 4 years ago

The serializer serializes using whatever type information you gave it. Static typing on serialization prevents accidental data disclosure from derived types that didn't understand they were being serialized.

If you really want polymorphic serialization, you can accomplish it in one of three different ways:

If you just have code like JsonSerializer.Serialize(value, ...) then generic inference binds it as the static type of the value parameter.

mauricio-bv commented 4 years ago

Hi Bartonjs. Your suggestion works for the simple example I provided, but in the example below it does not work (just put the whole code in the Program.cs file of a console application and run). The instances of Vehicle do not serialize properly.

` using System.Collections.Generic; using System.Text.Json;

namespace NetCoreSerializerProblem {

public interface IVehicle : IProduct
{
    ISpecs Specs { get; set; }
}

public interface IProduct
{
    string Name { get; set; }
}

public interface ISpecs
{
    int Doors { get; set; }
}

public class Truck : Vehicle
{
    public Truck(string name) : base(name) { }
}

public abstract class Vehicle : IVehicle
{
    protected Vehicle(string name)
    {
        Name = name;
    }
    public string Name { get; set; }
    public ISpecs Specs { get; set; }
}

public class Fleet
{
    public IList<IProduct> Vehicles { get; set; }
}

public class Specs : ISpecs
{
    public int Doors { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        IProduct truck1 = new Truck("The Name")
        {
            Specs = new Specs() { Doors = 2 }
        };

        var fleet = new Fleet()
        {
            Vehicles = new List<IProduct>() { truck1 },
        };
        string serialized1 = JsonSerializer.Serialize(fleet);
        string serialized2 = JsonSerializer.Serialize(fleet, typeof(Fleet));
        string serialized3 = JsonSerializer.Serialize(fleet, fleet.GetType());
    }
}

} `

bartonjs commented 4 years ago

I neglected to notice that my first bullet the generic specifier got eaten as a bad HTML tag, it should have been JsonSerializer.Serialize<object>(value).

If you want "deeply polymorphic" you need to serialize as object. Any other type, including getting the type by generic inference, is intentionally static typed, not polymorphic.

mauricio-bv commented 4 years ago

Do you mean

string serialized4 = JsonSerializer.Serialize<Fleet>(fleet);

I did that and get the same result

{
  "Vehicles":
  [
    {"Name":"The Name"}
  ]
}
bartonjs commented 4 years ago

@mauricio-bv No, literally the word "object", as in System.Object.

mauricio-bv commented 4 years ago

Oh. I see. Still, I tried that with the same result

string serialized4 = JsonSerializer.Serialize<object>(fleet);

Still getting the same result (no Specs property)

{"Vehicles":[{"Name":"The Name"}]}

mauricio-bv commented 4 years ago

Anyone that can help on my last message?

bailei1987 commented 4 years ago

I have the same problem.the inherit property can not be transfered to web front.

    [HttpGet("TT")]
    public object TT()
    {
        A obj = new A { Name = "xb" };
        obj.Items.Add(new B { Age = 5 });
        obj.Items.Add(new C { Age = 6, CAge = 7 });
        return obj;
    }
    public class A
    {
        public string Name { get; set; }
        public List<B> Items { get; set; } = new List<B>();
    }
    public class B
    {
        public int Age { get; set; }
    }
    public class C : B
    {
        public int CAge { get; set; }
    }

//api request result expect {"name":"xb","items":[{"age":5},{"age":6,"cAge":7}]} //actual {"name":"xb","items":[{"age":5},{"age":6}]}

Arash-Sabet commented 4 years ago

I think it makes perfect sense to have the behavior of the serializer changed especially by considering the issue that @bailei1987 reported. How would serializer behave in signalR context when a polymorphic object is expected?

Basically a simple JsonSerializer.Serialize(value, ...) should serialize an object properly without having to be worried about its polymorphism.

/cc @bartonjs @terrajobst

bartonjs commented 4 years ago

Basically a simple JsonSerializer.Serialize(value, ...) should serialize an object properly without having to be worried about its polymorphism.

I agree with your statement, but I think I have to fundamentally disagree with your implied conclusion. Statically typed serialization is more secure (you can understand what data is written ahead of time, so no "I didn't remember this object was serialized and this property shouldn't have been" information disclosure) and able to be pregenerated for much more optimal code.

I'm willing to concede there should be an option to turn it on, but it should neither be the only behavior, nor the default.

mauricio-bv commented 4 years ago

I think it makes perfect sense to have the behavior of the serializer changed especially by considering the issue that @bailei1987 reported. How would serializer behave in signalR context when a polymorphic object is expected? Basically a simple JsonSerializer.Serialize(value, ...) should serialize an object properly without having to be worried about its polymorphism. /cc @bartonjs @terrajobst

Serialization goes well beyond sending data through the web and a good library should not limit polymorphism/SOLID principles, specially in .Net 3.0 that is a multiplatform framework. I am trying to move to the .Net core 3.0 serializer from Newtonsoft.JSon (which serializes perfectly deep polymorphic cobjects) because it is the recommended serializer for .Net Core 3.0, but I can't compromise my SOLID pattern/Interfaces Segregation.

Arash-Sabet commented 4 years ago

I'm willing to concede there should be an option to turn it on, but it should neither be the only behavior, nor the default.

I think this would be acceptable too. Bottom line something that can turn on and automatically serialize a derived class's object without having to explicitly specify the type.

@bartonjs

bstordrup commented 4 years ago

I have a similar problem. I have a class that contains some properties that are classes being leafs in a class hierarchy. These properties are not being serialized at all with System.Text.Json.

My code is working perfectly right in .NET 4.7.2 with Newtonsoft.Json being used. When migrating to .NET core 3.1 using System.Text.Json, I don't get the same result passed back to the client. And it is the Json serialization that is the problem. Tested by manually converting to Json before passing my result object back into the pipeline.

layomia commented 4 years ago

Closing as duplicate of https://github.com/dotnet/runtime/issues/29937.

We only offer polymorphic support for System.Object instances: see https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-how-to#serialize-properties-of-derived-classes.

The exception for other types is the root (with the non-generic Serialize overload) where we call .GetType().

sake402 commented 4 years ago

Given

public class Base{
public string PropertyBase{ get; set; }="Base";
}
public class Foo:Base{
public string PropertyFoo{ get; set; } = "Foo";
}

public class Bar:Base{
public string PropertyBar{ get; set; } = "Bar";
}

If we then do

var bases = new[]{ new Foo(), new Bar() }
JsonSerializer.Serialize<object>(bases);

We get

[{"PropertyBase":"Base"}, {"PropertyBase":"Base"}]

Despite using object as the serialization type

layomia commented 4 years ago

@sake402 I couldn't get your repro to compile: https://dotnetfiddle.net/kLEyqt

sake402 commented 4 years ago

@layomia Please check again here. https://dotnetfiddle.net/NpiwnZ

qsdfplkj commented 4 years ago

@sake402 you should use: var bases = new object[]{ new Foo(), new Bar() };