Menu

JSON di C#: Serialize dan Deserialize dengan System.Text.Json

Cara bekerja dengan JSON di C# memakai System.Text.Json: JsonSerializer.Serialize dan Deserialize, output camelCase dan berindentasi, atribut seperti JsonPropertyName dan JsonIgnore, enum sebagai string, membaca JSON tanpa class lewat JsonDocument dan JsonNode, error, dan perbandingannya dengan Newtonsoft.Json.

JSON adalah cara sebagian besar program C# berkomunikasi dengan web API, menyimpan pengaturan dan bertukar data. .NET modern membaca dan menulisnya dengan System.Text.Json, yang menjadi bagian dari runtime sejak .NET Core 3.0: tidak perlu package NuGet. Pintu masuk utamanya adalah class static JsonSerializer, yang mengubah objek menjadi string JSON dan sebaliknya.

System.Text.Json sudah ada di .NET Core 3.0 dan setiap rilis setelahnya (.NET 5 sampai .NET 10). Proyek di .NET Framework 4.6.2 ke atas, atau di .NET Standard 2.0, juga bisa memakainya dengan menginstal package NuGet System.Text.Json. Contoh-contoh di halaman ini ditampilkan sebagai kode biasa dengan output-nya di dalam komentar. Tambahkan directive using berikut untuk menjalankannya di proyek .NET:

using System.Text.Json;
using System.Text.Json.Serialization;

Serialize: objek menjadi JSON

JsonSerializer.Serialize menulis setiap property publik dari sebuah objek, memakai nama property apa adanya:

public class Product
{
    public string Name { get; set; }
    public decimal Price { get; set; }
    public List<string> Tags { get; set; } = new List<string>();
    public bool InStock { get; set; }
}

var lamp = new Product { Name = "Desk lamp", Price = 34.90m, Tags = { "home", "light" }, InStock = true };

string json = JsonSerializer.Serialize(lamp);
Console.WriteLine(json);
// {"Name":"Desk lamp","Price":34.90,"Tags":["home","light"],"InStock":true}

Koleksi menjadi array, dictionary dengan key berupa string menjadi objek ({"apples":3,"pears":5}), null tetap null, dan DateTime menjadi string ISO 8601 ("2026-03-01T14:30:00"). Ada dua perilaku default yang sering menjebak:

  • Field dilewati. Hanya property yang di-serialize. Class dengan public int X; di-serialize sebagai {}, kecuali kamu mengatur IncludeFields = true di options atau mengubah field itu menjadi property.
  • Enum ditulis sebagai angka. OrderStatus.Shipped ditulis sebagai 1 (nilai dasar dari enum tersebut). Tambahkan JsonStringEnumConverter (di bawah) untuk menulis "Shipped".

Deserialize: JSON menjadi objek

JsonSerializer.Deserialize<T> membuat sebuah T dan mengisi property-nya dari JSON:

string json = "{\"Name\":\"Desk lamp\",\"Price\":34.90,\"Tags\":[\"home\",\"light\"]}";
Product p = JsonSerializer.Deserialize<Product>(json);
Console.WriteLine($"{p.Name} {p.Price} {p.Tags.Count}");   // Desk lamp 34.90 2

var many = JsonSerializer.Deserialize<List<Product>>("[{\"Name\":\"A\",\"Price\":1},{\"Name\":\"B\",\"Price\":2.5}]");
Console.WriteLine(many.Count);                               // 2

Property JSON yang tidak punya padanan property C# diabaikan, dan property C# yang tidak punya padanan di JSON tetap berisi nilai default-nya. Secara default, keduanya bukan error.

Aturan yang menjebak hampir semua orang: pencocokan nama membedakan huruf besar dan kecil. Sebagian besar web API mengirim camelCase, dan camelCase tidak cocok dengan property PascalCase:

string fromApi = "{\"name\":\"Mug\",\"price\":8.5}";

var a = JsonSerializer.Deserialize<Product>(fromApi);
Console.WriteLine($"[{a.Name}] {a.Price}");    // [] 0: nothing matched, no error

var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var b = JsonSerializer.Deserialize<Product>(fromApi, options);
Console.WriteLine($"[{b.Name}] {b.Price}");    // [Mug] 8.5

