Десериализация смешанного списка объектов из JSON - PullRequest
3 голосов
/ 24 мая 2011

Я использую DataContractJsonSerializer для десериализации объектов из внешнего сервиса.В большинстве случаев это отлично сработало для меня.Однако есть один случай, когда мне нужно десериализовать JSON, который содержит список объектов, которые все наследуются от одного и того же базового класса, но в этом списке есть много разных типов объектов.

Я знаю, что он можетсделать это легко, включив список известных типов в конструктор сериализатора, но у меня нет доступа к коду, который сгенерировал этот сервис JSON.Типы, которые я использую, будут отличаться от типов, используемых в сервисе (в основном будут отличаться только имя класса и пространство имен).Другими словами, классы, с которыми данные были сериализованы, не будут теми же классами, которые я буду использовать для десериализации их, даже если они будут очень похожи.

С XML DataContractSerializer я могупередать DataContractResolver для сопоставления типов служб моим собственным типам, но для DataContractJsonSerializer такого конструктора нет. Есть ли способ сделать это? Единственные варианты, которые мне удалось найти: написать свой собственный десериализатор или использовать Microsoft JsonObject , который не тестировался и "долженне может использоваться в производственных средах. "

Вот пример:

[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);
        }
    }
}

Вот ошибка, упомянутая в комментарии выше:

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.

Ответы [ 3 ]

1 голос
/ 26 мая 2011

Я нашел ответ. Это было очень просто. Мне просто нужно обновить свой атрибут DataContract, чтобы указать, какое пространство имен (вы также можете указать другое имя) они сопоставляют в исходном JSON следующим образом:

[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; }
}
0 голосов
/ 25 мая 2011

Вы можете создать DTO перед сериализацией.

Я использую такой класс: (псевдокод)

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

Используя JsonDto, вы можете легко сериализовать произвольные объекты в JSON идесериализовать их в их общий базовый тип, потому что десериализатор всегда будет знать исходный тип и возвращает тип ссылки на объект, который будет приведен, если вы используете общий метод Deserialize<T>.

Одно предупреждение: если вы установитеType свойство, вы должны использовать AssemblyQualifiedName типа, но без атрибута версии (например: MyCompany.SomeNamespace.MyType, MyCompany.SomeAssembly).Если вы просто используете свойство AssemblyQualifiedName класса Type, вы получите ошибки, если ваша версия сборки изменится.

Я реализовал JsonDtoCollection таким же образом, который происходит от List<JsonDto> ипредоставляет методы для обработки коллекций объектов.

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
0 голосов
/ 24 мая 2011

То, что JsonObject был образцом для .NET 3.5. В codeplex есть проект - http://wcf.codeplex.com, в котором есть протестированная реализация классов JsonValue / JsonObject / JsonArray / JsonPrimitive, включая исходный код и модульные тесты. С этим вы можете разобрать "нетипизированный" JSON. Другая хорошо используемая среда JSON - это JSON.NET на http://json.codeplex.com.

...