Jak serializować właściwości klas pochodnych za pomocą polecenia System.Text.Json

W tym artykule dowiesz się, jak serializować właściwości klas pochodnych przy użyciu System.Text.Json przestrzeni nazw.

Serializowanie właściwości klas pochodnych

W wersjach wcześniejszych niż .NET 7 System.Text.Jsonnie obsługuje serializacji hierarchii typów polimorficznych. Jeśli na przykład typ właściwości jest interfejsem lub klasą abstrakcyjną, tylko właściwości zdefiniowane w interfejsie lub klasie abstrakcyjnej są serializowane, nawet jeśli typ środowiska uruchomieniowego ma dodatkowe właściwości. Wyjątki od tego zachowania zostały wyjaśnione w tej sekcji. Aby uzyskać informacje o obsłudze na platformie .NET 7, zobacz Serializacja polimorficzna na platformie .NET 7.

Załóżmy na przykład, że masz klasę i klasę WeatherForecastWeatherForecastDerivedpochodną :

public class WeatherForecast
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string? Summary { get; set; }
}
Public Class WeatherForecast
    Public Property [Date] As DateTimeOffset
    Public Property TemperatureCelsius As Integer
    Public Property Summary As String
End Class
public class WeatherForecastDerived : WeatherForecast
{
    public int WindSpeed { get; set; }
}
Public Class WeatherForecastDerived
    Inherits WeatherForecast
    Public Property WindSpeed As Integer
End Class

Załóżmy, że argumentem Serialize typu metody w czasie kompilacji jest WeatherForecast:

var options = new JsonSerializerOptions
{
    WriteIndented = true
};
jsonString = JsonSerializer.Serialize<WeatherForecast>(weatherForecast, options);
Dim options As JsonSerializerOptions = New JsonSerializerOptions With {
    .WriteIndented = True
}
jsonString = JsonSerializer.Serialize(weatherForecast1, options)

W tym scenariuszu WindSpeed właściwość nie jest serializowana, nawet jeśli weatherForecast obiekt jest obiektem WeatherForecastDerived . Serializowane są tylko właściwości klasy bazowej:

{
  "Date": "2019-08-01T00:00:00-07:00",
  "TemperatureCelsius": 25,
  "Summary": "Hot"
}

To zachowanie ma pomóc w zapobieganiu przypadkowemu narażeniu danych w pochodnym typie środowiska uruchomieniowego.

Aby serializować właściwości typu pochodnego w poprzednim przykładzie, użyj jednej z następujących metod:

  • Wywołaj przeciążenie Serialize , które umożliwia określenie typu w czasie wykonywania:

    options = new JsonSerializerOptions
    {
        WriteIndented = true
    };
    jsonString = JsonSerializer.Serialize(weatherForecast, weatherForecast.GetType(), options);
    
    options = New JsonSerializerOptions With {
        .WriteIndented = True
    }
    jsonString = JsonSerializer.Serialize(weatherForecast1, weatherForecast1.[GetType](), options)
    
  • Zadeklaruj obiekt, który ma być serializowany jako object.

    options = new JsonSerializerOptions
    {
        WriteIndented = true
    };
    jsonString = JsonSerializer.Serialize<object>(weatherForecast, options);
    
    options = New JsonSerializerOptions With {
        .WriteIndented = True
    }
    jsonString = JsonSerializer.Serialize(Of Object)(weatherForecast1, options)
    

W poprzednim przykładowym scenariuszu oba podejścia powodują WindSpeed , że właściwość zostanie uwzględniona w danych wyjściowych JSON:

{
  "WindSpeed": 35,
  "Date": "2019-08-01T00:00:00-07:00",
  "TemperatureCelsius": 25,
  "Summary": "Hot"
}

Ważne

Te podejścia zapewniają serializacji polimorficznej tylko dla obiektu głównego do serializacji, a nie dla właściwości tego obiektu głównego.

Można uzyskać serializacji polimorficznej dla obiektów niższego poziomu, jeśli zdefiniujesz je jako typ object. Załóżmy na przykład, że klasa WeatherForecast ma właściwość o nazwie PreviousForecast , którą można zdefiniować jako typ WeatherForecast lub object:

