# JSON di Python: membaca dan menulis berkas, CSV, pathlib, dan encoding UTF-8

URL: https://skillaria.top/id/guides/python/json-python-berkas

Untuk bekerja dengan JSON di Python tersedia modul bawaan json: json.load membaca data dari berkas yang sudah dibuka, json.dump menulisnya ke berkas, sedangkan json.loads dan json.dumps melakukan hal yang sama pada string. Berkas dibuka dengan with open(path, encoding="utf-8"), path dibangun dengan pathlib.Path, dan tabel dibaca dengan modul csv.

Bekerja dengan JSON di Python tidak memerlukan pustaka tambahan: modul `json` sudah termasuk dalam pustaka standar dan mengubah dict serta list menjadi teks dan sebaliknya. Di sampingnya hampir selalu dibutuhkan tiga hal lain: fungsi `open` dengan konstruksi `with`, modul `pathlib` untuk path, dan modul `csv` untuk tabel. Berikut semuanya secara berurutan, dengan kode yang bisa dijalankan dan jebakan yang paling sering menjerat.

## Fungsi open di Python: mode dan encoding

Setiap berkas teks dibuka dengan fungsi bawaan `open(path, mode, encoding=...)`. Ia mengembalikan objek berkas yang punya metode `read`, `readline`, `write`, dan bisa ditelusuri baris demi baris dengan perulangan.

| Mode | Fungsi |
| --- | --- |

| `"r"` | Membaca (bawaan). Jika berkas tidak ada — `FileNotFoundError` |

| `"w"` | Menulis. Berkas dibuat atau **langsung dikosongkan saat dibuka** |

| `"a"` | Menambah di akhir, isi lama tidak diubah |

| `"x"` | Membuat berkas baru; jika sudah ada — `FileExistsError` |

| `"b"` | Tambahan untuk mode: bekerja dengan byte (`"rb"`, `"wb"`) |

### Untuk apa with

Berkas harus ditutup: jika tidak, data mungkin belum tertulis ke disk, dan sistem tetap menahan deskriptornya. Konstruksi `with` menutup berkas secara otomatis — bahkan jika di dalam blok terjadi error.

```
with open("notes.txt", "w", encoding="utf-8") as f:
    f.write("Baris pertama\n")
    f.write("Baris kedua\n")

with open("notes.txt", encoding="utf-8") as f:
    for line in f:
        print(line.rstrip("\n"))
```

Perulangan pada berkas membaca satu baris setiap kali dan tidak memuat seluruh isinya ke memori, sehingga log berukuran gigabyte pun bisa diproses. Metode `f.read()` mengembalikan seluruh isi sebagai satu string, dan `f.readlines()` mengembalikan list baris.

### Cara menulis baris ke berkas tanpa menghapus isi lama

Kesalahan paling umum pemula adalah membuka berkas dengan mode `"w"` untuk «menambahkan» satu baris. Mode `"w"` mengosongkan berkas saat dibuka, sebelum `write` pertama. Untuk menambah di akhir, gunakan `"a"`, dan untuk memastikan tidak menimpa berkas orang lain, gunakan `"x"`.

```
with open("log.txt", "a", encoding="utf-8") as f:
    f.write("entri baru\n")
```

Perhatikan bahwa `write` tidak menambahkan pindah baris sendiri — `"\n"` harus ditulis secara eksplisit. Anda juga bisa memakai `print(..., file=f)`, yang otomatis menambahkan pindah baris.

## Encoding UTF-8: mengapa muncul karakter aneh

Sebelum Python 3.15, `open()` tanpa parameter `encoding` memakai encoding sistem. Di Linux dan macOS itu UTF-8, sedangkan di Windows dengan locale Rusia adalah cp1251. Skrip yang berjalan baik di server bisa menampilkan «Р•РґР°» alih-alih «Еда» di laptop rekan, atau berakhir dengan `UnicodeDecodeError`.

