Serialisieren von Eigenschaften abgeleiteter Klassen mit System.Text.Json

In diesem Artikel erfahren Sie, wie Sie Eigenschaften abgeleiteter Klassen mit dem System.Text.Json-Namespace serialisieren können.

Serialisieren von Eigenschaften abgeleiteter Klassen

In Versionen vor .NET 7 wird die Serialisierung polymorpher Typhierarchien von System.Text.Jsonnicht unterstützt. Wenn es sich beim Typ einer Eigenschaft beispielsweise um eine Schnittstelle oder eine abstrakte Klasse handelt, werden nur die für die Schnittstelle oder abstrakte Klasse definierten Eigenschaften serialisiert, auch wenn der Laufzeittyp über zusätzliche Eigenschaften verfügt. Die Ausnahmen bei diesem Verhalten werden in diesem Abschnitt erläutert. Informationen zur Unterstützung in .NET 7 finden Sie unter Polymorphe Serialisierung in .NET 7.

Nehmen Sie beispielsweise an, Sie besitzen eine WeatherForecast-Klasse und eine abgeleitete Klasse WeatherForecastDerived:

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

Ferner sei angenommen, dass das Typargument der Serialize-Methode zur Kompilierzeit WeatherForecast ist:

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)

In diesem Szenario wird die WindSpeed-Eigenschaft nicht serialisiert, auch dann nicht, wenn das weatherForecast-Objekt ein WeatherForecastDerived-Objekt ist. Nur die Basisklasseneigenschaften werden serialisiert:

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

Dieses Verhalten soll die versehentliche Verfügbarmachung von Daten in einem abgeleiteten, zur Laufzeit erstellten Typ verhindern.

Verwenden Sie einen der folgenden Ansätze, um die Eigenschaften des abgeleiteten Typs im vorherigen Beispiel zu serialisieren:

  • Rufen Sie eine Überladung von Serialize auf, bei der Sie den Typ zur Laufzeit angeben können:

    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)
    
  • Deklarieren Sie das zu serialisierende Objekt als 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)
    

Im vorherigen Beispielszenario bewirken beide Ansätze, dass die WindSpeed-Eigenschaft in die JSON-Ausgabe aufgenommen wird:

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

Wichtig

Diese Ansätze bieten polymorphe Serialisierung nur für das Stammobjekt, das serialisiert werden soll, und nicht für die Eigenschaften dieses Stammobjekts.

Sie können polymorphe Serialisierung für Objekte auf niedrigerer Ebene erzielen, wenn Sie diese als object-Typ definieren. Angenommen, Ihre WeatherForecast-Klasse verfügt über eine Eigenschaft mit dem Namen PreviousForecast, die als WeatherForecast-Typ oder als object-Typ definiert werden kann:

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

Wenn die Eigenschaft PreviousForecast eine Instanz von WeatherForecastDerived enthält:

  • Die JSON-Ausgabe der Serialisierung von WeatherForecastWithPreviousenthält WindSpeed nicht.
  • Die JSON-Ausgabe der Serialisierung von WeatherForecastWithPreviousAsObjectenthält WindSpeed.

Um WeatherForecastWithPreviousAsObject zu serialisieren, ist es nicht erforderlich, Serialize<object> oder GetType aufzurufen, da das Stammobjekt nicht das Objekt ist, das von einem abgeleiteten Typ sein darf. Im folgenden Codebeispiel wird weder Serialize<object> noch GetType aufgerufen:

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

Der vorangehende Code serialisiert WeatherForecastWithPreviousAsObject korrekt:

{
  "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"
  }
}

Derselbe Ansatz zum Definieren von Eigenschaften als object funktioniert mit Schnittstellen. Angenommen, Sie haben die folgende Schnittstelle und Implementierung, und Sie möchten eine Klasse mit Eigenschaften serialisieren, die Implementierungsinstanzen enthalten:

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

Wenn Sie eine Instanz von Forecasts serialisieren, zeigt nur Tuesday die WindSpeed-Eigenschaft an, da Tuesday als object definiert ist:

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)

Im folgenden Beispiel wird der JSON-Code gezeigt, der aus dem vorangehenden Code resultiert:

{
  "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
  }
}

Hinweis

