Hampir semua API modern bertukar pesan lewat JSON (JavaScript Object Notation): respons REST, pesan webhook, hingga berkas konfigurasi aplikasi. Format berbasis teks ini mudah dibaca manusia dan dipahami mesin, sehingga menjadi bahasa pengantar default antar sistem yang berbeda bahasa pemrograman. Meski terlihat mirip literal Python, JSON punya aturan tipe sendiri dan beberapa pinggiran yang mengejutkan — key duplikat yang diam-diam tertimpa dan nilai NaN yang sebenarnya tidak sah. Chapter ini memakai data transaksi toko yang sama dengan chapter CSV agar perbandingannya terasa menyatu.
Tipe Data JSON dan Pemetaannya ke Python #
Spesifikasi di json.org hanya mengenal enam jenis nilai: object, array, string, number, boolean, dan null. Pemetaannya ke Python cukup rapat, dengan beberapa catatan penting:
| Tipe JSON | Tipe Python | Catatan |
|---|---|---|
| object | dict | kunci object selalu string |
| array | list | boleh campur tipe apa pun |
| string | str | selalu UTF-8, penggunaan tanda kutip ganda |
| number | int atau float | JSON tidak membedakan bilangan bulat dan desimal |
true / false | bool | ditulis huruf kecil semua di JSON |
null | None | representasi nilai kosong |
Dua konsekuensi praktis dari tabel ini: kunci dictionary yang berupa angka harus dikonversi ke string saat diserialisasi, dan JSON tidak punya jenis timestamp — tanggal lazim direpresentasikan sebagai string ISO 8601 seperti "2026-01-15T08:30:00Z".
Serialisasi dan Deserialisasi dengan json.dumps dan json.loads #
Module json
menyediakan dua fungsi utama: json.dumps() mengubah objek Python menjadi string JSON (serialisasi), sedangkan json.loads() mengembalikan string JSON menjadi objek Python (deserialisasi). Berikut satu transaksi lengkap dengan berbagai tipe:
import json
transaksi = {
"id": 5,
"kasir": "Siti",
"produk": "Café Latte",
"qty": 1,
"harga": 32000.0,
"lunas": True,
"catatan": None,
}
teks = json.dumps(transaksi, indent=2, ensure_ascii=False)
print(teks)
kembali = json.loads(teks)
print(type(kembali).__name__, kembali["produk"], kembali["lunas"], kembali["catatan"])Output yang Dihasilkan:
{
"id": 5,
"kasir": "Siti",
"produk": "Café Latte",
"qty": 1,
"harga": 32000.0,
"lunas": true,
"catatan": null
}
dict Café Latte True NoneMari kita telaah baris-baris kode di atas:
- Baris 3-11 (
transaksi = {...}): satu dictionary memuat seluruh jenis tipe —int,str,float,bool, danNone. - Baris 13 (
json.dumps(..., indent=2, ensure_ascii=False)):indent=2menambah baris dan spasi agar hasil mudah dibaca manusia;ensure_ascii=Falsemembuat karakter non-ASCII (hurufépadaCafé) tetap ditulis apa adanya. - Baris 16 (
json.loads(teks)): mem-parse kembali string JSON menjadi objek Python asli — bukti proses bolak-balik tidak merusak data. - Baris 17 (
print(...)): hasil cetak menunjukkanTruedanNonekembali menjadi nilai Python, bukantruedannullversi JSON.
ensure_ascii dan Struktur Nested #
Secara bawaan json.dumps() mengubah karakter non-ASCII menjadi urutan escape seperti \u00e9 — hasilnya tetap JSON yang sah, tetapi lebih sulit dibaca. Parameter ensure_ascii=False mematikan perilaku tersebut. JSON juga mendukung struktur bersarang: object di dalam array, dan sebaliknya, cocok untuk struk belanja yang memuat banyak item:
import json
print(json.dumps({"produk": "Café Latte"}))
print(json.dumps({"produk": "Café Latte"}, ensure_ascii=False))
struk = {
"id": 5,
"kasir": "Siti",
"items": [
{"produk": "Kopi Robusta", "qty": 2, "harga": 45000},
{"produk": "Teh Melati", "qty": 1, "harga": 15000},
],
}
teks_struk = json.dumps(struk, ensure_ascii=False)
print(teks_struk)
print("total:", sum(i["qty"] * i["harga"] for i in json.loads(teks_struk)["items"]))Output yang Dihasilkan:
{"produk": "Caf\u00e9 Latte"}
{"produk": "Café Latte"}
{"id": 5, "kasir": "Siti", "items": [{"produk": "Kopi Robusta", "qty": 2, "harga": 45000}, {"produk": "Teh Melati", "qty": 1, "harga": 15000}]}
total: 105000Dua baris print pertama memperlihatkan perbedaan escape secara langsung. Struktur nested pada struk diserialisasi menjadi satu baris padat karena tanpa indent — bentuk ringkas inilah yang biasa dikirim antar sistem, sementara indent dipakai untuk berkas yang dibaca manusia.
Jebakan: Key Duplikat dan NaN #
Dua pinggiran JSON ini sering mengundang galat diam-diam. Pertama, spesifikasi tidak melarang key duplikat pada object, tetapi json.loads() hanya menyimpan kemunculan terakhir. Kedua, json.dumps() bawaan bersedia menulis NaN — padahal NaN bukan nilai yang sah dalam JSON, sehingga parser ketat di bahasa lain akan menolaknya:
import json
hasil = json.loads('{"kasir": "Budi", "kasir": "Siti"}')
print("key duplikat:", hasil)
print("NaN ditulis:", json.dumps({"diskon": float("nan")}))
try:
json.dumps({"diskon": float("nan")}, allow_nan=False)
except ValueError as galat:
print("ValueError:", galat)Output yang Dihasilkan:
key duplikat: {'kasir': 'Siti'}
NaN ditulis: {"diskon": NaN}
ValueError: Out of range float values are not JSON compliant: nanJSON murni berbentuk satu dokumen utuh — satu tanda kurung kurawal yang salah membuat seluruh dokumen gagal diparse. Karakteristik tersebut menjadi masalah saat data mengalir terus-menerus, dan itulah alasan lahirnya JSON Lines.