public class WeatherForecastWithPrevious
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string? Summary { get; set; }
    public WeatherForecast? PreviousForecast { get; set; }
}
Public Class WeatherForecastWithPrevious
    Public Property [Date] As DateTimeOffset
    Public Property TemperatureCelsius As Integer
    Public Property Summary As String
    Public Property PreviousForecast As WeatherForecast
End Class
public class WeatherForecastWithPreviousAsObject
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string? Summary { get; set; }
    public object? PreviousForecast { get; set; }
}
Public Class WeatherForecastWithPreviousAsObject
    Public Property [Date] As DateTimeOffset
    Public Property TemperatureCelsius As Integer
    Public Property Summary As String
    Public Property PreviousForecast As Object
End Class

PreviousForecast Jeśli właściwość zawiera wystąpienie klasy WeatherForecastDerived:

  • Dane wyjściowe JSON z serializacji WeatherForecastWithPreviousnie obejmująWindSpeed.
  • Dane wyjściowe JSON z serializacji WeatherForecastWithPreviousAsObjectobejmująWindSpeed.

Aby serializować WeatherForecastWithPreviousAsObject, nie jest konieczne wywołanie Serialize<object> lub GetType ponieważ obiekt główny nie jest tym, który może być typu pochodnego. Poniższy przykład kodu nie wywołuje metody Serialize<object> lub GetType:

options = new JsonSerializerOptions
{
    WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecastWithPreviousAsObject, options);
options = New JsonSerializerOptions With {
    .WriteIndented = True
}
jsonString = JsonSerializer.Serialize(weatherForecastWithPreviousAsObject1, options)

Powyższy kod poprawnie serializuje WeatherForecastWithPreviousAsObject:

{
  "Date": "2019-08-01T00:00:00-07:00",
  "TemperatureCelsius": 25,
  "Summary": "Hot",
  "PreviousForecast": {
    "WindSpeed": 35,
    "Date": "2019-08-01T00:00:00-07:00",
    "TemperatureCelsius": 25,
    "Summary": "Hot"
  }
}

To samo podejście do definiowania właściwości, co object działa z interfejsami. Załóżmy, że masz następujący interfejs i implementację i chcesz serializować klasę z właściwościami zawierającymi wystąpienia implementacji:

namespace SystemTextJsonSamples
{
    public interface IForecast
    {
        public DateTimeOffset Date { get; set; }
        public int TemperatureCelsius { get; set; }
        public string? Summary { get; set; }
    }

    public class Forecast : IForecast
    {
        public DateTimeOffset Date { get; set; }
        public int TemperatureCelsius { get; set; }
        public string? Summary { get; set; }
        public int WindSpeed { get; set; }
    }

    public class Forecasts
    {
        public IForecast? Monday { get; set; }
        public object? Tuesday { get; set; }
    }
}
Namespace SystemTextJsonSamples

    Public Interface IForecast
        Property [Date] As DateTimeOffset
        Property TemperatureCelsius As Integer
        Property Summary As String
    End Interface

    Public Class Forecast
        Implements IForecast
        Public Property [Date] As DateTimeOffset Implements IForecast.[Date]
        Public Property TemperatureCelsius As Integer Implements IForecast.TemperatureCelsius
        Public Property Summary As String Implements IForecast.Summary
        Public Property WindSpeed As Integer
    End Class

    Public Class Forecasts
        Public Property Monday As IForecast
        Public Property Tuesday As Object
    End Class

End Namespace

Podczas serializacji wystąpienia Forecastsklasy program wyświetla WindSpeed tylko Tuesday właściwość , ponieważ Tuesday jest zdefiniowana jako object:

var forecasts = new Forecasts
{
    Monday = new Forecast
    {
        Date = DateTime.Parse("2020-01-06"),
        TemperatureCelsius = 10,
        Summary = "Cool",
        WindSpeed = 8
    },
    Tuesday = new Forecast
    {
        Date = DateTime.Parse("2020-01-07"),
        TemperatureCelsius = 11,
        Summary = "Rainy",
        WindSpeed = 10
    }
};

