CSV dan Excel memaksa data hidup di dalam kisi baris-kolom, padahal data di dunia nyata sering kali lebih kaya daripada itu: satu catatan cuaca menyimpan suhu pagi dan siang sekaligus, satu respons API memuat daftar pesanan yang masing-masing berisi daftar barang. Format JSON (JavaScript Object Notation) menjawab kebutuhan itu dengan struktur kunci-nilai yang boleh saling bercabang, dan karenanya menjadi bahasa pertukaran data paling umum di web — respons API, berkas konfigurasi, hingga ekspor dari basis data NoSQL. Pada chapter ini kita membaca cuaca.json bertingkat dengan modul json bawaan Python, meratakannya menjadi tabel, lalu meringkas pekerjaan itu dengan pandas.

Anatomi Data JSON #

JSON hanya mengenal enam jenis nilai, dan kelimanya punya pasangan langsung di Python:

Jenis di JSONContohPadanan Python
object{"kota": "Bandung"}dict
array["cerah", "berawan"]list
string"Bandung"str
number26.0int / float
booleantrue / falseTrue / False
nullnullNone

Struktur bertingkat terbentuk karena nilai boleh berupa object atau array lagi — di sinilah letak kekuatan sekaligus tantangannya: baris data tidak lagi rata, melainkan menempel di dalam cabang-cabang. Berkas latihan kita memperlihatkan pola umum JSON dari layanan cuaca:

json
[
  {
    "kota": "Bandung",
    "tanggal": "2026-01-15",
    "cuaca": "cerah",
    "kelembapan": 78,
    "suhu": {"pagi": 19.5, "siang": 26.0}
  },
  {
    "kota": "Bandung",
    "tanggal": "2026-01-16",
    "cuaca": "berawan",
    "kelembapan": 82,
    "suhu": {"pagi": 20.1, "siang": 24.8}
  },
  {
    "kota": "Surabaya",
    "tanggal": "2026-01-15",
    "cuaca": "cerah",
    "kelembapan": 71,
    "suhu": {"pagi": 26.3, "siang": 31.5}
  },
  {
    "kota": "Surabaya",
    "tanggal": "2026-01-16",
    "cuaca": "hujan",
    "kelembapan": 88,
    "suhu": {"pagi": 25.9, "siang": 29.4}
  }
]

Berkas ini berupa array berisi empat object; setiap object punya lima kunci, dan nilai pada kunci suhu bukan angka melainkan object kecil berisi dua angka. Ketika nanti kita ingin membandingkan suhu siang antar kota, nilai itu harus kita telusuri satu tingkat lebih dalam.

Membaca JSON dengan Modul json #

Modul json adalah bagian dari pustaka standar Python — tidak ada yang perlu dipasang. Fungsi utamanya dua: json.load() membaca dari berkas terbuka, sedangkan json.loads() membaca dari string (namanya singkatan load string). Keduanya menghasilkan struktur Python biasa: dict, list, str, float. Dokumentasi lengkapnya ada di dokumentasi resmi modul json .

python
import json

with open("cuaca.json", encoding="utf-8") as f:
    cuaca = json.load(f)

print("Jumlah catatan:", len(cuaca))
print("Kunci catatan pertama:", list(cuaca[0]))
print("Kota:", cuaca[0]["kota"])
print("Suhu siang Bandung 15 Jan:", cuaca[0]["suhu"]["siang"], "derajat")
print("Kota pada catatan:", [c["kota"] for c in cuaca])

Output yang Dihasilkan:

text
Jumlah catatan: 4
Kunci catatan pertama: ['kota', 'tanggal', 'cuaca', 'kelembapan', 'suhu']
Kota: Bandung
Suhu siang Bandung 15 Jan: 26.0 derajat
Kota pada catatan: ['Bandung', 'Bandung', 'Surabaya', 'Surabaya']

Mari kita telaah baris-baris pentingnya:

  1. Baris 3-4 (open + json.load): berkas dibuka biasa dengan with, lalu json.load(f) mengubah seluruh isi berkas menjadi list of dict. Mulai titik ini kita bekerja dengan struktur Python murni — tidak ada lagi ketergantungan pada format berkas.
  2. Baris 6 (len(cuaca)): cuaca adalah list, jadi len() menghitung jumlah catatan langsung.
  3. Baris 8 (cuaca[0]["suhu"]["siang"]): akses bertingkat dibaca dari dalam ke luar — ambil catatan pertama, ambil nilai kunci "suhu" (sebuah dict), lalu ambil nilai kunci "siang" di dalamnya.
  4. Baris 9 (list comprehension): pola [c["kota"] for c in cuaca] menelusuri seluruh catatan dan mengumpulkan satu kunci dari masing-masing — idiom yang akan sering kita pakai saat menjelajahi JSON.

