Geonode logo
Geonode Team

Geonode Team

Diperbarui: 7 Oktober 2026

Diterbitkan: 2 September 2026

JSON.parse() dalam JavaScript: Panduan Lengkap

`JSON.parse()` mengubah string menjadi nilai JavaScript. Itulah keseluruhan API-nya, dan hanya butuh sekitar sepuluh detik untuk mempelajarinya. Bagian yang menarik justru ada di sekitarnya: fungsi reviver, kehilangan presisi bilangan yang tanpa disadari masuk ke lingkungan produksi, kasus khusus ``__proto__``, serta penambahan standar terbaru yang akhirnya memungkinkan Anda melihat teks sumber aslinya. Panduan ini membahas semuanya, termasuk skenario kegagalan, bukan hanya skenario yang berjalan lancar.

Satu catatan mengenai alasan mengapa sebuah perusahaan penyedia proxy menulis tentang JSON.parse. Kami adalah Geonode dan kami menjual layanan proxy, dan SyntaxError yang paling sering dilaporkan oleh pelanggan kami sama sekali tidak ada hubungannya dengan JSON — melainkan Unexpected token '<', yang berarti responsnya berupa HTML, bukan JSON, artinya API tersebut mengembalikan halaman blokir, pengalihan login, atau halaman kesalahan. Jadi, inilah peringatan jujur: jika JSON.parse muncul pada data yang Anda ambil, catat isi respons mentah sebelum mengubah apa pun. Sembilan dari sepuluh kali, parser berfungsi dengan sempurna dan melaporkan secara akurat bahwa Anda menerima halaman web. Membeli proxy hanya akan memperbaiki masalah tersebut dalam kasus spesifik di mana Anda diblokir; hal itu tidak akan membantu jika URL salah, token kedaluwarsa, atau Anda melanggar batas frekuensi yang seharusnya dipatuhi. Cetak responsnya terlebih dahulu.

Setelah itu, mari kita bahas API yang sebenarnya.

Dasar-dasar dan Kesalahan yang Sebenarnya Akan Anda Temui

const data = JSON.parse('{"name": "Ada", "born": 1815}');
// { name: "Ada", born: 1815 }

Ada dua parameter: teks, dan fungsi reviver yang bersifat opsional. Itu saja.

Fungsi ini akan melemparkan kesalahan ``SyntaxError`

` jika input melanggar tata bahasa JSON, dan tata bahasa JSON lebih ketat daripada sintaks literal objek JavaScript dalam hal-hal yang sering menjebak pengguna. Empat kasus berikut mencakup hampir semua kegagalan.

Tanda kutip tunggal. MDN sangat jelas: "String JSON harus dibatasi oleh tanda kutip ganda (bukan tunggal)." Valid dalam JavaScript, tetapi tidak valid dalam JSON.

JSON.parse("{'name': 'Ada'}");  // SyntaxError
JSON.parse('{"name": "Ada"}');  // fine

Komma di akhir. Diperbolehkan dalam JavaScript modern, tetapi tidak diperbolehkan dalam JSON:

JSON.parse("[1, 2, 3, 4, ]");  // SyntaxError

Kunci yang tidak diapit tanda kutip. {name: "Ada"}

adalah literal objek yang sah, tetapi bukan JSON. Kunci harus berupa string yang diapit tanda kutip.

Respons tersebut bukan JSON. Seperti yang dijelaskan di atas. Unexpected token '<'

berarti badan respons dimulai dengan <

, yang berarti HTML. Unexpected end of JSON input

biasanya berarti badan respons kosong — respons 204, respons yang terpotong, atau permintaan fetch yang Anda lupa tunggu.

Selalu bungkus respons tersebut dan catat apa yang sebenarnya Anda terima:

function parseOrThrow(text, url) {
  try {
    return JSON.parse(text);
  } catch (err) {
    throw new Error(
      `Failed to parse JSON from ${url}: ${err.message}. ` +
      `First 200 chars: ${text.slice(0, 200)}`
    );
  }
}

Bagian text.slice(0, 200)

adalah bagian yang penting. SyntaxError

tanpa tambahan apa pun menunjukkan bahwa parsing gagal; 200 karakter pertama menjelaskan alasannya, dan biasanya jawabannya langsung terlihat.

Fungsi Reviver dan Kegunaannya

Argumen kedua mengubah nilai saat nilai-nilai tersebut diparsing:

const data = JSON.parse(text, (key, value) => {
  if (key === "created") return new Date(value);
  return value;
});

Tiga perilaku yang perlu dipahami dengan tepat.

Fungsi ini berjalan secara kedalaman-dulu. Properti bersarang dikunjungi sebelum properti induknya, dan panggilan terakhir menggunakan string kosong sebagai kunci untuk nilai akar. Jadi, pada saat reviver Anda mendeteksi sebuah objek, anak-anaknya sudah terlebih dahulu diproses.

**Mengembalikundefined

akan menghapus properti tersebut.** MDN: "Jika fungsi reviver

mengembalikundefined

(atau tidak mengembalikan nilai apa pun), properti tersebut akan dihapus dari objek." Hal ini mudah terjadi secara tidak sengaja — reviver dengan cabang kondisional yang melampaui batas akhir akan mengembalikundefined

dan menghapus kunci secara diam-diam. Selalu kembalikan value

secara eksplisit sebagai nilai default.

Nilai akar dapat diganti sepenuhnya. "Jika Anda mengembalikan nilai lain dari reviver

, nilai tersebut akan sepenuhnya menggantikan nilai yang diparsing semula. Hal ini bahkan berlaku untuk nilai akar."

Penggunaan klasiknya adalah pemulihan tanggal, karena JSON tidak memiliki tipe data tanggal:

const ISO_DATE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;

const data = JSON.parse(text, (key, value) =>
  typeof value === "string" && ISO_DATE.test(value)
    ? new Date(value)
    : value
);

Berhati-hatilah dengan pencocokan pola. Sebuah reviver yang mengonversi apa pun yang berbentuk tanggal akan mengonversi string yang ingin Anda pertahankan sebagai string — nomor versi, pengenal, atau konten pengguna yang kebetulan terlihat seperti cap waktu. Sebaiknya lakukan pencocokan berdasarkan nama kunci jika Anda mengetahui skemanya.

Perhatikan juga biayanya: reviver dipanggil sekali per nilai dalam dokumen. Pada muatan data yang besar, hal ini menjadi pertimbangan kinerja yang nyata, dan seringkali lebih efisien untuk mem-parsing secara langsung dan mentransformasi hanya bidang-bidang yang Anda perlukan setelahnya.

Akses Teks Sumber: context.source dan JSON.rawJSON

Ini adalah penambahan terbaru yang paling signifikan, dan banyak pengembang belum mengenalnya.

Usulan akses teks sumber JSON.parse telah mencapai tahap 4 dalam proses TC39, yang berarti usulan tersebut telah disetujui untuk menjadi standar. Usulan ini memecahkan masalah yang disebutkan secara langsung dalam usulan tersebut: "Transformasi antara nilai ECMAScript dan teks JSON bersifat lossy."

Fungsi reviver kini menerima argumen ketiga untuk nilai-nilai primitif. MDN mendeskripsikan ``context.source`

sebagai "String JSON asli yang mewakili nilai ini" — dan usulan tersebut lebih tepat, menyebutnya sebagai teks sumber "termasuk tanda baca tetapi tidak termasuk spasi kosong yang tidak signifikan di awal/akhir", bersama dengan ``index

, ``input

, dan ``keys

`.

Mengapa hal ini penting menjadi jelas dengan satu contoh:

const text = '{"id": 9007199254740993}';

JSON.parse(text).id;
// 9007199254740992  — wrong, silently

JSON.parse(text, (key, value, context) =>
  key === "id" ? BigInt(context.source) : value
).id;
// 9007199254740993n  — correct

Tanpa akses ke teks sumber, angka tersebut telah dikonversi menjadi bilangan ganda JavaScript pada saat reviver Anda melihatnya. Presisi tersebut hilang sebelum Anda dapat melakukan intervensi. context.source

memberikan digit aslinya.

Usulan ini juga menambahkan JSON.rawJSON()

, yang memungkinkan Anda menyediakan teks JSON mentah yang akan dikeluarkan oleh JSON.stringify

tanpa perubahan — sehingga melengkapi siklus penuh sehingga BigInt yang dibaca dari JSON dapat ditulis kembali tanpa kerusakan.

JSON.stringify({ id: JSON.rawJSON("9007199254740993") });
// '{"id":9007199254740993}'

Periksa dukungan untuk lingkungan target Anda sebelum mengandalkannya, tetapi ini sekarang merupakan solusi yang tepat untuk masalah bilangan besar, bukan sekadar solusi sementara.

Presisi Bilangan Adalah Bug yang Akan Anda Rilis

Topik ini layak mendapat bagian tersendiri karena bug ini terjadi tanpa gejala yang jelas dan gejalanya muncul jauh dari sumber masalahnya.

Bilangan JSON diubah menjadi bilangan JavaScript, yang merupakan bilangan ganda IEEE 754. Bilangan bulat di atas Number.MAX_SAFE_INTEGER — 9.007.199.254.740.991 — tidak semuanya dapat direpresentasikan secara tepat. MDN menjelaskannya dengan jelas: bilangan "mungkin kehilangan presisi dalam proses tersebut".

Sifat berbahaya dari hal ini adalah tidak ada pengecualian yang dilemparkan. Anda mendapatkan sebuah bilangan. Namun, bilangan tersebut bukanlah bilangan yang dikirimkan.

JSON.parse('{"id": 12345678901234567890}').id;
// 12345678901234567000

Contoh kasus di mana hal ini menjadi masalah dalam praktik:

  • Pengidentifikasi basis data. Kunci utama (primary key) bilangan bulat 64-bit melebihi rentang aman. Dua catatan yang berbeda dapat diparsing menjadi bilangan JavaScript yang sama.
  • ID bergaya Snowflake. Digunakan oleh beberapa platform besar, yang secara rutin melebihi batas.
  • Jumlah keuangan dalam satuan kecil. Jumlah besar dalam sen atau satoshi.
  • Cap waktu dalam nanosekon. Setiap nilai epoch dalam nanosekon sejak tahun 1970 sudah berada di luar rentang aman.

Tiga langkah mitigasi, berdasarkan urutan prioritas:

Minta string. Jika Anda mengontrol API, serialisasikan pengenal besar sebagai string. Ini adalah solusi paling bersih, berfungsi di mana saja, dan tidak memerlukan biaya apa pun. MDN merekomendasikan hal ini: "Salah satu cara untuk mentransfer angka besar tanpa kehilangan presisi adalah dengan menserialisasikannya sebagai string, lalu mengonversinya kembali menjadi BigInt."

Gunakan context.source dengan fungsi pengonversi. Seperti di atas, jika Anda tidak mengontrol pihak penghasil dan lingkungan Anda mendukungnya.

Gunakan pustaka JSON yang mendukung BigInt. Untuk lingkungan yang lebih lama, beberapa parser dapat menangani hal ini. Cara ini memerlukan dependensi tambahan dan sedikit mengorbankan kinerja.

Yang tidak efektif: memeriksa apakah angka tersebut "terlihat benar" setelah diparsing. Pada tahap itu, informasi aslinya sudah hilang, dan nilai yang salah tidak dapat dibedakan dari nilai yang benar.

"__proto__" dan "Prototype Pollution"

MDN mengidentifikasi satu-satunya kasus di mana makna JSON dan JavaScript berbeda: "Satu-satunya situasi di mana teks JSON mewakili nilai yang berbeda dari ekspresi JavaScript yang sama adalah ketika berurusan dengan kunci \"__proto__\"."

