简体   繁体   中英

Serialize multiple objects of different types with JSON

I have two classes which inherits from an abstract class

public class Class1 : MainBaseClass
{
   public int attrib1 {get; set;}
   public int attrib2 {get; set;}
}

public class Class2 : MainBaseClass
{
   public int attribx {get; set;}
   public int attriby {get; set;}
}

Then I created a list of type MainBaseClass in order to serialize both classes in one JSON string but I got this exception

An exception of type 'System.Runtime.Serialization.SerializationException' occurred in System.Runtime.Serialization.dll but was not handled in user code

Additional information: Type 'MyProject.Class1' with data contract name 'Class1: http://schemas.datacontract.org/2004/07/MyProject ' is not expected. Add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.

My method does this:

Class1 class1 = getData();
Class2 class2 = getData();
Package<MainBaseClass> package = new Package<MainBaseClass>();
package.AddObject(class1)
package.AddObject(class2);
//Here's the error
new ServiceClass().Serialize<Package<MainBaseClass>>(package);

My package class

public class Package<T>
{
    public List<T> Objects = new List<T>();

    public Package() { }

    public void AddObject(T dto)
    {
        this.Objects.Add(dto);
    }
}

My serializer method

    public static string Serialize<T>(T entity)
    {
        MemoryStream stream = new MemoryStream();
        DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(T));
        //Here's the exception
        ser.WriteObject(stream, entity);
        stream.Position = 0;
        StreamReader sr = new StreamReader(stream);
        return sr.ReadToEnd();
    }

I also added [DataContract()] on the MainBaseClass and the child classes and the exception persists.

It only works if I do this, removing the [DataContract()] from the base class and the child classes previously. If not, I receive the results as an empty string "{}"

Class1 class1 = getData();
Package<Class1> package = new Package<Class1>();
package.AddObject(class1)
string str = new ServiceClass().Serialize<Package<Class>>(package);

Or this:

Class1 class1 = getData();
string str = new ServiceClass().Serialize<Class1>(class1);

So, how can I serialize multiple objects of different types?

I got it. The only thing to do is to add DataContract attribute on the main base class only

[DataContract()]
public class MainBaseClass {}

Then, on each child class, we need to add the KnownType attribute

[KnownType(typeof(Class1))]
public class Class1 : MainBaseClass
{
}

[KnownType(typeof(Class2))]
public class Class2 : MainBaseClass
{
}

And that's it! That solved my initial problem.

If you are going to use DataContractJsonSerializer , you need to decorate MainBaseClass with the KnownType attribute to inform the serializer of all possible derived types at compile time . This requirement is described in the documentation here: Data Contract Known Types and here: Stand-Alone JSON Serialization: Polymorphism

[DataContract]
[KnownType(typeof(Class1))]
[KnownType(typeof(Class2))]
public abstract class MainBaseClass
{
    [DataMember]
    public int Id { get; set; } // For instance.
}

[DataContract]
public class Class1 : MainBaseClass
{
    [DataMember]
    public int attrib1 { get; set; }
    [DataMember]
    public int attrib2 { get; set; }
}

[DataContract]
public class Class2 : MainBaseClass
{
    [DataMember]
    public int attribx { get; set; }
    [DataMember]
    public int attriby { get; set; }
}

Having done so, an extra JSON property "__type" will be emitted for polymorphic fields of type MainBaseClass with value "DataContractName:DataContractNamespace". This syntax is a .Net extension to the JSON standard and gives a hint which concrete type to use later when deserializing. Thus if your Package class looks like:

[DataContract]
public class Package<T>
{
    [DataMember]
    public List<T> Objects = new List<T>();

    public Package() { }

    public void AddObject(T dto)
    {
        this.Objects.Add(dto);
    }
}

The JSON emitted will look like:

{"Objects":[{"__type":"Class1:#Tile.Question28612192","Id":101,"attrib1":1,"attrib2":2},{"__type":"Class2:#Tile.Question28612192","Id":-101,"attribx":-1,"attriby":-2}]}

If you don't want this, in .Net 4.5 and above, output of type information with DataContractJsonSerializer can be suppressed by setting DataContractJsonSerializerSettings.EmitTypeInformation to EmitTypeInformation.Never :

var settings = new DataContractJsonSerializerSettings { EmitTypeInformation = EmitTypeInformation.Never };

However, without the type information, you won't be able to deserialize your JSON with DataContractJsonSerializer later.

As an alternative, you could consider using Json.NET, which does not require advance knowledge of all possible derived types for serialization. See here: JSON serialization of array with polymorphic objects for details.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM