Сериализация JSON массива с полиморфными объектами

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

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

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

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

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

Это выполнимо в Java с Jackson, используя атрибут JsonTypeInfo.As.WRAPPER_OBJECT.

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

 new JsonSerializer { TypeNameHandling = TypeNameHandling.Auto }; 

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

 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 _employees = new List(); public List Employees { get { return _employees; } } } 

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

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

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

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

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

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

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

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

Вероятно, самое близкое, что я видел, это использовать 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 products = new List() { 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 .

EDIT : если я создаю свой собственный 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"} ] - [ {"__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"} ] 

Использование этого 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; } } 

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

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

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 animals = new List(); 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(); animal.Add(animals[i].GetType().Name, animals[i]); animals[i] = animal; } var serializer = new JavaScriptSerializer(); var json = serializer.Serialize(animals.ToArray()); animals = (List)serializer.Deserialize(json, animals.GetType()); // convert every item in the dictionary back into a list item for (int i = 0; i < animals.Count; i++) { var animal = (Dictionary)animals[i]; animal = (Dictionary)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 map; public AnimalsConverter(IDictionary map) { this.map = map; } public override IEnumerable SupportedTypes { get { return new Type[]{typeof(AnimalBase)}; } } public override IDictionary Serialize(object obj, JavaScriptSerializer serializer) { var result = new Dictionary(); var type = obj.GetType(); var name = from x in this.map where x.Value == type select x.Key; if (name.Count() == 0) return null; var value = new Dictionary(); foreach (var prop in type.GetProperties()) { if(!prop.CanRead) continue; value.Add(prop.Name, prop.GetValue(obj, null)); } result.Add(name.First(), value); return result; } public override object Deserialize(IDictionary dictionary, Type type, JavaScriptSerializer serializer) { var keys = from x in this.map.Keys where dictionary.ContainsKey(x) select x; if (keys.Count() <= 0) return null; var key = keys.First(); var poly = this.map[key]; var animal = (AnimalBase)Activator.CreateInstance(poly); var values = (Dictionary)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(); animals.Add(new Dog() { Omnivore = true }); animals.Add(new Aardvark() { Insectivore = false }); animals.Add(new Aardvark() { Insectivore = true }); var convertMap = new Dictionary(); 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[]))); } } 

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

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

 public class Container : IShape { public virtual List 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 }} }, 

и т.п.

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

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

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

Сериализовать, и он производит требуемый вывод.

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

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

  • Сериализация нулевого значения в JSON.NET
  • Ошибка JSON.NET Локальный цикл привязки для типа
  • Django rest framework, используйте разные сериализаторы в том же ModelViewSet
  • Преобразование записей в последовательную форму данных для отправки по HTTP
  • Использовать имя classа в качестве корневого ключа для сериализации JSON Jackson
  • Невозможно десериализовать lambda
  • Использование преобразователей Json.NET для десериализации свойств
  • Сериализация типа Json.Net с полиморфным дочерним объектом
  • XSLT: как преобразовать XML-узел в строку
  • StackOverflowError при сериализации объекта в Java
  • Jackson ObjectMapper - указать порядок сортировки свойств объекта
  • Давайте будем гением компьютера.