JSON Toolkit

Membaca batas tata bahasa JSON dari RFC 8259

Intinya: Menerjemahkan produksi tata bahasa RFC 8259 ke bahasa sederhana: karakter mana yang boleh muncul langsung, mana yang harus di-escape, dan di mana spesifikasi sengaja memberi ruang.

Pemula±11 menit bacaDiterbitkan 2026-10-30Diperbarui 2026-10-30
Sampul untuk Membaca batas tata bahasa JSON dari RFC 8259

Intinya: saat error sintaks JSON muncul, temukan karakter ilegal pertama dan klasifikasikan sebagai kutip tunggal, koma di akhir, komentar, atau karakter kontrol alih-alih membaca ulang seluruh dokumen; pada sisi encoding, pastikan UTF-8 tanpa BOM dan bawa integer di atas 2^53 - 1 sebagai string.

Isi spesifikasi hanya belasan halaman, tetapi di balik setiap "MUST", "SHOULD", dan "MAY" tersembunyi insiden yang pernah membuat satu tim begadang semalaman.

1. Bagaimana ketiga teks saling berhubungan

Mempelajari JSON sering membingungkan karena tiga nomor: RFC 4627, RFC 7159, RFC 8259, plus ECMA-404. Hubungannya sebenarnya cukup jelas:

Teks Tanggal Status Perbedaan utama
RFC 4627 2006 Digantikan Tingkat teratas harus object atau array
RFC 7159 2014 Digantikan Tingkat teratas dilonggarkan menjadi nilai JSON apa pun
RFC 8259 2017.12 Standar saat ini UTF-8 eksplisit, ditambahkan panduan interoperabilitas
ECMA-404 2013, edisi kedua 2017.12 Spesifikasi tata bahasa Hanya mendefinisikan tata bahasa, tidak mencakup encoding atau transport

Mana yang harus dibaca? RFC 8259 saja cukup. ECMA-404 setara pada bagian tata bahasanya; perbedaannya adalah RFC 8259 juga menetapkan "transport sebagai UTF-8" dan "masalah interoperabilitas yang harus diperhatikan implementator".

2. Enam tipe nilai

Produksi tata bahasa spesifikasi dapat diringkas menjadi satu diagram:

JSON-text  = ws value ws
value      = object / array / number / string / "true" / "false" / "null"
object     = "{" [ member *( "," member ) ] "}"
member     = string ws ":" ws value
array      = "[" [ value *( "," value ) ] "]"

Hanya ada enam poin kunci:

  1. Object menggunakan kurung kurawal, anggota dipisahkan koma, dan koma setelah anggota terakhir tidak diizinkan;
  2. Array menggunakan kurung siku, elemen dipisahkan koma, dan koma di akhir juga tidak diizinkan;
  3. Number tidak mengizinkan + di depan, tidak mengizinkan nol di depan (01 tidak valid), tidak mengizinkan titik desimal tanpa angka (1. tidak valid), dan tidak mengizinkan NaN atau Infinity;
  4. String harus menggunakan kutip ganda; kutip tunggal tidak valid;
  5. Boolean dan null adalah literal huruf kecil; True dan NULL keduanya tidak valid;
  6. Hanya karakter whitespace 0x20 (spasi), 0x09 (tab), 0x0A (line feed), dan 0x0D (carriage return) yang boleh muncul di antara elemen struktural.

Cuplikan di bawah adalah JSON valid, meskipun banyak orang mengira salah saat pertama kali melihatnya — karena itu adalah string tingkat teratas yang berdiri sendiri:

"just a string"

Sejak RFC 7159, tingkat teratas memang boleh berupa nilai apa pun. Namun dalam API, tetap disarankan hanya menggunakan object atau array; alasannya ada di bagian 6.

3. Apa yang sebenarnya boleh muncul langsung di dalam string

Ini bagian yang paling mudah dikelabui oleh "di mesin saya jalan normal". Aturan spesifikasi untuk string adalah:

  • Karakter yang boleh muncul langsung: %x20-21, %x23-5B, %x5D-10FFFF;
  • Dengan kata lain, kutip ganda " (0x22) dan backslash \ (0x5C) harus di-escape;
  • Semua karakter kontrol di bawah 0x20 harus di-escape, termasuk line feed, tab, dan carriage return;
  • Urutan escape yang tersedia hanya \", \\, \/, \b, \f, \n, \r, \t, dan \uXXXX.
{
  "ok": "Chinese text, emoji 🎉 and full-width symbols can be written directly",
  "escaped": "A line break goes here\nand a tab\tfinishes it",
  "control": "\u0007 is the bell character and must be escaped",
  "quote": "He said: \"don't use single quotes\""
}

\/ adalah escape warisan (mencegah </script> menutup terlalu awal). Skenario modern jarang membutuhkannya, tetapi parser harus mendukungnya.

4. Encoding: UTF-8, tanpa BOM

Bagian 8.1 RFC 8259 menyebutkan tiga hal:

  1. Teks JSON yang digunakan untuk interchange harus di-encode dalam UTF-8;
  2. Implementasi tidak boleh menambahkan BOM (EF BB BF) di awal teks;
  3. Parser boleh mengabaikan BOM agar lebih tangguh.

Dua kegagalan khas di dunia nyata:

  • Notepad Windows menyimpan sebagai "UTF-8 dengan BOM", file ditolak oleh JSON.parse dengan Unexpected token;
  • File JSON Tionghoa yang disimpan sebagai GBK berubah menjadi teks rusak pada pipeline yang hanya mendekode sebagai UTF-8, tetapi sintaksnya kebetulan tetap lolos, sehingga error tertunda ke lapisan bisnis.

