JSON Toolkit

Membaca sempadan tatabahasa JSON daripada RFC 8259

Inti pati: Mentafsir produksi tatabahasa RFC 8259 ke dalam bahasa mudah: aksara mana boleh muncul secara terus, mana yang mesti di-escape, dan di mana spesifikasi sengaja memberi ruang.

Pemula~11 min bacaanDiterbitkan 2026-10-30Dikemas kini 2026-10-30
Kulit untuk Membaca sempadan tatabahasa JSON daripada RFC 8259

Kesimpulan utama: apabila kesilapan sintaks JSON muncul, cari aksara tidak sah pertama dan klasifikasikannya sebagai petikan tunggal, koma hujung, komen, atau aksara kawalan dan jangan membaca semula seluruh dokumen; pada bahagian pengekodan, sahkan UTF-8 tanpa BOM dan bawa integer melebihi 2^53 - 1 sebagai rentetan.

Badan spesifikasi itu hanya belasan halaman, namun di sebalik setiap "MUST", "SHOULD", dan "MAY" tersembunyi satu insiden yang pernah membuat satu pasukan berjaga sepanjang malam.

1. Bagaimana ketiga-tiga teks itu berkaitan

Mempelajari JSON sering menjadi keliru kerana tiga nombor: RFC 4627, RFC 7159, RFC 8259, serta ECMA-404. Hubungannya sebenarnya cukup jelas:

Teks Tarikh Status Perbezaan utama
RFC 4627 2006 Digantikan Peringkat atas mesti objek atau tatasusunan
RFC 7159 2014 Digantikan Peringkat atas dilonggarkan kepada sebarang nilai JSON
RFC 8259 2017.12 Standard semasa UTF-8 eksplisit, panduan keserasian ditambah
ECMA-404 2013, edisi kedua 2017.12 Spesifikasi tatabahasa Mentakrifkan tatabahasa sahaja, tidak merangkumi pengekodan atau pengangkutan

Yang mana perlu anda baca? RFC 8259 sahaja sudah cukup. ECMA-404 setara pada bahagian tatabahasanya; perbezaannya ialah RFC 8259 juga menetapkan "pengangkutan sebagai UTF-8" dan "isu keserasian yang perlu diberi perhatian oleh pelaksana".

2. Enam jenis nilai

Produksi tatabahasa spesifikasi itu boleh diringkaskan kepada satu rajah:

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 perkara penting:

  1. Objek menggunakan pendakap keras, ahli dipisahkan oleh koma, dan koma selepas ahli terakhir tidak dibenarkan;
  2. Tatasusunan menggunakan pendakap segi empat, elemen dipisahkan oleh koma, dan koma hujung juga tidak dibenarkan;
  3. Nombor tidak membenarkan + di hadapan, tidak membenarkan sifar di hadapan (01 tidak sah), tidak membenarkan titik perpuluhan kosong (1. tidak sah), dan tidak membenarkan NaN atau Infinity;
  4. Rentetan mesti menggunakan tanda petikan berganda; petikan tunggal tidak sah;
  5. Boolean dan null ialah literal huruf kecil; True dan NULL kedua-duanya tidak sah;
  6. Hanya aksara ruang putih 0x20 (ruang), 0x09 (tab), 0x0A (suapan baris), dan 0x0D (kembali gerabak) boleh muncul antara elemen struktur.

Serpihan di bawah ialah JSON yang sah, walaupun ramai orang menganggapnya salah kali pertama melihatnya — kerana ia ialah rentetan peringkat atas yang kosong:

"just a string"

Sejak RFC 7159, peringkat atas memang boleh jadi sebarang nilai. Dalam API, bagaimanapun, masih disyorkan untuk hanya menggunakan objek atau tatasusunan; alasannya ada di bahagian 6.

3. Apa yang sebenarnya boleh muncul terus di dalam rentetan

Ini bahagian yang paling mudah tertipu oleh "ia berjalan lancar pada mesin saya". Peraturan spesifikasi untuk rentetan ialah:

  • Aksara yang dibenarkan muncul terus: %x20-21, %x23-5B, %x5D-10FFFF;
  • Dengan kata lain, tanda petikan berganda " (0x22) dan garis miring terbalik \ (0x5C) mesti di-escape;
  • Semua aksara kawalan di bawah 0x20 mesti di-escape, termasuk suapan baris, tab, dan kembali gerabak;
  • Satu-satunya jujukan escape yang tersedia ialah \", \\, \/, \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\""
}

\/ ialah escape warisan (ia menghalang </script> daripada ditutup lebih awal). Senario moden jarang memerlukannya, tetapi penghurai mesti menyokongnya.

4. Pengekodan: UTF-8, tiada BOM

Bahagian 8.1 RFC 8259 menyatakan tiga perkara:

  1. Teks JSON yang digunakan untuk pertukaran mesti dikodkan dalam UTF-8;
  2. Pelaksanaan tidak boleh menambah BOM (EF BB BF) pada permulaan teks;
  3. Penghurai boleh mengabaikan BOM supaya lebih teguh.

Dua kegagalan tipikal dalam dunia sebenar:

  • Notepad Windows menyimpan sebagai "UTF-8 dengan BOM", fail itu ditolak oleh JSON.parse dengan Unexpected token;
  • Fail JSON Cina yang disimpan sebagai GBK bertukar menjadi teks rosak pada saluran yang hanya menyahkod sebagai UTF-8, namun sintaksnya masih kebetulan lulus, jadi kesilapannya ditangguhkan ke lapisan perniagaan.

