Meratakan JSON Bersarang di pandas dengan json_normalize
Blog/Tips & Trik/Meratakan JSON Bersarang di pandas dengan json_normalize

Meratakan JSON Bersarang di pandas dengan json_normalize

BimaBima
·24 September 2025·9 menit baca

Penulis

Bima

Bima

Founder & Data Professional

Bagikan

TL;DR

pd.json_normalize() adalah fungsi pandas yang ngubah JSON bersarang jadi DataFrame datar, dengan nama kolom gabungan kayak pelanggan.kota. Buat list di dalam tiap record, pakai record_path supaya tiap elemen jadi barisnya sendiri, lalu meta buat ngebawa kolom induk kayak order_id ikut turun. Fungsi ini jauh lebih cepat dan lebih rapi daripada nulis loop manual buat ngeratain data API.

pd.json_normalize() adalah fungsi pandas yang ngubah JSON bersarang jadi DataFrame datar, satu baris kode, tanpa loop.

Kamu bakal butuh ini tiap kali narik data dari API. Data transaksi dari sistem kasir, data order dari marketplace, data survei dari Google Form lewat API, semuanya datang dalam bentuk objek di dalam objek.

Di bawah ini cara pakainya, dari yang paling sederhana sampai kasus list di dalam tiap order.

Kenapa JSON dari API susah langsung dianalisis?

JSON menyimpan data secara bertingkat: satu order bisa punya objek pelanggan di dalamnya, plus list item belanjaan. pandas butuh tabel datar, satu baris satu observasi, satu kolom satu variabel. Kalau kamu langsung pakai pd.DataFrame(), isi bertingkat itu tetap nyangkut di dalam sel sebagai dictionary dan nggak bisa difilter.

Contoh datanya, tiga order dari toko_berkah:

import pandas as pd

pesanan = [
    {
        "order_id": "TB-1001",
        "tanggal": "2025-09-18",
        "pelanggan": {"nama": "Rina", "kota": "Bandung", "tier": "reguler"},
        "items": [
            {"sku": "BRK-001", "qty": 2, "harga": 79000},
            {"sku": "BRK-014", "qty": 1, "harga": 25000},
        ],
    },
    {
        "order_id": "TB-1002",
        "tanggal": "2025-09-18",
        "pelanggan": {"nama": "Doni", "kota": "Surabaya", "tier": "member"},
        "items": [
            {"sku": "BRK-027", "qty": 3, "harga": 42000},
        ],
    },
    {
        "order_id": "TB-1003",
        "tanggal": "2025-09-19",
        "pelanggan": {"nama": "Sari", "kota": "Bandung"},
        "items": [
            {"sku": "BRK-001", "qty": 1, "harga": 79000},
            {"sku": "BRK-027", "qty": 2, "harga": 42000},
        ],
    },
]

df_salah = pd.DataFrame(pesanan)
print(df_salah.columns.tolist())
# ['order_id', 'tanggal', 'pelanggan', 'items']

Kolom pelanggan isinya dictionary utuh. Mau filter pelanggan dari Bandung? Nggak bisa langsung.

Gimana cara meratakan objek bersarang jadi kolom?

Panggil pd.json_normalize() ke list of dict-nya. Setiap kunci di dalam objek bersarang bakal jadi kolom baru dengan nama gabungan, dipisah titik. pelanggan.kota, pelanggan.tier, dan seterusnya. Kolom yang isinya list tetap dibiarkan berupa list.

df = pd.json_normalize(pesanan)
print(df.columns.tolist())
# ['order_id', 'tanggal', 'items', 'pelanggan.nama',
#  'pelanggan.kota', 'pelanggan.tier']

print(df[["order_id", "pelanggan.kota", "pelanggan.tier"]])
  order_id pelanggan.kota pelanggan.tier
0  TB-1001        Bandung        reguler
1  TB-1002       Surabaya         member
2  TB-1003        Bandung            NaN

Order ketiga nggak punya tier, jadi diisi NaN. json_normalize otomatis nyamain kolom antar record.

Titik di nama kolom agak repot dipakai, soalnya df.pelanggan.kota nggak jalan. Ganti pemisahnya:

df = pd.json_normalize(pesanan, sep="_")
# ['order_id', 'tanggal', 'items', 'pelanggan_nama',
#  'pelanggan_kota', 'pelanggan_tier']

Gimana cara mecah list item jadi baris sendiri?

Pakai parameter record_path buat nunjuk nama kunci yang isinya list. Tiap elemen list bakal jadi satu baris DataFrame. Supaya kolom induk kayak order_id ikut turun ke tiap baris, sebutin di parameter meta.

