Json.net serialize/deserialize derived types?
Asked Answered
S

6

144

json.net (newtonsoft)
I am looking through the documentation but I can't find anything on this or the best way to do it.

public class Base
{
    public string Name;
}
public class Derived : Base
{
    public string Something;
}

JsonConvert.Deserialize<List<Base>>(text);

Now I have Derived objects in the serialized list. How do I deserialize the list and get back derived types?

Seif answered 14/12, 2011 at 23:7 Comment(3)
That isn't how the inheritance works. You can specify JsonConvert.Deserialize<Derived>(text); to include the Name field. Since Derived IS A Base (not the other way around), Base doesn't know anything about Derived's definition.Mikemikel
Sorry, clarified a bit. The issue is I have a list which contains both base and derived objects. So I need to figure out how I tell newtonsoft how to deserialize the derived items.Seif
I did you solve this. I have the same problemCounterinsurgency
S
60

If you are storing the type in your text (as you should be in this scenario), you can use the JsonSerializerSettings.

See: how to deserialize JSON into IEnumerable<BaseType> with Newtonsoft JSON.NET

Be careful, though. Using anything other than TypeNameHandling = TypeNameHandling.None could open yourself up to a security vulnerability - see "How to configure Json.NET to create a vulnerable web API".

Sitton answered 6/1, 2012 at 20:2 Comment(6)
You can also use TypeNameHandling = TypeNameHandling.Auto - this will add a $type property ONLY for instances where the declared type (i.e. Base) does not match the instance type (i.e. Derived). This way, it doesn't bloat your JSON as much as TypeNameHandling.All.Popple
I keep receiving Error resolving type specified in JSON '..., ...'. Path '$type', line 1, position 82. Any ideas?Chutney
Be careful when using this on a public endpoint as it opens up security issues: alphabot.com/security/blog/2017/net/…Christcrossrow
@Christcrossrow JEEZ thanks for this, I did not know about this. Will add to my post.Sitton
For those searching, this is how to do the same thing with System.Text.Json.Serialization: learn.microsoft.com/en-us/dotnet/standard/serialization/…Newmint
6 months later... I would strongly discourage the use of this. Aside from the security concerns, this also makes it a pain to rename your classes or move them into other namespaces. Refactoring will become hell. I now use a custom "discriminator field" on my objects to know what subclass to deserialize them into. It's the same idea basically, but it's just not tied to class namespaces...Eboh
F
130

You have to enable Type Name Handling and pass that to the (de)serializer as a settings parameter for both Serialize and Deserialize operations.

Base object1 = new Base() { Name = "Object1" };
Derived object2 = new Derived() { Something = "Some other thing" };
List<Base> inheritanceList = new List<Base>() { object1, object2 };

JsonSerializerSettings settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };
string Serialized = JsonConvert.SerializeObject(inheritanceList, settings);
List<Base> deserializedList = JsonConvert.DeserializeObject<List<Base>>(Serialized, settings);

This will result in correct deserialization of derived classes. A drawback to it is that it will name all the objects you are using, as such it will name the list you are putting the objects in.

Facilitation answered 18/3, 2014 at 17:39 Comment(5)
+1. I was googling for 30 minutes until I actually found out that you need to use same settings for SerializeObject & DeserializeObject. I assumed it would use $type implicitly if it is there when deserializing, silly me.Febrifugal
TypeNameHandling.Auto will do it too, and is nicer because it doesn't write the instance type name when it matches the type of the field/property, which is often the case for most fields/properties.Sadler
This doesn't work when deserialization is performed on another solution/project. On serialization the name of the Solution is embedded within as type: "SOLUTIONNAME.Models.Model". On deserialization on the other solution it will throw "JsonSerializationException: Could not load assembly 'SOLUTIONNAME'.Lipstick
I never know it could be so easy! I dug through Google and damn near pull all my hair out trying to figure out how to write a custom serializer/deserializer! Then I finally came across this answer, and finally solved it! You're my lifesaver! Thanks trillions!Paphos
@Jebathon, you have in both solutions have at least projects with the same name and classes with the same namespace. But better to share the same libraryAppease
S
60

If you are storing the type in your text (as you should be in this scenario), you can use the JsonSerializerSettings.

See: how to deserialize JSON into IEnumerable<BaseType> with Newtonsoft JSON.NET

Be careful, though. Using anything other than TypeNameHandling = TypeNameHandling.None could open yourself up to a security vulnerability - see "How to configure Json.NET to create a vulnerable web API".

