Method .json() terasa seperti mantra: satu panggilan, dan data API berubah menjadi dictionary Python. Mantra itu berhenti menyenangkan begitu responsnya punya lima tingkat struktur bersarang, atau begitu key yang kita harapkan ternyata tidak ada dan program berhenti dengan KeyError. Pada Chapter ini kita membedah cara membaca respons JSON secara sistematis: mengenali bentuknya, menjelajahinya, mengaksesnya dengan aman, dan menyimpannya untuk diproses kemudian.

Bentuk JSON dalam Python #

Aturan terjemahannya singkat: object JSON ({...}) menjadi dict, array JSON ([...]) menjadi list, string menjadi str, angka menjadi int atau float, dan true/false/null menjadi True/False/None. Kombinasi keduanya membentuk struktur bersarang — dictionary berisi list yang berisi dictionary lagi — persis seperti data Python yang sudah kita kenal, sehingga seluruh kemampuan indexing dan method dari Section Struktur Data Dasar langsung berlaku.

Konsekuensi praktisnya: langkah pertama membaca API baru adalah memastikan bentuk akarnya. Respons berupa [...] berarti kita mengiterasi list; respons berupa {...} berarti kita mengakses dengan key. Program yang menulis for item in respons.json() pada respons object akan gagal dengan TypeError — loop hanya berlaku untuk list.

Menjelajah Respons API Lokal #

Kita pakai server lokal yang mengirim JSON bersarang: sebuah toko berisi daftar produk, dan setiap produk punya stok yang terbagi dua lokasi. Simpan sebagai server_toko.py dan jalankan:

python
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import json

TOKO = {
    "nama": "Toko Sembako Berkah",
    "kota": "Bandung",
    "produk": [
        {
            "id": 1,
            "nama": "Beras Premium",
            "stok": {"gudang": 40, "etalase": 5},
        },
        {
            "id": 2,
            "nama": "Minyak Goreng",
            "stok": {"gudang": 25, "etalase": 8},
        },
    ],
}


class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        body = json.dumps(TOKO).encode("utf-8")
        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def log_message(self, format, *args):
        pass


ThreadingHTTPServer(("127.0.0.1", 8123), Handler).serve_forever()

Klien berikut mengambil respons, menelusuri strukturnya, mengintip satu elemen, lalu menyimpan seluruh data ke file toko.json dan membacanya kembali:

python
import json

import requests

respons = requests.get("http://127.0.0.1:8123/", timeout=3)
data = respons.json()

print(type(data))
print(data["nama"])

for produk in data["produk"]:
    total = produk["stok"]["gudang"] + produk["stok"]["etalase"]
    print(f"{produk['nama']}: total stok {total}")

print(json.dumps(data["produk"][0], indent=2, ensure_ascii=False))

kupon = data.get("promosi", {}).get("kupon", "tidak ada")
print("Kupon:", kupon)

with open("toko.json", "w", encoding="utf-8") as berkas:
    json.dump(data, berkas, indent=2, ensure_ascii=False)

with open("toko.json", encoding="utf-8") as berkas:
    data_balik = json.load(berkas)
print("Nama toko dari file:", data_balik["nama"])

Output yang Dihasilkan:

text
<class 'dict'>
Toko Sembako Berkah
Beras Premium: total stok 45
Minyak Goreng: total stok 33
{
  "id": 1,
  "nama": "Beras Premium",
  "stok": {
    "gudang": 40,
    "etalase": 5
  }
}
Kupon: tidak ada
Nama toko dari file: Toko Sembako Berkah

Mari kita telaah teknik pada tiap bagiannya:

  1. Baris 6 (data = respons.json()): Body JSON diparse menjadi dict — terlihat dari <class 'dict'> — dengan key nama, kota, dan produk.
  2. Baris 11-13 (loop data["produk"]): Value produk adalah list, sehingga kita bisa mengiterasinya; setiap elemennya dict, dan produk["stok"]["gudang"] adalah akses dua tingkat ke dictionary di dalam dictionary.
  3. Baris 15 (json.dumps(..., indent=2)): Function json.dumps() melakukan arah sebaliknya — Python ke string JSON. Parameter indent=2 merapikan menjadi multi-baris; ensure_ascii=False menjaga teks non-ASCII tetap terbaca. Mencetak satu elemen seperti ini adalah cara cepat mengintip bentuk data sebelum menulis kode akses.
  4. Baris 17 (data.get("promosi", {})): Key promosi tidak ada pada respons; .get() mengembalikan {} alih-alih menaikkan KeyError, lalu .get("kupon", "tidak ada") pada dictionary kosong menghasilkan nilai default "tidak ada".
  5. Baris 20-24 (json.dump / json.load): Pasangan function berkas menyimpan data ke toko.json lalu membacanya kembali menjadi struktur Python yang sama — round trip penuh tanpa kehilangan data.

Isi file toko.json yang tercipta:

json
{
  "nama": "Toko Sembako Berkah",
  "kota": "Bandung",
  "produk": [
    {
      "id": 1,
      "nama": "Beras Premium",
      "stok": {
        "gudang": 40,
        "etalase": 5
      }
    },
    {
      "id": 2,
      "nama": "Minyak Goreng",
      "stok": {
        "gudang": 25,
        "etalase": 8
      }
    }
  ]
}

Ketika Skema Berubah #

Dua sumber ketidakpastian selalu ada pada API nyata: field optional yang kadang hilang, dan versi endpoint yang berubah. Kombinasi type() untuk memastikan bentuk, json.dumps(indent=2) untuk mengintip isi, dan rantai .get() untuk akses yang toleran merupakan tiga alat pertahanan utama kita. Tambahkan satu kebiasaan lagi: baca dokumentasi respons dengan teliti sebelum mengakses key — dokumentasi API yang baik mencantumkan mana field yang dijamin selalu ada dan mana yang bersifat optional. Pembahasan module json yang lebih dalam — termasuk custom serialization — tersedia pada Chapter Membaca dan Menulis JSON di Section File Handling.