Di Python 3.15, menurut PEP 686, mode UTF-8 aktif secara bawaan: `open()` tanpa `encoding` membaca dan menulis UTF-8 di semua OS. Perilaku lama bisa dikembalikan dengan variabel lingkungan `PYTHONUTF8=0` atau flag `-X utf8=0`, dan encoding sistem bisa diminta secara eksplisit dengan `encoding="locale"`.

```
import sys
print(sys.flags.utf8_mode)   # 1 di Python 3.15

open("legacy.txt", encoding="locale")   # encoding sistem
open("old_excel.csv", encoding="cp1251")  # berkas yang disimpan dalam cp1251
```

Sisi lainnya: CSV lama dari Excel atau skrip Windows yang disimpan dalam cp1251 sekarang akan dibaca sebagai UTF-8 dan memunculkan `UnicodeDecodeError`. Solusinya: baca dengan `encoding="cp1251"`, lalu simpan ulang dalam UTF-8. Kebiasaan baik tetap sama — tulis `encoding` secara eksplisit, sehingga kode berperilaku sama di versi Python apa pun.

## pathlib di Python: path sebagai objek

Menggabungkan path dengan string (`"data" + "/" + name`) merepotkan dan bisa rusak di OS yang berbeda. Modul `pathlib` menyediakan kelas `Path`, tempat path digabung dengan operator `/`, dan objeknya punya properti yang jelas.

```
from pathlib import Path

data = Path("data")
csv_path = data / "expenses.csv"

print(csv_path.name)    # expenses.csv
print(csv_path.stem)    # expenses
print(csv_path.suffix)  # .csv
print(csv_path.parent)  # data
print(csv_path.exists())

data.mkdir(parents=True, exist_ok=True)
(data / "hello.txt").write_text("Halo", encoding="utf-8")
print((data / "hello.txt").read_text(encoding="utf-8"))

for p in data.glob("*.csv"):
    print(p)
```

Metode `read_text` dan `write_text` menggantikan kombinasi `open` + `with` untuk berkas kecil. Objek `Path` bisa diberikan ke `open`, `json`, maupun `csv` — semuanya menerimanya. Path yang relatif terhadap skrip itu sendiri, bukan folder kerja, didapat dengan `Path(__file__).parent / "data"`.

Di Python 3.14, `Path` mendapat metode `copy()` dan `move()`, sehingga untuk penyalinan sederhana `shutil` tidak lagi diperlukan. Di versi yang sama, pustaka standar juga mendapat modul `compression.zstd` untuk kompresi Zstandard (PEP 784).

## JSON di Python: load, dump, loads, dumps

JSON adalah format teks untuk pertukaran data: API mengirimnya, dan konfigurasi serta laporan sering disimpan dalam bentuk ini. Modul `json` melakukan dua operasi: serialisasi (objek Python → teks JSON) dan deserialisasi (teks → objek). Ada empat fungsi, dan kebingungan antara keempatnya adalah pertanyaan yang sering muncul.

| Fungsi | Apa yang dilakukan |
| --- | --- |

| `json.dumps(obj)` | Objek → string JSON |

| `json.loads(s)` | String JSON → objek |

| `json.dump(obj, f)` | Objek → ditulis ke berkas yang sudah dibuka |

| `json.load(f)` | Membaca JSON dari berkas yang sudah dibuka |

Cara mengingatnya: huruf `s` di akhir berarti string — bekerja dengan string; tanpa `s` — bekerja dengan berkas.

### Kesesuaian tipe

| Python | JSON |
| --- | --- |

| `dict` | objek `{}` |

| `list`, `tuple` | array `[]` |

| `str` | string |

| `int`, `float` | angka |

| `True` / `False` | `true` / `false` |

| `None` | `null` |

Tuple akan menjadi list saat dibaca kembali. Set, tanggal, dan `Decimal` tidak dikenali oleh modul ini dan akan menghasilkan `TypeError: Object of type ... is not JSON serializable`.

### Cara menyimpan JSON ke berkas