Dieser Artikel behandelt die Serialisierung, nicht die Deserialisierung. Die polymorphe Deserialisierung wird in Versionen vor .NET 7 nicht unterstützt, Sie können aber als Problemumgehung einen benutzerdefinierten Konverter schreiben, wie etwa das Beispiel in Unterstützung polymorpher Deserialisierung. Weitere Informationen dazu, wie .NET 7 polymorphe Serialisierung und Deserialisierung unterstützt, finden Sie unter Serialisieren von Eigenschaften abgeleiteter Klassen mit System.Text.Json in .NET 7.

Ab .NET 7 unterstützt System.Text.Json die Serialisierung und Deserialisierung der polymorphen Typhierarchie mit Attributanmerkungen.

attribute BESCHREIBUNG
JsonDerivedTypeAttribute Gibt beim Platzieren in einer Typdeklaration an, dass der angegebene Untertyp für die polymorphe Serialisierung ausgewählt werden soll. Dadurch wird zugleich die Möglichkeit zur Angabe eines Typdiskriminators verfügbar gemacht.
JsonPolymorphicAttribute Gibt bei Platzierung in einer Typdeklaration an, dass der Typ polymorph serialisiert werden soll. Außerdem werden verschiedene Optionen zum Konfigurieren der polymorphen Serialisierung und Deserialisierung für diesen Typ verfügbar gemacht.

Nehmen Sie beispielsweise an, Sie besitzen eine WeatherForecastBase-Klasse und eine abgeleitete Klasse WeatherForecastWithCity:

