Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JSON serialization of array with polymorphic objects

Tags:

Is it possible with .NET standard JavascriptSerializer/JsonDataContractSerializer or external parsers, to serialize objects array using a wrapper approach including the object type?

For example, to generate this JSON from a List:

[{ 'dog': { ...dog properties... } },
 { 'cat': { ...cat properties... } }]

instead of typical:

[{ ...dog properties... },
 { ...cat properties... }]

This is doable in Java with Jackson using JsonTypeInfo.As.WRAPPER_OBJECT attribute.

like image 832
ggarber Avatar asked Mar 03 '11 21:03

ggarber


People also ask

Is polymorphic deserialization possible in system text JSON?

In contrast to the serialization case, there is no simple way to perform deserialization (simple or polymorphic) on a JSON string. The deserializer cannot infer the appropriate type for an object from the string. But how can then the custom converter infer the correct polymorphic type from the JSON object?

What is polymorphic deserialization?

A polymorphic deserialization allows a JSON payload to be deserialized into one of the known gadget classes that are documented in SubTypeValidator. java in jackson-databind in GitHub. The deserialized object is assigned to a generic base class in your object model, such as java. lang.

Which one is correct class for JsonSerializer?

The JsonSerializer is a static class in the System. Text. Json namespace. It provides functionality for serializing objects to a JSON string and deserializing from a JSON string to objects.

What is serializing in JSON?

JSON is a format that encodes objects in a string. Serialization means to convert an object into that string, and deserialization is its inverse operation (convert string -> object).


2 Answers

Json.NET has a neat solution for this. There is a setting that intelligently adds type information - declare it like this:

new JsonSerializer { TypeNameHandling = TypeNameHandling.Auto };

This will determine whether type embedding is required and add it where necessary. Lets say I had the following classes:

public class Message
{
    public object Body { get; set; }
}

public class Person
{
    public string Name { get; set; }
}

public class Manager : Person
{

}

public class Department
{
    private List<Person> _employees = new List<Person>();
    public List<Person> Employees { get { return _employees; } }
}

Notice the Message Body is of type object, and that Manager subclasses Person. If I serialize a Message with a Department Body that has a single Manager I get this:

{
    "Body":
    {
        "$type":"Department, MyAssembly",
        "Employees":[
            {
                "$type":"Manager, MyAssembly",
                "Name":"Tim"
            }]
    }
}

Notice how it's added the $type property to describe the Department and Manager types. If I now add a Person to the Employees list and change the Message Body to be of type Department like this:

public class Message
{
    public Department Body { get; set; }
}

then the Body type annotation is no longer needed and the new Person is not annotated - absence of annotation assumes the element instance is of the declared array type. The serialized format becomes:

{
    "Body":
    {
        "Employees":[
            {
                "$type":"Manager, MyAssembly",
                "Name":"Tim"
            },
            {
                "Name":"James"
            }]
    }
}

This is an efficient approach - type annotation is only added where required. While this is .NET specific, the approach is simple enough to handle that deserializers/message types on other platforms should be fairly easily extended to handle this.

I'd be reticent about using this in a public API though, as it is non-standard. In that case you'd want to avoid polymorphism, and make versioning and type information very explicit properties in the message.

like image 135
James World Avatar answered Nov 30 '22 08:11

James World


Probably the closest that I've seen is to use the JavaScriptSerializer and pass in a JavaScriptTypeResolver to the constructor. It doesn't produce JSON formatted exactly as you have it in your question, but it does have a _type field that describes the type of the object that's being serialized. It can get a little ugly, but maybe it will do the trick for you.

Here's my sample code:

public abstract class ProductBase
{
    public String Name { get; set; }
    public String Color { get; set; }
}

public class Drink : ProductBase
{
}

public class Product : ProductBase
{
}

class Program
{
    static void Main(string[] args)
    {
        List<ProductBase> products = new List<ProductBase>()
        {
            new Product() { Name="blah", Color="Red"},
            new Product(){ Name="hoo", Color="Blue"},
            new Product(){Name="rah", Color="Green"},
            new Drink() {Name="Pepsi", Color="Brown"}
        };

        JavaScriptSerializer ser = new JavaScriptSerializer(new SimpleTypeResolver());

        Console.WriteLine(ser.Serialize(products));    
    }
}

And the result looks like this:

[
  {"__type":"TestJSON1.Product, TestJSON1, Version=1.0.0.0, Culture=neutral, Publ
icKeyToken=null","Name":"blah","Color":"Red"},
  {"__type":"TestJSON1.Product, Test
JSON1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null","Name":"hoo","Colo
r":"Blue"},
  {"__type":"TestJSON1.Product, TestJSON1, Version=1.0.0.0, Culture=neu
tral, PublicKeyToken=null","Name":"rah","Color":"Green"},
  {"__type":"TestJSON1.Dr
ink, TestJSON1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null","Name":"P
epsi","Color":"Brown"}
]

I'm using the SimpleTypeConverter, which is part of the framework by default. You can create your own to shorten what's returned by __type.

EDIT: If I create my own JavaScriptTypeResolver to shorten the type name returned, I can produce something like this:

[
  {"__type":"TestJSON1.Product","Name":"blah","Color":"Red"},
  {"__type":"TestJSON1.Product","Name":"hoo","Color":"Blue"},
  {"__type":"TestJSON1.Product","Name":"rah","Color":"Green"},
  {"__type":"TestJSON1.Drink","Name":"Pepsi","Color":"Brown"}
]

Using this converter class:

public class MyTypeResolver : JavaScriptTypeResolver
{
    public override Type ResolveType(string id)
    {
        return Type.GetType(id);
    }

    public override string ResolveTypeId(Type type)
    {
        if (type == null)
        {
            throw new ArgumentNullException("type");
        }

        return type.FullName;
    }
}

And just passing it into my JavaScriptSerializer constructor (instead of the SimpleTypeConverter).

I hope this helps!

like image 25
David Hoerster Avatar answered Nov 30 '22 09:11

David Hoerster