Json.net serializar / deserializar tipos derivados?

98

json.net (newtonsoft)
Estoy revisando la documentación pero no puedo encontrar nada sobre esto o la mejor manera de hacerlo.

public class Base
{
    public string Name;
}
public class Derived : Base
{
    public string Something;
}

JsonConvert.Deserialize<List<Base>>(text);

Ahora tengo objetos derivados en la lista serializada. ¿Cómo deserializo la lista y recupero los tipos derivados?

Será
fuente
No es así como funciona la herencia. Puede especificar JsonConvert.Deserialize <Derived> (text); para incluir el campo Nombre. Dado que Derived IS A Base (no al revés), Base no sabe nada sobre la definición de Derived.
M.Babcock
Lo siento, aclaro un poco. El problema es que tengo una lista que contiene tanto objetos base como derivados. Así que necesito averiguar cómo le digo a newtonsoft cómo deserializar los elementos derivados.
Será el
Hice que resolviste esto. Tengo el mismo problema
Luis Carlos Chavarría

Respuestas:

46

Si está almacenando el tipo en su text(como debería estar en este escenario), puede usar el JsonSerializerSettings.

Ver: cómo deserializar JSON en IEnumerable <BaseType> con Newtonsoft JSON.NET

Pero ten cuidado. Usar cualquier otra cosa TypeNameHandling = TypeNameHandling.Nonepodría exponerse a una vulnerabilidad de seguridad .

Kamranicus
fuente
24
También puede usar TypeNameHandling = TypeNameHandling.Auto: esto agregará una $typepropiedad SOLO para instancias donde el tipo declarado (es decir Base) no coincide con el tipo de instancia (es decir Derived). De esta manera, no hincha tanto tu JSON como TypeNameHandling.All.
AJ Richardson
Sigo recibiendo Error al resolver el tipo especificado en JSON '..., ...'. Ruta '$ type', línea 1, posición 82. ¿Alguna idea?
briba
3
Tenga cuidado al usar esto en un punto final público, ya que abre problemas de seguridad: alphabot.com/security/blog/2017/net/…
gjvdkamp
1
@gjvdkamp JEEZ gracias por esto, no sabía nada de esto. Agregará a mi publicación.
kamranicus
96

Debe habilitar el Manejo de nombres de tipo y pasarlo al (des) serializador como parámetro de configuración.

Base object1 = new Base() { Name = "Object1" };
Derived object2 = new Derived() { Something = "Some other thing" };
List<Base> inheritanceList = new List<Base>() { object1, object2 };

JsonSerializerSettings settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };
string Serialized = JsonConvert.SerializeObject(inheritanceList, settings);
List<Base> deserializedList = JsonConvert.DeserializeObject<List<Base>>(Serialized, settings);

Esto dará como resultado la deserialización correcta de las clases derivadas. Un inconveniente es que nombrará todos los objetos que está usando, como tal, nombrará la lista en la que está colocando los objetos.

Madmenyo
fuente
31
+1. Estuve buscando en Google durante 30 minutos hasta que descubrí que necesita usar la misma configuración para SerializeObject y DeserializeObject. Supuse que usaría $ type implícitamente si estaba allí al deserializar, tonto.
Erti-Chris Eelmaa
24
TypeNameHandling.Autotambién lo hará, y es más agradable porque no escribe el nombre del tipo de instancia cuando coincide con el tipo de campo / propiedad, que suele ser el caso de la mayoría de los campos / propiedades.
Roman Starkov
2
Esto no funciona cuando la deserialización se realiza en otra solución / proyecto. En la serialización, el nombre de la Solución se incrusta como tipo: "SOLUTIONNAME.Models.Model". En la deserialización en la otra solución, arrojará "JsonSerializationException: no se pudo cargar el ensamblado 'SOLUTIONNAME'.
Triste desarrollador CRUD
19

Dado que la pregunta es tan popular, puede ser útil agregar qué hacer si desea controlar el nombre de la propiedad de tipo y su valor.

El camino más largo es escribir correos electrónicos personalizados JsonConverterpara manejar la (des) serialización comprobando y configurando manualmente la propiedad de tipo.

Una forma más sencilla es usar JsonSubTypes , que maneja todo el texto estándar a través de atributos:

[JsonConverter(typeof(JsonSubtypes), "Sound")]
[JsonSubtypes.KnownSubType(typeof(Dog), "Bark")]
[JsonSubtypes.KnownSubType(typeof(Cat), "Meow")]
public class Animal
{
    public virtual string Sound { get; }
    public string Color { get; set; }
}

public class Dog : Animal
{
    public override string Sound { get; } = "Bark";
    public string Breed { get; set; }
}

public class Cat : Animal
{
    public override string Sound { get; } = "Meow";
    public bool Declawed { get; set; }
}
rzippo
fuente
3
Entiendo la necesidad, pero no soy un fanático de tener que informar a la clase base de todos los "KnownSubType" s ...
Matt Knowles
2
Hay otras opciones si miras la documentación. Solo proporcioné el ejemplo que más me gusta.
rzippo
1
Este es el enfoque más seguro que no expone su servicio a cargar tipos arbitrarios después de la deserialización.
David Burg
3

Use este JsonKnownTypes , es una forma muy similar de usar, solo agrega discriminador a json:

[JsonConverter(typeof(JsonKnownTypeConverter<BaseClass>))]
[JsonKnownType(typeof(Base), "base")]
[JsonKnownType(typeof(Derived), "derived")]
public class Base
{
    public string Name;
}
public class Derived : Base
{
    public string Something;
}

Ahora al serializar objeto en JSON será agrega "$type"con "base"y "derived"valor y se puede utilizar para deserializar

Ejemplo de lista serializada:

[
    {"Name":"some name", "$type":"base"},
    {"Name":"some name", "Something":"something", "$type":"derived"}
]
Dmitry
fuente