```
import json
from pathlib import Path

report = {
    "month": "2026-10",
    "total": 15230.5,
    "categories": {"makan": 8200, "transportasi": 2100},
    "tags": ["rumah", "kerja"],
}

path = Path("data/report.json")
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("w", encoding="utf-8") as f:
    json.dump(report, f, ensure_ascii=False, indent=2)
```

Parameter `ensure_ascii=False` menyimpan huruf non-ASCII apa adanya. Tanpa itu, «makan» di berkas akan berupa escape `\u` — itu JSON yang valid, tetapi tidak nyaman dibaca manusia. `indent=2` membuat format rapi dengan indentasi.

### Cara membaca JSON dari berkas

```
with open("data/report.json", encoding="utf-8") as f:
    data = json.load(f)

print(data["total"])               # 15230.5
print(data["categories"]["makan"]) # 8200
```

Hasilnya adalah dict atau list biasa, dan Anda bisa bekerja dengannya seperti biasa. Jika berkas bukan JSON atau terpotong, akan muncul exception `json.JSONDecodeError` dengan nomor baris dan posisi — sebaiknya ditangkap ketika data datang dari luar.

### Parsing string JSON dan tampilan rapi

```
import json

raw = '{"name": "Rina", "skills": ["python", "sql"], "active": true}'
user = json.loads(raw)
print(user["skills"][0])   # python

print(json.dumps(user, ensure_ascii=False, indent=2, sort_keys=True))
```

`sort_keys=True` mengurutkan kunci saat ditampilkan — berguna agar perbedaan berkas mudah dibandingkan di git. Untuk merapikan berkas dari terminal ada utilitas bawaan: `python -m json.tool data/report.json`.

Respons HTTP API juga berupa string JSON. Pustaka `requests` menguraikannya dengan metode `response.json()`, dan di dalamnya itu tetap `json.loads`.

### Jebakan: kunci JSON selalu string

```
d = {1: "januari", 2: "februari"}
back = json.loads(json.dumps(d))
print(back == d)   # False
print(back)        # {'1': 'januari', '2': 'februari'}
# back[1] -> KeyError, harus back["1"]

fixed = {int(k): v for k, v in back.items()}
```

Dalam JSON, kunci objek hanya boleh berupa string, sehingga kunci angka berubah menjadi string setelah melewati serialisasi. Kembalikan tipenya saat membaca.

### Tipe sendiri: tanggal dan Decimal

```
import json
from datetime import date

def default(obj):
    if isinstance(obj, date):
        return obj.isoformat()
    raise TypeError(f"tidak bisa menserialisasi {type(obj).__name__}")

print(json.dumps({"day": date(2026, 10, 1)}, default=default))
# {"day": "2026-10-01"}
```

Fungsi di parameter `default` dipanggil untuk setiap objek yang tidak dikenali. Untuk kasus yang lebih rumit, Anda bisa mewarisi `json.JSONEncoder` dan menimpa metode `default`.

### Cara menambah elemen ke array JSON

JSON tidak bisa ditambahi di akhir seperti log teks: berkas harus dibaca, diubah, lalu ditulis ulang secara utuh.

```
path = Path("data/items.json")
items = json.loads(path.read_text(encoding="utf-8")) if path.exists() else []
items.append({"id": len(items) + 1, "title": "baru"})
path.write_text(json.dumps(items, ensure_ascii=False, indent=2), encoding="utf-8")
```

Jika datanya banyak dan terus bertambah, lebih nyaman memakai format JSON Lines: satu objek JSON per baris, dan berkasnya dibuka dengan mode `"a"`.

## CSV di Python: modul csv

CSV adalah tabel dalam berkas teks: baris adalah catatan, nilainya dipisahkan koma. Modul `csv` menangani tanda kutip, koma di dalam nilai, dan pindah baris dengan benar — hal yang tidak bisa dijamin oleh `split(",")` buatan sendiri.

```
import csv

with open("data/expenses.csv", newline="", encoding="utf-8") as f:
    reader = csv.DictReader(f)
    total = 0.0
    for row in reader:
        total += float(row["amount"])
        print(row["date"], row["category"], row["amount"])
print("Total:", total)
```

