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 JSONTipe PythonCatatan
objectdictkunci object selalu string
arraylistboleh campur tipe apa pun
stringstrselalu UTF-8, penggunaan tanda kutip ganda
numberint atau floatJSON tidak membedakan bilangan bulat dan desimal
true / falseboolditulis huruf kecil semua di JSON
nullNonerepresentasi 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:

python
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:

text
{
  "id": 5,
  "kasir": "Siti",
  "produk": "Café Latte",
  "qty": 1,
  "harga": 32000.0,
  "lunas": true,
  "catatan": null
}
dict Café Latte True None

Mari kita telaah baris-baris kode di atas:

  1. Baris 3-11 (transaksi = {...}): satu dictionary memuat seluruh jenis tipe — int, str, float, bool, dan None.
  2. Baris 13 (json.dumps(..., indent=2, ensure_ascii=False)): indent=2 menambah baris dan spasi agar hasil mudah dibaca manusia; ensure_ascii=False membuat karakter non-ASCII (huruf é pada Café) tetap ditulis apa adanya.
  3. Baris 16 (json.loads(teks)): mem-parse kembali string JSON menjadi objek Python asli — bukti proses bolak-balik tidak merusak data.
  4. Baris 17 (print(...)): hasil cetak menunjukkan True dan None kembali menjadi nilai Python, bukan true dan null versi 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:

python
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:

text
{"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: 105000

Dua 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:

python
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:

text
key duplikat: {'kasir': 'Siti'}
NaN ditulis: {"diskon": NaN}
ValueError: Out of range float values are not JSON compliant: nan

JSON 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.