Сериализация JSON массива с полиморфными объектами
Возможно ли это с помощью стандартного JavascriptSerializer / JsonDataContractSerializer или внешних парсеров, для сериализации массива объектов с использованием метода обертки, включая тип объекта?
Например, чтобы сгенерировать этот JSON из списка:
[{ 'dog': { ...dog properties... } }, { 'cat': { ...cat properties... } }]
вместо типичного:
- Когда и почему сущности JPA должны реализовывать интерфейс Serializable?
- Добавление к ObjectOutputStream
- Почему java.util.Optional не является Serializable, как сериализовать объект с такими полями
- Сериализация / десериализация пользовательской коллекции с дополнительными свойствами с помощью Json.Net
- Игнорирование нулевых полей в Json.net
[{ ...dog properties... }, { ...cat properties... }]
Это выполнимо в Java с Jackson, используя атрибут JsonTypeInfo.As.WRAPPER_OBJECT.
- Исключение «Исключительная привязка к реляционной ссылке» с JSON.Net
- Задача не сериализуема: java.io.NotSerializableException при вызове функции закрытие только для classов не объектов
- Сериализовать статический class?
- Json.net сериализует определенное частное поле
- C # JSON.NET - десериализация ответа, использующего необычную структуру данных
- jackson не десериализует общий список, который он сериализовал
- Стандартный способ сериализации JSON для строки запроса?
- Невозможно сериализовать словарь с помощью сложного ключа с помощью Json.net
У 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
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 и я все делаю через интерфейсы, это, вероятно, так же просто сделать мой собственный сериализатор, так или иначе …….