JSON-сериализация массива с полиморфными объектами - PullRequest
15 голосов
/ 04 марта 2011

Возможно ли с помощью .NET стандарта JavascriptSerializer / JsonDataContractSerializer или внешних анализаторов сериализовать массив объектов, используя подход обертки, включая тип объекта?

Например, чтобы сгенерировать этот JSON из списка:

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

вместо типичного:

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

Это возможно в Java с Джексоном, используя атрибут JsonTypeInfo.As.WRAPPER_OBJECT.

Ответы [ 4 ]

21 голосов
/ 17 июля 2012

Json.NET предлагает для этого удобное решение.Существует параметр, который интеллектуально добавляет информацию о типе - объявите его следующим образом:

new JsonSerializer { TypeNameHandling = TypeNameHandling.Auto };

Это определит, требуется ли встраивание типа, и добавит его при необходимости.Допустим, у меня были следующие классы:

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

Обратите внимание, что тело сообщения относится к типу объекта, и этот подкласс менеджера Person.Если я сериализирую сообщение с органом отдела, в котором есть один менеджер, я получаю следующее:

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

Обратите внимание, как добавлено свойство $ type для описания типов отдела и менеджера.Если я теперь добавлю Person в список Employees и изменим Тело сообщения на тип Department следующим образом:

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

, тогда аннотация Body body больше не нужна, а новый Person не аннотирован - отсутствиеаннотации предполагает, что экземпляр элемента имеет объявленный тип массива.Серийный формат становится следующим:

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

Это эффективный подход - аннотация типа добавляется только при необходимости.Несмотря на то, что это специфично для .NET, подход достаточно прост для обработки, что десериализаторы / типы сообщений на других платформах должны быть достаточно легко расширены, чтобы справиться с этим.

Я бы не стал использовать это в общедоступном API, хотя, как это нестандартно.В этом случае вы бы хотели избежать полиморфизма и сделать информацию о версиях и типе информации очень явными в сообщении.

11 голосов
/ 04 марта 2011

Возможно, самое близкое, что я видел, - это использование JavaScriptSerializer и передача JavaScriptTypeResolver в конструктор. Он не производит JSON, отформатированный так, как у вас есть в вашем вопросе, но у него есть поле _type, которое описывает тип сериализуемого объекта. Это может стать немного уродливым, но, возможно, это поможет вам.

Вот мой пример кода:

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

И результат выглядит так:

[
  {"__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"}
]

Я использую SimpleTypeConverter, который является частью фреймворка по умолчанию. Вы можете создать свой собственный, чтобы сократить то, что возвращается на __type.

РЕДАКТИРОВАТЬ : Если я создам свой собственный JavaScriptTypeResolver, чтобы сократить возвращаемое имя типа, я могу создать что-то вроде этого:

[
  {"__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"}
]

Используя этот класс конвертера:

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

И просто передать его в мой JavaScriptSerializer конструктор (вместо SimpleTypeConverter).

Надеюсь, это поможет!

0 голосов
/ 13 ноября 2017

Я сделал это в соответствии с вопросом.Было не совсем просто, но здесь идет.В Json.NET нет простого способа сделать это.Было бы здорово, если бы он поддерживал обратный вызов до сериализации, куда вы могли бы вставить свою собственную информацию о типе, но это уже другая история.

У меня есть интерфейс (IShape), который реализуют полиморфные классы.Один из классов представляет собой контейнер (составной шаблон) и содержит список содержащихся объектов.Я сделал это с интерфейсами, но та же концепция применима к базовым классам.

public class Container : IShape
{
    public virtual List<IShape> contents {get;set;}
    // implement interface methods

Что касается вопроса, я хочу, чтобы это сериализовалось как:

