JSON Toolkit

FAQ Sintaks dan Format: Komentar, Koma di Ujung, dan Presisi

Intinya: Apakah komentar diperbolehkan, mengapa koma di ujung menimbulkan error, apakah key perlu tanda kutip, mengapa 9007199254740993 berubah — dua belas kesimpulan, masing-masing diperiksa terhadap RFC 8259.

Pemula±12 menit bacaDiterbitkan 2026-10-30Diperbarui 2026-10-30
Sampul untuk FAQ sintaks dan format

Intinya: tulis JSON RFC 8259 murni — tanda kutip ganda, tanpa komentar, tanpa koma di ujung, UTF-8 tanpa BOM — untuk apa pun yang melintasi batas proses; gunakan JSONC atau JSON5 untuk konfigurasi yang membutuhkan komentar dan konversi kembali saat runtime; serta serialisasi bilangan bulat di atas 2^53-1 sebagai string di server.

Kesamaan pertanyaan-pertanyaan ini: semuanya tampak "seharusnya berfungsi", padahal spesifikasinya sebenarnya sangat ketat.

1. Apa yang dianggap JSON valid

Apa perbedaan JSON, JSON5, dan JSONC?

JSON standar hanya mengenali string bertanda kutip ganda dan tidak mengizinkan komentar maupun koma di ujung. JSON5 dan JSONC keduanya menambahkan fitur di atasnya:

Fitur JSON (RFC 8259) JSONC JSON5
String bertanda kutip ganda Didukung Didukung Didukung
String bertanda kutip tunggal Tidak didukung Tidak didukung Didukung
Komentar Tidak didukung Didukung Didukung
Koma di ujung Tidak didukung Tidak didukung Didukung
Key tanpa kutip Tidak didukung Tidak didukung Didukung
Bilangan heksadesimal Tidak didukung Tidak didukung Didukung
NaN / Infinity Tidak didukung Tidak didukung Didukung
Ekstensi berkas .json .jsonc .json5

Prinsip pemilihannya sederhana: selalu gunakan JSON standar saat melintasi batas proses, sementara berkas konfigurasi boleh memakai JSONC atau JSON5 tergantung dukungan editor Anda. tsconfig.json dan .vscode/settings.json boleh berisi komentar karena editor mengurainya sebagai JSONC; memberikannya ke JSON.parse yang ketat tetap akan error.

Bisakah JSON berisi komentar?

Tidak. Tata bahasa dalam RFC 8259 tidak memiliki produksi komentar, jadi // atau /* */ apa pun membuat parsing gagal.

Jika Anda ingin menyimpan penjelasan dalam konfigurasi, ada tiga alternatif:

{
  "_comment": "以下超时单位是毫秒",
  "timeout": 3000,
  "retry": {
    "$doc": "重试次数,0 表示不重试",
    "count": 3
  }
}
  • Gunakan field konvensional seperti _comment yang diabaikan begitu saja oleh pembaca;
  • Gunakan field berawalan $ untuk mengurangi peluang bertabrakan dengan field bisnis;
  • Cukup beralih ke JSONC atau YAML untuk konfigurasi dan konversi ke JSON standar saat runtime.

Mengapa koma di ujung menyebabkan error?

Karena dalam tata bahasa, array dan objek sama-sama berstruktur "elemen + pemisah", dan tidak ada koma yang diizinkan setelah elemen terakhir.

{
  "a": 1,
  "b": 2,
}

Cuplikan di atas memiliki koma tambahan sebelum }, dan kesalahan biasanya dilaporkan pada koma atau pada kurung kurawal penutup tergantung implementasi parser. Ini juga jebakan termudah saat bermigrasi dari JavaScript — objek literal JS mengizinkan koma di ujung, JSON tidak.

Apakah tanda kutip tunggal diperbolehkan?

Tidak. String harus menggunakan tanda kutip ganda; 'text' adalah kesalahan sintaks.

Jika data disalin dari repr() Python, skrip shell, atau konfigurasi yang ditulis tangan, konversikan dulu dengan alat alih-alih berharap parser bersifat longgar.

Bisakah key tanpa tanda kutip?

Tidak. Nama anggota objek harus berupa string yang dibungkus tanda kutip ganda.

{ name: "json" }

Itu adalah objek literal JavaScript, bukan JSON. Menambahkan tanda kutip membuatnya valid:

{ "name": "json" }

Bisakah level teratas berupa array atau string?

Ya. RFC 8259 mengizinkan nilai JSON apa pun di level teratas: objek, array, string, angka, true, false, dan null semuanya valid.