Meratakan JSON Bertingkat Menjadi Tabel #

Struktur bertingkat enak dibaca manusia, tetapi analisis tabular — rata-rata, pengelompokan, penggabungan — membutuhkan baris yang rata. Langkah awalnya sederhana: telusuri setiap catatan, ambil nilainya termasuk yang bersarang, dan susun ulang sebagai baris baru dengan nama kolom datar:

python
import json

with open("cuaca.json", encoding="utf-8") as f:
    cuaca = json.load(f)

baris = []
for catatan in cuaca:
    baris.append({
        "kota": catatan["kota"],
        "tanggal": catatan["tanggal"],
        "cuaca": catatan["cuaca"],
        "suhu_pagi": catatan["suhu"]["pagi"],
        "suhu_siang": catatan["suhu"]["siang"],
    })

for b in baris:
    print(b["tanggal"], b["kota"], b["cuaca"], b["suhu_pagi"], b["suhu_siang"])

rata_siang = sum(b["suhu_siang"] for b in baris) / len(baris)
print("Rata-rata suhu siang:", rata_siang)

Output yang Dihasilkan:

text
2026-01-15 Bandung cerah 19.5 26.0
2026-01-16 Bandung berawan 20.1 24.8
2026-01-15 Surabaya cerah 26.3 31.5
2026-01-16 Surabaya hujan 25.9 29.4
Rata-rata suhu siang: 27.925

Mari kita telaah baris-baris pentingnya:

  1. Baris 8-15 (loop per catatan): setiap catatan adalah dict; dict baru dibangun dengan lima kunci datar. Nilai suhu_pagi dan suhu_siang diambil dari dalam object suhu — tanda kurung siku dua tingkat seperti pada bagian sebelumnya.
  2. Pemisahan nama (suhu_pagi, bukan pagi): nama kolom datar sebaiknya menyimpan asal-usul nilainya agar tabel hasil tetap bermakna berdiri sendiri.
  3. Baris 18-19 (agregasi): begitu data rata, sum() dan len() bekerja tanpa upaya tambahan — pembandingan antarkota pun tinggal menunggu groupby yang kita kenal di tutorial pandas .

Meringkas Pekerjaan dengan pandas #

Meratakan manual seperti di atas memberi kendali penuh, tetapi untuk pola umum “array of objects dengan object bersarang”, pandas sudah menyediakan pd.json_normalize(): sekali panggil, object bersarang dipecah menjadi kolom bernama induk.anak.

python
import json
import pandas as pd

with open("cuaca.json", encoding="utf-8") as f:
    cuaca = json.load(f)

df = pd.json_normalize(cuaca)
print(df)
print()
print(df.dtypes)

Output yang Dihasilkan:

text
       kota     tanggal    cuaca  kelembapan  suhu.pagi  suhu.siang
0   Bandung  2026-01-15    cerah          78       19.5        26.0
1   Bandung  2026-01-16  berawan          82       20.1        24.8
2  Surabaya  2026-01-15    cerah          71       26.3        31.5
3  Surabaya  2026-01-16    hujan          88       25.9        29.4

kota           object
tanggal        object
cuaca          object
kelembapan      int64
suhu.pagi     float64
suhu.siang    float64
dtype: object

Mari kita telaah baris-baris pentingnya:

  1. Baris 9 (pd.json_normalize(cuaca)): menerima list of dict dan menghasilkan DataFrame. Kolom dari object suhu muncul sebagai suhu.pagi dan suhu.siang — nama cabangnya dirangkai dengan titik.
  2. Hasil dtypes: kolom bertingkat terdeteksi float64 dan kelembapan terdeteksi int64 — tipe numerik ditebak benar karena nilainya memang angka di seluruh catatan.
  3. Kolom bertitik dalam praktik: akses kolomnya memakai kurung siku, misalnya df["suhu.siang"], karena nama bertitik bukan nama identifier yang sah untuk df.suhu.siang.

Galat Umum Saat Membaca JSON #

Dua galat paling sering menyambut pembaca JSON. Pertama, berkas atau teks yang bentuknya tidak sah — koma tertinggal, tanda kutip tidak tertutup — membuat json.loads() menghentikan pembacaan dan melempar json.JSONDecodeError. Kedua, kunci yang kita minta tidak ada pada catatan tertentu, yang muncul sebagai KeyError biasa. Keduanya sebaiknya ditangkap agar pesan galatnya dapat dicatat rapi:

python
import json