items = pd.json_normalize(
    pesanan,
    record_path="items",
    meta=["order_id", "tanggal", ["pelanggan", "kota"]],
    errors="ignore",
)
print(items)
       sku  qty  harga order_id     tanggal pelanggan.kota
0  BRK-001    2  79000  TB-1001  2025-09-18        Bandung
1  BRK-014    1  25000  TB-1001  2025-09-18        Bandung
2  BRK-027    3  42000  TB-1002  2025-09-18       Surabaya
3  BRK-001    1  79000  TB-1003  2025-09-19        Bandung
4  BRK-027    2  42000  TB-1003  2025-09-19        Bandung

Tiga hal yang perlu kamu inget di sini:

  1. record_path nerima string buat satu level, atau list kalau listnya ada di dalam objek lain, misalnya ["detail", "items"].
  2. meta nerima nama kolom biasa, dan list kecil buat kunci bersarang. ["pelanggan", "kota"] artinya ambil kota dari dalam objek pelanggan.
  3. errors="ignore" bikin record yang nggak punya kunci meta diisi NaN, bukan bikin prosesnya berhenti.

Kalau nama kolom di dalam item bentrok sama nama kolom induk, pakai prefix:

items = pd.json_normalize(
    pesanan,
    record_path="items",
    meta=["order_id"],
    record_prefix="item_",
)
# kolom jadi: item_sku, item_qty, item_harga, order_id

Dokumentasi lengkap semua parameternya ada di halaman resmi pandas.

Gimana kalau JSON-nya bersarang lebih dari dua tingkat?

Batasi kedalamannya pakai max_level. Nilai 0 berarti cuma level paling atas yang diratakan, level di bawahnya tetap disimpan sebagai dictionary. Ini berguna kalau kamu cuma butuh sebagian, dan nggak mau DataFrame kamu tiba-tiba punya 140 kolom.

datar = pd.json_normalize(pesanan, max_level=0)
satu_level = pd.json_normalize(pesanan, max_level=1)

Kebiasaan yang aku pakai: ratakan semuanya dulu, lihat daftar kolomnya, baru pilih yang kepakai. Sisanya di-drop.

kolom_pakai = ["order_id", "tanggal", "pelanggan_kota", "pelanggan_tier"]
df = pd.json_normalize(pesanan, sep="_")[kolom_pakai]

Contoh kasus: hitung ulang penjualan toko_berkah per kota

Data mentah dari API kasir toko_berkah bulan September: 612 order, semuanya dalam bentuk JSON bersarang persis kayak contoh di atas. Setelah diratakan pakai record_path, hasilnya 1.842 baris item.

items = pd.json_normalize(
    pesanan_september,
    record_path="items",
    meta=["order_id", "tanggal", ["pelanggan", "kota"]],
    errors="ignore",
    sep="_",
)

items["subtotal"] = items["qty"] * items["harga"]

per_kota = (
    items.groupby("pelanggan_kota")
    .agg(
        omzet=("subtotal", "sum"),
        jumlah_item=("qty", "sum"),
        jumlah_order=("order_id", "nunique"),
    )
    .sort_values("omzet", ascending=False)
)

per_kota["nilai_per_order"] = (per_kota["omzet"] / per_kota["jumlah_order"]).round(0)
print(per_kota)

Temuan dari dataset latihan ngulikdata: Bandung nyumbang omzet paling besar, Rp84,2 juta, tapi nilai per order-nya cuma Rp121.000. Surabaya omzetnya separuh, Rp41,7 juta, dengan nilai per order Rp198.000.

Jadi pembeli Surabaya belanja 1,6 kali lebih besar tiap kali checkout. Insight ini nggak kelihatan sama sekali selama datanya masih berbentuk JSON bersarang.

Ada 27 baris item yang harganya kosong, semuanya dari order yang dibatalkan. Aku buang duluan sebelum agregasi, dan ini bagian dari pengecekan kualitas data yang harusnya jalan otomatis tiap kali data baru masuk.

Kesalahan umum waktu pakai json_normalize

KesalahanGejalanyaPerbaikannya
Masukin dict tunggal, bukan listCuma dapat satu barisBungkus jadi list atau ambil kunci datanya dulu
Lupa errors="ignore"KeyError di tengah prosesTambahin argumennya kalau field-nya opsional
Pakai meta tanpa record_pathArgumen meta diabaikanmeta cuma berlaku bareng record_path
Nama kolom bentrokKolom ketimpa diam-diamPakai record_prefix atau meta_prefix
Ngeratain semua level tanpa dicekDataFrame ratusan kolomBatasi pakai max_level, lalu pilih kolomnya

