In der JSON-Bibliothek System.Text.Json
gibt es seit Version 7.0 die Annotation [JsonDerivedType]
. Damit kann man bei einer Basisklasse sogenannte Typ-Diskriminatoren für die Basisklasse und die abgeleiteten Klassen deklarieren. Diese werden bei der Serialisierung und Deserialisierung berücksichtigt.
Anzeige
Dr. Holger Schwichtenberg ist Chief Technology Expert bei MAXIMAGO, die Innovations- und Experience-getriebener Softwareentwicklung, u.a. in hochkritischen sicherheitstechnischen Bereichen, anbietet. Zudem ist er Leiter des Expertennetzwerks www.IT-Visions.de, das mit 38 renommierten Experten zahlreiche mittlere und große Unternehmen durch Beratung und Schulung bei der Entwicklung sowie dem Betrieb von Software unterstützt.
Beispiel: Gegeben sei eine Basisklasse Person
und eine abgeleitete Klasse Consultant
.
[JsonDerivedType(typeof(Person), typeDiscriminator: "P")]
[JsonDerivedType(typeof(Consultant), typeDiscriminator: "C")]
public class Person
{
public required int ID { get; set; }
public required string Name { get; set; }
public override string ToString()
{
return $"Person {Name}";
}
}
public class Consultant : Person
{
public string? Company { get; set; }
public override string ToString()
{
return $"Consultant {Name} arbeitet bei {Company}.";
}
}
Serialisierung erzeugt die Zusatzeigenschaft $type
Wenn man nun eine Instanz von Person erzeugt und diese in JSON serialisiert
Person p = new Person() { ID = 123, Name = "Holger Schwichtenberg" };
var json1 = JsonSerializer.Serialize(p);
erhält man diese JSON-Zeichenkette mit dem Zusatz "$type":"P":
Anzeige
{"$type":"P","ID":123,"Name":"Holger Schwichtenberg"}
Ohne die Angabe von [JsonDerivedType]
hätte man bekommen:
{"ID":123,"Name":"Holger Schwichtenberg"}
Ebenso erhält man hier ein "C", wenn man ein Consultant-Objekt serialisiert, selbst wenn die Variable vom Basistyp der Basisklasse Person ist, d. h.
Person c = new Consultant() { ID = 123, Name = "Holger Schwichtenberg", Company = "www.IT-Visions.de" };
var json2 = JsonSerializer.Serialize(c);
liefert
{"$type":"C","Company":"www.IT-Visions.de","ID":123,"Name":"Holger Schwichtenberg"}
Ohne die Angabe von [JsonDerivedType]
hätte man wieder nur das bekommen:
{"ID":123,"Name":"Holger Schwichtenberg"}
Würde man bei der Deklarierung ohne einen Typ-Diskriminator die Variable c
auf Consultant
statt auf Person
typisieren
Consultant c = new Consultant() { ID = 123, Name = "Holger Schwichtenberg", Company = "www.IT-Visions.de" };
var json2 = JsonSerializer.Serialize(c);
dann wäre das Ergebnis
{"Company":"www.IT-Visions.de","ID":123,"Name":"Holger Schwichtenberg"}
Das bedeutet: [JsonDerivedType]
dient nicht nur dazu, die Zusatzangabe $type in der JSON-Zeichenkette zu bekommen, sondern auch die zusätzlichen Eigenschaften eines abgeleiteten Typs zu serialisieren, wenn im Code nicht der konkrete Typ, sondern eine Basisklasse verwendet wird. [JsonDerivedType]
unterstützt also polymorphes Programmieren.
Wichtig: Wenn es noch eine weitere abgeleitete Klasse Developer
gibt, für die aber keine Annotation [JsonDerivedType]
in der Basisklasse existiert
public class Developer : Person
{
public string? Company { get; set; }
public override string ToString()
{
return $"Developer {Name} entwickelt bei {Company}";
}
}
dann gibt es einen Laufzeitfehler
Runtime type 'Developer' is not supported by polymorphic type 'Person'
,
wenn man das versucht:
Person d = new Developer() { ID = 123, Name = "Holger Schwichtenberg", Company = "MAXIMAGO GmbH" };
var json3 = JsonSerializer.Serialize(d);
Dieses Verhalten kann man ändern. Mit einem zusätzlichen
[JsonPolymorphic(UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)]
Auf der Basisklasse erreicht man, dass immer die Personen-Property des Developer-Objekts serialisiert werden, wenn es keinen passenden Typ-Diskriminator gibt:
{"$type":"P","ID":123,"Name":"Holger Schwichtenberg"}
Weitere Optionen
Der typeDiscriminator
kann anstelle einer Zeichenkette auch eine Zahl sein:
[JsonDerivedType(typeof(Person), typeDiscriminator: 0)]
[JsonDerivedType(typeof(Consultant), typeDiscriminator: 1)]
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
public override string ToString()
{
return $"Person {Name}";
}
}
Statt $type
kann man bei der Serialisierung und Deserialisierung einen anderen Namen verwenden, indem man dies mit der Annotation [JsonPolymorphic]
auf der Basisklasse deklariert:
[JsonDerivedType(typeof(Person), typeDiscriminator: "P")]
[JsonDerivedType(typeof(Consultant), typeDiscriminator: "C")]
[JsonPolymorphic(TypeDiscriminatorPropertyName = "$class")] // Standard ist $type
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
public override string ToString()
{
return $"Person {Name}";
}
}
Auch in den in Teil 22 behandelten Type Info Resolvers kann man das polymorphe Verhalten via typeInfo.PolymorphismOptions
konfigurieren (siehe dazu den Microsoft-Blogeintrag zu System.Text.Json).
Verfügbarkeit
System.Text.Json
ist zusammen mit .NET 7.0 als NuGet-Paket erschienen, läuft aber auch unter .NET Standard 2.0 und damit auch auf .NET Core 2.x/3.x sowie .NET 5.0/.NET 6.0 auf dem klassischen .NET Framework ab Version 4.6.2.
Ausblick
Im nächsten Teil dieser Serie, der in der kommenden Woche erscheinen wird, geht es um Polymorphismus beim JSON-Deserialisieren.
(map)