Dalam literal objek JavaScript, __proto__ menetapkan prototipe. Dalam JSON.parse, hal ini menciptakan properti biasa: `

const fromLiteral = { __proto__: { admin: true } };
fromLiteral.admin;             // true — prototype was set

const fromJson = JSON.parse('{"__proto__": {"admin": true}}');
fromJson.admin;                // undefined — plain own property
Object.hasOwn(fromJson, "__proto__");  // true

Jadi, JSON.parse sendiri aman di sini — ini adalah perilaku yang disengaja dan benar.

Bahayanya terletak pada apa yang terjadi selanjutnya. Kerentanan polusi prototipe hampir selalu terjadi ketika data yang diparsing digabungkan ke dalam objek lain oleh kode yang tidak menyaring kunci-kunci berbahaya:

// Unsafe: a naive deep merge can walk into Object.prototype
function merge(target, source) {
  for (const key in source) {
    if (typeof source[key] === "object") {
      merge(target[key] ?? (target[key] = {}), source[key]);
    } else {
      target[key] = source[key];
    }
  }
}

Berikan payload yang berisi __proto__ dan Anda dapat memodifikasi Object.prototype untuk seluruh program. Langkah pencegahan:

Saring kunci-kunci berbahaya secara eksplisit — __proto__, constructor, prototype — dalam setiap penggabungan atau penugasan yang melibatkan data yang tidak tepercaya.

Gunakan Object.create(null) untuk objek yang menyimpan kunci-kunci yang tidak tepercaya, sehingga tidak ada prototipe yang dapat tercemar.

Gunakan Map jika Anda benar-benar sedang membangun penyimpanan kunci-nilai, bukan objek terstruktur.

Lakukan validasi berdasarkan skema. Ini adalah jawaban umum, dan juga yang dapat mendeteksi masalah lain. Parsing dan validasi adalah langkah terpisah, dan keduanya diperlukan.

JSON.parse versus eval versus Response.json()

Jangan pernah menggunakan eval. Fitur ini menjalankan kode sembarangan, lebih lambat untuk keperluan ini, dan menerima data yang bukan JSON. Tidak ada situasi di mana eval merupakan alat yang tepat untuk mengurai JSON.

Response.json() adalah yang Anda butuhkan saat bekerja dengan fetch. Fitur ini membaca isi permintaan dan mengurainya dalam satu langkah:

const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();

Pemeriksaan res.ok adalah bagian yang sering dilewati orang, dan melewatkannya adalah penyebab langsung dari kesalahan Unexpected token '<' yang disebutkan di bagian pengantar. fetch tidak menolak status kesalahan HTTP — status 403 atau 500 diproses secara normal, dan kemudian .json() mencoba memproses halaman kesalahan. Periksa statusnya terlebih dahulu, dan periksa content-type jika Anda ingin lebih teliti:

const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status} from ${url}`);
const type = res.headers.get("content-type") ?? "";
if (!type.includes("application/json")) {
  const body = await res.text();
  throw new Error(`Expected JSON, got ${type}: ${body.slice(0, 200)}`);
}
const data = await res.json();

Perhatikan bahwa Response.json() tidak menerima reviver. Jika Anda membutuhkannya, gunakan res.text() diikuti dengan JSON.parse.

Pustaka juga berbeda dalam hal ini — beberapa di antaranya mengurai secara otomatis dan melempar pengecualian pada status selain 2xx, yang mengubah tempat penanganan kesalahan Anda. Kami membandingkan perilakunya dalam axios vs fetch.

Memparsing JSON Berukuran Besar Tanpa Membuat Halaman Macet

JSON.parse bersifat sinkron dan memblokir. Di utas utama, memarsing dokumen berukuran besar akan membuat antarmuka macet selama proses tersebut berlangsung — ini adalah penyebab umum dan mudah didiagnosis dari jank.

Panduan kasar: di bawah satu megabyte, jangan dipikirkan. Antara satu hingga sepuluh megabyte, lakukan pengukuran pada perangkat target yang paling lambat. Di atas sepuluh megabyte, lakukan hal lain.

Pilihan-pilihan, dalam urutan tingkat kesulitan yang meningkat:

Pindahkan ke Web Worker. Solusi nyata yang paling sederhana. Lakukan pemrosesan di luar utas utama dan kirimkan hasilnya kembali. Perhatikan bahwa mentransfer hasilnya memiliki biaya kloning terstruktur tersendiri, sehingga cara ini paling membantu jika worker juga melakukan pemrosesan selanjutnya.

Minta data yang lebih sedikit. Paginasi, pemilihan bidang, atau endpoint yang lebih sempit. Hampir selalu merupakan jawaban yang tepat, namun sering diabaikan karena memerlukan koordinasi dengan pihak yang mengelola API.

Gunakan parser streaming. Terdapat pustaka yang mengeluarkan nilai saat data tiba, alih-alih membangun seluruh pohon data. Cara ini layak dilakukan jika dokumen benar-benar besar atau jika Anda hanya memerlukan sebagian dari muatan data.

Gunakan JSON yang dipisahkan oleh baris baru. Untuk kumpulan data yang besar, satu dokumen JSON per baris jauh lebih mudah diproses secara bertahap, karena setiap baris diparsing secara independen dan aliran data yang terpotong tetap menghasilkan catatan yang lengkap. Jika Anda mengontrol formatnya, ini sering kali merupakan desain yang lebih baik — dan pertimbangan pro dan kontra dibandingkan format lain dibahas dalam perbandingan kami antara JSON dan CSV.

Kapan JSON.parse Bukanlah Alat yang Tepat

Ketika inputnya bukan JSON. JSON5, JSONC, dan berkas konfigurasi yang berisi komentar serta koma di akhir baris semuanya memerlukan parser tersendiri. JSON.parse akan menolak input tersebut dengan benar, dan Anda sebaiknya tidak mencoba menghapus komentar menggunakan ekspresi reguler — langkah tersebut akan mengarah pada parser yang sebenarnya tidak Anda maksudkan untuk ditulis.

Ketika Anda membutuhkan validasi, bukan sekadar parsing. Parsing yang berhasil hanya menunjukkan bahwa sintaksnya valid. Hal itu tidak menjamin apakah bidang-bidang yang diperlukan ada atau memiliki tipe yang benar. Lakukan parsing terlebih dahulu, lalu validasi; pustaka validasi skema adalah alat yang tepat untuk langkah kedua, sedangkan try/catch bukanlah alat yang tepat.

Ketika data harus dikirim bolak-balik tanpa kehilangan data. Bilangan bulat besar, tanggal, undefined, fungsi, Map, Set, NaN, Infinity — tidak ada yang bertahan utuh dalam JSON. Jika pengiriman bolak-balik tanpa kehilangan data merupakan persyaratan, gunakan context.source dan JSON.rawJSON secara sengaja, atau gunakan format yang dirancang untuk itu.

Saat Anda melakukan parsing pada setiap render. Memparsing string yang sama berulang kali dalam jalur yang sering diakses adalah pemborosan belaka. Lakukan parsing sekali dan simpan dalam cache.

Ketika string tersebut berasal dari permintaan yang belum Anda periksa. Ini adalah poin yang kami bahas di awal, diulang karena ini yang paling umum terjadi. Jika JSON.parse melempar pengecualian pada data yang diambil, bugnya ada di hulu. Periksa kode status, periksa tipe konten, catat isi data. Parser tersebut memberi tahu Anda kebenarannya.

Pertanyaan Terkait

Apa fungsi JSON.parse?

Fungsi ini mengubah string berformat JSON menjadi nilai JavaScript — objek, array, string, bilangan, boolean, atau null. Fungsi ini menerima fungsi reviver opsional yang dapat mengubah setiap nilai saat diparsing. Fungsi ini akan melemparkan kesalahan "SyntaxError" jika inputnya bukan JSON yang valid.

Mengapa JSON.parse menampilkan pesan "Unexpected token '<'"?

Karena string tersebut dimulai dengan <, yang berarti Anda menerima HTML alih-alih JSON — biasanya halaman kesalahan, pengalihan login, atau halaman pemblokiran. Parser tersebut berfungsi dengan benar; masalahnya ada pada permintaan. Catat 200 karakter pertama dari isi respons, dan penyebabnya biasanya akan jelas.

Bagaimana cara memparsing JSON yang berisi angka besar di JavaScript?