new JsonSerializerOptions(JsonSerializerDefaults.Web) memberi pengaturan yang dipakai ASP.NET Core: membaca tanpa membedakan huruf besar dan kecil, menulis dalam camelCase, dan menerima angka yang ditulis sebagai string berkutip.

Deserialization butuh cara untuk mengisi setiap nilai: setter publik, accessor init, atau constructor yang nama parameternya cocok dengan property. Aturan terakhir itulah sebabnya record langsung bisa dipakai:

public record Point(int X, int Y);

Point pt = JsonSerializer.Deserialize<Point>("{\"X\":1,\"Y\":2}");
Console.WriteLine(pt);   // Point { X = 1, Y = 2 }

Options: camelCase dan output berindentasi

JsonSerializerOptions mengatur penamaan, format dan lainnya. Buat satu instance lalu pakai berulang kali: serializer menyimpan metadata dalam cache per instance options, jadi membuat options baru di setiap pemanggilan terasa lebih lambat.

private static readonly JsonSerializerOptions Options = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    WriteIndented = true,
};

Console.WriteLine(JsonSerializer.Serialize(lamp, Options));
// {
//   "name": "Desk lamp",
//   "price": 34.90,
//   "tags": [
//     "home",
//     "light"
//   ],
//   "inStock": true
// }

Options lain yang perlu kamu tahu: DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull untuk tidak menulis property yang null, IncludeFields = true, NumberHandling untuk menerima angka yang ditulis sebagai string, serta ReadCommentHandling = JsonCommentHandling.Skip ditambah AllowTrailingCommas = true untuk file konfigurasi yang diedit manual. .NET 8 menambahkan JsonNamingPolicy.SnakeCaseLower untuk API yang memakai snake_case.

Atribut: ganti nama, abaikan, enum sebagai string

Atribut pada class mengatur satu property dalam satu waktu dan mengalahkan pengaturan di options:

public enum OrderStatus { Pending, Shipped }

public class Order
{
    [JsonPropertyName("order_id")]
    public int Id { get; set; }

    public string Customer { get; set; }

    [JsonIgnore]
    public string InternalNote { get; set; }                  // never written or read

    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public string Coupon { get; set; }                        // left out when null

    [JsonConverter(typeof(JsonStringEnumConverter))]
    public OrderStatus Status { get; set; }

    public DateTime PlacedAt { get; set; }
}

var order = new Order
{
    Id = 1042, Customer = "Ana", InternalNote = "vip",
    Status = OrderStatus.Shipped, PlacedAt = new DateTime(2026, 3, 1, 14, 30, 0),
};
Console.WriteLine(JsonSerializer.Serialize(order));
// {"order_id":1042,"Customer":"Ana","Status":"Shipped","PlacedAt":"2026-03-01T14:30:00"}

Untuk menulis semua enum sebagai string tanpa menandai setiap property, tambahkan converter-nya ke options: options.Converters.Add(new JsonStringEnumConverter());.

Membaca JSON tanpa class: JsonDocument dan JsonNode

Saat kamu hanya butuh beberapa nilai dari response yang besar, atau bentuknya berubah-ubah, tidak perlu membuat class. JsonDocument mem-parse JSON menjadi pohon read-only berisi nilai JsonElement:

string weather = "{\"city\":\"Lisbon\",\"current\":{\"temp\":21.5,\"conditions\":[\"sunny\",\"windy\"]},\"alerts\":null}";

using (JsonDocument doc = JsonDocument.Parse(weather))
{
    JsonElement root = doc.RootElement;
    Console.WriteLine(root.GetProperty("city").GetString());                        // Lisbon
    Console.WriteLine(root.GetProperty("current").GetProperty("temp").GetDecimal()); // 21.5

    foreach (JsonElement c in root.GetProperty("current").GetProperty("conditions").EnumerateArray())
        Console.WriteLine(c.GetString());                                            // sunny, windy

    Console.WriteLine(root.TryGetProperty("humidity", out _));                       // False
    Console.WriteLine(root.GetProperty("alerts").ValueKind);                         // Null
}

