Union-Typen mit System.Text.Json serialisieren

Ab .NET 11 JsonSerializer unterstützt C# 15 Union-Typen. Eine Vereinigung enthält einen der Falltypen in ihrer Erklärung. JsonSerializer schreibt den Wert des aktiven Falls und kann ihn zurücklesen.

Union-Werte serialisieren und deserialisieren

Deklarieren Sie eine Union, deren Fälle unterschiedliche JSON-Tokentypen aufweisen:

public union Payload(int, string, Message);
public sealed record Message(string Text);

JsonSerializer zum Serialisieren und Deserialisieren der Union verwenden:

Payload payload = new Message("Ready");
string json = JsonSerializer.Serialize(payload);
Payload copy = JsonSerializer.Deserialize<Payload>(json);

Die serialisierte JSON enthält den Wert des aktiven Falls und nicht einen Wrapper oder Diskriminator:

{"Text":"Ready"}

Standardmäßig klassifiziert der Serialisierer eingehende JSON nach Tokentyp. Bei der vorangehenden Union wählt eine JSON-Zahl int, eine JSON-Zeichenfolge wählt string und ein JSON-Objekt wählt Message.

Mit JsonSerializerOptions.Web kann auch ein int aus einer JSON-Zeichenfolge gelesen werden. Sowohl die string- als auch die Payload-Fälle von int beanspruchen dann den Token-Typ für Zeichenfolgen für sich. Sogar "25%" löst JsonException wegen Mehrdeutigkeit aus, bevor der Serializer einen der beiden Fälle parst. Um Zeichenfolgen mit Webstandardeinstellungen zu lesen, stellen Sie einen benutzerdefinierten Klassifizierer bereit, der den Fall auswählen soll.

Unterscheiden von Fällen mit demselben JSON-Tokentyp

Die Tokenklassifizierung kann nicht zwei Fälle unterscheiden, die beide als JSON-Objekte serialisiert werden. Anwenden JsonUnionAttribute und Auswählen JsonUnionTypeStructuralClassifier , um Objektfälle nach ihren Eigenschaftsnamen zu klassifizieren:

[JsonUnion(TypeClassifier = typeof(JsonUnionTypeStructuralClassifier))]
public union Pet(Dog, Cat);
public sealed record Dog(string Name, string Breed);
public sealed record Cat(string Name, int Lives);

Hier wählt Pet einen Klassifizierer für die vorhandene JsonUnionAttribute-Union aus; wenn es auf einen gewöhnlichen Typ angewendet wird, macht es diesen Typ nicht zu einer Union.

Der Klassifizierer wählt Dog aus, wenn die Nutzlast Breed enthält, und wählt Cat aus, wenn sie Lives enthält:

Pet pet = JsonSerializer.Deserialize<Pet>(
    """{"Name":"Rex","Breed":"Husky"}""");

Bei JSON-Objekten beginnt der strukturelle Klassifizierer mit den kompatiblen Objektvarianten und grenzt diese Menge ein, während er erkannte Eigenschaftsnamen auf der Stammebene liest. Erforderliche Eigenschaften schließen Fälle aus, wenn sie fehlen. JsonUnmappedMemberHandling.Disallow entfernt einen Fall, wenn die Nutzlast eine Eigenschaft enthält, die der Fall nicht deklariert. Die Klassifizierung ist nur erfolgreich, wenn ein Fall verbleibt.

Der strukturelle Klassifizierer prüft keine Eigenschaftswerte, geschachtelten Objekte, Zeichenfolgeninhalte oder Arrayelemente. Beachten Sie die folgenden Konsequenzen:

  • Eine Payload, die null oder mehrere Kandidaten hinterlässt, löst JsonException aus.
  • Überlappende oder schattierte Objektverträge werden möglicherweise abgelehnt, wenn der Klassifizierer erstellt wird.
  • Mehrere Nicht-Objekt-Fälle, die denselben JSON-Tokentyp verwenden, werden nicht unterstützt. Verwenden Sie beispielsweise JSON-Zeichenfolgen. Guidstring
  • Ein Nur-Objekt-Fall kann nicht mit einem Wörterbuch, JsonObject oder einem anderen nicht-POCO-objektförmigen Fall gemischt werden.
  • Geschachtelte Union-Fälle und polymorphe Fälle werden nicht unterstützt.
  • ReferenceHandler.Preserve wird nicht unterstützt.
  • Eine Konfiguration, die nicht zwischen ihren Fällen unterscheiden kann, löst NotSupportedException aus, wenn der Serialisierer den Klassifizierer erstellt.

