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

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

Closed
mauricio-bv opened this issue Dec 30, 2019 · 18 comments

Comments

@mauricio-bv
Copy link

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
Copy link
Member

bartonjs commented Dec 30, 2019

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.

@mauricio-bv
Copy link
Author

mauricio-bv commented Dec 31, 2019

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
Copy link
Member

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
Copy link
Author

Do you mean

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

I did that and get the same result

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

@bartonjs
Copy link
Member

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

@mauricio-bv
Copy link
Author

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
Copy link
Author

Anyone that can help on my last message?

@bailei1987
Copy link

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
Copy link

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
Copy link
Member

bartonjs commented Jan 7, 2020

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
Copy link
Author

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
Copy link

Arash-Sabet commented Jan 7, 2020

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

@scalablecory scalablecory transferred this issue from dotnet/core Feb 4, 2020
@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added area-System.Text.Json untriaged New issue has not been triaged by the area owner labels Feb 4, 2020
@bstordrup
Copy link

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
Copy link
Contributor

layomia commented Feb 20, 2020

Closing as duplicate of #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().

@layomia layomia closed this as completed Feb 20, 2020
@ahsonkhan ahsonkhan removed the untriaged New issue has not been triaged by the area owner label Feb 21, 2020
@ahsonkhan ahsonkhan added this to the 5.0 milestone Feb 21, 2020
@sake402
Copy link

sake402 commented Jun 23, 2020

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
Copy link
Contributor

layomia commented Jun 23, 2020

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

@sake402
Copy link

sake402 commented Jun 23, 2020

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

@qsdfplkj
Copy link
Contributor

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

@ghost ghost locked as resolved and limited conversation to collaborators Dec 11, 2020
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

10 participants