Namun dalam praktik rekayasa, disarankan hanya menggunakan objek atau array di level teratas, karena dua alasan:

  1. Kompatibilitas: parser lama (era RFC 4627) mensyaratkan level teratas berupa objek atau array;
  2. Keamanan: array di level teratas secara historis membawa risiko JSON hijacking. Browser modern sudah memperbaikinya, tetapi sistem lama masih mengasumsikan konvensi tersebut.

Jadi "hello" adalah JSON valid dan ["a", "b"] juga, tetapi API yang mengembalikan {"data": [...]} lebih aman.

2. Encoding dan string

Apakah BOM di awal berkas merusak parsing?

Ya. RFC 8259 secara eksplisit mensyaratkan teks JSON ditransmisikan sebagai UTF-8 dan tidak boleh menambahkan byte order mark (BOM).

BOM adalah tiga byte EF BB BF di awal berkas. Ia sepenuhnya tak terlihat, tetapi menempati posisi pertama string, sehingga parser menyimpulkan bahwa "karakter pertama bukan {" dan error — dan baris serta kolom yang dilaporkan biasanya baris 1, kolom 1, yang tampak membingungkan.

"Save as UTF-8" di Notepad Windows menyertakan BOM secara default. Untuk memeriksa dan menghapusnya:

# Periksa: output yang diawali efbbbf berarti ada BOM
head -c 3 data.json | xxd

# Hapus: lewati 3 byte pertama dengan tail
tail -c +4 data.json > clean.json

JSON.parse JavaScript modern menoleransi BOM (V8 melewatinya secara internal), tetapi implementasi ketat di Java, Go, dan Python tidak, yang merupakan alasan umum mengapa "berkas yang sama memberikan hasil berbeda di bahasa berbeda".

Apakah karakter Tionghoa perlu dikonversi ke \uXXXX?

Tidak. RFC 8259 mensyaratkan teks JSON berupa UTF-8, jadi teks Tionghoa bisa muncul langsung; {"name": "张三"} sepenuhnya valid.

\uXXXX hanyalah ejaan alternatif yang setara, dipakai untuk kanal yang tidak dapat merepresentasikan karakter secara langsung. Aturan praktisnya:

  • Body permintaan HTTP, berkas, basis data: tulis teks Tionghoa langsung, menghemat 3 byte per karakter;
  • Header HTTP, parameter URL, penyematan di dalam string lain: gunakan escape untuk menghindari masalah encoding.

Satu-satunya karakter yang harus di-escape adalah karakter kontrol (U+0000 sampai U+001F). Misalnya, baris baru harus ditulis sebagai \n; Anda tidak dapat menaruh baris baru literal di dalam string.

Bisakah emoji langsung masuk ke string?

Ya, tetapi ingat bahwa ia menempati dua code unit UTF-16.

Emoji dan banyak karakter langka (seperti 𠮷) berada di luar BMP, sehingga di JavaScript "😀".length === 2. Memvalidasi berdasarkan jumlah karakter karena itu mudah salah:

const text = "😀";
console.log(text.length);          // 2, jumlah code unit UTF-16
console.log([...text].length);     // 1, jumlah code point
console.log(Buffer.byteLength(text)); // 4, jumlah byte UTF-8

Jika batas panjang nickname dihitung dengan .length, satu emoji memakan dua slot; jika backend menghitung byte, hasilnya empat. Saat menyelaraskan definisi, nyatakan dengan jelas apakah yang dimaksud code point, code unit, atau byte.

3. Angka dan nilai khusus

Mengapa bilangan bulat besar 9007199254740993 menjadi 9007199254740992?

Karena JSON.parse JavaScript mengubah angka menjadi float presisi ganda IEEE 754, dan bilangan bulat aman terbesar adalah 2^53-1.

Number.MAX_SAFE_INTEGER === 9007199254740991  // true
9007199254740993                              // sebenarnya menghasilkan 9007199254740992

Angka apa pun di atas 2^53-1 mungkin dibulatkan pada digit-digit terakhirnya. Korban tipikal adalah user ID 19 digit yang dihasilkan algoritma snowflake, nomor pesanan, dan timestamp nanodetik.

Tiga perbaikan, diurutkan menurut preferensi:

  1. Serialisasi ID sebagai string di server: "id": "9007199254740993", menghilangkan masalah presisi sampai ke akarnya;
  2. Gunakan pustaka parsing lossless: pengganti JSON.parse dapat mempertahankan bilangan bulat besar apa adanya, misalnya json-bigint;
  3. Ekstrak dengan ekspresi reguler hanya untuk tampilan: jangan parse seluruh dokumen, cukup cocokkan field target sebagai teks.

Perhatikan bahwa JSON.stringify memiliki masalah simetris: angka di luar rentang bilangan bulat aman mungkin tidak lagi mempertahankan nilai aslinya setelah serialisasi.

