Cara menserialisasi properti kelas turunan dengan System.Text.Json
Dalam artikel ini, Anda mempelajari cara membuat serialisasi properti kelas turunan dengan System.Text.Json
namespace.
Menserialisasikan properti kelas turunan
Dimulai dengan .NET 7, System.Text.Json
mendukung serialisasi dan deserialisasi hierarki jenis polimorfik dengan anotasi atribut.
Atribut | Deskripsi |
---|---|
JsonDerivedTypeAttribute | Ketika ditempatkan pada deklarasi jenis, menunjukkan bahwa subjenis yang ditentukan harus dipilih ke dalam serialisasi polimorfik. Ini juga mengekspos kemampuan untuk menentukan jenis diskriminator. |
JsonPolymorphicAttribute | Ketika ditempatkan pada deklarasi jenis, menunjukkan bahwa jenis harus diserialisasikan secara polimorfik. Ini juga mengekspos berbagai opsi untuk mengonfigurasi serialisasi polimorfik dan deserialisasi untuk jenis tersebut. |
Misalnya, Anda memiliki WeatherForecastBase
kelas dan kelas turunan 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
Dan misalkan argumen jenis Serialize<TValue>
metode pada waktu kompilasi adalah WeatherForecastBase
:
options = new JsonSerializerOptions
{
WriteIndented = true
};
jsonString = JsonSerializer.Serialize<WeatherForecastBase>(weatherForecastBase, options);
options = New JsonSerializerOptions With {
.WriteIndented = True
}
jsonString = JsonSerializer.Serialize(WeatherForecastBase, options)
Dalam skenario ini, properti diserialisasikan City
karena weatherForecastBase
objek sebenarnya adalah WeatherForecastWithCity
objek. Konfigurasi ini memungkinkan serialisasi polimorfik untuk WeatherForecastBase
, khususnya ketika jenis runtime adalah WeatherForecastWithCity
:
{
"City": "Milwaukee",
"Date": "2022-09-26T00:00:00-05:00",
"TemperatureCelsius": 15,
"Summary": "Cool"
}
Meskipun round-tripping payload seperti WeatherForecastBase
yang didukung, payload tidak akan terwujud sebagai jenis run-time .WeatherForecastWithCity
Sebaliknya, itu akan terwujud sebagai jenis run-time dari 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
Bagian berikut menjelaskan cara menambahkan metadata untuk mengaktifkan round-tripping dari jenis turunan.
Diskriminator jenis polimorfik
Untuk mengaktifkan deserialisasi polimorfik, Anda harus menentukan jenis diskriminator untuk kelas turunan:
[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
Dengan metadata tambahan, khususnya, diskriminator jenis, serializer dapat menserialisasikan dan mendeserialisasi payload sebagai WeatherForecastWithCity
jenis dari jenis WeatherForecastBase
dasarnya . Serialisasi memancarkan JSON bersama dengan metadata diskriminator jenis:
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"
' }
Dengan jenis diskriminator, serializer dapat mendeserialisasi payload secara polimorfik sebagai 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
Catatan
Secara default, $type
diskriminator harus ditempatkan di awal objek JSON, dikelompokkan bersama dengan properti metadata lainnya seperti $id
dan $ref
. Jika Anda membaca data dari API eksternal yang menempatkan $type
diskriminator di tengah objek JSON, atur JsonSerializerOptions.AllowOutOfOrderMetadataProperties ke true
:
JsonSerializerOptions options = new() { AllowOutOfOrderMetadataProperties = true };
JsonSerializer.Deserialize<Base>("""{"Name":"Name","$type":"derived"}""", options);
Berhati-hatilah saat Anda mengaktifkan bendera ini, karena dapat mengakibatkan kegagalan over-buffering (dan kegagalan di luar memori) saat melakukan deserialisasi streaming objek JSON yang sangat besar.
Mencampur dan mencocokkan format diskriminator jenis
Pengidentifikasi diskriminator jenis valid dalam bentuk string
atau int
, sehingga berikut ini valid:
[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...
' }
Meskipun API mendukung konfigurasi diskriminator jenis pencampuran dan pencocokan, api tidak disarankan. Rekomendasi umumnya adalah menggunakan semua string
jenis diskriminator, semua int
jenis diskriminator, atau tidak ada diskriminator sama sekali. Contoh berikut menunjukkan cara mencampur dan mencocokkan konfigurasi diskriminator jenis:
[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
Dalam contoh sebelumnya, BasePoint
jenis tidak memiliki diskriminator jenis, sementara ThreeDimensionalPoint
jenis memiliki int
diskriminator jenis, dan FourDimensionalPoint
memiliki string
diskriminator jenis.
Penting
Agar serialisasi polimorfik berfungsi, jenis nilai berseri harus dari jenis dasar polimorfik. Ini termasuk menggunakan jenis dasar sebagai parameter jenis generik saat menserialisasikan nilai tingkat akar, sebagai jenis properti serial yang dideklarasikan, atau sebagai elemen koleksi dalam koleksi yang diserialisasikan.
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
Mengkustomisasi nama diskriminator jenis
Nama properti default untuk jenis diskriminator adalah $type
. Untuk mengkustomisasi nama properti, gunakan seperti yang JsonPolymorphicAttribute diperlihatkan dalam contoh berikut:
[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
Dalam kode sebelumnya, JsonPolymorphic
atribut mengonfigurasi TypeDiscriminatorPropertyName
ke "$discriminator"
nilai . Dengan nama diskriminator jenis yang dikonfigurasi, contoh berikut menunjukkan jenis yang diserialisasikan ThreeDimensionalPoint
sebagai 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
Hindari menggunakan JsonPolymorphicAttribute.TypeDiscriminatorPropertyName yang berkonflik dengan properti dalam hierarki jenis Anda.
Menangani jenis turunan yang tidak diketahui
Untuk menangani jenis turunan yang tidak diketahui, Anda harus ikut serta dalam dukungan tersebut menggunakan anotasi pada jenis dasar. Pertimbangkan hierarki jenis berikut:
[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
Karena konfigurasi tidak secara eksplisit memilih dukungan untuk FourDimensionalPoint
, mencoba membuat serialisasi instans sebagaimana FourDimensionalPoint
BasePoint
akan mengakibatkan pengecualian run-time:
JsonSerializer.Serialize<BasePoint>(new FourDimensionalPoint()); // throws NotSupportedException
JsonSerializer.Serialize(Of BasePoint)(New FourDimensionalPoint()) ' throws NotSupportedException
Anda dapat mengubah perilaku default dengan menggunakan JsonUnknownDerivedTypeHandling enum, yang dapat ditentukan sebagai berikut:
[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
Alih-alih kembali ke jenis dasar, Anda dapat menggunakan FallBackToNearestAncestor
pengaturan untuk kembali ke kontrak jenis turunan terdekat yang dinyatakan:
[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
Dengan konfigurasi seperti contoh sebelumnya, ThreeDimensionalPoint
jenis akan diserialisasikan sebagai BasePoint
:
// Serializes using the contract for BasePoint
JsonSerializer.Serialize<IPoint>(new ThreeDimensionalPoint());
' Serializes using the contract for BasePoint
JsonSerializer.Serialize(Of IPoint)(New ThreeDimensionalPoint())
Namun, jatuh kembali ke leluhur terdekat mengakui kemungkinan ambiguitas "berlian". Pertimbangkan hierarki jenis berikut sebagai contoh:
[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
Dalam hal ini, jenisnya BasePointWithTimeSeries
dapat diserialisasikan sebagai atau BasePoint
IPointWithTimeSeries
karena keduanya adalah leluhur langsung. Ambiguitas ini akan menyebabkan NotSupportedException dilemparkan ketika mencoba menserialisasikan instans sebagai BasePointWithTimeSeries
IPoint
.
// throws NotSupportedException
JsonSerializer.Serialize<IPoint>(new BasePointWithTimeSeries());
' throws NotSupportedException
JsonSerializer.Serialize(Of IPoint)(New BasePointWithTimeSeries())
Mengonfigurasi polimorfisme dengan model kontrak
Untuk kasus penggunaan di mana anotasi atribut tidak praktis atau tidak mungkin (seperti model domain besar, hierarki perakitan silang, atau hierarki dalam dependensi pihak ketiga), untuk mengonfigurasi polimorfisme menggunakan model kontrak. Model kontrak adalah sekumpulan API yang dapat digunakan untuk mengonfigurasi polimorfisme dalam hierarki jenis dengan membuat subkelas kustom DefaultJsonTypeInfoResolver yang secara dinamis menyediakan konfigurasi polimorfik per jenis, seperti yang ditunjukkan dalam contoh berikut:
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
Detail serialisasi polimorfik tambahan
- Serialisasi polimorfik mendukung jenis turunan yang telah secara eksplisit dipilih melalui JsonDerivedTypeAttribute. Jenis yang tidak dinyatakan akan menghasilkan pengecualian run-time. Perilaku dapat diubah dengan mengonfigurasi JsonPolymorphicAttribute.UnknownDerivedTypeHandling properti.
- Konfigurasi polimorfik yang ditentukan dalam jenis turunan tidak diwariskan oleh konfigurasi polimorfik dalam jenis dasar. Jenis dasar harus dikonfigurasi secara independen.
- Hierarki polimorfik didukung untuk jenis
interface
danclass
. - Polimorfisme menggunakan diskriminator jenis hanya didukung untuk hierarki jenis yang menggunakan pengonversi default untuk objek, koleksi, dan jenis kamus.
- Polimorfisme didukung dalam pembuatan sumber berbasis metadata, tetapi bukan pembuatan sumber jalur cepat.