Auswählen von Gewerkschaften oder geschlossenen Hierarchien

Verwenden Sie eine Union, wenn Sie ein diskriminatorfreies JSON-Format beibehalten müssen, das Sie nicht steuern oder wenn die Fälle unterschiedliche JSON-Shapes aufweisen. Beispielsweise lassen sich die Fälle int und string von Payload anhand des JSON-Token-Typs unterscheiden. Bei Objektfällen wie Pet(Dog, Cat) können Änderungen an Eigenschaftsnamen jedoch beeinflussen, welchen Fall ein struktureller Klassifizierer auswählt.

Wenn Sie die Typen und den JSON-Vertrag steuern, kann eine geschlossene Hierarchie mit abgeleitetem Polymorphismus stattdessen Objektfälle mit einem Diskriminator identifizieren:

[JsonPolymorphic(InferClosedTypePolymorphism = true)]
public closed record Event;
public sealed record Created(int Id) : Event;
public sealed record Deleted(int Id) : Event;

JsonSerializer.Serialize<Event>(new Created(42)) schreibt {"$type":"Created","Id":42}. Beide abgeleiteten Typen deklarieren Id, aber der Diskriminator identifiziert den Fall unabhängig von seinen Eigenschaften. Dadurch wird die Fallauswahl stabiler, da sich die Eigenschaften entwickeln. Im Gegensatz zu Union-Fällen müssen die abgeleiteten Typen eine Basisklasse gemeinsam nutzen, und Sie müssen sich für abgeleitete Polymorphität entscheiden. Der closed Modifizierer allein fügt keinen Diskriminator hinzu.

Bereitstellen eines benutzerdefinierten Klassifizierers

Leiten Sie von JsonTypeClassifierFactory ab, wenn die Standardtokenklassifizierung oder die integrierte JsonUnionTypeStructuralClassifier Ihre Anforderungen nicht erfüllt. Ein benutzerdefinierter Klassifizierer kann andere strukturelle Regeln verwenden, um einen Union-Fall auszuwählen. Registrieren Sie die Factory an einem dieser Orte:

Ein Klassifizierer liest den aktuellen JSON-Wert und gibt einen der Falltypen aus JsonTypeClassifierContext.UnionCases. Der Serialisierer überprüft zuerst den Vertragsdelegat, gefolgt von der Union-Factory, den Fabriken auf Optionsebene und der integrierten Tokenklassifizierung.

Bei mehrdeutigen Unionen gibt der Quellcode-Generator eine Fehlermeldung aus, sofern zum Zeitpunkt der Generierung kein Klassifikator konfiguriert ist.

Behandeln von Null- und Standardwerten für Union

Eine Union kann nullable Case-Typen deklarieren. JSON null wählt den ersten nullfähigen Fall aus. Wenn die Union keinen nullfähigen Fall aufweist, erzeugt JSON null den Standardwert union. Für eine vom Compiler generierte Strukturunion verfügt der Standardwert über keinen aktiven Fall und serialisiert als JSON null.

Anpassen eines Unionsvertrags

Passen Sie für erweiterte Szenarien die Union-Metadaten über JsonTypeInfo an. Der Wert einer Union JsonTypeInfo.Kind ist JsonTypeInfoKind.Union. Der Vertrag macht Folgendes verfügbar:

Weitere Informationen zum Ändern JsonTypeInfofinden Sie unter Anpassen eines JSON-Vertrags.

Siehe auch