Neu in .NET 7.0 [24]: Polymorphismus beim Serialisieren mit System.Text.Json
Die Annotation [JsonDerivedType] in der JSON-Bibliothek System.Text.Json ermöglicht polymorphes Programmieren.
(Bild: unclelkt, gemeinfrei)
- Dr. Holger Schwichtenberg
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.
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":
{"$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)