options = new JsonSerializerOptions
{
    WriteIndented = true
};
jsonString = JsonSerializer.Serialize(forecasts, options);
Dim forecasts1 As New Forecasts With {
    .Monday = New Forecast With {
        .[Date] = Date.Parse("2020-01-06"),
        .TemperatureCelsius = 10,
        .Summary = "Cool",
        .WindSpeed = 8
    },
    .Tuesday = New Forecast With {
        .[Date] = Date.Parse("2020-01-07"),
        .TemperatureCelsius = 11,
        .Summary = "Rainy",
        .WindSpeed = 10
    }
}

options = New JsonSerializerOptions With {
    .WriteIndented = True
}
jsonString = JsonSerializer.Serialize(forecasts1, options)

W poniższym przykładzie pokazano kod JSON, który wynika z poprzedniego kodu:

{
  "Monday": {
    "Date": "2020-01-06T00:00:00-08:00",
    "TemperatureCelsius": 10,
    "Summary": "Cool"
  },
  "Tuesday": {
    "Date": "2020-01-07T00:00:00-08:00",
    "TemperatureCelsius": 11,
    "Summary": "Rainy",
    "WindSpeed": 10
  }
}

Uwaga

Ten artykuł dotyczy serializacji, a nie deserializacji. Deserializacji polimorficznej nie jest obsługiwana w wersjach wcześniejszych niż .NET 7, ale jako obejście można napisać konwerter niestandardowy, taki jak przykład w temacie Obsługa deserializacji polimorficznej. Aby uzyskać więcej informacji o tym, jak platforma .NET 7 obsługuje serializacji polimorficznej i deserializacji, zobacz How to serialize properties of derived classes with in .NET 7 (Jak serializować właściwości klas pochodnych za pomocą System.Text.Json platformy .NET 7).

Począwszy od platformy .NET 7, System.Text.Json obsługuje serializacji hierarchii typów polimorficznych i deserializacji z adnotacjami atrybutów.

Atrybut opis
JsonDerivedTypeAttribute W przypadku umieszczenia na deklaracji typu wskazuje, że określony podtyp powinien zostać wybrany do serializacji polimorficznej. Uwidacznia również możliwość określenia dyskryminującego typu.
JsonPolymorphicAttribute W przypadku umieszczenia na deklaracji typu wskazuje, że typ powinien być serializowany polimorficznie. Udostępnia również różne opcje konfigurowania serializacji polimorficznej i deserializacji dla tego typu.

Załóżmy na przykład, że masz klasę i klasę WeatherForecastBaseWeatherForecastWithCitypochodną :

[JsonDerivedType(typeof(WeatherForecastWithCity))]
public class WeatherForecastBase
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string? Summary { get; set; }
}
<JsonDerivedType(GetType(WeatherForecastWithCity))>
Public Class WeatherForecastBase
    Public Property [Date] As DateTimeOffset
    Public Property TemperatureCelsius As Integer
    Public Property Summary As String
End Class
public class WeatherForecastWithCity : WeatherForecastBase
{
    public string? City { get; set; }
}
Public Class WeatherForecastWithCity
    Inherits WeatherForecastBase
    Public Property City As String
End Class

Załóżmy, że argumentem Serialize<TValue> typu metody w czasie kompilacji jest WeatherForecastBase:

options = new JsonSerializerOptions
{
    WriteIndented = true
};
jsonString = JsonSerializer.Serialize<WeatherForecastBase>(weatherForecastBase, options);
options = New JsonSerializerOptions With {
    .WriteIndented = True
}
jsonString = JsonSerializer.Serialize(WeatherForecastBase, options)

W tym scenariuszu City właściwość jest serializowana, ponieważ weatherForecastBase obiekt jest w rzeczywistości obiektem WeatherForecastWithCity . Ta konfiguracja umożliwia serializacji polimorficznej dla WeatherForecastBaseprogramu , w szczególności w przypadku, gdy typ środowiska uruchomieniowego to WeatherForecastWithCity:

{
  "City": "Milwaukee",
  "Date": "2022-09-26T00:00:00-05:00",
  "TemperatureCelsius": 15,
  "Summary": "Cool"
}