Bisakah NaN dan Infinity masuk ke JSON?

Tidak. Produksi angka JSON hanya mengizinkan notasi desimal, dan NaN, Infinity, serta -Infinity bukan nilai JSON yang valid.

Pendekatan JavaScript adalah "degradasi diam-diam":

JSON.stringify({ value: NaN });        // '{"value":null}'
JSON.stringify({ value: Infinity });   // '{"value":null}'

Tanpa error, NaN cukup berubah menjadi null. Inilah adegan klasik di mana "frontend mengirim NaN dan backend menerima null". Saat Anda perlu menyampaikan semantik seperti itu, nyatakan secara eksplisit dengan string atau nilai sentinel yang disepakati:

{ "ratio": null, "ratioState": "not-a-number" }

Bisakah angka ditulis sebagai 1e3 atau 0.5?

Ya. Angka JSON mendukung desimal dan eksponen, jadi 1e3, 1E3, 0.5, dan -0.5 semuanya valid.

Tetapi ada batasan:

  • Tidak boleh dimulai atau diakhiri dengan .: .5 dan 5. keduanya tidak valid, jadi tulis 0.5 dan 5;
  • Tidak boleh ada nol di depan: 01 tidak valid;
  • Tidak boleh heksadesimal: 0x1F tidak valid, itu ekstensi JSON5;
  • -0 valid, tetapi JSON.stringify(-0) menghasilkan 0, sehingga tanda negatifnya hilang.

4. Semantik objek

Apa yang terjadi dengan key duplikat?

Perilaku ditentukan oleh implementasi; RFC 8259 hanya mengatakan nama key "sebaiknya" unik dan tidak menetapkan apa yang terjadi pada duplikat.

Dalam praktik, sebagian besar implementasi mengambil nilai terakhir:

JSON.parse('{"a":1,"a":2}')  // { a: 2 }

Tetapi Anda tidak dapat mengandalkannya. Sebagian pustaka Java, encoding/json milik Go, dan json milik Python berbeda antar versi: sebagian mengambil nilai pertama, sebagian error, dan sebagian mengambil yang terakhir. Memperlakukan key duplikat sebagai error adalah pendekatan paling aman lintas bahasa.

Anda tidak dapat menemukan key duplikat dengan mata — dalam JSON yang diminify, key yang sama bisa terpisah ribuan karakter. Gunakan skrip:

# Ekstrak key yang muncul lebih dari sekali pada level yang sama, sebagai pemeriksaan kasar
jq -r 'paths(scalars) | join(".")' data.json | sort | uniq -d

Apa yang terjadi pada undefined?

Tergantung, dan ini titik paling rawan bug:

JSON.stringify({ a: undefined });              // '{}'  key dibuang seluruhnya
JSON.stringify({ a: undefined, b: 1 });        // '{"b":1}'
JSON.stringify([undefined]);                   // '[null]'  menjadi null di dalam array
JSON.stringify({ a: function () {} });         // '{}'  fungsi juga dibuang

undefined yang sama berarti "field menghilang" di dalam objek dan "menjadi null" di dalam array. Jika backend mengambil cabang berbeda untuk "field tidak ada" dan "field bernilai null", Anda mendapat ketidakonsistenan di sini.

Saat Anda perlu menyatakan "tidak ada nilai" secara eksplisit, gunakan null alih-alih undefined.

Apa yang terjadi setelah parsing {"__proto__": {...}}?

JSON.parse sendiri aman: __proto__ diperlakukan sebagai properti own biasa dan tidak mengubah prototipe.

Yang berbahaya adalah operasi merge setelah parsing:

const patch = JSON.parse('{"__proto__": {"isAdmin": true}}');
Object.assign({}, patch);       // sebagian implementasi mencemari prototipe

Pendekatan yang benar adalah mengiterasi properti own dan melewati __proto__, constructor, dan prototype. Detailnya ada di FAQ Performa dan Keamanan.

5. Rujukan cepat

Sintaks Valid Catatan
{"a": 1,} Tidak Koma di ujung
{'a': 1} Tidak Kutip tunggal
{a: 1} Tidak Key tanpa tanda kutip
{"a": 1} // comment Tidak Komentar
"hello" Ya String di level teratas valid, tetapi jarang dipakai di API
{"a": 1e3} Ya Eksponen didukung
{"a": 01} Tidak Nol di depan
{"a": NaN} Tidak Literal tidak valid; stringify mengubahnya menjadi null
\uFEFF{"a":1} Tidak BOM bukan bagian dari teks JSON
{"a":1,"a":2} Diperdebatkan Implementasi mengambil nilai terakhir, tetapi jangan mengandalkannya