Serializace vlastností odvozených tříd pomocí System.Text.Json
V tomto článku se dozvíte, jak serializovat vlastnosti odvozených tříd s oborem System.Text.Json
názvů.
Serializace vlastností odvozených tříd
Počínaje rozhraním .NET 7 System.Text.Json
podporuje serializaci polymorfního typu a deserializaci pomocí poznámek atributů.
Atribut | Popis |
---|---|
JsonDerivedTypeAttribute | Při umístění na deklaraci typu označuje, že zadaný podtyp by měl být optován do polymorfní serializace. Zpřístupňuje také možnost určit diskriminátor typu. |
JsonPolymorphicAttribute | Při umístění na deklaraci typu označuje, že typ by měl být serializován polymorfně. Také zveřejňuje různé možnosti konfigurace polymorfní serializace a deserializace pro tento typ. |
Předpokládejme například, že máte WeatherForecastBase
třídu a odvozenou třídu 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
A předpokládejme, že argument Serialize<TValue>
typu metody v době kompilace je WeatherForecastBase
:
options = new JsonSerializerOptions
{
WriteIndented = true
};
jsonString = JsonSerializer.Serialize<WeatherForecastBase>(weatherForecastBase, options);
options = New JsonSerializerOptions With {
.WriteIndented = True
}
jsonString = JsonSerializer.Serialize(WeatherForecastBase, options)
V tomto scénáři je vlastnost serializována, City
protože weatherForecastBase
objekt je ve skutečnosti WeatherForecastWithCity
objekt. Tato konfigurace umožňuje polymorfní serializaci pro WeatherForecastBase
, konkrétně v případě, že typ modulu runtime je WeatherForecastWithCity
:
{
"City": "Milwaukee",
"Date": "2022-09-26T00:00:00-05:00",
"TemperatureCelsius": 15,
"Summary": "Cool"
}
I když je datová část zaokrouhlena tak, jak WeatherForecastBase
je podporována, nebude se materializovat jako typ WeatherForecastWithCity
běhu . Místo toho se materializuje jako typ WeatherForecastBase
běhu:
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
Následující část popisuje, jak přidat metadata pro povolení odezvy odvozeného typu.
Diskriminátor polymorfního typu
Chcete-li povolit polymorfní deserializaci, je nutné zadat typ diskriminátoru pro odvozenou třídu:
[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
S přidanými metadaty, konkrétně, typ diskriminátor, serializátor může serializovat a deserializovat datovou část jako WeatherForecastWithCity
typ ze svého základního typu WeatherForecastBase
. Serializace generuje JSON spolu s nediskriminačními metadaty typu:
WeatherForecastBase weather = new WeatherForecastWithCity
{
City = "Milwaukee",
Date = new DateTimeOffset(2022, 9, 26, 0, 0, 0, TimeSpan.FromHours(-5)),
TemperatureCelsius = 15,
Summary = "Cool"
}
var json = JsonSerializer.Serialize<WeatherForecastBase>(weather, options);
Console.WriteLine(json);
// Sample output:
// {
// "$type" : "withCity",
// "City": "Milwaukee",
// "Date": "2022-09-26T00:00:00-05:00",
// "TemperatureCelsius": 15,
// "Summary": "Cool"
// }
Dim weather As WeatherForecastBase = New WeatherForecastWithCity With
{
.City = "Milwaukee",
.[Date] = New DateTimeOffset(2022, 9, 26, 0, 0, 0, TimeSpan.FromHours(-5)),
.TemperatureCelsius = 15,
.Summary = "Cool"
}
Dim json As String = JsonSerializer.Serialize(weather, options)
Console.WriteLine(json)
' Sample output:
' {
' "$type" : "withCity",
' "City": "Milwaukee",
' "Date": "2022-09-26T00:00:00-05:00",
' "TemperatureCelsius": 15,
' "Summary": "Cool"
' }
Při diskriminátoru typu může serializátor deserializovat datovou část polymorfně jako 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
Poznámka:
Ve výchozím nastavení $type
musí být diskriminátor umístěn na začátku objektu JSON seskupené spolu s dalšími vlastnostmi metadat, jako $id
jsou a $ref
. Pokud čtete data z externího rozhraní API, které umístí $type
diskriminátor uprostřed objektu JSON, nastavte JsonSerializerOptions.AllowOutOfOrderMetadataProperties na true
:
JsonSerializerOptions options = new() { AllowOutOfOrderMetadataProperties = true };
JsonSerializer.Deserialize<Base>("""{"Name":"Name","$type":"derived"}""", options);
Při povolení tohoto příznaku buďte opatrní, protože při provádění deserializace streamování velmi velkých objektů JSON může vést k nadměrnému ukládání do vyrovnávací paměti (a selhání nedostatku paměti).
Diskriminující formáty typu mix a shoda
Diskriminační identifikátory typu jsou platné buď ve string
int
formulářích, takže platí následující:
[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...
' }
I když rozhraní API podporuje kombinování a porovnávání konfigurací diskriminátoru typu, nedoporučuje se. Obecné doporučení je použít buď všechny string
diskriminátory typu, všechny int
diskriminátory typu, nebo vůbec žádné diskriminátory. Následující příklad ukazuje, jak kombinovat a shodovat konfigurace diskriminátoru typu:
[JsonDerivedType(typeof(ThreeDimensionalPoint), typeDiscriminator: 3)]
[JsonDerivedType(typeof(FourDimensionalPoint), typeDiscriminator: "4d")]
public class BasePoint
{
public int X { get; set; }
public int Y { get; set; }
}
public class ThreeDimensionalPoint : BasePoint
{
public int Z { get; set; }
}
public sealed class FourDimensionalPoint : ThreeDimensionalPoint
{
public int W { get; set; }
}
<JsonDerivedType(GetType(ThreeDimensionalPoint), 3)>
<JsonDerivedType(GetType(FourDimensionalPoint), "4d")>
Public Class BasePoint
Public Property X As Integer
Public Property Y As Integer
End Class
Public Class ThreeDimensionalPoint
Inherits BasePoint
Public Property Z As Integer
End Class
Public NotInheritable Class FourDimensionalPoint
Inherits ThreeDimensionalPoint
Public Property W As Integer
End Class
V předchozím příkladu BasePoint
typ nemá diskriminátor typu, zatímco ThreeDimensionalPoint
typ má int
diskriminátor typu a FourDimensionalPoint
má string
diskriminátor typu.
Důležité
Aby polymorfní serializace fungovala, typ serializované hodnoty by měl být typ polymorfního základního typu. To zahrnuje použití základního typu jako parametr obecného typu při serializaci hodnot kořenové úrovně, jako deklarovaný typ serializovaných vlastností nebo jako element kolekce v serializovaných kolekcích.
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
Přizpůsobení názvu diskriminátoru typu
Výchozí název vlastnosti pro typ diskriminátoru je $type
. Pokud chcete přizpůsobit název vlastnosti, použijte název JsonPolymorphicAttribute vlastnosti, jak je znázorněno v následujícím příkladu:
[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
V předchozím kódu JsonPolymorphic
atribut nakonfiguruje TypeDiscriminatorPropertyName
hodnotu "$discriminator"
. Když je nakonfigurovaný název diskriminátoru typu, následující příklad ukazuje ThreeDimensionalPoint
typ serializovaný jako JSON:
BasePoint point = new ThreeDimensionalPoint { X = 1, Y = 2, Z = 3 };
var json = JsonSerializer.Serialize<BasePoint>(point);
Console.WriteLine(json);
// Sample output:
// { "$discriminator": "3d", "X": 1, "Y": 2, "Z": 3 }
Dim point As BasePoint = New ThreeDimensionalPoint With { .X = 1, .Y = 2, .Z = 3 }
Dim json As String = JsonSerializer.Serialize(Of BasePoint)(point)
Console.WriteLine(json)
' Sample output:
' { "$discriminator": "3d", "X": 1, "Y": 2, "Z": 3 }
Tip
JsonPolymorphicAttribute.TypeDiscriminatorPropertyName Nepoužívejte konflikt s vlastností v hierarchii typů.
Zpracování neznámých odvozených typů
Pokud chcete zpracovávat neznámé odvozené typy, musíte se k takové podpoře přihlásit pomocí poznámky základního typu. Zvažte následující hierarchii typů:
[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
Vzhledem k tomu, že konfigurace explicitně nepřihlašuje podporu FourDimensionalPoint
, pokus o serializaci instancí FourDimensionalPoint
v důsledku BasePoint
výjimky za běhu:
JsonSerializer.Serialize<BasePoint>(new FourDimensionalPoint()); // throws NotSupportedException
JsonSerializer.Serialize(Of BasePoint)(New FourDimensionalPoint()) ' throws NotSupportedException
Výchozí chování můžete změnit pomocí výčtu JsonUnknownDerivedTypeHandling , který lze zadat následujícím způsobem:
[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
Místo vrácení zpět na základní typ můžete použít FallBackToNearestAncestor
nastavení pro návrat ke smlouvě nejbližšího deklarovaného odvozeného typu:
[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
S konfigurací jako v předchozím příkladu ThreeDimensionalPoint
bude typ serializován jako BasePoint
:
// Serializes using the contract for BasePoint
JsonSerializer.Serialize<IPoint>(new ThreeDimensionalPoint());
' Serializes using the contract for BasePoint
JsonSerializer.Serialize(Of IPoint)(New ThreeDimensionalPoint())
Návrat k nejbližšímu předku však uznává možnost nejednoznačnosti "kosočtverec". Jako příklad zvažte následující hierarchii typů:
[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
V tomto případě BasePointWithTimeSeries
může být typ serializován jako buď BasePoint
nebo IPointWithTimeSeries
protože jsou oba přímí nadřazené. Tato nejednoznačnost způsobí NotSupportedException vyvolání při pokusu o serializaci instance BasePointWithTimeSeries
jako IPoint
.
// throws NotSupportedException
JsonSerializer.Serialize<IPoint>(new BasePointWithTimeSeries());
' throws NotSupportedException
JsonSerializer.Serialize(Of IPoint)(New BasePointWithTimeSeries())
Konfigurace polymorfismu pomocí modelu kontraktu
Pro případy použití, kdy jsou poznámky atributů nepraktické nebo nemožné (například velké doménové modely, hierarchie křížových sestavení nebo hierarchie závislostí třetích stran), ke konfiguraci polymorfismu použijte model kontraktu. Model kontraktu je sada rozhraní API, která lze použít ke konfiguraci polymorfismu v hierarchii typů vytvořením vlastní DefaultJsonTypeInfoResolver podtřídy, která dynamicky poskytuje polymorfní konfiguraci na typ, jak je znázorněno v následujícím příkladu:
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
Další podrobnosti polymorfní serializace
- Polymorfní serializace podporuje odvozené typy, které byly explicitně optovány prostřednictvím JsonDerivedTypeAttribute. Nedelarované typy způsobí výjimku za běhu. Chování lze změnit konfigurací JsonPolymorphicAttribute.UnknownDerivedTypeHandling vlastnosti.
- Polymorfní konfigurace zadaná v odvozených typech není zděděna polymorfní konfigurací v základních typech. Základní typ musí být nakonfigurován nezávisle.
- Polymorfní hierarchie jsou podporovány pro oba
interface
typy.class
- Polymorfismus využívající diskriminátor typů je podporován pouze pro hierarchie typů, které používají výchozí převaděče pro objekty, kolekce a typy slovníků.
- Polymorfismus se podporuje ve generování zdroje založeném na metadatech, ale ne ve generování zdroje s rychlou cestou.