[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

Ferner sei angenommen, dass das Typargument der Serialize<TValue>-Methode zur Kompilierzeit WeatherForecastBase ist:

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

In diesem Szenario wird die City-Eigenschaft serialisiert, da es sich bei dem weatherForecastBase-Objekt tatsächlich um ein WeatherForecastWithCity-Objekt handelt. Diese Konfiguration ermöglicht die polymorphe Serialisierung für WeatherForecastBase, insbesondere wenn der Laufzeittyp WeatherForecastWithCity lautet:

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

Während der Roundtrip der Nutzdaten als WeatherForecastBase unterstützt wird, materialisieren sie sich nicht als Laufzeittyp von WeatherForecastWithCity. Stattdessen tauchen sie als Laufzeittyp WeatherForecastBase auf:

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

Im folgenden Abschnitt wird beschrieben, wie Sie Metadaten hinzufügen, um das Roundtripping des abgeleiteten Typs zu ermöglichen.

Polymorphe Typdiskriminatoren

Zum Aktivieren der polymorphen Deserialisierung müssen Sie einen Typdiskriminator für die abgeleitete Klasse angeben:

[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

Mit den hinzugefügten Metadaten, insbesondere dem Typdiskriminator, kann der Serialisierer die Nutzdaten als Typ WeatherForecastWithCity von ihrem Basistyp WeatherForecastBase serialisieren und deserialisieren. Die Serialisierung gibt JSON zusammen mit den Metadaten des Typdiskriminators aus:

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"
'   }

Mit dem Typdiskriminator kann der Serialisierer die Nutzdaten polymorph als WeatherForecastWithCity deserialisieren:

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

Hinweis

Der Typdiskriminator muss am Anfang des JSON-Objekts platziert und mit anderen Metadateneigenschaften wie $id und $ref gruppiert werden.

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

Gemischte Verwendung und Abgleich von Typdiskriminatorformaten

Bezeichner von Typdiskriminatoren sind sowohl in der Form string als auch als int gültig, daher ist Folgendes gültig:

[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...
'  }

Die API unterstützt zwar das Mischen und Abgleichen von Typdiskriminatorkonfigurationen, dies wird jedoch nicht empfohlen. Die allgemeine Empfehlung lautet, entweder nur string-Typdiskriminatoren, nur int-Typdiskriminatoren oder überhaupt keine Diskriminatoren zu verwenden. Das folgende Beispiel zeigt die gemischte Verwendung und den Abgleich von Typdiskriminatorkonfigurationen:

[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

Im vorherigen Beispiel verfügt der Typ BasePoint nicht über einen Typdiskriminator, während der Typ ThreeDimensionalPoint einen int-Typdiskriminator aufweist und der FourDimensionalPoint einen string-Typdiskriminator besitzt.

Wichtig

Damit die polymorphe Serialisierung funktioniert, sollte der Typ des serialisierten Werts dem des polymorphen Basistyps entsprechen. Dazu gehört die Verwendung des Basistyps beim Serialisieren von Werten auf Stammebene als generischer Typparameter, als deklarierter Typ serialisierter Eigenschaften oder als Auflistungselement in serialisierten Auflistungen.

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

Anpassen des Typdiskriminatornamens

Der Standardeigenschaftsname für den Typdiskriminator ist $type. Verwenden Sie zum Anpassen des Eigenschaftsnamens das JsonPolymorphicAttribute, wie im folgenden Beispiel gezeigt:

[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

Im vorstehenden Code konfiguriert das JsonPolymorphic-Attribut den TypeDiscriminatorPropertyName mit dem Wert "$discriminator". Das folgende Beispiel zeigt den ThreeDimensionalPoint-Typ mit konfiguriertem Typdiskriminatornamen als JSON serialisiert:

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 }

Tipp

Vermeiden Sie die Verwendung eines JsonPolymorphicAttribute.TypeDiscriminatorPropertyName, wenn dies zu einem Konflikt mit einer Eigenschaft in Ihrer Typhierarchie führt.

Behandeln unbekannter abgeleiteter Typen

Zur Behandlung unbekannter abgeleiteter Typen müssen Sie sich für eine entsprechende Unterstützung mithilfe einer Anmerkung für den Basistyp entscheiden. Sehen Sie sich die folgende Typhierarchie an:

[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

Da die Konfiguration die Unterstützung für FourDimensionalPoint nicht explizit aktiviert, führt der Versuch, Instanzen von FourDimensionalPoint als BasePoint zu serialisieren, zu einer Laufzeitausnahme:

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

Sie können das Standardverhalten mithilfe der JsonUnknownDerivedTypeHandling-Enumeration ändern, die wie folgt angegeben werden kann:

[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

Anstatt auf den Basistyp zurückzugreifen, können Sie die FallBackToNearestAncestor-Einstellung verwenden, um auf den Vertrag des nächstgelegenen deklarierten abgeleiteten Typs zurückzugreifen:

[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

Bei einer Konfiguration wie im vorherigen Beispiel wird der ThreeDimensionalPoint-Typ als BasePointserialisiert:

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

Der Rückgriff auf den nächsten Vorgänger lässt jedoch die Möglichkeit einer „Rauten-Mehrdeutigkeit“ (diamond ambiguity) zu. Betrachten Sie die folgende Typhierarchie als Beispiel:

[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

In diesem Fall kann der Typ BasePointWithTimeSeries entweder als BasePoint oder als IPointWithTimeSeries serialisiert werden, da beide direkte Vorgänger sind. Diese Mehrdeutigkeit führt dazu, dass bei dem Versuch, eine Instanz von BasePointWithTimeSeries als IPoint zu serialisieren, die NotSupportedException ausgelöst wird.

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

Konfigurieren von Polymorphismus mit dem Vertragsmodell

Für Anwendungsfälle, in denen Attributanmerkungen nicht praktikabel oder unmöglich sind (z. B. in großen Domänenmodellen, assemblyübergreifenden Hierarchien oder Hierarchien in Abhängigkeiten von Drittanbietern), verwenden Sie zum Konfigurieren von Polymorphismus das Vertragsmodell. Das Vertragsmodell ist eine Sammlung von APIs, die zum Konfigurieren von Polymorphismus in einer Typhierarchie verwendet werden können, indem eine benutzerdefinierte Unterklasse DefaultJsonTypeInfoResolver erstellt wird, die dynamisch eine polymorphe Konfiguration pro Typ bereitstellt, wie im folgenden Beispiel gezeigt:

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

Zusätzliche Details zur polymorphen Serialisierung

  • Die polymorphe Serialisierung unterstützt abgeleitete Typen, die explizit über das JsonDerivedTypeAttribute aktiviert wurden. Nicht deklarierte Typen führen zu einer Laufzeitausnahme. Sie können dieses Verhalten ändern, indem Sie die JsonPolymorphicAttribute.UnknownDerivedTypeHandling-Eigenschaft festlegen.
  • Eine polymorphe Konfiguration, die für abgeleitete Typen angegeben ist, wird nicht von der polymorphen Konfiguration für Basistypen geerbt. Der Basistyp muss unabhängig konfiguriert werden.
  • Polymorphe Hierarchien werden sowohl für interface- als auch für class-Typen unterstützt.
  • Polymorphismus wird für Typdiskriminatoren nur für Typhierarchien unterstützt, die die Standardkonverter für Objekte, Sammlungen und Wörterbuchtypen verwenden.
  • Polymorphismus wird bei der metadatenbasierten Quellgenerierung unterstützt, aber nicht bei der Generierung von Quellgenerierung im Schnelldurchgang.

Weitere Informationen