简体   繁体   中英

Deserializing a mixed list of objects from JSON

I'm using the DataContractJsonSerializer to deserialize objects from an external service. In most cases, this has worked great for me. However, there is one case where I need to deserialize JSON that contains a list of objects that all inherit from the same base class, but there are many different types of objects in that list.

I know that it can be done easily by including a list of known types in the serializer's constructor, but I don't have access to the code that generated this JSON service. The types that I'm using will be different from the types used in the service (mostly just the class name and namespace will be different). In other words, the classes that the data was serialized with will not be the same classes that I'll use to deserialize it even though they'll be very similar.

With the XML DataContractSerializer , I can pass in a DataContractResolver to map the services types to my own types, but there is no such constructor for the DataContractJsonSerializer . Is there any way to do this? The only options that I've been able to find are: write my own deserializer, or use Microsoft's JsonObject that isn't tested and "should not be used in production environments."

Here is an example:

[DataContract]
public class Person
{
    [DataMember]
    public string Name { get; set; }
}

[DataContract]
public class Student : Person
{
    [DataMember]
    public int StudentId { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        var jsonStr = "[{\"__type\":\"Student:#UnknownProject\",\"Name\":\"John Smith\",\"StudentId\":1},{\"Name\":\"James Adams\"}]";

        using (var stream = new MemoryStream())
        {
            var writer = new StreamWriter(stream);
            writer.Write(jsonStr);
            writer.Flush();

            stream.Position = 0;
            var s = new DataContractJsonSerializer(typeof(List<Person>), new Type[] { typeof(Student), typeof(Person) });
            // Crashes on this line with the error below
            var personList = (List<Person>)s.ReadObject(stream);
        }
    }
}

Here is the error mentioned in the comment above:

Element ':item' contains data from a type that maps to the name
'http://schemas.datacontract.org/2004/07/UnknownProject:Student'. The
deserializer has no knowledge of any type that maps to this name. Consider using
a DataContractResolver or add the type corresponding to 'Student' to the list of
known types - for example, by using the KnownTypeAttribute attribute or by adding
it to the list of known types passed to DataContractSerializer.

I found the answer. It was very simple. I just needed to update my DataContract attribute to specify which namespace (you can also specify a different name) they map to in the source JSON like this:

[DataContract(Namespace = "http://schemas.datacontract.org/2004/07/UnknownProject")]
public class Person
{
    [DataMember]
    public string Name { get; set; }
}

[DataContract(Namespace = "http://schemas.datacontract.org/2004/07/UnknownProject"]
public class Student : Person
{
    [DataMember]
    public int StudentId { get; set; }
}

That JsonObject was a sample for .NET 3.5. There is a project in codeplex - http://wcf.codeplex.com - which has a tested implementation of the JsonValue/JsonObject/JsonArray/JsonPrimitive classes, including source code and unit tests. With that you can parse "untyped" JSON. Another well-used JSON framework is the JSON.NET at http://json.codeplex.com .

You may create a DTO before serializing.

I use a class like: (pseudo code)

class JsonDto

string Content {get;set;}
string Type {get;set;}

ctor(object) => sets Content & Type Properties

static JsonDto FromJson(string) // => Reads a Serialized JsonDto 
                                //    and sets Content+Type Properties

string ToJson() // => serializes itself into a json string

object Deserialize() // => deserializes the wrapped object to its saved Type
                     //    using Content+Type properties

T Deserialize<T>()   // deserializes the object as above and tries to cast to T

Using the JsonDto you can easily serialize arbitrary objects to JSON and deserialize them to their common base type because the deserializer will always know the original type and returns an type of object reference which will be casted if you use the generic Deserialize<T> method.

One caveat: If you set the Type property you should use the AssemblyQualifiedName of the type, however without the version attribute (ex: MyCompany.SomeNamespace.MyType, MyCompany.SomeAssembly ). If you just use the AssemblyQualifiedName property of the Type class you will end up with errors if your assembly version changes.

I implemented a JsonDtoCollection the same way, which derives from List<JsonDto> and provides methods to handle collections of objects.

class JsonDtoCollection : List<JsonDto>

ctor(List<T>) => wraps all items of the list and adds them to itself

static JsonDtoCollection FromJson(string) // => Reads a collection of serialized
                                          //    JsonDtos and deserializes them, 
                                          //    returning a Collection

string ToJson() // => serializes itself into a json string

List<object> Deserialize() // => deserializes the wrapped objects using
                           //    JsonDto.Deserialize

List<T> Deserialize<T>()   // deserializes the as above and tries to cast to T

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