Runtime: System.Text.Json.JsonSerializer doesn't serialize properties from derived classes

Created on 30 Dec 2019  路  18Comments  路  Source: dotnet/runtime

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?

area-System.Text.Json

Most helpful comment

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

All 18 comments

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:

  • JsonSerializer.Serialize<object>(value, ...)
  • JsonSerializer.Serialize(value, typeof(object), ...);
  • JsonSerializer.Serialize(value, value.GetType(), ...);

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

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());
    }
}

}
`

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.

Do you mean

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

I did that and get the same result

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

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

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"}]}

Anyone that can help on my last message?

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}]}

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

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.

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.

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

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.

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().

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

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

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

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

Was this page helpful?
0 / 5 - 0 ratings