Eigenschappen van afgeleide klassen serialiseren met System.Text.Json
In dit artikel leert u hoe u eigenschappen van afgeleide klassen serialiseert met de System.Text.Json
naamruimte.
Eigenschappen van afgeleide klassen serialiseren
Vanaf .NET 7 ondersteunt System.Text.Json
polymorfe hiërarchieserialisatie en deserialisatie met kenmerkaantekeningen.
Kenmerk | Beschrijving |
---|---|
JsonDerivedTypeAttribute | Wanneer het wordt geplaatst op een typedeclaratie, geeft u aan dat het opgegeven subtype moet worden gekozen voor polymorfe serialisatie. Het geeft ook de mogelijkheid om een typediscriminator op te geven. |
JsonPolymorphicAttribute | Wanneer u een typedeclaratie plaatst, geeft u aan dat het type polymorf moet worden geserialiseerd. Er worden ook verschillende opties weergegeven voor het configureren van polymorfische serialisatie en deserialisatie voor dat type. |
Stel dat u een WeatherForecastBase
klasse en een afgeleide klasse WeatherForecastWithCity
hebt:
[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
Stel dat het typeargument van de Serialize<TValue>
methode tijdens het compileren het volgende is WeatherForecastBase
:
options = new JsonSerializerOptions
{
WriteIndented = true
};
jsonString = JsonSerializer.Serialize<WeatherForecastBase>(weatherForecastBase, options);
options = New JsonSerializerOptions With {
.WriteIndented = True
}
jsonString = JsonSerializer.Serialize(WeatherForecastBase, options)
In dit scenario wordt de City
eigenschap geserialiseerd omdat het weatherForecastBase
object eigenlijk een WeatherForecastWithCity
object is. Deze configuratie maakt polymorfische serialisatie mogelijk voorWeatherForecastBase
, met name wanneer het runtimetype:WeatherForecastWithCity
{
"City": "Milwaukee",
"Date": "2022-09-26T00:00:00-05:00",
"TemperatureCelsius": 15,
"Summary": "Cool"
}
Terwijl round-tripping van de nettolading wordt WeatherForecastBase
ondersteund, wordt deze niet gerealiseerd als een runtimetype van WeatherForecastWithCity
. In plaats daarvan wordt het gerealiseerd als een runtimetype van WeatherForecastBase
:
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
In de volgende sectie wordt beschreven hoe u metagegevens toevoegt om round-tripping van het afgeleide type mogelijk te maken.
Polymorfische typediscriminatoren
Als u polymorfische deserialisatie wilt inschakelen, moet u een typediscriminatie opgeven voor de afgeleide klasse:
[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
Met de toegevoegde metagegevens, met name het typediscriminator, kan de serialisatiefunctie de nettolading serialiseren en deserialiseren als het type van het WeatherForecastWithCity
basistype WeatherForecastBase
. Serialisatie verzendt JSON samen met de typediscriminatiemetagegevens:
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"
' }
Met het typediscriminator kan de serializer deserialiseren van de nettolading polymorf als WeatherForecastWithCity
:
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
Notitie
Standaard moet de $type
discriminator aan het begin van het JSON-object worden geplaatst, gegroepeerd samen met andere metagegevenseigenschappen, zoals $id
en $ref
. Als u gegevens leest van een externe API die de $type
discriminator in het midden van het JSON-object plaatst, stelt u JsonSerializerOptions.AllowOutOfOrderMetadataProperties true
het volgende in:
JsonSerializerOptions options = new() { AllowOutOfOrderMetadataProperties = true };
JsonSerializer.Deserialize<Base>("""{"Name":"Name","$type":"derived"}""", options);
Wees voorzichtig wanneer u deze vlag inschakelt, omdat dit kan leiden tot overbuffering (en onvoldoende geheugenfouten) bij het uitvoeren van streamingdeserialisatie van zeer grote JSON-objecten.
Onderscheidsindelingen voor combinatie- en overeenkomsttypen
Type discriminator-id's zijn geldig in een string
van beide of int
formulieren, dus het volgende is geldig:
[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...
' }
Hoewel de API ondersteuning biedt voor het combineren en vergelijken van typediscriminatorconfiguraties, wordt dit niet aanbevolen. De algemene aanbeveling is om alle string
typediscriminaties, alle int
typediscriminaties of helemaal geen discriminators te gebruiken. In het volgende voorbeeld ziet u hoe u onderscheidsconfiguraties voor typen combineert en matcht:
[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
In het voorgaande voorbeeld heeft het BasePoint
type geen typediscriminator, terwijl het ThreeDimensionalPoint
type een int
typediscriminator heeft en het FourDimensionalPoint
type een string
typediscriminator heeft.
Belangrijk
Polymorfische serialisatie werkt alleen als het type geserialiseerde waarde dat van het polymorfe basistype is. Dit omvat het gebruik van het basistype als de algemene typeparameter bij het serialiseren van waarden op basisniveau, als het gedeclareerde type geserialiseerde eigenschappen of als het verzamelingselement in geserialiseerde verzamelingen.
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
De naam van het type discriminator aanpassen
De standaardeigenschapsnaam voor het type discriminator is $type
. Als u de naam van de eigenschap wilt aanpassen, gebruikt u de JsonPolymorphicAttribute naam zoals wordt weergegeven in het volgende voorbeeld:
[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
In de voorgaande code configureert het JsonPolymorphic
kenmerk de TypeDiscriminatorPropertyName
"$discriminator"
waarde. Wanneer de typediscriminatienaam is geconfigureerd, wordt in het volgende voorbeeld het ThreeDimensionalPoint
type geserialiseerd als JSON weergegeven:
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 }
Tip
Vermijd het gebruik van een JsonPolymorphicAttribute.TypeDiscriminatorPropertyName eigenschap die conflicteert met een eigenschap in uw typehiërarchie.
Onbekende afgeleide typen verwerken
Als u onbekende afgeleide typen wilt verwerken, moet u zich aanmelden voor dergelijke ondersteuning met behulp van een aantekening op het basistype. Houd rekening met de volgende typehiërarchie:
[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
Omdat de configuratie niet expliciet opt-in-ondersteuning voor FourDimensionalPoint
, pogingen om exemplaren FourDimensionalPoint
van als BasePoint
te serialiseren resulteert in een runtime-uitzondering:
JsonSerializer.Serialize<BasePoint>(new FourDimensionalPoint()); // throws NotSupportedException
JsonSerializer.Serialize(Of BasePoint)(New FourDimensionalPoint()) ' throws NotSupportedException
U kunt het standaardgedrag wijzigen met behulp van de JsonUnknownDerivedTypeHandling enum, die als volgt kan worden opgegeven:
[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
In plaats van terug te vallen op het basistype, kunt u de FallBackToNearestAncestor
instelling gebruiken om terug te vallen op het contract van het dichtstbijzijnde gedeclareerde afgeleide type:
[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
Met een configuratie zoals in het voorgaande voorbeeld wordt het ThreeDimensionalPoint
type geserialiseerd als BasePoint
:
// Serializes using the contract for BasePoint
JsonSerializer.Serialize<IPoint>(new ThreeDimensionalPoint());
' Serializes using the contract for BasePoint
JsonSerializer.Serialize(Of IPoint)(New ThreeDimensionalPoint())
Als u echter terugvalt op de dichtstbijzijnde voorouder, geeft u de mogelijkheid van dubbelzinnigheid "diamant" toe. Bekijk de volgende typehiërarchie als voorbeeld:
[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 dit geval kan het BasePointWithTimeSeries
type worden geserialiseerd als een van BasePoint
beide of IPointWithTimeSeries
omdat ze beide directe voorouders zijn. Deze dubbelzinnigheid zorgt ervoor dat de NotSupportedException fout wordt gegenereerd bij het serialiseren van een exemplaar als BasePointWithTimeSeries
IPoint
.
// throws NotSupportedException
JsonSerializer.Serialize<IPoint>(new BasePointWithTimeSeries());
' throws NotSupportedException
JsonSerializer.Serialize(Of IPoint)(New BasePointWithTimeSeries())
Polymorfisme configureren met het contractmodel
Voor gebruiksvoorbeelden waarbij kenmerkaantekeningen onpraktisch of onmogelijk zijn (zoals grote domeinmodellen, cross-assemblyhiërarchieën of hiërarchieën in afhankelijkheden van derden), gebruikt u het contractmodel om polymorfisme te configureren. Het contractmodel is een set API's die kunnen worden gebruikt om polymorfisme in een typehiërarchie te configureren door een aangepaste DefaultJsonTypeInfoResolver subklasse te maken die dynamisch polymorf configuratie per type biedt, zoals wordt weergegeven in het volgende voorbeeld:
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
Aanvullende polymorfe serialisatiedetails
- Polymorfische serialisatie ondersteunt afgeleide typen die expliciet zijn aangemeld via de JsonDerivedTypeAttribute. Niet-declaratieve typen resulteren in een runtime-uitzondering. Het gedrag kan worden gewijzigd door de JsonPolymorphicAttribute.UnknownDerivedTypeHandling eigenschap te configureren.
- Polymorfe configuratie die is opgegeven in afgeleide typen wordt niet overgenomen door polymorfe configuratie in basistypen. Het basistype moet onafhankelijk worden geconfigureerd.
- Polymorfische hiërarchieën worden ondersteund voor beide
interface
enclass
typen. - Polymorfisme met behulp van typediscriminatoren wordt alleen ondersteund voor typehiërarchieën die gebruikmaken van de standaardconversieprogramma's voor objecten, verzamelingen en woordenlijsttypen.
- Polymorfisme wordt ondersteund bij het genereren van bron op basis van metagegevens, maar niet bij het genereren van snel padbronnen.