Pengendaliannya mudah: buang BOM semasa membaca fail, kemudian nyahkod secara eksplisit sebagai UTF-8; semasa menulis, pastikan tiada BOM ditambah.

/**
 * 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;
}

Nota tentang jenis media sementara kita di sini: application/json tidak lagi menerima parameter charset; menulisnya tidak menimbulkan kesilapan, tetapi ia amalan yang lapuk.

5. Tiga tempat di mana spesifikasi sengaja memberi ruang

Tempat di mana spesifikasi enggan menetapkan sesuatu dengan pasti ialah tepat tempat perbezaan pelaksanaan berasal.

1. Julat dan ketepatan nombor

RFC 8259 hanya menyatakan "spesifikasi ini tidak mengehadkan julat atau ketepatan nombor", sambil mencatat bahawa kebanyakan pelaksanaan menggunakan ketepatan berganda IEEE 754. Ini bermaksud 9223372036854775807 yang sama menjadi 9223372036854776000 dalam JavaScript, namun kekal tepat dalam long Java.

Kesimpulan: integer melebihi 2^53 - 1, dan jumlah wang yang memerlukan banyak tempat perpuluhan ketepatan, harus sentiasa dibawa sebagai rentetan, dengan pattern ditulis secara eksplisit dalam skema.

2. Kunci pendua

Susunan kata spesifikasi ialah "nama dalam sesuatu objek sepatutnya (SHOULD) unik"; ia tidak menyatakan apa yang perlu dilakukan apabila ia tidak unik.

  • JSON.parse mengekalkan yang terakhir;
  • Sesetengah penghurai mengekalkan yang pertama;
  • Sesetengah pustaka terus menimbulkan kesilapan.

Kesimpulan: jangan menulis kunci pendua, dan jangan bergantung pada sebarang tingkah laku tertentu dalam pengesah anda. Menambah langkah "pengesanan kunci pendua" pada masa pembinaan jauh lebih murah daripada menyiasat selepas itu.

3. Had kedalaman dan panjang

Spesifikasi tidak menetapkan had pada kedalaman sarang, tetapi setiap pelaksanaan mempunyai hadnya. V8 melambungkan RangeError: Maximum call stack size exceeded pada puluhan ribu tahap sarang. Pelayan mesti menetapkan hadnya sendiri; ini keperluan keselamatan, bukan keperluan spesifikasi.

6. Menguji pemahaman anda dengan contoh berlawanan

Setiap baris di bawah ialah JSON tidak sah. Berikan kepada penghurai dan lihat sama ada mesej kesilapannya 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

Satu kes sempadan yang menarik: {"a": 1} diikuti dengan komen adalah sah dalam JSON5, sah dalam JSONC, dan tidak sah dalam JSON standard. Jadi "fail konfigurasi berjalan lancar tetapi API menolaknya" hampir selalu masalah dialek.

7. Senarai semak pengeluaran

  • [ ] Semua JSON yang dihantar keluar menggunakan application/json tanpa charset;
  • [ ] Saluran baca/tulis fail membuang BOM dan menggunakan UTF-8 eksplisit;
  • [ ] Integer melebihi 2^53 dibawa sebagai rentetan dan diberi anotasi dalam skema;
  • [ ] Kunci pendua dikesan pada masa pembinaan;
  • [ ] Pelayan mengehadkan saiz badan permintaan dan kedalaman sarang;
  • [ ] Nilai peringkat atas API awam ialah objek atau tatasusunan, tidak pernah skalar kosong.

Soalan Lazim

Bagaimana saya boleh mengetahui dengan pantas sama ada sebahagian teks ialah JSON yang sah?

Berikan teks itu kepada penghurai atau alat penghuraian dalam talian, baca kedudukan aksara tidak sah pertama, kemudian semaknya terhadap enam jenis nilai dan peraturan ruang putih RFC 8259. Peringkat atas boleh jadi sebarang nilai JSON, tetapi antara elemen struktur hanya ruang, tab, suapan baris, dan kembali gerabak dibenarkan. Petikan tunggal, kunci tanpa petikan, koma hujung, atau komen bermaksud ia bukan JSON standard.

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

Bahagian 8.1 RFC 8259 menghendaki teks JSON yang digunakan untuk pertukaran dikodkan dalam UTF-8, menyatakan bahawa pelaksanaan tidak boleh menambah BOM pada permulaan, dan membenarkan penghurai mengabaikan BOM demi keteguhan. Fail dengan BOM ditolak oleh JSON.parse dengan Unexpected token, walaupun tiada apa yang kelihatan salah pada mata. Jadi buang BOM semasa membaca dan nyahkod secara eksplisit sebagai UTF-8.

Patutkah saya menggunakan JSON5 atau JSONC untuk mengelak had tatabahasa ini?

Ya, tetapi hanya untuk bacaan setempat seperti fail konfigurasi; data yang ditukar antara sistem masih perlu JSON standard. Contoh berlawanan menunjukkan bahawa {"a": 1} diikuti dengan komen adalah sah dalam JSON5 dan JSONC tetapi tidak sah dalam JSON standard, itulah sebabnya "fail konfigurasi berjalan tetapi API menolaknya" hampir selalu masalah dialek. Semasa menukar, buang komen, petikan tunggal, dan koma hujung dahulu dan berikan hasilnya kepada penghurai standard.