System.Text.Json

Serialisasi dan deserialisasi JSON beserta opsi dan kebijakan penamaan.

Daftar isi

System.Text.Json adalah pustaka bawaan .NET untuk mengubah objek menjadi teks JSON dan sebaliknya tanpa paket tambahan. Topik ini membahas serialisasi, yaitu proses mengubah objek menjadi teks JSON, beserta deserialisasi yang mengembalikan teks JSON menjadi objek, kemudian opsi JsonSerializerOptions dan kebijakan penamaan yang mengatur bentuk nama properti pada JSON. Seluruh contoh memakai tipe record sederhana dan dijalankan langsung agar bentuk JSON-nya terlihat persis.

Serialisasi dan deserialisasi dengan JsonSerializer#

Kelas JsonSerializer menyediakan dua metode generik yang menjadi pintu utama pustaka ini. Metode Serialize menerima objek apa pun dan mengembalikan teks JSON-nya, sedangkan Deserialize<T> menerima teks JSON dan mengembalikan objek bertipe T. Nama properti pada JSON secara default mengikuti nama property C# secara persis, sehingga property IdSensor menjadi kunci IdSensor. Contoh berikut menyerialisasi satu record dengan kebijakan default, kemudian dengan opsi camelCase dan WriteIndented, lalu membaca JSON lain kembali menjadi objek.

csharp Program.cs
#:property JsonSerializerIsReflectionEnabledByDefault=true
#:property EnableTrimAnalyzer=false
#:property EnableAotAnalyzer=false

// Program: serialisasi dan deserialisasi JSON dengan System.Text.Json
using System.Text.Json;

var opsi = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    WriteIndented = true
};

BacaanSensor bacaan = new("sensor-utama", 24.6, true);
Console.WriteLine("--- PascalCase (default) ---");
Console.WriteLine(JsonSerializer.Serialize(bacaan));
Console.WriteLine("--- camelCase dan WriteIndented ---");
Console.WriteLine(JsonSerializer.Serialize(bacaan, opsi));

string json = """{"idSensor":"sensor-cadangan","suhu":21,"aktif":false}""";
BacaanSensor? hasil = JsonSerializer.Deserialize<BacaanSensor>(json, opsi);
Console.WriteLine($"Dibaca kembali: {hasil?.IdSensor}, suhu {hasil?.Suhu}, aktif {hasil?.Aktif}");

record BacaanSensor(string IdSensor, double Suhu, bool Aktif);
text Keluaran
--- PascalCase (default) ---
{"IdSensor":"sensor-utama","Suhu":24.6,"Aktif":true}
--- camelCase dan WriteIndented ---
{
  "idSensor": "sensor-utama",
  "suhu": 24.6,
  "aktif": true
}
Dibaca kembali: sensor-cadangan, suhu 21, aktif False

Untuk menjalankan program tersebut dari folder topik, gunakan perintah berikut.

bash
# aktifkan .NET yang terpasang di sandbox
export DOTNET_ROOT=/home/kal/.dotnet PATH=/home/kal/.dotnet:$PATH DOTNET_CLI_TELEMETRY_OPTOUT=1 DOTNET_NOLOGO=1
# jalankan program contoh dari folder topik
dotnet run --file Program.cs
text Keluaran
--- PascalCase (default) ---
{"IdSensor":"sensor-utama","Suhu":24.6,"Aktif":true}
--- camelCase dan WriteIndented ---
{
  "idSensor": "sensor-utama",
  "suhu": 24.6,
  "aktif": true
}
Dibaca kembali: sensor-cadangan, suhu 21, aktif False

Tanpa opsi, keluarannya satu baris dengan nama properti PascalCase, sedangkan opsi WriteIndented menambahkan spasi dan indentasi agar teks JSON mudah dibaca. Opsi yang sama berlaku dua arah, sehingga Deserialize mencocokkan kunci idSensor, suhu, dan aktif pada teks JSON dengan property IdSensor, Suhu, dan Aktif lewat kebijakan camelCase yang sama. Apabila salah satu kunci tidak ditemukan, properti terkait diisi nilai default tipenya tanpa menimbulkan error.

Record menjadi pilihan wajar untuk contoh serialisasi karena System.Text.Json membaca konstruktornya saat deserialisasi dan mencocokkan nama parameter dengan kunci JSON. Kelas biasa dengan property yang bisa diisi juga didukung, dan larik, Dictionary, serta kelas bersarang mengikuti aturan yang sama. Sebaliknya, tipe tanpa konstruktor yang cocok dan tanpa property yang dapat diisi memicu NotSupportedException saat deserialisasi.

Kebijakan penamaan dan opsi lainnya#

