Json.net sérialise / désérialise les types dérivés?

98

json.net (newtonsoft)
Je suis en train de parcourir la documentation mais je ne trouve rien sur ceci ou la meilleure façon de le faire.

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

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

Maintenant, j'ai des objets dérivés dans la liste sérialisée. Comment désérialiser la liste et récupérer les types dérivés?

Volonté
la source
Ce n'est pas ainsi que fonctionne l'héritage. Vous pouvez spécifier JsonConvert.Deserialize <Derived> (texte); pour inclure le champ Nom. Puisque Derived IS A Base (et non l'inverse), Base ne sait rien de la définition de Derived.
M.Babcock
Désolé, clarifié un peu. Le problème est que j'ai une liste qui contient à la fois des objets de base et dérivés. J'ai donc besoin de comprendre comment je dis à newtonsoft comment désérialiser les éléments dérivés.
Will
J'ai résolu ça. J'ai le même problème
Luis Carlos Chavarría

Réponses:

46

Si vous stockez le type dans votre text(comme vous devriez l'être dans ce scénario), vous pouvez utiliser le JsonSerializerSettings.

Voir: comment désérialiser JSON en IEnumerable <BaseType> avec Newtonsoft JSON.NET

Soyez prudent, cependant. Utiliser autre chose que TypeNameHandling = TypeNameHandling.Nonepourrait vous ouvrir à une faille de sécurité .

Kamranicus
la source
24
Vous pouvez également utiliser TypeNameHandling = TypeNameHandling.Auto- cela ajoutera une $typepropriété UNIQUEMENT pour les instances où le type déclaré (ie Base) ne correspond pas au type d'instance (ie Derived). De cette façon, cela ne gonfle pas autant votre JSON que TypeNameHandling.All.
AJ Richardson
Je continue de recevoir le type de résolution d'erreur spécifié dans JSON '..., ...'. Chemin '$ type', ligne 1, position 82. Des idées?
briba
3
Soyez prudent lorsque vous utilisez ceci sur un point de terminaison public car cela pose
gjvdkamp
1
@gjvdkamp JEEZ merci pour cela, je ne savais pas à ce sujet. Ajoutera à mon message.
kamranicus
96

Vous devez activer la gestion du nom de type et le transmettre au (dé) sérialiseur en tant que paramètre de configuration.

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

Cela entraînera une désérialisation correcte des classes dérivées. Un inconvénient est qu'il nommera tous les objets que vous utilisez, en tant que tel, il nommera la liste dans laquelle vous placez les objets.

Madmenyo
la source
31
+1. J'ai cherché sur Google pendant 30 minutes jusqu'à ce que je découvre que vous devez utiliser les mêmes paramètres pour SerializeObject & DeserializeObject. J'ai supposé qu'il utiliserait $ type implicitement s'il était là lors de la désérialisation, idiot moi.
Erti-Chris Eelmaa
24
TypeNameHandling.Autole fera aussi, et c'est plus agréable car il n'écrit pas le nom du type d'instance lorsqu'il correspond au type du champ / propriété, ce qui est souvent le cas pour la plupart des champs / propriétés.
Roman Starkov
2
Cela ne fonctionne pas lorsque la désérialisation est effectuée sur une autre solution / projet. Lors de la sérialisation, le nom de la solution est incorporé dans le type: "SOLUTIONNAME.Models.Model". Lors de la désérialisation sur l'autre solution, il lancera "JsonSerializationException: Impossible de charger l'assembly 'SOLUTIONNAME'.
Triste développeur CRUD
19

Étant donné que la question est si populaire, il peut être utile d'ajouter ce qu'il faut faire si vous souhaitez contrôler le nom de la propriété de type et sa valeur.

Le long chemin est d'écrire des JsonConverters personnalisés pour gérer la (dé) sérialisation en vérifiant et en définissant manuellement la propriété type.

Un moyen plus simple consiste à utiliser JsonSubTypes , qui gère tout le passe-partout via des attributs:

[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
la source
3
J'en ai besoin, mais je ne suis pas fan de devoir informer la classe de base de tous les "KnownSubType" ...
Matt Knowles
2
Il existe d'autres options si vous consultez la documentation. Je n'ai fourni que l'exemple que j'aime le plus.
rzippo
1
Il s'agit de l'approche la plus sûre qui n'expose pas votre service au chargement de types arbitraires lors de la désérialisation.
David Burg
3

Utilisez ce JsonKnownTypes , c'est une manière très similaire d'utiliser, il suffit d'ajouter un discriminateur à 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;
}

Maintenant , lorsque vous sérialiser objet JSON sera ajouter "$type"avec "base"et "derived"valeur et il sera utilisé pour deserialize

Exemple de liste sérialisée:

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