Delen via


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 WeatherForecastWithCityhebt:

[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 truehet 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 en class 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.

Zie ook