GetProperty melempar KeyNotFoundException untuk nama yang tidak ada, jadi pakai TryGetProperty untuk field yang opsional. JsonDocument meminjam memori dari pool, itulah sebabnya ia di-dispose dengan using.

Untuk mengubah JSON, pakai JsonNode dari System.Text.Json.Nodes (.NET 6 ke atas), yang memberi pohon yang bisa diubah lengkap dengan indexer:

JsonNode node = JsonNode.Parse(weather);
Console.WriteLine((string)node["city"]);   // Lisbon

node["current"]["temp"] = 23;
node["updated"] = true;
Console.WriteLine(node.ToJsonString());
// {"city":"Lisbon","current":{"temp":23,"conditions":["sunny","windy"]},"alerts":null,"updated":true}

File dan stream

JSON di disk adalah sebuah string di dalam file, jadi method file bisa langsung digabung dengan serializer:

File.WriteAllText("settings.json", JsonSerializer.Serialize(settings, Options));
var loaded = JsonSerializer.Deserialize<Settings>(File.ReadAllText("settings.json"), Options);
// Same options both ways: camelCase names written with Options would not match on a default read.

Untuk file besar dan body HTTP, overload stream yang async tidak perlu membangun seluruh string di memori:

await using FileStream stream = File.OpenRead("orders.json");
List<Order> orders = await JsonSerializer.DeserializeAsync<List<Order>>(stream);

Di ASP.NET Core dan dengan HttpClient, kamu jarang memanggil serializer sendiri: controller mengikat body JSON secara otomatis, dan httpClient.GetFromJsonAsync<Order>(url) (di System.Net.Http.Json) melakukan request sekaligus deserialization dalam satu pemanggilan.

Error

JSON yang tidak valid, atau nilai yang tidak bisa dikonversi ke tipe property-nya, melempar JsonException. Pesannya menyebut path dan posisi di JSON, yang biasanya cukup untuk menemukan masalahnya:

try
{
    JsonSerializer.Deserialize<Product>("{\"Name\": \"Lamp\", \"Price\": \"cheap\"}");
}
catch (JsonException e)
{
    Console.WriteLine(e.Message);
    // The JSON value could not be converted to System.Decimal. Path: $.Price | LineNumber: 0 | BytePositionInLine: 33.
}

Deserialize mengembalikan null (bukan exception) saat teks JSON-nya adalah literal null, jadi periksa hasilnya saat input-nya berasal dari luar.

Karakter yang di-escape di output

Secara default, serializer meng-escape karakter non-ASCII dan karakter yang tidak aman di dalam HTML:

Console.WriteLine(JsonSerializer.Serialize(new { city = "São Paulo", note = "5 > 3" }));
// {"city":"S\u00E3o Paulo","note":"5 \u003E 3"}

Ini JSON yang valid dan saat dibaca kembali menjadi teks aslinya; karakter itu di-escape supaya output-nya aman dimasukkan ke halaman HTML. Untuk file yang mudah dibaca manusia, atur Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping (dari System.Text.Encodings.Web) di options. Kata "unsafe" di sana hanya merujuk pada menanam hasilnya di HTML.

System.Text.Json vs Newtonsoft.Json

Newtonsoft.Json (Json.NET, class JsonConvert) menjadi standar selama satu dekade dan masih ada di mana-mana. Perbedaan utamanya:

System.Text.JsonNewtonsoft.Json
Ketersediaanbawaan .NET Core 3.0+; package NuGet untuk .NET Framework 4.6.2+package NuGet untuk .NET Framework dan .NET
Serialize / deserializeJsonSerializer.Serialize(obj) / Deserialize<T>(json)JsonConvert.SerializeObject(obj) / DeserializeObject<T>(json)
Pencocokan namamembedakan huruf besar dan kecil secara defaulttidak membedakan huruf besar dan kecil
Membaca tanpa tipeJsonDocument, JsonNodeJObject, JToken, dengan query JSONPath
Mengganti nama property[JsonPropertyName("x")][JsonProperty("x")]
Kelonggaranketat: tanpa komentar, koma di akhir atau angka berkutip kecuali diaktifkanlonggar secara default
Performalebih cepat, lebih sedikit alokasi, source generation untuk trimming dan AOTlebih lambat, berbasis reflection

