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 JSON | Contoh | Padanan Python |
|---|---|---|
| object | {"kota": "Bandung"} | dict |
| array | ["cerah", "berawan"] | list |
| string | "Bandung" | str |
| number | 26.0 | int / float |
| boolean | true / false | True / False |
| null | null | None |
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:
[
{
"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
.
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:
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:
- Baris 3-4 (
open+json.load): berkas dibuka biasa denganwith, lalujson.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. - Baris 6 (
len(cuaca)):cuacaadalah list, jadilen()menghitung jumlah catatan langsung. - 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. - 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:
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:
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.925Mari kita telaah baris-baris pentingnya:
- Baris 8-15 (loop per catatan): setiap
catatanadalah dict; dict baru dibangun dengan lima kunci datar. Nilaisuhu_pagidansuhu_siangdiambil dari dalam objectsuhu— tanda kurung siku dua tingkat seperti pada bagian sebelumnya. - Pemisahan nama (
suhu_pagi, bukanpagi): nama kolom datar sebaiknya menyimpan asal-usul nilainya agar tabel hasil tetap bermakna berdiri sendiri. - Baris 18-19 (agregasi): begitu data rata,
sum()danlen()bekerja tanpa upaya tambahan — pembandingan antarkota pun tinggal menunggugroupbyyang 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.
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:
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: objectMari kita telaah baris-baris pentingnya:
- Baris 9 (
pd.json_normalize(cuaca)): menerima list of dict dan menghasilkan DataFrame. Kolom dari objectsuhumuncul sebagaisuhu.pagidansuhu.siang— nama cabangnya dirangkai dengan titik. - Hasil
dtypes: kolom bertingkat terdeteksifloat64dankelembapanterdeteksiint64— tipe numerik ditebak benar karena nilainya memang angka di seluruh catatan. - Kolom bertitik dalam praktik: akses kolomnya memakai kurung siku, misalnya
df["suhu.siang"], karena nama bertitik bukan nama identifier yang sah untukdf.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:
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:
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:
- Baris 9-12 (
JSONDecodeError): pesan galatnya informatif — ia menyebut posisi persis masalahnya (kolom 29, karakter ke-28), tepat di tempat nilaisuhuseharusnya muncul sebelum}. Posisi ini mempercepat perbaikan berkas besar. - Baris 15-18 (
KeyError): pola kunci hilang lazim pada JSON nyata karena catatan lama sering tidak memuat kunci baru yang ditambahkan penyedia data. Mencetaksorted(data)menunjukkan kunci yang benar-benar tersedia. f"..."dengan{kunci}: nama kunci dimasukkan ke pesan agar log galat bisa dibaca tanpa harus membuka kode.
Menulis JSON dari Python #
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:
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:
{
"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.0Mari kita telaah baris-baris pentingnya:
- Baris 9 (
json.dumps(...)): dict Python diubah kembali menjadi teks JSON; object bersarangperiodedan arraykondisiditulis dengan takarir dua spasi berkatindent=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 denganjson.dump(ringkasan, f)(tanpas).
Rangkuman #
- JSON memakai enam jenis nilai dengan padanan langsung di Python: object menjadi
dict, array menjadilist, dan seterusnya; struktur bertingkat terbentuk karena nilai boleh berisi object atau array lagi. json.load()membaca dari berkas danjson.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 kolominduk.anak. - Galat khas pembacaan adalah
JSONDecodeErroruntuk teks tidak sah danKeyErroruntuk kunci yang absen; tangkap keduanya, dan pakai.get()untuk catatan yang tidak seragam. json.dumps()menuliskan kembali ke JSON — selalu denganindentagar terbaca danensure_ascii=Falseagar 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.