Serialisieren von Eigenschaften abgeleiteter Klassen mit System.Text.Json
In diesem Artikel lernen Sie, wie Sie Eigenschaften von abgeleiteten 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.Json
nicht 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
WeatherForecastWithPrevious
enthält keineWindSpeed
. - Die JSON-Ausgabe der Serialisierung von
WeatherForecastWithPreviousAsObject
enthältWindSpeed
.
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. Bei der Serialisierung wird JSON zusammen mit den Metadaten des Typdiskriminators ausgegeben:
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
Dim value As WeatherForecastBase = JsonSerializer.Deserialize(json)
Console.WriteLine(value is WeatherForecastWithCity) // True
Hinweis
Standardmäßig muss der $type
-Diskriminator am Anfang des JSON-Objekts platziert werden, zusammen mit anderen Metadaten-Eigenschaften wie $id
und $ref
. Wenn Sie Daten aus einer externen API lesen, die den $type
-Diskriminator in der Mitte des JSON-Objekts platziert, legen Sie JsonSerializerOptions.AllowOutOfOrderMetadataProperties auf true
fest:
JsonSerializerOptions options = new() { AllowOutOfOrderMetadataProperties = true };
JsonSerializer.Deserialize<Base>("""{"Name":"Name","$type":"derived"}""", options);
Seien Sie vorsichtig, wenn Sie dieses Flag aktivieren, da es bei der Streaming-Deserialisierung von sehr großen JSON-Objekten zu Overbuffering (und Out-of-Memory-Fehlern) führen kann.
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 BasePoint
serialisiert:
// 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ürclass
-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.