  "container": {
    "contents": [
      {"box": { "TopLeft": {"X": 0.0,"Y": 0.0},"BottomRight": {"X": 1.0, "Y": 1.0} } },
      {"line": {"Start": { "X": 0.0,"Y": 0.0},"End": {"X": 1.0,"Y": 1.0 }} },

и т. Д.

Для этого я написал класс-обертку.Каждый из объектов, реализующих интерфейс, имеет свойство в оболочке.Это устанавливает имя свойства в сериализаторе.Условная сериализация гарантирует, что используется правильное свойство.Все методы интерфейса делегируются в упакованный класс, а вызовы Accept () посетителя направляются в упакованный класс.Это означает, что в контексте использования интерфейса классы Wrapped или unrawrap будут вести себя одинаково.

    public class SerializationWrapper : IShape
    {
        [JsonIgnore]
        public IShape Wrapped { get; set; }
        // Accept method for the visitor - redirect visitor to the wrapped class
        // so visitors will behave the same with wrapped or unwrapped.
        public void Accept(IVisitor visitor) => Wrapped.Accept(visitor);

        public bool ShouldSerializeline() => line != null;
        // will serialize as line : { ...
        public Line line { get =>Wrapped as Line;}

        public bool ShouldSerializebox() => box != null;
        public Box box { get => Wrapped as Box; }

        public bool ShouldSerializecontainer() => container != null;
        public Container container { get => Wrapped as Container; }

        // IShape methods delegated to Wrapped
        [JsonIgnore]
        public Guid Id { get => Wrapped.Id; set => Wrapped.Id = value; }

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

    public class SerializationVisitor : IVisitor
    {
        public void Visit(IContainer shape)
        {
            // replace list items with wrapped list items
            var wrappedContents = new List<IShape>();
            shape.Contents.ForEach(s => { wrappedContents.Add(new SerializationWrapper(){ Wrapped = s}); s.Accept(this); });
            shape.Contents = wrappedContents;
        }

        public void Visit(ILine shape){}
        public void Visit(IBox shape){}
    }

Посетитель заменяет содержимое класса Containerс завернутыми версиями классов.

Serialize, и он производит требуемый вывод.

        SerializationVisitor s = new SerializationVisitor();
        s.Visit(label);

Поскольку у меня уже есть Посетитель и я все делаю через интерфейсы, вероятно, так же легкоВ любом случае, я делаю свой собственный сериализатор ...

0 голосов
/ 18 ноября 2013

1) Вы можете использовать словарь для выполнения работы, ...

[{ "Cat": { "Имя": "Pinky"}}, { "Cat": { "Имя": "Винки"}}, { "Собака": { "Имя": "Макс"} }]

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

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


    internal static void Main()
    {
        List<object> animals = new List<object>();
        animals.Add(new Cat() { Name = "Pinky" });
        animals.Add(new Cat() { Name = "Winky" });
        animals.Add(new Dog() { Name = "Max" });
        // Convert every item in the list into a dictionary
        for (int i = 0; i < animals.Count; i++)
        {
            var animal = new Dictionary<string, object>();
            animal.Add(animals[i].GetType().Name, animals[i]);
            animals[i] = animal;
        }
        var serializer = new JavaScriptSerializer();
        var json = serializer.Serialize(animals.ToArray());


        animals = (List<object>)serializer.Deserialize(json, animals.GetType());
        // convert every item in the dictionary back into a list<object> item
        for (int i = 0; i < animals.Count; i++)
        {
            var animal = (Dictionary<string, object>)animals[i];
            animal = (Dictionary<string, object>)animal.Values.First();
            animals[i] = animal.Values.First();
        }
    }

2) Или используя JavaScriptConverter, можно обработать сериализацию для типа.

[{ "кошка": { "Всеядная": истинно}}, { "муравьед": { "насекомоядный" ложь}}, { "муравьед": { "насекомоядный": истинно}}]

abstract class AnimalBase { }

class Aardvark : AnimalBase
{
    public bool Insectivore { get; set; }
}

class Dog : AnimalBase
{
    public bool Omnivore { get; set; }
}

class AnimalsConverter : JavaScriptConverter
{
    private IDictionary<string, Type> map;

    public AnimalsConverter(IDictionary<string, Type> map) { this.map = map; }

    public override IEnumerable<Type> SupportedTypes
    {
        get { return new Type[]{typeof(AnimalBase)}; }
    }

    public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
    {
        var result = new Dictionary<string, object>();
        var type = obj.GetType();
        var name = from x in this.map where x.Value == type select x.Key;
        if (name.Count<string>() == 0)
            return null;
        var value = new Dictionary<string, object>();
        foreach (var prop in type.GetProperties())
        {
            if(!prop.CanRead) continue;
            value.Add(prop.Name, prop.GetValue(obj, null));
        }
        result.Add(name.First<string>(), value);
        return result;
    }

    public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
    {
        var keys = from x in this.map.Keys where dictionary.ContainsKey(x) select x;
        if (keys.Count<string>() <= 0) return null;
        var key = keys.First<string>();
        var poly = this.map[key];
        var animal = (AnimalBase)Activator.CreateInstance(poly);
        var values = (Dictionary<string, object>)dictionary[key];
        foreach (var prop in poly.GetProperties())
        {
            if(!prop.CanWrite) continue;
            var value = serializer.ConvertToType(values[prop.Name], prop.PropertyType);
            prop.SetValue(animal, value, null);
        }
        return animal;
    }
}

class Program
{
    static void Main(string[] args)
    {
        var animals = new List<AnimalBase>();
        animals.Add(new Dog() { Omnivore = true });
        animals.Add(new Aardvark() { Insectivore = false });
        animals.Add(new Aardvark() { Insectivore = true });
        var convertMap = new Dictionary<string, Type>();
        convertMap.Add("cat", typeof(Dog));
        convertMap.Add("aardvark", typeof(Aardvark));
        var converter = new AnimalsConverter(convertMap);
        var serializer = new JavaScriptSerializer();
        serializer.RegisterConverters(new JavaScriptConverter[] {converter});
        var json = serializer.Serialize(animals.ToArray());
        animals.Clear();
        animals.AddRange((AnimalBase[])serializer.Deserialize(json, typeof(AnimalBase[])));
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...