Json.net abgeleitete Typen serialisieren / deserialisieren?

98

json.net (newtonsoft)
Ich sehe mir die Dokumentation an, finde aber nichts dazu oder den besten Weg, dies zu tun.

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

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

Jetzt habe ich Objekte in der serialisierten Liste abgeleitet. Wie deserialisiere ich die Liste und erhalte abgeleitete Typen zurück?

Wille
quelle
So funktioniert die Vererbung nicht. Sie können JsonConvert.Deserialize <Derived> (Text) angeben. um das Feld Name einzuschließen. Da Derived IS A Base ist (nicht umgekehrt), weiß Base nichts über die Definition von Derived.
M.Babcock
Entschuldigung, ein bisschen geklärt. Das Problem ist, dass ich eine Liste habe, die sowohl Basis- als auch abgeleitete Objekte enthält. Ich muss also herausfinden, wie ich newtonsoft sage, wie die abgeleiteten Elemente deserialisiert werden sollen.
Wird
Ich habe das gelöst. Ich habe das gleiche Problem
Luis Carlos Chavarría

Antworten:

46

Wenn Sie den Typ in Ihrem speichern text(wie Sie es in diesem Szenario tun sollten), können Sie das verwenden JsonSerializerSettings.

Siehe: So deserialisieren Sie JSON mit Newtonsoft JSON.NET in IEnumerable <BaseType>

Sei aber vorsichtig. Wenn Sie etwas anderes verwenden, als TypeNameHandling = TypeNameHandling.Nonesich einer Sicherheitslücke öffnen könnten .

Kamranicus
quelle
24
Sie können auch verwenden TypeNameHandling = TypeNameHandling.Auto- dies fügt eine $typeEigenschaft NUR für Fälle hinzu, in denen der deklarierte Typ (dh Base) nicht mit dem Instanztyp (dh Derived) übereinstimmt . Auf diese Weise wird Ihr JSON nicht so stark aufgebläht wie TypeNameHandling.All.
AJ Richardson
Ich erhalte weiterhin den in JSON '..., ...' angegebenen Fehlerbehebungstyp. Pfad '$ type', Zeile 1, Position 82. Irgendwelche Ideen?
Briba
3
Seien Sie vorsichtig, wenn Sie dies auf einem öffentlichen Endpunkt verwenden, da dies zu Sicherheitsproblemen führt: alphabot.com/security/blog/2017/net/…
gjvdkamp
1
@gjvdkamp JEEZ danke dafür, ich wusste nichts davon. Wird zu meinem Beitrag hinzufügen.
Kamranicus
96

Sie müssen die Behandlung von Typnamen aktivieren und diese als Einstellungsparameter an den (De-) Serializer übergeben.

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

Dies führt zu einer korrekten Deserialisierung abgeleiteter Klassen. Ein Nachteil dabei ist, dass alle von Ihnen verwendeten Objekte benannt werden. Als solche wird die Liste benannt, in die Sie die Objekte einfügen.

Madmenyo
quelle
31
+1. Ich habe 30 Minuten gegoogelt, bis ich tatsächlich herausgefunden habe, dass Sie dieselben Einstellungen für SerializeObject & DeserializeObject verwenden müssen. Ich nahm an, dass es implizit $ type verwenden würde, wenn es beim Deserialisieren vorhanden ist, dumm mich.
Erti-Chris Eelmaa
24
TypeNameHandling.Autowird es auch tun und ist schöner, weil es den Namen des Instanztyps nicht schreibt, wenn er mit dem Typ des Feldes / der Eigenschaft übereinstimmt, was bei den meisten Feldern / Eigenschaften häufig der Fall ist.
Roman Starkov
2
Dies funktioniert nicht, wenn die Deserialisierung für eine andere Lösung / ein anderes Projekt durchgeführt wird. Bei der Serialisierung wird der Name der Lösung als Typ eingebettet: "SOLUTIONNAME.Models.Model". Bei der Deserialisierung der anderen Lösung wird "JsonSerializationException: Assembly 'SOLUTIONNAME' konnte nicht geladen werden.
Sad CRUD Developer
19

Da die Frage so beliebt ist, kann es hilfreich sein, hinzuzufügen, was zu tun ist, wenn Sie den Namen der Typ-Eigenschaft und ihren Wert steuern möchten.

Der lange Weg besteht darin, benutzerdefinierte JsonConverters zu schreiben , um die (De-) Serialisierung zu handhaben, indem die type-Eigenschaft manuell überprüft und festgelegt wird.

Eine einfachere Möglichkeit ist die Verwendung von JsonSubTypes , das die gesamte Boilerplate über Attribute verarbeitet:

[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
quelle
3
Ich bekomme das Bedürfnis, aber ich bin kein Fan davon, die Basisklasse auf alle "KnownSubType" aufmerksam machen zu müssen ...
Matt Knowles
2
Es gibt andere Möglichkeiten, wenn Sie sich die Dokumentation ansehen. Ich habe nur das Beispiel angegeben, das mir besser gefällt.
Rzippo
1
Dies ist der sicherere Ansatz, bei dem Ihr Dienst beim De-Serialisieren keine beliebigen Typen laden kann.
David Burg
3

Verwenden Sie diese JsonKnownTypes , es ist sehr ähnlich zu verwenden, es fügt nur einen Diskriminator zu json hinzu:

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

Wenn Sie nun ein Objekt in json serialisieren, wird es "$type"mit "base"und "derived"value hinzugefügt und zum Deserialisieren verwendet

Beispiel für eine serialisierte Liste:

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