Nama dan namespace atributnya berbeda, jadi memindahkan sebuah codebase berarti pekerjaan cari dan ganti ditambah pengujian untuk parsing yang lebih ketat. Untuk kode .NET baru, mulailah dengan System.Text.Json.

Source generation (.NET 6+)

JsonSerializer biasanya memeriksa tipe-tipemu dengan reflection saat program berjalan. Untuk aplikasi yang di-trim atau di-compile lebih dulu (Native AOT, Blazor WebAssembly), sebuah source generator menulis kode itu saat compile:

[JsonSerializable(typeof(Product))]
internal partial class AppJsonContext : JsonSerializerContext { }

string json = JsonSerializer.Serialize(lamp, AppJsonContext.Default.Product);

Kesalahan yang sering terjadi

  • JSON camelCase ke property PascalCase dengan options default. Setiap property tetap kosong, tanpa peringatan. Pakai PropertyNameCaseInsensitive atau JsonSerializerDefaults.Web.
  • Field publik, bukan property. Field tidak di-serialize kecuali IncludeFields diatur.
  • Property tanpa setter. Property yang hanya punya get dan tidak punya parameter constructor yang cocok tidak diisi saat deserialization.
  • JsonSerializerOptions baru di setiap pemanggilan. Pakai berulang satu instance static.
  • Uang dalam floating-point. double dari 0.1 + 0.2 di-serialize sebagai 0.30000000000000004. Pakai decimal untuk jumlah uang.

Pertanyaan yang sering diajukan

Bagaimana cara mengubah objek menjadi JSON di C#?

Panggil JsonSerializer.Serialize(obj) dari System.Text.Json, yang sudah ada di .NET Core 3.0 ke atas tanpa perlu menginstal package. Method ini menulis setiap property publik: {"Name":"Desk lamp","Price":34.90}. Kirim new JsonSerializerOptions { WriteIndented = true } untuk output yang mudah dibaca dan PropertyNamingPolicy = JsonNamingPolicy.CamelCase untuk nama dalam camelCase.

Bagaimana cara mengubah JSON menjadi objek di C#?

var product = JsonSerializer.Deserialize<Product>(json); membuat sebuah Product dan mengisi property publiknya yang bisa diubah dari nama JSON yang cocok. Pencocokan nama secara default membedakan huruf besar dan kecil, jadi JSON dalam camelCase membuat property PascalCase tetap kosong, kecuali kamu mengirim PropertyNameCaseInsensitive = true atau new JsonSerializerOptions(JsonSerializerDefaults.Web). JSON yang rusak atau tipe nilai yang salah melempar JsonException.

Bagaimana cara membaca JSON tanpa membuat class di C#?

Pakai JsonDocument.Parse(json) lalu telusuri RootElement dengan GetProperty("name"), GetString(), GetInt32() dan EnumerateArray(); cara ini read-only dan cepat, dan harus di-dispose. Untuk JSON yang ingin kamu ubah, JsonNode.Parse(json) (.NET 6+) memberi pohon yang bisa diubah: node["city"], pemberian nilai, dan ToJsonString().

Sebaiknya pakai System.Text.Json atau Newtonsoft.Json?

Untuk kode baru di .NET Core 3.0 ke atas, pakai System.Text.Json: sudah bawaan, lebih cepat, lebih sedikit alokasi memori, dan ASP.NET Core memakainya secara default. Newtonsoft.Json (Json.NET) masih umum di proyek .NET Framework (tempat System.Text.Json hanya tersedia sebagai package NuGet), di kode yang bergantung pada fitur tambahannya (query JSONPath, parsing yang sangat longgar, TypeNameHandling), dan di codebase besar yang sudah dibangun di atasnya.

Kenapa System.Text.Json meng-escape karakter seperti é dan <?

Encoder default meng-escape karakter non-ASCII dan karakter yang sensitif di HTML (<, >, &, ') menjadi \uXXXX, supaya output-nya aman ditanam di HTML. Hasilnya tetap JSON yang valid dan saat di-deserialize kembali menjadi teks yang sama. Untuk output yang mudah dibaca, atur Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping di options, tapi hanya saat JSON-nya tidak ditulis ke dalam HTML.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai