Serializzare i tipi di unione con System.Text.Json

A partire da .NET 11, JsonSerializer supporta i tipi di unione C# 15. Un'unione contiene uno dei tipi case nella relativa dichiarazione. JsonSerializer scrive il valore del caso attivo e può leggerlo successivamente.

Serializzare e deserializzare i valori di unione

Dichiarare un'unione i cui casi hanno tipi di token JSON distinti:

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

Usare JsonSerializer per serializzare e deserializzare l'unione:

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

Il codice JSON serializzato contiene il valore del case attivo anziché un wrapper o un discriminatorio:

{"Text":"Ready"}

Per impostazione predefinita, il serializzatore classifica json in ingresso per tipo di token. Nell'unione precedente, un numero JSON seleziona int, una stringa JSON seleziona string e un oggetto JSON seleziona Message.

Con JsonSerializerOptions.Web, è possibile leggere anche un oggetto int a partire da una stringa JSON. Sia il caso di int sia quello di string di Payload identificano quindi il tipo di token stringa. Anche "25%" genera JsonException a causa dell'ambiguità prima che il serializzatore analizzi uno dei due casi. Per leggere le stringhe con valori predefiniti Web, specificare un classificatore personalizzato che sceglie il caso.

Distinguere i casi con lo stesso tipo di token JSON

La classificazione dei token non può distinguere due casi che vengono serializzati come oggetti JSON. Applica JsonUnionAttribute e seleziona JsonUnionTypeStructuralClassifier per classificare i casi di oggetto in base ai nomi delle relative proprietà:

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

In questo caso, JsonUnionAttribute seleziona un classificatore per l'unione esistente Pet . L'applicazione a un tipo ordinario non trasforma tale tipo in un'unione.

Il classificatore seleziona Dog quando il payload contiene Breed e seleziona Cat quando contiene Lives:

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

Per gli oggetti JSON, il classificatore strutturale inizia con i tipi di oggetto compatibili e restringe tale insieme man mano che legge i nomi riconosciuti delle proprietà di primo livello. Le proprietà obbligatorie escludono i casi in cui sono assenti. JsonUnmappedMemberHandling.Disallow rimuove un caso quando il payload contiene una proprietà che il case non dichiara. La classificazione ha esito positivo solo quando rimane un caso.

Il classificatore strutturale non controlla i valori delle proprietà, gli oggetti annidati, il contenuto della stringa o gli elementi della matrice. Tenere presenti queste conseguenze:

  • Un payload che lascia zero o più candidati genera JsonException.
  • I contratti di oggetti sovrapposti o ombreggiati potrebbero essere rifiutati quando viene creato il classificatore.
  • Non sono supportati più casi non oggetto che usano lo stesso tipo di token JSON. Ad esempio, Guid e string entrambi usano stringhe JSON.
  • Un caso di oggetto semplice non può essere mescolato con un dizionario, JsonObject, o un altro caso con struttura a oggetto non POCO.
  • I casi di unione annidati e i casi polimorfici non sono supportati.
  • ReferenceHandler.Preserve non è supportata.
  • Una configurazione che non può distinguere i relativi case genera NotSupportedException quando il serializzatore compila il classificatore.

Scegliere unioni o gerarchie chiuse

Usa un'unione quando devi preservare un formato JSON privo di discriminatore che non controlli, oppure quando i casi hanno strutture JSON distinte. Ad esempio, i casi int e string di Payload sono distinguibili in base al tipo di token JSON. Per i casi di oggetto, Pet(Dog, Cat)ad esempio , le modifiche apportate ai nomi delle proprietà possono influire sul caso in cui viene selezionato un classificatore strutturale.

Quando si controllano i tipi e il contratto JSON, una gerarchia chiusa con polimorfismo dedotto può invece identificare i casi oggetto con un discriminatorio:

[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)) scrive {"$type":"Created","Id":42}. Entrambi i tipi derivati dichiarano Id, ma il discriminatorio identifica il caso indipendentemente dalle relative proprietà. Questo rende la selezione dei casi più stabile con l’evolversi delle proprietà. A differenza dei casi di unione, i tipi derivati devono condividere una classe base ed è necessario acconsentire esplicitamente al polimorfismo dedotto; il closed modificatore da solo non aggiunge un discriminatorio.

Fornire un classificatore personalizzato

Derivare da JsonTypeClassifierFactory quando la classificazione dei token predefinita o la funzionalità predefinita JsonUnionTypeStructuralClassifier non soddisfa i requisiti. Un classificatore personalizzato può usare altre regole strutturali per selezionare un caso di unione. Registrare la factory in una delle posizioni seguenti:

Un classificatore legge il valore JSON corrente e restituisce uno dei tipi di caso definiti in JsonTypeClassifierContext.UnionCases. Il serializzatore controlla prima il delegato del contratto, seguito dalla funzione factory per ogni unione, dalle funzioni factory definite nelle opzioni e dalla classificazione predefinita dei token.

Per le unioni ambigue, il generatore di origine segnala una diagnostica a meno che non sia configurato un classificatore in fase di generazione.

Gestire valori di unione null e predefiniti

Un tipo unione può dichiarare tipi di caso nullable. JSON null seleziona il primo caso nullable. Se il tipo union non include alcun caso nullable, JSON null produce il valore predefinito del tipo union. Per una union di struct generata dal compilatore, il valore predefinito non ha alcuna variante attiva e viene serializzato in JSON come null.

Personalizzare un contratto di unione

Per scenari avanzati, personalizzare i metadati di unione tramite JsonTypeInfo. Il valore di un'JsonTypeInfo.Kindunione è JsonTypeInfoKind.Union. Il contratto espone:

Per altre informazioni sulla modifica di JsonTypeInfo, vedere Personalizzare un contratto JSON.

Vedere anche