`csv.DictReader` mengubah setiap baris menjadi dict dengan nama kolom dari header berkas. Semua nilainya berupa string: angka dan tanggal harus diubah sendiri (`float`, `int`, `date.fromisoformat`). Berkas dengan pemisah «;» (format yang dihasilkan Excel versi Rusia) dibaca dengan parameter `delimiter=";"`.

```
rows = [
    {"date": "2026-10-01", "category": "makan", "amount": 450},
    {"date": "2026-10-02", "category": "transportasi", "amount": 60},
]
with open("data/out.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.DictWriter(f, fieldnames=["date", "category", "amount"])
    writer.writeheader()
    writer.writerows(rows)
```

Saat menulis, `newline=""` wajib: tanpanya di Windows akan muncul baris kosong di antara baris data. Jika berkas akan dibuka di Excel, gunakan `encoding="utf-8-sig"` — penanda BOM membantu Excel mengenali UTF-8.

## Dari CSV ke JSON: contoh lengkap

```
import csv
import json
from collections import defaultdict
from pathlib import Path

src = Path("data/expenses.csv")
dst = Path("data/report.json")

by_category = defaultdict(float)
with src.open(newline="", encoding="utf-8") as f:
    for row in csv.DictReader(f):
        by_category[row["category"]] += float(row["amount"])

report = {"total": sum(by_category.values()), "categories": dict(by_category)}
dst.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
print(dst.read_text(encoding="utf-8"))
```

Ini inti dari setiap skrip laporan: masuk dari CSV, agregasi di dalam dict, keluar ke JSON.

## Kesalahan umum

- `FileNotFoundError` — path relatif dihitung dari folder tempat program dijalankan, bukan dari skrip. Gunakan `Path(__file__).parent`.
- Isi berkas hilang — berkas dibuka dengan `"w"` padahal seharusnya `"a"`.
- `UnicodeDecodeError` — berkas memakai encoding lain; tentukan secara eksplisit (`cp1251`, `utf-8-sig`).
- `TypeError: Object of type set is not JSON serializable` — ubah ke list, atau berikan `default`.
- `KeyError` setelah memuat JSON — kunci angka sudah menjadi string.
- Berkas sendiri dinamai `json.py` atau `csv.py` — ia menutupi modul standar, dan `import json` akan mengimpor berkas itu.

## Ringkasan

Untuk JSON di Python cukup pustaka standar: `json.load`/`json.dump` untuk berkas, `loads`/`dumps` untuk string, dengan `ensure_ascii=False` dan `indent` untuk keluaran yang mudah dibaca. Buka berkas dengan `with` dan encoding yang eksplisit, bangun path dengan `pathlib`, dan baca tabel dengan `csv.DictReader`. Topik ini dipraktikkan dalam kursus Python: pencatat pengeluaran membaca data dari CSV dan menyimpan laporan ke JSON.

## FAQ

**Apa bedanya json.load dan json.loads?**

json.load membaca JSON dari objek berkas yang sudah dibuka, sedangkan json.loads mengurai string. Demikian juga json.dump menulis ke berkas, dan json.dumps mengembalikan string.

**Bagaimana menyimpan huruf non-ASCII di JSON tanpa escape \u?**

Berikan ensure_ascii=False ke json.dump atau json.dumps, dan buka berkasnya dengan encoding="utf-8".

**Perlukah memasang pustaka khusus untuk JSON di Python?**

Tidak. Modul json sudah ada di pustaka standar, cukup tulis import json.

**Bisakah JSON menyimpan tanggal atau set?**

Tidak langsung. Keduanya harus diubah dulu: tanggal menjadi string ISO dengan isoformat, set menjadi list; atau berikan fungsi ke parameter default.

**Bagaimana membaca JSON yang terlalu besar untuk memori?**

json.load membaca seluruh berkas sekaligus. Untuk data besar, simpan dalam format JSON Lines dan urai baris demi baris dengan json.loads.


---
Source: Skillaria.Top (https://skillaria.top)