Kebijakan penamaan ditetapkan melalui property PropertyNamingPolicy pada JsonSerializerOptions dan mengubah cara nama properti ditulis pada JSON. Nilai JsonNamingPolicy.CamelCase mengubah IdSensor menjadi idSensor, sedangkan JsonNamingPolicy.SnakeCaseLower yang tersedia sejak .NET 9 mengubahnya menjadi id_sensor. Kebijakan berlaku pada serialisasi dan deserialisasi sekaligus, sehingga satu objek opsi cukup dipakai untuk kedua arah. Di samping kebijakan penamaan, opsi yang sering diatur mencakup WriteIndented untuk format berindentasi, PropertyNameCaseInsensitive yang membuat pencocokan nama tidak memedulikan besar kecilnya huruf, dan DefaultIgnoreCondition untuk melewatkan properti yang nilainya default. Kebijakan penamaan hanya mengubah nama, bukan bentuk datanya, sehingga struktur bersarang tetap mengikuti struktur kelasnya. Satu objek opsi juga dapat dibagikan ke seluruh bagian program, misalnya disimpan sebagai field statis, agar setiap serialisasi memakai konfigurasi yang sama dan keluaran JSON tetap seragam.

Nama properti kustom dengan JsonPropertyName#

Ketika nama pada JSON sudah ditentukan oleh pihak lain, misalnya API luar yang memakai snake_case, atribut JsonPropertyName menetapkan nama properti secara satu per satu. Atribut ini menimpa kebijakan penamaan apa pun, sehingga nama yang dipaku tetap sama di semua konfigurasi. Contoh berikut menuliskan NamaSesi sebagai nama_sesi pada JSON, kemudian membaca kembali teks JSON dari luar dengan bantuan opsi PropertyNameCaseInsensitive.

csharp Contoh: JsonPropertyName menetapkan nama properti pada JSON
#:property JsonSerializerIsReflectionEnabledByDefault=true
#:property EnableTrimAnalyzer=false
#:property EnableAotAnalyzer=false

// Contoh: JsonPropertyName menetapkan nama properti pada JSON
using System.Text.Json;
using System.Text.Json.Serialization;

BacaanCuaca bacaan = new("pagi", 27.5);
Console.WriteLine(JsonSerializer.Serialize(bacaan));

var opsi = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
string json = """{"nama_sesi":"siang","suhu":30.5}""";
BacaanCuaca? hasil = JsonSerializer.Deserialize<BacaanCuaca>(json, opsi);
Console.WriteLine($"{hasil?.NamaSesi}: {hasil?.Suhu}");

record BacaanCuaca([property: JsonPropertyName("nama_sesi")] string NamaSesi, double Suhu);
text Keluaran
{"nama_sesi":"pagi","Suhu":27.5}
siang: 30.5

Keluaran serialisasi memperlihatkan dua gaya sekaligus, yaitu nama_sesi yang dipaku atribut dan Suhu yang mengikuti kebijakan default. Pada pembacaan kembali, kunci suhu dengan huruf kecil tetap terbaca karena opsi PropertyNameCaseInsensitive dinyatakan true, sedangkan tanpa opsi itu kunci tersebut akan diabaikan dan Suhu bernilai 0. Awalan property: pada atribut diperlukan karena atribut ditulis pada parameter konstruktor record, sedangkan sasarannya adalah property yang dihasilkan parameter tersebut.

Kesalahan umum#

Empat kesalahan berikut sering muncul ketika JSON mulai dipertukarkan dengan pihak lain. Semuanya berhubungan dengan bentuk nama properti, isi teks JSON, atau bentuk tipe yang dipertukarkan.

  • Teks JSON yang tidak valid. Kegagalan penulisan seperti tanda kutip yang hilang memicu JsonException pada pemanggilan Deserialize, dan pesannya menunjuk posisi karakter yang bermasalah.
  • Huruf besar-kecil yang tidak cocok. Deserialize tanpa kebijakan atau opsi tidak mencocokkan kunci suhu dengan property Suhu, sehingga propertinya terisi nilai default secara diam-diam. Aktifkan PropertyNameCaseInsensitive atau sesuaikan kebijakan penamaannya.
  • Objek opsi dibuat berulang di dalam perulangan. Setiap JsonSerializerOptions baru membangun metadata tipenya sendiri, sehingga pembuatan berulang memperlambat serialisasi. Deklarasikan satu objek opsi pada lingkup yang lebih luas lalu teruskan ke setiap pemanggilan.
  • Isi objek yang bukan data. Property bertipe stream, delegate, atau referensi ke sumber daya luar tidak dapat diserialisasi dan memicu exception, sehingga tipe yang dipertukarkan sebaiknya murni berisi data.

Ringkasan#

  • JsonSerializer.Serialize mengubah objek menjadi teks JSON, sedangkan Deserialize<T> mengembalikan teks JSON menjadi objek.
  • JsonSerializerOptions mengatur bentuk keluaran, dan PropertyNamingPolicy menentukan gaya nama properti seperti camelCase atau snake_case.
  • Atribut JsonPropertyName menetapkan nama satu properti secara permanen dan menimpa kebijakan penamaan.
  • Nama yang tidak cocok tidak menimbulkan error, melainkan mengisi properti dengan nilai default, sehingga pencocokan huruf besar-kecil perlu dijaga.

Referensi#