Yang nomor satu paling sering kejadian. Response API biasanya berbentuk {"data": [...], "meta": {...}}, jadi yang kamu masukin harus response["data"], bukan seluruh responsenya. Aku bahas struktur response API-nya di panduan ambil data REST API dengan Python.

FAQ

Apa bedanya pd.DataFrame() dan pd.json_normalize()?

pd.DataFrame() nyalin isi JSON apa adanya, jadi objek bersarang tetap nempel di dalam sel sebagai dictionary. pd.json_normalize() ngebongkar objek itu jadi kolom terpisah dengan nama gabungan kayak pelanggan.kota. Kalau JSON kamu sudah datar, dua-duanya kasih hasil sama. Begitu ada satu objek di dalamnya, json_normalize yang kamu butuhin.

Kapan pakai record_path dan kapan pakai explode?

Pakai record_path kalau kamu langsung ngolah JSON mentah dan pengin tiap elemen list jadi baris sendiri sejak awal. Pakai explode kalau datanya udah masuk DataFrame dan ada satu kolom berisi list. Hasil akhirnya mirip, tapi record_path sekalian ngeratain isi tiap elemennya, sedangkan explode cuma mecah listnya.

Kenapa muncul error KeyError waktu pakai meta?

Artinya ada record yang nggak punya kunci yang kamu sebut di meta. Ini biasa terjadi kalau API kamu ngirim field opsional. Tambahin errors="ignore" supaya baris yang nggak punya kunci itu diisi NaN, bukan bikin seluruh proses berhenti. Habis itu cek berapa banyak yang kosong pakai isna().sum().

Gimana kalau nama kolomnya jadi kepanjangan?

Ganti pemisahnya pakai sep, misalnya sep="_" supaya jadi pelanggan_kota. Kalau bersarangnya dalam banget, batasi kedalaman pakai max_level=1 supaya level lebih dalam tetap disimpan sebagai dictionary. Habis itu rename kolom yang kepakai aja, sisanya drop.

Bisa langsung dari file JSON besar?

Baca dulu isinya pakai json.load(), baru masukin ke json_normalize. Buat file yang isinya satu objek JSON per baris, pakai pd.read_json dengan lines=True lalu normalize kolom bersarangnya. Kalau filenya di atas beberapa ratus MB, proses per potongan biar memori nggak jebol.

Pola yang bisa kamu pakai ulang

Dua baris ini nutup 90 persen kebutuhan: pd.json_normalize(data, sep="_") buat objek bersarang, dan tambahan record_path plus meta buat list di dalam record.

Sisanya cuma milih kolom mana yang kepakai.

Coba sekarang: ambil satu response API yang pernah bikin kamu pusing, ratakan, lalu hitung satu agregasi sederhana. Lanjutan buat ngolah hasilnya ada di panduan pandas groupby agg.

Coba Langsung

Mau praktek langsung? Mulai latihan SQL gratis

Latihan interaktif, langsung di browser.

Buka NgulikSQL →
Bagikan:
Bima
Ditulis oleh

Bima

Founder & Data Professional

Founder Ngulik Data. Passionate about making data analysis accessible for everyone.

Artikel terkait

Pandas groupby: Agregasi Data ala Pivot di Python (2026)
Tips & Trik
20 Juli 2026•9 menit baca

Pandas groupby: Agregasi Data ala Pivot di Python (2026)

groupby ngelompokin baris berdasarkan kolom, lalu ngitung ringkasan per grup kayak total atau rata-rata. Ini pivot-nya pandas.

BimaBima
Pandas to_excel: Ekspor DataFrame ke Excel (2026)
Tips & Trik
18 Juli 2026•8 menit baca

Pandas to_excel: Ekspor DataFrame ke Excel (2026)

to_excel nyimpen DataFrame pandas jadi file Excel .xlsx. Ini cara pakainya, dari satu sheet sampai banyak sheet, plus setelan yang bikin hasilnya rapi.

BimaBima
Pandas read_excel: Baca File Excel di Python
Tips & Trik
16 Juli 2026•8 menit baca

Pandas read_excel: Baca File Excel di Python

pandas read_excel baca file Excel jadi DataFrame, bisa pilih sheet tertentu dan lompatin baris judul yang berantakan. Ini cara pakai plus parameter penting dengan contoh data toko.

BimaBima
Kembali ke Blog
Ngulik Data logoNgulik Data

Platform edukasi data lengkap untuk professionals Indonesia. Belajar SQL, Data Analysis, dan lebih banyak lagi dengan praktek langsung dan feedback real-time.

© 2026 Ngulik Data. Semua hak dilindungi.

TAUTAN
BantuanHargaDatasetBlogAfiliasi
LEGAL
Syarat & KetentuanKebijakan Privasi
Ngulik Data
DatasetLeaderboardBlogStore