Sebuah List<Pengiriman> bertipe abstract class boleh berisi object turunan: pengiriman reguler dan kargo. Serializer menghadapi masalah dua arah pada situasi ini. Tanpa petunjuk, JsonSerializer.Serialize hanya menulis properti tipe deklarasi, sehingga properti turunan seperti EstimasiHari hilang; sebaliknya, deserialisasi ke tipe abstract tidak tahu class turunan mana yang harus dibuat.

Deklarasi Hierarki dengan Atribut #

Atribut [JsonPolymorphic] menetapkan nama properti discriminator, dan setiap [JsonDerivedType] mendaftarkan satu turunan beserta labelnya:

csharp
[JsonPolymorphic(TypeDiscriminatorPropertyName = "$tipe")]
[JsonDerivedType(typeof(PengirimanReguler), "reguler")]
[JsonDerivedType(typeof(PengirimanKargo), "kargo")]
public abstract class Pengiriman
{
    public int Biaya { get; set; }
}

Serialisasi dan Round-Trip #

csharp
List<Pengiriman> daftar = new()
{
    new PengirimanReguler { Biaya = 15000, EstimasiHari = 2 },
    new PengirimanKargo { Biaya = 250000, Kilogram = 40 }
};

string json = JsonSerializer.Serialize(daftar);
Console.WriteLine(json);

List<Pengiriman> hasil = JsonSerializer.Deserialize<List<Pengiriman>>(json);
foreach (Pengiriman p in hasil)
{
    Console.WriteLine(p.GetType().Name);
}

Output yang Dihasilkan:

text
[{"$tipe":"reguler","EstimasiHari":2,"Biaya":15000},{"$tipe":"kargo","Kilogram":40,"Biaya":250000}]
PengirimanReguler
PengirimanKargo

Mari kita bedah hasilnya:

  1. Properti "$tipe": label discriminator menempel pada setiap object JSON dan menjadi kunci pemilihan tipe saat dibaca kembali.
  2. Properti turunan lebih dulu: serializer menulis properti class turunan (EstimasiHari, Kilogram) sebelum properti base class Biaya.
  3. Deserialization: Deserialize<List<Pengiriman>> membaca "$tipe", mencocokkannya dengan daftar [JsonDerivedType], lalu membuat object turunan yang tepat; GetType().Name membuktikan keduanya kembali sebagai class asal.

Perlu dicatat bahwa label discriminator dapat berupa angka alih-alih teks, misalnya [JsonDerivedType(typeof(PengirimanKargo), 2)], dan payload JSON menjadi menyimpan "$tipe":2. Mekanisme ini juga berlaku pada properti bertipe interface, bukan hanya abstract class. Yang tidak berubah adalah aturan keamanannya: hanya turunan yang terdaftar pada atribut yang boleh dibuat, sehingga daftar discriminator berfungsi sebagai allowlist.