Serializacja typów unii przy użyciu System.Text.Json

Począwszy od .NET 11, JsonSerializer obsługuje typy union języka C# 15. Związek posiada jeden z typów przypadków w deklaracji. JsonSerializer zapisuje aktywną wartość przypadku i może ją odczytać z powrotem.

Serializowanie i deserializowanie wartości unii

Zadeklaruj unię, której przypadki mają różne typy tokenów JSON:

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

Użyj JsonSerializer, aby serializować i deserializować unię:

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

Zserializowany obiekt JSON zawiera wartość aktywnego wariantu, a nie opakowanie ani dyskryminator:

{"Text":"Ready"}

Domyślnie serializator klasyfikuje przychodzący kod JSON według typu tokenu. W poprzedniej unii numer JSON wybiera int, ciąg JSON wybiera string, a obiekt JSON wybiera Message.

Za pomocą JsonSerializerOptions.Web można również odczytać element int z ciągu JSON. Zarówno przypadki int, jak i string, następnie przypisują Payload typ tokenu ciągu znaków. Nawet "25%" zgłasza JsonException z powodu niejednoznaczności, zanim serializator przeanalizuje którykolwiek z tych przypadków. Aby odczytać ciągi z wartościami domyślnymi sieci Web, podaj niestandardowy klasyfikator , który wybiera przypadek.

Rozróżniaj przypadki z tym samym typem tokenu JSON

Klasyfikacja tokenów nie może odróżnić dwóch przypadków, które obie serializują jako obiekty JSON. Zastosuj JsonUnionAttribute i wybierz, JsonUnionTypeStructuralClassifier aby sklasyfikować przypadki obiektów według ich nazw właściwości:

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

Tutaj JsonUnionAttribute wybiera klasyfikator dla istniejącej unii Pet; zastosowanie go do zwykłego typu nie przekształca tego typu w unię.

Klasyfikator wybiera Dog , gdy ładunek zawiera Breed i wybiera Cat , kiedy zawiera Liveselement :

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

W przypadku obiektów JSON klasyfikator strukturalny rozpoczyna się od zgodnych przypadków obiektów i zawęża ten zestaw podczas odczytywania rozpoznanych nazw właściwości na poziomie głównym. Wymagane właściwości eliminują przypadki, w których ich brakuje. JsonUnmappedMemberHandling.Disallow usuwa przypadek, gdy ładunek zawiera właściwość, którą przypadek nie deklaruje. Klasyfikacja kończy się powodzeniem tylko wtedy, gdy pozostaje jeden przypadek.

Klasyfikator strukturalny nie sprawdza wartości właściwości, zagnieżdżonych obiektów, zawartości ciągu ani elementów tablicy. Należy pamiętać o tych konsekwencjach:

  • Ładunek danych, który nie pozostawia żadnego kandydata lub pozostawia wielu kandydatów, powoduje zgłoszenie wyjątku JsonException.
  • Nakładające się lub zacienione kontrakty obiektów mogą zostać odrzucone podczas tworzenia klasyfikatora.
  • Wiele przypadków innych niż obiekty, które używają tego samego typu tokenu JSON, nie są obsługiwane. Na przykład Guid i string oba używają ciągów JSON.
  • Zwykłego przypadku obiektu nie można łączyć ze słownikiem, JsonObject, ani innym przypadkiem o kształcie obiektu, który nie jest obiektem POCO.
  • Zagnieżdżone przypadki unii i przypadki polimorficzne nie są obsługiwane.
  • ReferenceHandler.Preserve nie jest obsługiwany.
  • Konfiguracja, która nie potrafi rozróżnić swoich wariantów, zgłasza NotSupportedException, gdy serializator tworzy klasyfikator.

Wybieranie związków lub zamkniętych hierarchii

Użyj unii, jeśli musisz zachować format JSON bez dyskryminatora, który nie jest kontrolowany, lub gdy przypadki mają odrębne kształty JSON. Na przykład przypadki int i string są rozróżniane przez typ tokenu Payload JSON. W przypadku przypadków obiektów, takich jak Pet(Dog, Cat), zmiany nazw właściwości mogą mieć wpływ na przypadek, w którym wybiera klasyfikator strukturalny.

Gdy kontrolujesz typy i kontrakt JSON, zamknięta hierarchia z wywnioskowanym polimorfizmem może zamiast tego identyfikować przypadki obiektów za pomocą dyskryminatora:

[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)) zapisuje {"$type":"Created","Id":42}. Oba typy pochodne deklarują Id, ale dyskryminator rozróżnia przypadek niezależnie od swoich właściwości. Dzięki temu dobór formy przypadka jest bardziej stabilny wraz ze zmianami właściwości. W przeciwieństwie do przypadków unii typy pochodne muszą współużytkować klasę bazową i należy wyrazić zgodę na wnioskowanie polimorfizmu; closed sam modyfikator nie dodaje dyskryminującego.

Podawanie niestandardowego klasyfikatora

Dziedzicz po JsonTypeClassifierFactory, gdy domyślna klasyfikacja tokenów lub wbudowane JsonUnionTypeStructuralClassifier nie spełniają twoich wymagań. Klasyfikator niestandardowy może użyć innych reguł strukturalnych, aby wybrać przypadek unii. Zarejestruj fabrykę w jednej z następujących lokalizacji:

Klasyfikator odczytuje bieżącą wartość JSON i zwraca jeden z typów wariantów z JsonTypeClassifierContext.UnionCases. Serializator najpierw sprawdza delegata kontraktu, następnie fabrykę przypisaną do unii, fabryki zdefiniowane na poziomie opcji oraz wbudowaną klasyfikację tokenów.

W przypadku niejednoznacznych unii generator kodu źródłowego zgłasza diagnostykę, chyba że podczas generowania skonfigurowano klasyfikator.

Obsługuj wartości null i domyślne wartości typu unii

Unia może zadeklarować typy przypadków dopuszczanych do wartości null. JSON null wybiera pierwszy przypadek dopuszczający wartość null. Jeśli unia nie ma przypadku dopuszczającego wartość null, JSON null zwraca domyślną wartość typu unii. W przypadku unii struktur wygenerowanej przez kompilator wartość domyślna nie ma aktywnego wariantu i jest serializowana do postaci JSON null.

Dostosowywanie umowy związkowej

W przypadku zaawansowanych scenariuszy dostosuj metadane unii za pomocą JsonTypeInfo. Wartość unii JsonTypeInfo.Kind to JsonTypeInfoKind.Union. Jego kontrakt ujawnia:

Aby uzyskać więcej informacji na temat modyfikowania JsonTypeInfo, zobacz Dostosowywanie kontraktu JSON.

Zobacz także