Sérialiser des types d’union avec System.Text.Json

À compter de .NET 11, JsonSerializer prend en charge les types d’union C# 15. Une union contient l’un des types de cas dans sa déclaration. JsonSerializer écrit la valeur du cas actif et peut également la relire.

Sérialiser et désérialiser des valeurs de type union

Déclarez une union dont les cas ont des types de jetons JSON distincts :

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

Utilisez JsonSerializer pour sérialiser et désérialiser l’union :

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

Le JSON sérialisé contient la valeur du cas actif plutôt qu’un encapsulage ou un discriminateur :

{"Text":"Ready"}

Par défaut, le sérialiseur classifie le JSON entrant par type de jeton. Dans l’union précédente, un numéro JSON sélectionne int, une chaîne JSON sélectionne stringet un objet JSON sélectionne Message.

Avec JsonSerializerOptions.Web, un int peut également être lu à partir d’une chaîne JSON. Les cas string et int de Payload revendiquent alors le type de jeton de chaîne. Même "25%" lève une exception JsonException en raison de l’ambiguïté avant même que le sérialiseur n’analyse l’un ou l’autre des cas. Pour lire des chaînes avec des valeurs par défaut web, fournissez un classifieur personnalisé qui choisit le cas.

Distinguer les cas avec le même type de jeton JSON

La classification des jetons ne peut pas distinguer deux cas qui sérialisent tous deux en tant qu’objets JSON. Appliquez JsonUnionAttribute et sélectionnez cette option JsonUnionTypeStructuralClassifier pour classifier les cas d’objet par leurs noms de propriétés :

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

Ici, JsonUnionAttribute sélectionne un classifieur pour l’union existante Pet ; l’appliquer à un type ordinaire ne transforme pas ce type en union.

Le classifieur sélectionne Dog lorsque la charge utile contient Breed et sélectionne Cat lorsqu’elle contient Lives:

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

Pour les objets JSON, le classifieur structurel commence par l’ensemble des cas d’objets compatibles, puis restreint cet ensemble à mesure qu’il lit les noms de propriétés reconnus au niveau racine. Les propriétés requises éliminent les cas où elles sont absentes. JsonUnmappedMemberHandling.Disallow supprime un cas lorsque la charge utile contient une propriété que le cas ne déclare pas. La classification réussit uniquement lorsqu’un cas reste.

Le classifieur structurel n’inspecte pas les valeurs de propriété, les objets imbriqués, le contenu de chaîne ou les éléments de tableau. Gardez ces conséquences à l’esprit :

  • Une charge qui ne laisse aucun candidat ou en laisse plusieurs lève JsonException.
  • Les contrats d’objets superposés ou ombrés peuvent être rejetés lors de la création du classifieur.
  • Plusieurs cas autres que des objets qui utilisent le même type de jeton JSON ne sont pas pris en charge. Par exemple, Guid et string les deux utilisent des chaînes JSON.
  • Un cas d’objet simple ne peut pas être mélangé avec un dictionnaire, JsonObject ou un autre cas non-POCO de type objet.
  • Les cas d’union imbriqués et les cas polymorphes ne sont pas pris en charge.
  • ReferenceHandler.Preserve n’est pas pris en charge.
  • Une configuration qui ne peut pas distinguer ses cas lève NotSupportedException lorsque le sérialiseur génère le classifieur.

Choisir des syndicats ou des hiérarchies fermées

Utilisez une union lorsque vous devez conserver un format JSON sans discrimination que vous ne contrôlez pas, ou lorsque les cas ont des formes JSON distinctes. Par exemple, les cas int et string de Payload se distinguent par le type de jeton JSON. Toutefois, pour les cas d’objet tels que Pet(Dog, Cat)les modifications apportées aux noms de propriétés peuvent affecter le cas où un classifieur structurel sélectionne.

Lorsque vous contrôlez les types et le contrat JSON, une hiérarchie fermée avec polymorphisme déduit peut identifier les cas d’objet avec un discriminateur à la place :

[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)) écrit {"$type":"Created","Id":42}. Les deux types dérivés déclarent Id, mais le discriminateur identifie le cas indépendamment de ses propriétés. Cela rend la sélection de cas plus stable à mesure que les propriétés évoluent. Contrairement aux cas d’union, les types dérivés doivent partager une classe de base, et vous devez choisir de déduire le polymorphisme ; le closed modificateur seul n’ajoute pas de discriminateur.

Fournir un classifieur personnalisé

Dérivez de JsonTypeClassifierFactory lorsque la classification des jetons par défaut ou le JsonUnionTypeStructuralClassifier intégré ne répond pas à vos besoins. Un classifieur personnalisé peut utiliser d’autres règles structurelles pour sélectionner un cas d’union. Inscrivez la fabrique à l’un des emplacements suivants :

Un classifieur lit la valeur JSON actuelle et retourne l’un des types de cas de JsonTypeClassifierContext.UnionCases. Le sérialiseur vérifie d’abord le délégué du contrat, puis la fabrique propre à chaque union, les fabriques définies au niveau des options et la classification intégrée des jetons.

Pour les unions ambiguës, le générateur source signale un diagnostic, sauf si un classifieur est configuré au moment de la génération.

Gérer les valeurs d’union null et par défaut

Une union peut déclarer des types de cas nullables. JSON null sélectionne le premier cas nullable. Si l’union n’a pas de cas nullable, JSON null produit la valeur d’union par défaut. Pour une union de struct générée par le compilateur, la valeur par défaut n’a pas de cas actif et sérialise au format JSON null.

Personnaliser un contrat d’union

Pour les scénarios avancés, personnalisez les métadonnées d’union via JsonTypeInfo. La valeur d’une JsonTypeInfo.Kind union est JsonTypeInfoKind.Union. Son contrat expose :

Pour plus d’informations sur la JsonTypeInfomodification, consultez Personnaliser un contrat JSON.

Voir aussi