2011-03-03 16 views
15

¿Es posible con .NET estándar JavascriptSerializer/JsonDataContractSerializer o analizadores externos, serializar objetos de matriz mediante un enfoque envoltorio que incluye el tipo de objeto?serialización JSON de matriz con objetos polimórficos

Por ejemplo, para generar este JSON a partir de una lista:

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

en lugar de la típica:

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

Esto es factible en Java con Jackson usando atributo JsonTypeInfo.As.WRAPPER_OBJECT.

+0

Oye, ¿a encontrar una solución para esto actualmente estoy enfrentando un problema similar? Un servidor (Java, Glassfish con Jersey) serializa objetos a JSON y el cliente (C#) necesita deserializar esto. Cuando se usa XML todo funciona bien ... – hage

Respuesta

10

Probablemente lo más cercano que he visto es usar el JavaScriptSerializer y pasar un JavaScriptTypeResolver al constructor. No produce formato JSON exactamente como lo tiene en su pregunta, pero tiene un campo _type que describe el tipo de objeto que se está serializando. Puede ponerse un poco feo, pero tal vez te sirva de algo.

Aquí está mi código de ejemplo:

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

Y el resultado es idéntico:

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

estoy usando el SimpleTypeConverter, que es parte del marco por defecto. Puede crear el suyo propio para acortar lo que devuelve __type.

EDITAR: Si creo mi propia JavaScriptTypeResolver para acortar el nombre del tipo devuelto, puedo producir algo como esto:

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

El uso de esta clase convertidor:

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

Y de paso en mi JavaScriptSerializer constructor (en lugar de SimpleTypeConverter).

Espero que esto ayude!

+0

Gracias por su respuesta detallada (voy a votar). Conocía esta solución pero quería interoperar con un cliente implementado en Java que no podía recibir esas propiedades "__type". – ggarber

+0

¡Muchas gracias, esto era justo lo que estaba buscando! –

+0

@PeterMorris, de nada. De hecho, terminé escribiendo una publicación en el blog sobre eso también. Fue un problema divertido de resolver. http://geekswithblogs.net/DavidHoerster/archive/2012/01/06/json.net-and-deserializing-anonymous-types.aspx –

21

Json.NET tiene una buena solución para esto. Hay un ajuste inteligente que añade información de tipo - declarar así:

new JsonSerializer { TypeNameHandling = TypeNameHandling.Auto }; 

Esto determinará si se requiere el tipo de incrustación y añadirlo cuando sea necesario. Vamos a decir que tenía las siguientes clases:

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

Aviso El cuerpo del mensaje es de tipo de objeto, y esa persona subclases Manager. Si serializar un mensaje con un cuerpo Departamento que tiene un único gestor me sale esto:

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

Aviso cómo se agregó la propiedad type $ para describir el Departamento y tipos Manager.Si ahora añadir una persona a la lista de empleados y cambiar el cuerpo del mensaje sea del departamento tipo así:

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

entonces el tipo de anotación cuerpo ya no es necesaria y la nueva persona no se anota - ausencia de anotación asume que la instancia del elemento es del tipo de matriz declarada. El formato serializado pasa a ser:

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

Este es un enfoque eficiente: la anotación de tipo solo se agrega cuando es necesario. Si bien esto es específico para .NET, el enfoque es lo suficientemente simple como para manejar que los deserializadores/tipos de mensajes en otras plataformas se deberían extender con bastante facilidad para manejar esto.

Sin embargo, me mostraría reticente a usar esto en una API pública, ya que no es estándar. En ese caso, querría evitar el polimorfismo y hacer que las versiones y la información del tipo sean propiedades muy explícitas en el mensaje.

+2

Es posible crear una clase derivada de SerializationBinder que puede asignar un poco más de cadenas amigables para los tipos . Esto está ligado a la propiedad SerializerSettings.Binder. Esto todavía no es tan limpio como me gustaría. Sería bueno tener un atributo que se pudiera agregar a las clases para especificar cuál debería ser el código de tipo. –

0

1) Se puede utilizar una cadena diccionario <, objeto > para hacer el trabajo, ...

[{ "gato": { "name": "Pinky"}}, { "Gato ": {" name ":" Winky "}}, {" perro ": {" name ":" Max "}}]

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) o utilizando el JavaScriptConverter es posible manejar la serialización para un tipo.

[{ "gato": { "omnívoro": true}}, { "oso hormiguero": { "Insectívoro": false}}, { "oso hormiguero": { "Insectívoro": true}}]

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

I esto hecho según la pregunta. No fue exactamente sencillo, pero aquí va. No hay una manera fácil de hacer esto en Json.NET. Sería increíble si admitiera una devolución de llamada previa a la serialización en la que pudiera insertar su propio tipo de información, pero esa es otra historia.

Tengo una interfaz (IShape) que implementan las clases polimórficas. Una de las clases es un contenedor (patrón compuesto) y contiene una lista de objetos contenidos. Hice esto con interfaces, pero el mismo concepto se aplica a las clases base.

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

Según la pregunta, quiero esto para serializar como:

"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 }} }, 

etc.

Para ello escribí una clase contenedora. Cada uno de los objetos que implementan la interfaz tiene una propiedad en el contenedor. Esto establece el nombre de la propiedad en el serializador. La serialización condicional asegura que se usa la propiedad correcta. Todos los métodos de interfaz se delegan a la clase envolvente, y las llamadas de aceptación de visitante() se dirigen a la clase envolvente. Esto significa que en contextos que usan la interfaz, las clases Envolturada o no envuelta se comportarán de la misma manera.

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

También tengo un patrón de visitante implementado para recorrer el gráfico del objeto. Ya tuve esto debido al resto del diseño del software, pero si solo tiene una colección simple, puede iterar la colección y agregar el contenedor.

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

El visitante reemplaza el contenido de la clase Container con versiones envueltas de las clases.

Serializar, y produce la salida requerida.

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

Puesto que ya tienen el visitante y estoy haciendo todo a través de las interfaces es probable que sea tan fácil de hacer mi propia serializador, de todos modos .......

Cuestiones relacionadas