Gunakan argumen context.source pada reviver untuk membaca digit aslinya dan membangun objek BigInt, karena saat nilai tersebut sampai ke reviver, presisinya sudah hilang. Lebih baik lagi, jika Anda mengontrol API-nya, serialisasikan pengenal yang besar sebagai string.

Apa itu fungsi reviver di JSON.parse?

Argumen kedua opsional yang dipanggil untuk setiap pasangan kunci-nilai, secara mendalam (depth-first), berakhir pada akar di bawah kunci string kosong. Apa pun yang dikembalikannya akan menggantikan nilai tersebut; mengembalikundefinedakan menghapus properti tersebut. Tugas umumnya adalah mengonversi string menjadi tipe data yang lebih kaya seperti Date.

Apakah JSON.parse aman?

Terhadap eksekusi kode, ya — tidak seperti eval, fungsi ini tidak pernah mengeksekusi apa pun. Fungsi ini juga menangani __proto__ dengan aman, dengan membuat properti sendiri yang sederhana alih-alih mengatur prototipe. Risikonya terletak pada apa yang Anda lakukan setelahnya: menggabungkan data yang telah diparsing dan tidak terpercaya ke dalam objek lain tanpa menyaring __proto__ dan constructor adalah cara terjadinya polusi prototipe.

Apa perbedaan antara JSON.parse dan Response.json()?

Response.json() membaca isi respons fetch dan mem-parse-nya dalam satu langkah, serta tidak menerima reviver. JSON.parse bekerja pada string yang sudah Anda miliki. Perhatikan bahwa fetch tidak akan menolak jika terjadi kesalahan HTTP, jadi periksa res.ok sebelum memanggil .json() atau Anda akan mem-parse halaman kesalahan.

Apakah JSON.parse dapat menangani komentar atau koma di akhir?

Tidak. Keduanya merupakan JSON yang tidak valid dan keduanya akan memicu pengecualian SyntaxError. Jika input Anda mengandung keduanya, itu adalah JSON5 atau JSONC, dan memerlukan parser untuk format tersebut, bukan ekspresi reguler yang menghapusnya.

Apakah JSON.parse memblokir utas utama?

Ya, fungsi ini bersifat sinkron. Untuk dokumen berukuran di bawah satu megabyte, hal ini tidak menjadi masalah; namun, untuk muatan data yang besar, hal ini dapat menyebabkan pembekuan yang terlihat. Pindahkan proses parsing ke Web Worker, minta data dalam jumlah yang lebih sedikit, atau gunakan parser streaming.

Kesimpulan: `

`JSON.parse`` memiliki tanda tangan dua parameter dan kedalaman yang cukup mengejutkan di baliknya. Bagian-bagian yang patut diperhatikan adalah yang mengalami kegagalan secara diam-diam, bukan yang menimbulkan kesalahan yang mencolok.

Ketepatan bilangan adalah masalah paling serius: bilangan bulat besar rusak tanpa peringatan, tidak ada pengecualian yang dilemparkan, dan nilai yang salah tidak dapat dibedakan dari yang benar hingga terjadi kegagalan di tahap selanjutnya. Solusinya adalah menggunakan pengenal yang dikodekan sebagai string di sumber kode atau argumen context.source pada reviver, yang kini berada di tahap 4 dan menjadi bagian dari standar.

Fungsi reviver layak digunakan lebih sering daripada yang terjadi saat ini, terutama untuk tanggal, dengan catatan bahwa lupa mengembalikvalue pada jalur default akan menghapus properti secara diam-diam. Dan polusi prototipe sama sekali bukan masalah JSON.parse — fungsi tersebut menangani __proto__ dengan benar — tetapi ini menjadi masalah bagi apa pun yang menggabungkan hasilnya, yang cukup dekat untuk menjadi masalah.

Semua hal lainnya dapat diringkas menjadi satu kebiasaan: ketika parsing gagal pada data yang Anda ambil, catat isi mentah (raw body) sebelum mengubah kode apa pun. Pesan kesalahan hampir selalu berisi jawabannya, dan biasanya jawabannya adalah Anda sebenarnya tidak pernah menerima JSON sejak awal.