Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Json.NET not include $type for the root object when TypeNameHandling is Auto?

When I set Json.NET to serialize with TypeNameHandling set to TypeNameHandling.Auto, it correctly sets $type for child properties of an object but does not do so for the root object being serialized. Why?

Please consider the following repro:

public class Animal
{
    public Animal[] Offspring { get; set; }
}

public class Dog : Animal {}

Animal fido = new Dog
{
    Offspring = new Animal[] { new Dog() }
};

var json = JsonConvert.SerializeObject(fido, 
    new JsonSerializerSettings
    {
        TypeNameHandling = TypeNameHandling.Auto
    });

The Json emitted into the json variable is:

{
    "Offspring": [{
        "$type": "MyApp.Dog, MyApp",
        "Offspring": null
    }]
}

The Json.NET Documentation says that for TypeNameHandling.Auto the behavior is:

Include the .NET type name when the type of the object being serialized is not the same as its declared type.

My question is - Why does fido not have "$type": "MyApp.Dog, MyApp", like its puppy? :)


UPDATE: I've found out from the accepted answer to this question that I can force $type to be added by doing this:

var json = JsonConvert.SerializeObject(fido,
    typeof(Animal),
    new JsonSerializerSettings
    {
        TypeNameHandling = TypeNameHandling.Auto,
        Formatting = Formatting.Indented
    });

But my question still holds - Why does Json.NET not do this by itself as per the documentation?

like image 413
urig Avatar asked Aug 09 '16 19:08

urig


3 Answers

Short answer: it doesn't because it can't.

As you stated in your question, setting TypeNameHandling to Auto directs Json.Net to include the .NET type name when the actual (run-time) type of the object being serialized is not the same as its declared (compile-time) type. In order to do that, Json.Net needs to know both types for every object.

For everything inside the root object, this is straightforward: just get the runtime type of the root object via GetType(), then use reflection to get all of its declared properties and their types, and for each one compare the declared type to the actual type to see if they differ. If they do, output the type name.

But for the root object itself, Json.Net doesn't have access to both types. All the information it has is the object referenced by fido, whose runtime type is Dog. There's no way for Json.Net to discover that the fido variable was declared as Animal, unless you provide that context somehow. And that is exactly why Json.Net provides overloads of SerializeObject which allow you to specify the compile-time type of the object being serialized. You must use one of these overloads if you want the TypeNameHandling.Auto setting to work for the root object.

like image 183
Brian Rogers Avatar answered Oct 31 '22 12:10

Brian Rogers


Brian is absolutely correct, Json.NET has no way of knowing the compile-time declared type of the object it's being passed as the value parameter is declared as an object. The easy fix for this was if Json.NET added generic serialize methods so that the compile-time declared type would automatically flow over to Json.NET but the library's author has decided against my proposal for this here.

As an alternative, I've wrapped all my json (de)serialization needs in a JsonHelper class with generic serialize methods which use the typeof expression to automatically pass the compile-time declared type of the value to be serialized.

like image 26
TylerBrinkley Avatar answered Oct 31 '22 14:10

TylerBrinkley


Newer versions of Json.Net allow you to pass the expected type to the serialize method

 ser.Serialize(stream, rootObject, typeof(BaseClass));

You can pass the base class to the serialize method and TypeNameHandling.Auto will write the $type if the object and expected type do not match.

like image 1
Pop Catalin Avatar answered Oct 31 '22 13:10

Pop Catalin