Sitton answered 6/1, 2012 at 20:2 Comment(6)
You can also use TypeNameHandling = TypeNameHandling.Auto - this will add a $type property ONLY for instances where the declared type (i.e. Base) does not match the instance type (i.e. Derived). This way, it doesn't bloat your JSON as much as TypeNameHandling.All.Popple
I keep receiving Error resolving type specified in JSON '..., ...'. Path '$type', line 1, position 82. Any ideas?Chutney
Be careful when using this on a public endpoint as it opens up security issues: alphabot.com/security/blog/2017/net/…Christcrossrow
@Christcrossrow JEEZ thanks for this, I did not know about this. Will add to my post.Sitton
For those searching, this is how to do the same thing with System.Text.Json.Serialization: learn.microsoft.com/en-us/dotnet/standard/serialization/…Newmint
6 months later... I would strongly discourage the use of this. Aside from the security concerns, this also makes it a pain to rename your classes or move them into other namespaces. Refactoring will become hell. I now use a custom "discriminator field" on my objects to know what subclass to deserialize them into. It's the same idea basically, but it's just not tied to class namespaces...Eboh
H
36

Since the question is so popular, it may be useful to add on what to do if you want to control the type property name and its value.

The long way is to write custom JsonConverters to handle (de)serialization by manually checking and setting the type property.

A simpler way is to use JsonSubTypes, which handles all the boilerplate via attributes:

[JsonConverter(typeof(JsonSubtypes), "Sound")]
[JsonSubtypes.KnownSubType(typeof(Dog), "Bark")]
[JsonSubtypes.KnownSubType(typeof(Cat), "Meow")]
public class Animal
{
    public virtual string Sound { get; }
    public string Color { get; set; }
}

public class Dog : Animal
{
    public override string Sound { get; } = "Bark";
    public string Breed { get; set; }
}

public class Cat : Animal
{
    public override string Sound { get; } = "Meow";
    public bool Declawed { get; set; }
}
Hammerhead answered 19/3, 2019 at 11:41 Comment(8)
I get the need, but I'm not a fan of having to make the base class aware of all the "KnownSubType"s...Phonics
There are other options if you look at the documentation. I only provided the example I like more.Hammerhead
This is the safer approach that doesn't expose your service to load arbitrary types upon de-serialization.Bearce
Where is JsonSubtypes even defined? I'm using Newtonsoft.Json Version 12.0.0.0 and have no reference to JsonSubtypes, JsonSubTypes nor JsonSubtypesConverterBuilder (mentioned in that article).Scamander
@MattArnold It's a separate Nuget package.Hammerhead
I don't understand the need of the second parameter - is it not just possible to do something equivalent to [JsonSubtypes.KnownSubType(typeof(Cat))] to tell the deserializer to use Cat if it finds a Declawed value?Inesita
I can't get this to work! I've marked the base class alike above only with null for the second parameter for KnownSubType because I have nothing to give it; I've also tried KnownSubTypeWithPropertyAttribute(typeof(Cat), nameof(Cat.Declawed)) and even added jsonSerializer.Converters.Add(JsonSubtypesConverterBuilder.Of<Animal>(nameof(Cat.Declawed)).RegisterSubtype<Cat>(AnimalType.Cat).SerializeDiscriminatorProperty().Build()); to both the serializer and deserializer but only the base class properties are deserialized even though the sub class properties are present in the JSON!Scamander
See similar suggestion in a different question #19308252Appease
A
9

Use this JsonKnownTypes, it's very similar way to use, it just add discriminator to json:

[JsonConverter(typeof(JsonKnownTypeConverter<BaseClass>))]
[JsonKnownType(typeof(Base), "base")]
[JsonKnownType(typeof(Derived), "derived")]
public class Base
{
    public string Name;
}
public class Derived : Base
{
    public string Something;
}

Now when you serialize object in json will be add "$type" with "base" and "derived" value and it will be use for deserialize

Serialized list example:

[
    {"Name":"some name", "$type":"base"},
    {"Name":"some name", "Something":"something", "$type":"derived"}
]
Attract answered 19/2, 2020 at 9:25 Comment(0)
V
0

In .NET 6/7 you could use System.Text.Json.Serialization

[JsonDerivedType(typeof(Panel), typeDiscriminator: "panel")]

See https://learn.microsoft.com/en-us/dotnet/api/system.text.json.serialization.jsonderivedtypeattribute.-ctor?view=net-7.0&f1url=%3FappId%3DDev16IDEF1%26l%3DEN-US%26k%3Dk(System.Text.Json.Serialization.JsonDerivedTypeAttribute.%2523ctor)%3Bk(DevLang-csharp)%26rd%3Dtrue

Varhol answered 4/7, 2023 at 1:8 Comment(4)
Where should this code be put? Please also add an excerpt from the link. The link can become dead in the future and your answer would remain useless.Didier
I know this is a bit old at this point but JsonDerivedType is only available in .NET 7/8 according to that link.Streak
Are you suggesting to use System.Text.Json implementation ? It is describe in different question is-polymorphic-deserialization-possible-in-system-text-json This question is about newtonsoft json.netAppease
JsonDerivedType works for .NET7+ (no .NET6- implementations from MS). See "Applies to" section hereSedgemoor
F
-2

just add object in Serialize method

 var jsonMessageBody = JsonSerializer.Serialize<object>(model);
Female answered 23/9, 2022 at 10:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.