Podczas gdy zaokrąglanie ładunku w taki sposób, jak WeatherForecastBase jest obsługiwane, nie zmaterializuje się jako typ WeatherForecastWithCityczasu wykonywania . Zamiast tego zmaterializuje się jako typ WeatherForecastBaseczasu wykonywania :

WeatherForecastBase value = JsonSerializer.Deserialize<WeatherForecastBase>("""
    {
      "City": "Milwaukee",
      "Date": "2022-09-26T00:00:00-05:00",
      "TemperatureCelsius": 15,
      "Summary": "Cool"
    }
    """);

Console.WriteLine(value is WeatherForecastWithCity); // False
Dim value As WeatherForecastBase = JsonSerializer.Deserialize(@"
    {
      "City": "Milwaukee",
      "Date": "2022-09-26T00:00:00-05:00",
      "TemperatureCelsius": 15,
      "Summary": "Cool"
    }")

Console.WriteLine(value is WeatherForecastWithCity) // False

W poniższej sekcji opisano sposób dodawania metadanych w celu włączenia zaokrąglania typu pochodnego.

Dyskryminacje typu polimorficznego

Aby włączyć deserializację polimorficzną, należy określić dyskryminację typu dla klasy pochodnej:

[JsonDerivedType(typeof(WeatherForecastBase), typeDiscriminator: "base")]
[JsonDerivedType(typeof(WeatherForecastWithCity), typeDiscriminator: "withCity")]
public class WeatherForecastBase
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string? Summary { get; set; }
}

public class WeatherForecastWithCity : WeatherForecastBase
{
    public string? City { get; set; }
}
<JsonDerivedType(GetType(WeatherForecastBase), "base")>
<JsonDerivedType(GetType(WeatherForecastWithCity), "withCity")>
Public Class WeatherForecastBase
    Public Property [Date] As DateTimeOffset
    Public Property TemperatureCelsius As Integer
    Public Property Summary As String
End Class

Public Class WeatherForecastWithCity
    Inherits WeatherForecastBase
    Public Property City As String
End Class

W przypadku dodanych metadanych, w szczególności dyskryminujących typ, serializator może serializować i deserializować ładunek jako typ z jego typu WeatherForecastBasepodstawowego WeatherForecastWithCity . Serializacja emituje kod JSON wraz z metadanymi dyskryminującymi typu:

WeatherForecastBase weather = new WeatherForecastWithCity
{
    City = "Milwaukee",
    Date = new DateTimeOffset(2022, 9, 26, 0, 0, 0, TimeSpan.FromHours(-5)),
    TemperatureCelsius = 15,
    Summary = "Cool"
}
var json = JsonSerializer.Serialize<WeatherForecastBase>(weather, options);
Console.WriteLine(json);
// Sample output:
//   {
//     "$type" : "withCity",
//     "City": "Milwaukee",
//     "Date": "2022-09-26T00:00:00-05:00",
//     "TemperatureCelsius": 15,
//     "Summary": "Cool"
//   }
Dim weather As WeatherForecastBase = New WeatherForecastWithCity With
{
    .City = "Milwaukee",
    .[Date] = New DateTimeOffset(2022, 9, 26, 0, 0, 0, TimeSpan.FromHours(-5)),
    .TemperatureCelsius = 15,
    .Summary = "Cool"
}
Dim json As String = JsonSerializer.Serialize(weather, options)
Console.WriteLine(json)
' Sample output:
'   {
'     "$type" : "withCity",
'     "City": "Milwaukee",
'     "Date": "2022-09-26T00:00:00-05:00",
'     "TemperatureCelsius": 15,
'     "Summary": "Cool"
'   }

Z dyskryminującym typem serializator może deserializować ładunek polimorficznie jako WeatherForecastWithCity:

WeatherForecastBase value = JsonSerializer.Deserialize<WeatherForecastBase>(json);
Console.WriteLine(value is WeatherForecastWithCity); // True

Uwaga

Dyskryminujące typy należy umieścić na początku obiektu JSON, pogrupowane razem z innymi właściwościami metadanych, takimi jak $id i $ref.

Dim value As WeatherForecastBase = JsonSerializer.Deserialize(json)
Console.WriteLine(value is WeatherForecastWithCity) // True