teks_valid = '{"kota": "Bandung", "suhu": 26.0}'
data = json.loads(teks_valid)
print("Muat berhasil:", data["kota"], data["suhu"])

teks_rusak = '{"kota": "Bandung", "suhu": }'
try:
    json.loads(teks_rusak)
except json.JSONDecodeError as e:
    print("Galat tertangkap:", e)

kunci = "suhu_pagi"
try:
    nilai = data[kunci]
except KeyError:
    print(f"Kunci '{kunci}' tidak ditemukan; kunci yang ada: {sorted(data)}")

Output yang Dihasilkan:

text
Muat berhasil: Bandung 26.0
Galat tertangkap: Expecting value: line 1 column 29 (char 28)
Kunci 'suhu_pagi' tidak ditemukan; kunci yang ada: ['kota', 'suhu']

Mari kita telaah baris-baris pentingnya:

  1. Baris 9-12 (JSONDecodeError): pesan galatnya informatif — ia menyebut posisi persis masalahnya (kolom 29, karakter ke-28), tepat di tempat nilai suhu seharusnya muncul sebelum }. Posisi ini mempercepat perbaikan berkas besar.
  2. Baris 15-18 (KeyError): pola kunci hilang lazim pada JSON nyata karena catatan lama sering tidak memuat kunci baru yang ditambahkan penyedia data. Mencetak sorted(data) menunjukkan kunci yang benar-benar tersedia.
  3. f"..." dengan {kunci}: nama kunci dimasukkan ke pesan agar log galat bisa dibaca tanpa harus membuka kode.

Arah sebaliknya sama pentingnya: menyimpan hasil analisis agar dapat dibaca program lain. Perannya dipegang json.dumps() (dump string), dengan dua pilihan yang layak jadi kebiasaan — indent=2 agar berkas enak dibaca manusia, dan ensure_ascii=False agar teks non-Inggris (misalnya nama kota dengan tanda baca apa pun) tetap tersimpan sebagai mana ditulis:

python
import json

ringkasan = {
    "kota": "Bandung",
    "periode": {"mulai": "2026-01-15", "akhir": "2026-01-16"},
    "rata_kelembapan": 80.0,
    "kondisi": ["cerah", "berawan"],
}

teks_json = json.dumps(ringkasan, indent=2, ensure_ascii=False)
print(teks_json)

kembali = json.loads(teks_json)
print("Rata-rata kelembapan dari hasil baca ulang:", kembali["rata_kelembapan"])

Output yang Dihasilkan:

text
{
  "kota": "Bandung",
  "periode": {
    "mulai": "2026-01-15",
    "akhir": "2026-01-16"
  },
  "rata_kelembapan": 80.0,
  "kondisi": [
    "cerah",
    "berawan"
  ]
}
Rata-rata kelembapan dari hasil baca ulang: 80.0

Mari kita telaah baris-baris pentingnya:

  1. Baris 9 (json.dumps(...)): dict Python diubah kembali menjadi teks JSON; object bersarang periode dan array kondisi ditulis dengan takarir dua spasi berkat indent=2.
  2. Baris 12 (json.loads(teks_json)): putaran penuh — data Python menjadi teks JSON lalu dibaca lagi menjadi data Python tanpa perubahan nilai. Pola uji kecil ini layak diterapkan sebelum hasil analisis disimpan ke berkas sungguhan dengan json.dump(ringkasan, f) (tanpa s).

Rangkuman #

  • JSON memakai enam jenis nilai dengan padanan langsung di Python: object menjadi dict, array menjadi list, dan seterusnya; struktur bertingkat terbentuk karena nilai boleh berisi object atau array lagi.
  • json.load() membaca dari berkas dan json.loads() dari string; keduanya menghasilkan struktur Python biasa yang diakses dengan [...] bertingkat.
  • Analisis tabular menuntut baris yang rata: ratakan manual lewat loop, atau ringkas dengan pd.json_normalize() yang memecah object bersarang menjadi kolom induk.anak.
  • Galat khas pembacaan adalah JSONDecodeError untuk teks tidak sah dan KeyError untuk kunci yang absen; tangkap keduanya, dan pakai .get() untuk catatan yang tidak seragam.
  • json.dumps() menuliskan kembali ke JSON — selalu dengan indent agar terbaca dan ensure_ascii=False agar teks asli tersimpan apa adanya.

Sejauh ini sumber data kita masih berupa berkas yang sudah tersedia di komputer. JSON justru memainkan peran terbesarnya saat data bergerak: layanan API di seluruh web mengirim respons dalam format ini. Pada chapter berikutnya, Pengambilan Data dari API , kita mengirim permintaan ke server, membaca respons JSON-nya, dan mengubahnya menjadi data siap analisis.