Penanganannya sederhana: hapus BOM saat membaca file, lalu dekode secara eksplisit sebagai UTF-8; saat menulis, pastikan tidak ada BOM yang ditambahkan.

/**
 * Removes the UTF-8 BOM at the start of a string.
 * @param text The original text
 * @returns The text without a BOM
 */
function stripBom(text: string): string {
  return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
}

Sekalian catatan tentang media type: application/json tidak lagi menerima parameter charset; menulisnya tidak memunculkan error, tetapi itu praktik yang sudah usang.

5. Tiga tempat di mana spesifikasi sengaja memberi ruang

Tempat di mana spesifikasi menolak memastikan sesuatu justru adalah sumber perbedaan implementasi.

1. Rentang dan presisi angka

RFC 8259 hanya menyatakan "spesifikasi ini tidak membatasi rentang atau presisi angka", sambil mencatat bahwa sebagian besar implementasi menggunakan presisi ganda IEEE 754. Ini berarti 9223372036854775807 yang sama menjadi 9223372036854776000 di JavaScript, tetapi tetap eksak di long milik Java.

Kesimpulan: integer di atas 2^53 - 1, dan jumlah uang yang membutuhkan banyak tempat desimal presisi, harus selalu dibawa sebagai string, dengan pattern ditulis eksplisit dalam schema.

2. Kunci duplikat

Rumusan spesifikasi adalah "nama di dalam object sebaiknya (SHOULD) unik"; tidak disebutkan apa yang harus dilakukan jika tidak unik.

  • JSON.parse menyimpan yang terakhir;
  • Sebagian parser menyimpan yang pertama;
  • Sebagian pustaka langsung memunculkan error.

Kesimpulan: jangan menulis kunci duplikat, dan jangan bergantung pada perilaku tertentu di validator Anda. Menambahkan langkah "deteksi kunci duplikat" saat build jauh lebih murah daripada menginvestigasi setelahnya.

3. Batas kedalaman dan panjang

Spesifikasi tidak menetapkan batas kedalaman penyarangan, tetapi setiap implementasi punya batasnya. V8 melempar RangeError: Maximum call stack size exceeded pada puluhan ribu level penyarangan. Server harus menetapkan batasnya sendiri; ini persyaratan keamanan, bukan persyaratan spesifikasi.

6. Menguji pemahaman Anda dengan contoh kontra

Setiap baris di bawah adalah JSON tidak valid. Berikan ke parser dan lihat apakah pesan errornya membantu:

{ 'name': 'json' }        // single quotes
{ name: "json" }          // key name not quoted
{"a": 1,}                 // trailing comma
[1, 2, 3,]                // trailing comma in an array
{"a": 01}                 // leading zero
{"a": +1}                 // leading plus sign
{"a": .5}                 // missing integer part
{"a": NaN}                // non-standard literal
{"a": undefined}          // not a JSON value
{"a": 1} // comment       // comment

Kasus batas yang menarik: {"a": 1} diikuti komentar valid di JSON5, valid di JSONC, dan tidak valid di JSON standar. Jadi "file konfigurasi berjalan normal tetapi API menolaknya" hampir selalu masalah dialek.

7. Daftar periksa produksi

  • [ ] Semua JSON yang ditransmisikan secara eksternal menggunakan application/json tanpa charset;
  • [ ] Pipeline baca/tulis file menghapus BOM dan menggunakan UTF-8 eksplisit;
  • [ ] Integer di atas 2^53 dibawa sebagai string dan dianotasi dalam schema;
  • [ ] Kunci duplikat dideteksi saat build;
  • [ ] Server membatasi ukuran body permintaan dan kedalaman penyarangan;
  • [ ] Nilai tingkat teratas API publik adalah object atau array, tidak pernah skalar telanjang.

FAQ

Bagaimana cara cepat mengetahui apakah suatu teks adalah JSON valid?

Berikan teks ke parser atau alat parsing online, baca posisi karakter ilegal pertama, lalu periksa terhadap enam tipe nilai dan aturan whitespace RFC 8259. Tingkat teratas boleh berupa nilai JSON apa pun, tetapi di antara elemen struktural hanya spasi, tab, line feed, dan carriage return yang diizinkan. Kutip tunggal, kunci tanpa kutip, koma di akhir, atau komentar berarti itu bukan JSON standar.

Mengapa JSON harus menggunakan UTF-8 dan tidak membawa BOM?

Bagian 8.1 RFC 8259 mewajibkan teks JSON yang digunakan untuk interchange di-encode dalam UTF-8, menyatakan implementasi tidak boleh menambahkan BOM di awal, dan mengizinkan parser mengabaikan BOM demi ketangguhan. File dengan BOM ditolak oleh JSON.parse dengan Unexpected token, meskipun tidak ada yang tampak salah bagi mata. Jadi hapus BOM saat membaca dan dekode secara eksplisit sebagai UTF-8.

Haruskah saya menggunakan JSON5 atau JSONC untuk menghindari batas tata bahasa ini?

Ya, tetapi hanya untuk pembacaan lokal seperti file konfigurasi; data yang dipertukarkan antar sistem tetap harus JSON standar. Contoh kontra menunjukkan bahwa {"a": 1} diikuti komentar valid di JSON5 dan JSONC tetapi tidak valid di JSON standar, itulah sebabnya "file konfigurasi berjalan tetapi API menolaknya" hampir selalu masalah dialek. Saat mengonversi, hapus dulu komentar, kutip tunggal, dan koma di akhir, lalu serahkan hasilnya ke parser standar.