Formaty dyskryminujące kombinacji i dopasowywania typów

Identyfikatory dyskryminujące typu są prawidłowe w formularzach string lub int , więc następujące elementy są prawidłowe:

[JsonDerivedType(typeof(WeatherForecastWithCity), 0)]
[JsonDerivedType(typeof(WeatherForecastWithTimeSeries), 1)]
[JsonDerivedType(typeof(WeatherForecastWithLocalNews), 2)]
public class WeatherForecastBase { }

var json = JsonSerializer.Serialize<WeatherForecastBase>(new WeatherForecastWithTimeSeries());
Console.WriteLine(json);
// Sample output:
//   {
//    "$type" : 1,
//    Omitted for brevity...
//   }
<JsonDerivedType(GetType(WeatherForecastWithCity), 0)>
<JsonDerivedType(GetType(WeatherForecastWithTimeSeries), 1)>
<JsonDerivedType(GetType(WeatherForecastWithLocalNews), 2)>
Public Class WeatherForecastBase
End Class

Dim json As String = JsonSerializer.Serialize(Of WeatherForecastBase)(New WeatherForecastWithTimeSeries())
Console.WriteLine(json)
' Sample output:
'  {
'    "$type" : 1,
'    Omitted for brevity...
'  }

Interfejs API obsługuje mieszanie i dopasowywanie konfiguracji dyskryminujących typów, ale nie jest to zalecane. Ogólne zalecenie polega na wykorzystaniu wszystkich string typów dyskryminujących, dyskryminujących wszystkich int typów lub brak dyskryminujących w ogóle. W poniższym przykładzie pokazano, jak mieszać i dopasowywać konfiguracje dyskryminujące typu:

[JsonDerivedType(typeof(ThreeDimensionalPoint), typeDiscriminator: 3)]
[JsonDerivedType(typeof(FourDimensionalPoint), typeDiscriminator: "4d")]
public class BasePoint
{
    public int X { get; set; }
    public int Y { get; set; }
}

public class ThreeDimensionalPoint : BasePoint
{
    public int Z { get; set; }
}

public sealed class FourDimensionalPoint : ThreeDimensionalPoint
{
    public int W { get; set; }
}
<JsonDerivedType(GetType(ThreeDimensionalPoint), 3)>
<JsonDerivedType(GetType(FourDimensionalPoint), "4d")>
Public Class BasePoint
    Public Property X As Integer
    Public Property Y As Integer
End Class

Public Class ThreeDimensionalPoint
    Inherits BasePoint
    Public Property Z As Integer
End Class

Public NotInheritable Class FourDimensionalPoint
    Inherits ThreeDimensionalPoint
    Public Property W As Integer
End Class

W poprzednim przykładzie BasePoint typ nie ma dyskryminującego typu, podczas gdy ThreeDimensionalPoint typ ma int dyskryminujący typ, a FourDimensionalPoint typ ma string dyskryminujący typ.

Ważne

Aby serializacja polimorficzna działała, typ wartości serializowanej powinien być typu polimorficznego typu podstawowego. Obejmuje to użycie typu podstawowego jako parametru typu ogólnego podczas serializacji wartości na poziomie głównym, jako zadeklarowanego typu właściwości serializacji lub jako elementu kolekcji w serializacji kolekcji.

using System.Text.Json;
using System.Text.Json.Serialization;

PerformRoundTrip<BasePoint>();
PerformRoundTrip<ThreeDimensionalPoint>();
PerformRoundTrip<FourDimensionalPoint>();

static void PerformRoundTrip<T>() where T : BasePoint, new()
{
    var json = JsonSerializer.Serialize<BasePoint>(new T());
    Console.WriteLine(json);

    BasePoint? result = JsonSerializer.Deserialize<BasePoint>(json);
    Console.WriteLine($"result is {typeof(T)}; // {result is T}");
    Console.WriteLine();
}
// Sample output:
//   { "X": 541, "Y": 503 }
//   result is BasePoint; // True
//
//   { "$type": 3, "Z": 399, "X": 835, "Y": 78 }
//   result is ThreeDimensionalPoint; // True
//
//   { "$type": "4d", "W": 993, "Z": 427, "X": 508, "Y": 741 }
//   result is FourDimensionalPoint; // True
Imports System.Text.Json
Imports System.Text.Json.Serialization

Module Program
    Sub Main()
        PerformRoundTrip(Of BasePoint)()
        PerformRoundTrip(Of ThreeDimensionalPoint)()
        PerformRoundTrip(Of FourDimensionalPoint)()
    End Sub

    Private Sub PerformRoundTrip(Of T As {BasePoint, New})()
        Dim json = JsonSerializer.Serialize(Of BasePoint)(New T())
        Console.WriteLine(json)

        Dim result As BasePoint = JsonSerializer.Deserialize(Of BasePoint)(json)
        Console.WriteLine($"result is {GetType(T)}; // {TypeOf result Is T}")
        Console.WriteLine()
    End Sub
End Module
' Sample output:
'   { "X": 649, "Y": 754 }
'   result is BasePoint; // True
'
'   { "$type": 3, "Z": 247, "X": 814, "Y": 56 }
'   result is ThreeDimensionalPoint; // True
'
'   { "$type": "4d", "W": 427, "Z": 193, "X": 112, "Y": 935 }
'   result is FourDimensionalPoint; // True

Dostosowywanie nazwy dyskryminującej typu

Domyślna nazwa właściwości dla typu dyskryminującego to $type. Aby dostosować nazwę właściwości, użyj elementu JsonPolymorphicAttribute , jak pokazano w poniższym przykładzie:

[JsonPolymorphic(TypeDiscriminatorPropertyName = "$discriminator")]
[JsonDerivedType(typeof(ThreeDimensionalPoint), typeDiscriminator: "3d")]
public class BasePoint
{
    public int X { get; set; }
    public int Y { get; set; }
}

public sealed class ThreeDimensionalPoint : BasePoint
{
    public int Z { get; set; }
}
<JsonPolymorphic(TypeDiscriminatorPropertyName:="$discriminator")>
<JsonDerivedType(GetType(ThreeDimensionalPoint), "3d")>
Public Class BasePoint
    Public Property X As Integer
    Public Property Y As Integer
End Class

Public Class ThreeDimensionalPoint
    Inherits BasePoint
    Public Property Z As Integer
End Class

W poprzednim kodzie JsonPolymorphic atrybut konfiguruje TypeDiscriminatorPropertyName wartość ."$discriminator" Po skonfigurowaniu nazwy dyskryminującej typu w poniższym przykładzie pokazano ThreeDimensionalPoint typ serializowany jako JSON:

BasePoint point = new ThreeDimensionalPoint { X = 1, Y = 2, Z = 3 };
var json = JsonSerializer.Serialize<BasePoint>(point);
Console.WriteLine(json);
// Sample output:
//  { "$discriminator": "3d", "X": 1, "Y": 2, "Z": 3 }
Dim point As BasePoint = New ThreeDimensionalPoint With { .X = 1, .Y = 2, .Z = 3 }
Dim json As String = JsonSerializer.Serialize(Of BasePoint)(point)
Console.WriteLine(json)
' Sample output:
'  { "$discriminator": "3d", "X": 1, "Y": 2, "Z": 3 }

Napiwek

Unikaj używania elementu, który JsonPolymorphicAttribute.TypeDiscriminatorPropertyName powoduje konflikt z właściwością w hierarchii typów.

Obsługa nieznanych typów pochodnych

Aby obsłużyć nieznane typy pochodne, należy zdecydować się na taką obsługę przy użyciu adnotacji w typie podstawowym. Rozważmy następującą hierarchię typów:

[JsonDerivedType(typeof(ThreeDimensionalPoint))]
public class BasePoint
{
    public int X { get; set; }
    public int Y { get; set; }
}

public class ThreeDimensionalPoint : BasePoint
{
    public int Z { get; set; }
}

public class FourDimensionalPoint : ThreeDimensionalPoint
{
    public int W { get; set; }
}
<JsonDerivedType(GetType(ThreeDimensionalPoint))>
Public Class BasePoint
    Public Property X As Integer
    Public Property Y As Integer
End Class

Public Class ThreeDimensionalPoint
    Inherits BasePoint
    Public Property Z As Integer
End Class

Public NotInheritable Class FourDimensionalPoint
    Inherits ThreeDimensionalPoint
    Public Property W As Integer
End Class

Ponieważ konfiguracja nie wyraża jawnej zgody na FourDimensionalPointobsługę programu , próba serializacji wystąpień programu FourDimensionalPoint w ten BasePoint sposób spowoduje wyjątek czasu wykonywania:

JsonSerializer.Serialize<BasePoint>(new FourDimensionalPoint()); // throws NotSupportedException
JsonSerializer.Serialize(Of BasePoint)(New FourDimensionalPoint()) ' throws NotSupportedException

Domyślne zachowanie można zmienić przy użyciu wyliczenia JsonUnknownDerivedTypeHandling , które można określić w następujący sposób:

[JsonPolymorphic(
    UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)]
[JsonDerivedType(typeof(ThreeDimensionalPoint))]
public class BasePoint
{
    public int X { get; set; }
    public int Y { get; set; }
}

public class ThreeDimensionalPoint : BasePoint
{
    public int Z { get; set; }
}

public class FourDimensionalPoint : ThreeDimensionalPoint
{
    public int W { get; set; }
}
<JsonPolymorphic(
    UnknownDerivedTypeHandling:=JsonUnknownDerivedTypeHandling.FallBackToBaseType)>
<JsonDerivedType(GetType(ThreeDimensionalPoint))>
Public Class BasePoint
    Public Property X As Integer
    Public Property Y As Integer
End Class

Public Class ThreeDimensionalPoint
    Inherits BasePoint
    Public Property Z As Integer
End Class

Public NotInheritable Class FourDimensionalPoint
    Inherits ThreeDimensionalPoint
    Public Property W As Integer
End Class

Zamiast wracać do typu podstawowego, możesz użyć FallBackToNearestAncestor ustawienia , aby powrócić do kontraktu najbliższego zadeklarowanego typu pochodnego:

[JsonPolymorphic(
    UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor)]
[JsonDerivedType(typeof(BasePoint)]
public interface IPoint { }

public class BasePoint : IPoint { }

public class ThreeDimensionalPoint : BasePoint { }
<JsonPolymorphic(
    UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor)>
<JsonDerivedType(GetType(BasePoint)>
Public Interface IPoint
End Interface

Public Class BasePoint
    Inherits IPoint
End Class

Public Class ThreeDimensionalPoint
    Inherits BasePoint
End Class

W przypadku konfiguracji podobnej do powyższego przykładu ThreeDimensionalPoint typ będzie serializowany jako BasePoint:

// Serializes using the contract for BasePoint
JsonSerializer.Serialize<IPoint>(new ThreeDimensionalPoint());
' Serializes using the contract for BasePoint
JsonSerializer.Serialize(Of IPoint)(New ThreeDimensionalPoint())

Jednak powrót do najbliższego przodka przyznaje możliwość "diamentu" niejednoznaczności. Rozważmy następującą hierarchię typów jako przykład:

[JsonPolymorphic(
    UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor)]
[JsonDerivedType(typeof(BasePoint))]
[JsonDerivedType(typeof(IPointWithTimeSeries))]
public interface IPoint { }

public interface IPointWithTimeSeries : IPoint { }

public class BasePoint : IPoint { }

public class BasePointWithTimeSeries : BasePoint, IPointWithTimeSeries { }
<JsonPolymorphic(
    UnknownDerivedTypeHandling:=JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor)>
<JsonDerivedType(GetType(BasePoint))>
<JsonDerivedType(GetType(IPointWithTimeSeries))>
Public Interface IPoint
End Interface

Public Interface IPointWithTimeSeries
    Inherits IPoint
End Interface

Public Class BasePoint
    Implements IPoint
End Class

Public Class BasePointWithTimeSeries
    Inherits BasePoint
    Implements IPointWithTimeSeries
End Class

W takim przypadku BasePointWithTimeSeries typ może być serializowany jako BasePoint albo albo IPointWithTimeSeries ponieważ są one bezpośrednimi przodkami. Ta niejednoznaczność spowoduje NotSupportedException , że element zostanie zgłoszony podczas próby serializacji wystąpienia BasePointWithTimeSeries jako IPoint.

// throws NotSupportedException
JsonSerializer.Serialize<IPoint>(new BasePointWithTimeSeries());
' throws NotSupportedException
JsonSerializer.Serialize(Of IPoint)(New BasePointWithTimeSeries())

Konfigurowanie polimorfizmu przy użyciu modelu kontraktu

W przypadku przypadków użycia, w których adnotacje atrybutów są niepraktyczne lub niemożliwe (takie jak duże modele domen, hierarchie między zestawami lub hierarchie w zależnościach innych firm), aby skonfigurować polimorfizm przy użyciu modelu kontraktu. Model kontraktu to zestaw interfejsów API, które mogą służyć do konfigurowania polimorfizmu w hierarchii typów przez utworzenie niestandardowej DefaultJsonTypeInfoResolver podklasy, która dynamicznie zapewnia konfigurację polimorficzną na typ, jak pokazano w poniższym przykładzie:

public class PolymorphicTypeResolver : DefaultJsonTypeInfoResolver
{
    public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
    {
        JsonTypeInfo jsonTypeInfo = base.GetTypeInfo(type, options);

        Type basePointType = typeof(BasePoint);
        if (jsonTypeInfo.Type == basePointType)
        {
            jsonTypeInfo.PolymorphismOptions = new JsonPolymorphismOptions
            {
                TypeDiscriminatorPropertyName = "$point-type",
                IgnoreUnrecognizedTypeDiscriminators = true,
                UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization,
                DerivedTypes =
                {
                    new JsonDerivedType(typeof(ThreeDimensionalPoint), "3d"),
                    new JsonDerivedType(typeof(FourDimensionalPoint), "4d")
                }
            };
        }

        return jsonTypeInfo;
    }
}
Public Class PolymorphicTypeResolver
    Inherits DefaultJsonTypeInfoResolver

    Public Overrides Function GetTypeInfo(
        ByVal type As Type,
        ByVal options As JsonSerializerOptions) As JsonTypeInfo

        Dim jsonTypeInfo As JsonTypeInfo = MyBase.GetTypeInfo(type, options)
        Dim basePointType As Type = GetType(BasePoint)

        If jsonTypeInfo.Type = basePointType Then
            jsonTypeInfo.PolymorphismOptions = New JsonPolymorphismOptions With {
                .TypeDiscriminatorPropertyName = "$point-type",
                .IgnoreUnrecognizedTypeDiscriminators = True,
                .UnknownDerivedTypeHandling =
                    JsonUnknownDerivedTypeHandling.FailSerialization
            }
            jsonTypeInfo.PolymorphismOptions.DerivedTypes.Add(
                New JsonDerivedType(GetType(ThreeDimensionalPoint), "3d"))
            jsonTypeInfo.PolymorphismOptions.DerivedTypes.Add(
                New JsonDerivedType(GetType(FourDimensionalPoint), "4d"))
        End If

        Return jsonTypeInfo
    End Function
End Class

Dodatkowe szczegóły serializacji polimorficznej

  • Serializacja polimorficzna obsługuje typy pochodne, które zostały jawnie wyrażeniu zgody za pośrednictwem klasy JsonDerivedTypeAttribute. Niezdecydowane typy spowodują wyjątek czasu wykonywania. Zachowanie można zmienić, konfigurując JsonPolymorphicAttribute.UnknownDerivedTypeHandling właściwość .
  • Konfiguracja polimorficzna określona w typach pochodnych nie jest dziedziczona przez konfigurację polimorficzną w typach bazowych. Typ podstawowy musi być skonfigurowany niezależnie.
  • Hierarchie polimorficzne są obsługiwane zarówno dla interface typów, jak i class .
  • Polimorfizm przy użyciu dyskryminujących typów jest obsługiwany tylko w przypadku hierarchii typów, które używają domyślnych konwerterów dla obiektów, kolekcji i typów słowników.
  • Polimorfizm jest obsługiwany w generowaniu źródła opartym na metadanych, ale nie w przypadku generowania źródła szybkiej ścieżki.

Zobacz też