1. Pengenalan JSON
JSON (JavaScript Object Notation) adalah format pertukaran data yang ringan dan mudah dibaca. JSON menjadi standar de facto untuk komunikasi data antara client-server, API, dan konfigurasi aplikasi.
JSON vs Python Data Types
| JSON Type | Contoh JSON | Python Type |
|---|---|---|
| Object | {"key": "value"} | dict |
| Array | [1, 2, 3] | list |
| String | "hello" | str |
| Number (int) | 42 | int |
| Number (float) | 3.14 | float |
| Boolean | true / false | True / False |
| Null | null | None |
Mengapa JSON Penting?
| Keunggulan | Penjelasan |
|---|---|
| Universal | Didukung oleh hampir semua bahasa pemrograman |
| Human-readable | Format teks yang mudah dibaca manusia |
| Lightweight | Lebih ringan dibanding XML |
| API Standard | Format default untuk REST API dan web services |
| Nested Support | Bisa mengandung objek bersarang dan array |
┌──────────────────────────────────────────────────────┐
│ JSON ↔ PYTHON CONVERSION │
│ │
│ JSON (string) Python (objects) │
│ ──────────── ──────────────── │
│ '{"name":"Budi"}' ←→ {"name": "Budi"} │
│ '[1, 2, 3]' ←→ [1, 2, 3] │
│ 'true' ←→ True │
│ 'null' ←→ None │
│ '3.14' ←→ 3.14 │
│ │
│ json.dumps() = Python → JSON string │
│ json.loads() = JSON string → Python │
│ json.dump() = Python → JSON file │
│ json.load() = JSON file → Python │
└──────────────────────────────────────────────────────┘
2. Modul json Dasar
Python memiliki modul json built-in yang menyediakan semua fungsi yang dibutuhkan untuk bekerja dengan data JSON.
import json
# ===== json.dumps(): Python → JSON string =====
data = {
"name": "Budi Santoso",
"age": 25,
"is_student": True,
"courses": ["Python", "JavaScript", "SQL"],
"address": None
}
json_string = json.dumps(data)
print(json_string)
# {"name": "Budi Santoso", "age": 25, "is_student": true,
# "courses": ["Python", "JavaScript", "SQL"], "address": null}
print(type(json_string)) # <class 'str'>
# ===== json.loads(): JSON string → Python =====
json_text = '{"name": "Sari", "age": 30, "scores": [85, 90, 78]}'
python_data = json.loads(json_text)
print(python_data)
# {'name': 'Sari', 'age': 30, 'scores': [85, 90, 78]}
print(type(python_data)) # <class 'dict'>
print(python_data["name"]) # Sari
# ===== Konversi tipe data =====
# dict → JSON string
d = {"key": "value"}
s = json.dumps(d)
# list → JSON string
lst = [1, 2, 3, "hello", True, None]
s = json.dumps(lst)
print(s) # [1, 2, 3, "hello", true, null]
# JSON string → dict
d = json.loads('{"a": 1, "b": 2}')
# JSON string → list
lst = json.loads('[1, 2, 3]')
# Nested structures
nested = json.dumps({
"user": {
"name": "Budi",
"contacts": [
{"type": "email", "value": "budi@email.com"},
{"type": "phone", "value": "+62812345678"}
]
}
})
print(json.dumps(json.loads(nested), indent=2))
3. Serialization (Python → JSON)
Serialization adalah proses mengkonversi objek Python ke format JSON string. Ada beberapa parameter yang bisa dikustomisasi.
import json
data = {
"name": "Budi",
"age": 25,
"scores": [85, 90, 78],
"active": True,
"address": None
}
# ===== indent: Pretty print =====
print(json.dumps(data, indent=2))
# {
# "name": "Budi",
# "age": 25,
# "scores": [85, 90, 78],
# "active": true,
# "address": null
# }
# Custom indent
print(json.dumps(data, indent=4))
# ===== separators: Custom delimiters =====
# Default: (', ', ': ')
# Compact:
print(json.dumps(data, separators=(',', ':')))
# {"name":"Budi","age":25,...}
# ===== sort_keys: Urutkan keys =====
print(json.dumps(data, indent=2, sort_keys=True))
# Keys diurutkan secara alfabetis
# ===== ensure_ascii: Non-ASCII characters =====
data_indo = {"kota": "Jakarta", "provinsi": "DKI Jakarta"}
# Default (True): escape non-ASCII
print(json.dumps(data_indo))
# {"kota": "Jakarta", "provinsi": "DKI Jakarta"}
# False: biarkan karakter asli
print(json.dumps(data_indo, ensure_ascii=False))
# {"kota": "Jakarta", "provinsi": "DKI Jakarta"}
# ===== default: Handle non-serializable objects =====
from datetime import datetime
from pathlib import Path
data_with_special = {
"created": datetime.now(),
"path": Path("/home/user"),
"set_data": {1, 2, 3},
}
def custom_serializer(obj):
"""Handler untuk objek yang tidak bisa di-serialize."""
if isinstance(obj, datetime):
return obj.isoformat()
elif isinstance(obj, Path):
return str(obj)
elif isinstance(obj, set):
return list(obj)
raise TypeError(f"Type {type(obj)} not serializable")
result = json.dumps(data_with_special, default=custom_serializer, indent=2)
print(result)
# ===== allow_nan: Handle NaN, Infinity =====
import math
data_special = {"value": float('nan'), "inf": float('inf')}
# Default: allow_nan=True → NaN dan Infinity diizinkan
print(json.dumps(data_special)) # {"value": NaN, "inf": Infinity}
# False: raise ValueError untuk NaN/Infinity
try:
json.dumps(data_special, allow_nan=False)
except ValueError as e:
print(f"Error: {e}")
4. Deserialization (JSON → Python)
Deserialization adalah proses mengkonversi JSON string kembali ke objek Python.
import json
# ===== Basic deserialization =====
json_str = '{"name": "Sari", "age": 30, "hobbies": ["coding", "reading"]}'
data = json.loads(json_str)
print(f"Name: {data['name']}")
print(f"Hobbies: {data['hobbies']}")
# ===== object_hook: Custom object creation =====
class User:
def __init__(self, name, age, email):
self.name = name
self.age = age
self.email = email
def __repr__(self):
return f"User(name='{self.name}', age={self.age})"
def as_user(dct):
"""Convert dict to User object."""
if 'name' in dct and 'age' in dct:
return User(dct['name'], dct['age'], dct.get('email', ''))
return dct
json_str = '{"name": "Budi", "age": 25, "email": "budi@mail.com"}'
user = json.loads(json_str, object_hook=as_user)
print(f"User: {user}") # User(name='Budi', age=25)
print(f"Name: {user.name}") # Budi
# ===== object_pairs_hook: Preserve key order =====
def ordered_pairs(pairs):
"""Convert to OrderedDict preserving order."""
from collections import OrderedDict
return OrderedDict(pairs)
json_str = '{"z": 1, "a": 2, "m": 3}'
ordered = json.loads(json_str, object_pairs_hook=ordered_pairs)
print(f"Ordered: {ordered}") # OrderedDict([('z', 1), ('a', 2), ('m', 3)])
# ===== parse_float dan parse_int =====
from decimal import Decimal
json_str = '{"price": 19.99, "quantity": 100}'
# Parse float as Decimal (untuk presisi tinggi)
data = json.loads(json_str, parse_float=Decimal)
print(f"Price: {data['price']}") # 19.99 (Decimal)
print(f"Type: {type(data['price'])}") # <class 'decimal.Decimal'>
# Parse int as string (untuk bilangan sangat besar)
json_big = '{"huge_number": 123456789012345678901234567890}'
data = json.loads(json_big, parse_int=str)
print(f"Huge: {data['huge_number']}") # String representation
# ===== Parse multiple JSON objects =====
json_lines = '{"name": "Budi"}\n{"name": "Sari"}\n{"name": "Andi"}'
objects = [json.loads(line) for line in json_lines.strip().split('\n')]
print(f"Found {len(objects)} objects")
for obj in objects:
print(f" {obj['name']}")
5. Custom JSON Encoder
Untuk menangani tipe data yang tidak bisa di-serialize secara default (datetime, Decimal, custom classes), kita membuat custom encoder.
import json
from datetime import datetime, date, time
from decimal import Decimal
from pathlib import Path
from uuid import UUID
from dataclasses import dataclass, asdict
from typing import Any
from enum import Enum
class Status(Enum):
ACTIVE = "active"
INACTIVE = "inactive"
PENDING = "pending"
class AdvancedJSONEncoder(json.JSONEncoder):
"""Custom encoder untuk menangani tipe data Python lanjutan."""
def default(self, obj: Any) -> Any:
# datetime dan date
if isinstance(obj, datetime):
return {
"_type": "datetime",
"value": obj.isoformat()
}
if isinstance(obj, date):
return {
"_type": "date",
"value": obj.isoformat()
}
if isinstance(obj, time):
return {
"_type": "time",
"value": obj.isoformat()
}
# Decimal
if isinstance(obj, Decimal):
return float(obj)
# Path
if isinstance(obj, Path):
return str(obj)
# UUID
if isinstance(obj, UUID):
return str(obj)
# Set
if isinstance(obj, set):
return sorted(list(obj))
# Enum
if isinstance(obj, Enum):
return obj.value
# Bytes
if isinstance(obj, bytes):
return obj.decode('utf-8', errors='replace')
# Fallback ke default encoder
return super().default(obj)
# Menggunakan custom encoder
data = {
"timestamp": datetime.now(),
"date": date.today(),
"price": Decimal("19.99"),
"path": Path("/home/user/file.txt"),
"status": Status.ACTIVE,
"tags": {"python", "json", "tutorial"},
}
result = json.dumps(data, cls=AdvancedJSONEncoder, indent=2, ensure_ascii=False)
print(result)
# Dataclass support
@dataclass
class Product:
id: int
name: str
price: float
created: datetime
tags: list
product = Product(
id=1,
name="Python Course",
price=49.99,
created=datetime.now(),
tags=["python", "programming"]
)
# Dataclass → dict → JSON
product_json = json.dumps(asdict(product), cls=AdvancedJSONEncoder, indent=2)
print(product_json)
Alternative Encoder Approaches
import json
from datetime import datetime
from decimal import Decimal
from typing import Any
# ===== Approach 1: default function (tanpa subclass) =====
def json_default(obj: Any) -> Any:
if isinstance(obj, datetime):
return obj.isoformat()
if isinstance(obj, Decimal):
return float(obj)
if isinstance(obj, set):
return list(obj)
raise TypeError(f"Object of type {type(obj)} is not JSON serializable")
data = {"time": datetime.now(), "amount": Decimal("99.99")}
print(json.dumps(data, default=json_default))
# ===== Approach 2: __json__ protocol =====
class JSONSerializable:
"""Mixin yang menambahkan __json__ method."""
def to_json(self) -> dict:
"""Override this method to define JSON representation."""
raise NotImplementedError
@classmethod
def from_json(cls, data: dict):
"""Override this method to create from JSON."""
raise NotImplementedError
class Employee(JSONSerializable):
def __init__(self, name: str, department: str, salary: Decimal):
self.name = name
self.department = department
self.salary = salary
def to_json(self) -> dict:
return {
"name": self.name,
"department": self.department,
"salary": float(self.salary),
}
@classmethod
def from_json(cls, data: dict):
return cls(
name=data["name"],
department=data["department"],
salary=Decimal(str(data["salary"])),
)
def smart_default(obj):
if hasattr(obj, 'to_json'):
return obj.to_json()
if isinstance(obj, datetime):
return obj.isoformat()
raise TypeError(f"Type {type(obj)} not serializable")
emp = Employee("Budi", "Engineering", Decimal("8500000"))
print(json.dumps(emp, default=smart_default, indent=2))
6. Custom JSON Decoder
import json
from datetime import datetime, date
from decimal import Decimal
# ===== Custom decoder dengan object_hook =====
def decode_special_types(dct):
"""Decode tipe data khusus yang di-encode oleh AdvancedJSONEncoder."""
if "_type" in dct:
type_name = dct["_type"]
value = dct["value"]
if type_name == "datetime":
return datetime.fromisoformat(value)
elif type_name == "date":
return date.fromisoformat(value)
return dct
json_data = '{"timestamp": {"_type": "datetime", "value": "2026-06-26T10:30:00"}}'
result = json.loads(json_data, object_hook=decode_special_types)
print(f"Type: {type(result['timestamp'])}") # datetime
print(f"Value: {result['timestamp']}")
# ===== Full roundtrip: Encode → Decode =====
# Encode
original = {
"event": "Konferensi Python Indonesia",
"date": datetime(2026, 8, 15, 9, 0),
"price": Decimal("250000"),
"attendees": {"Budi", "Sari", "Andi"},
}
from pathlib import Path
import json
class FullEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return {"__datetime__": True, "value": obj.isoformat()}
if isinstance(obj, Decimal):
return {"__decimal__": True, "value": str(obj)}
if isinstance(obj, set):
return {"__set__": True, "value": list(obj)}
return super().default(obj)
def full_decoder(dct):
if "__datetime__" in dct:
return datetime.fromisoformat(dct["value"])
if "__decimal__" in dct:
return Decimal(dct["value"])
if "__set__" in dct:
return set(dct["value"])
return dct
# Encode → Decode
encoded = json.dumps(original, cls=FullEncoder, ensure_ascii=False)
decoded = json.loads(encoded, object_hook=full_decoder)
print(f"Event: {decoded['event']}")
print(f"Date type: {type(decoded['date'])}") # datetime
print(f"Price type: {type(decoded['price'])}") # Decimal
print(f"Attendees type: {type(decoded['attendees'])}") # set
print(f"Perfect roundtrip: {original == decoded}") # True
7. Baca/Tulis JSON File
import json
from pathlib import Path
# ===== json.dump(): Python → File =====
config = {
"app_name": "MyApp",
"version": "2.1.0",
"database": {
"host": "localhost",
"port": 5432,
"name": "myapp_db"
},
"features": {
"dark_mode": True,
"notifications": True,
"max_upload_mb": 50
}
}
# Tulis ke file
with open("config.json", "w", encoding="utf-8") as f:
json.dump(config, f, indent=2, ensure_ascii=False)
# Dengan pathlib
config_path = Path("config.json")
config_path.write_text(json.dumps(config, indent=2), encoding="utf-8")
# ===== json.load(): File → Python =====
# Baca dari file
with open("config.json", "r", encoding="utf-8") as f:
loaded_config = json.load(f)
print(f"App: {loaded_config['app_name']}")
print(f"DB Host: {loaded_config['database']['host']}")
# Dengan pathlib
config_data = json.loads(config_path.read_text(encoding="utf-8"))
# ===== Read JSON Lines (JSONL) =====
jsonl_path = Path("data.jsonl")
# Write JSONL
with open(jsonl_path, "w") as f:
for i in range(5):
record = {"id": i, "name": f"User-{i}", "score": i * 10}
f.write(json.dumps(record) + "\n")
# Read JSONL
records = []
with open(jsonl_path, "r") as f:
for line in f:
line = line.strip()
if line:
records.append(json.loads(line))
print(f"Loaded {len(records)} records")
# ===== JSONL dengan pathlib =====
records = [
json.loads(line)
for line in jsonl_path.read_text().splitlines()
if line.strip()
]
# ===== Append ke JSON file (manual) =====
# JSON file tidak mendukung append secara native
# Tapi bisa dilakukan dengan load-modify-save
def append_to_json_array(filepath: str, new_item: dict):
"""Menambahkan item ke array dalam JSON file."""
path = Path(filepath)
if path.exists():
data = json.loads(path.read_text())
else:
data = []
data.append(new_item)
path.write_text(json.dumps(data, indent=2))
append_to_json_array("users.json", {"name": "Budi", "age": 25})
append_to_json_array("users.json", {"name": "Sari", "age": 30})
8. JSON dengan REST API
import json
import urllib.request
import urllib.parse
from typing import Dict, Any
# ===== GET Request dengan JSON response =====
def api_get(url: str) -> Dict[str, Any]:
"""Melakukan GET request dan parse JSON response."""
req = urllib.request.Request(url)
req.add_header("Accept", "application/json")
with urllib.request.urlopen(req) as response:
data = json.loads(response.read().decode("utf-8"))
return data
# Contoh penggunaan
# users = api_get("https://jsonplaceholder.typicode.com/users")
# for user in users[:3]:
# print(f" {user['name']} — {user['email']}")
# ===== POST Request dengan JSON body =====
def api_post(url: str, data: dict) -> Dict[str, Any]:
"""Melakukan POST request dengan JSON body."""
json_data = json.dumps(data).encode("utf-8")
req = urllib.request.Request(
url,
data=json_data,
method="POST"
)
req.add_header("Content-Type", "application/json")
req.add_header("Accept", "application/json")
with urllib.request.urlopen(req) as response:
return json.loads(response.read().decode("utf-8"))
# Contoh penggunaan
# new_user = api_post("https://jsonplaceholder.typicode.com/users", {
# "name": "Budi Santoso",
# "email": "budi@example.com",
# "phone": "+62812345678"
# })
# ===== JSON API Client =====
class JSONAPIClient:
"""Simple JSON API client."""
def __init__(self, base_url: str, headers: dict = None):
self.base_url = base_url.rstrip("/")
self.headers = headers or {}
self.headers.setdefault("Content-Type", "application/json")
self.headers.setdefault("Accept", "application/json")
def _request(self, method: str, endpoint: str, data: dict = None) -> dict:
url = f"{self.base_url}/{endpoint.lstrip('/')}"
body = json.dumps(data).encode("utf-8") if data else None
req = urllib.request.Request(url, data=body, method=method)
for key, value in self.headers.items():
req.add_header(key, value)
try:
with urllib.request.urlopen(req) as response:
content = response.read().decode("utf-8")
if content:
return json.loads(content)
return {}
except urllib.error.HTTPError as e:
error_body = e.read().decode("utf-8")
raise Exception(f"API Error {e.code}: {error_body}")
def get(self, endpoint: str) -> dict:
return self._request("GET", endpoint)
def post(self, endpoint: str, data: dict) -> dict:
return self._request("POST", endpoint, data)
def put(self, endpoint: str, data: dict) -> dict:
return self._request("PUT", endpoint, data)
def delete(self, endpoint: str) -> dict:
return self._request("DELETE", endpoint)
# Menggunakan client
# api = JSONAPIClient("https://api.example.com")
# users = api.get("/users")
# new_user = api.post("/users", {"name": "Budi"})
# api.put("/users/1", {"name": "Budi Updated"})
# api.delete("/users/1")
9. Pretty Print dan Formatting
import json
data = {
"users": [
{"id": 1, "name": "Budi", "scores": [85, 90, 78]},
{"id": 2, "name": "Sari", "scores": [92, 88, 95]},
],
"total": 2,
"metadata": {"page": 1, "per_page": 10}
}
# ===== Compact (tanpa spasi) =====
compact = json.dumps(data, separators=(',', ':'))
print(f"Compact: {compact[:80]}...")
print(f"Size: {len(compact)} bytes")
# ===== Standard (default) =====
standard = json.dumps(data)
print(f"Standard: {standard[:80]}...")
print(f"Size: {len(standard)} bytes")
# ===== Pretty (indent 2) =====
pretty = json.dumps(data, indent=2)
print(f"Pretty:\n{pretty}")
# ===== Custom formatting =====
def format_json(data, indent=2, max_inline=60):
"""Format JSON dengan inline array kecuali jika terlalu panjang."""
result = json.dumps(data, indent=indent, ensure_ascii=False)
return result
# ===== Colored JSON di terminal =====
def colorize_json(json_str: str) -> str:
"""Menambahkan warna ANSI ke JSON string."""
import re
# Warna untuk tipe data berbeda
json_str = re.sub(r'"([^"]+)":', r'\033[34m"\1"\033[0m:', json_str) # Keys: blue
json_str = re.sub(r': "([^"]*)"', r': \033[32m"\1"\033[0m', json_str) # Strings: green
json_str = re.sub(r': (\d+\.?\d*)', r': \033[33m\1\033[0m', json_str) # Numbers: yellow
json_str = re.sub(r': (true|false|null)', r': \033[35m\1\033[0m', json_str) # Boolean/null: magenta
return json_str
print(colorize_json(json.dumps(data, indent=2)))
# ===== JSON diff =====
def json_diff(obj1, obj2, path=""):
"""Mencari perbedaan antara dua JSON object."""
diffs = []
if isinstance(obj1, dict) and isinstance(obj2, dict):
for key in set(list(obj1.keys()) + list(obj2.keys())):
current = f"{path}.{key}" if path else key
if key not in obj1:
diffs.append(f" + {current}: {obj2[key]}")
elif key not in obj2:
diffs.append(f" - {current}: {obj1[key]}")
else:
diffs.extend(json_diff(obj1[key], obj2[key], current))
elif isinstance(obj1, list) and isinstance(obj2, list):
for i in range(max(len(obj1), len(obj2))):
current = f"{path}[{i}]"
if i >= len(obj1):
diffs.append(f" + {current}: {obj2[i]}")
elif i >= len(obj2):
diffs.append(f" - {current}: {obj1[i]}")
else:
diffs.extend(json_diff(obj1[i], obj2[i], current))
elif obj1 != obj2:
diffs.append(f" ~ {path}: {obj1} → {obj2}")
return diffs
old_data = {"name": "Budi", "age": 25, "city": "Jakarta"}
new_data = {"name": "Budi", "age": 26, "email": "budi@mail.com"}
print("\nJSON Diff:")
for diff in json_diff(old_data, new_data):
print(diff)
10. Error Handling
import json
# ===== JSONDecodeError =====
invalid_json_strings = [
'{"name": "Budi",}', # Trailing comma
"{'name': 'Budi'}", # Single quotes
'{"name": undefined}', # undefined bukan JSON
'{name: "Budi"}', # Key tanpa quotes
'{"value": 123abc}', # Invalid number
'', # Empty string
'Just plain text', # Bukan JSON
]
for s in invalid_json_strings:
try:
json.loads(s)
print(f" ✅ Valid: {s[:50]}")
except json.JSONDecodeError as e:
print(f" ❌ Error: {str(e)[:60]} | Input: {s[:40]}")
# ===== Safe JSON parsing =====
def safe_json_loads(text: str, default=None):
"""Parse JSON dengan aman, mengembalikan default jika gagal."""
try:
return json.loads(text)
except (json.JSONDecodeError, TypeError):
return default
# Test
result = safe_json_loads('{"valid": true}', default={})
print(f"Valid: {result}")
result = safe_json_loads('invalid json', default={})
print(f"Invalid: {result}")
# ===== Handle serialization errors =====
def safe_json_dumps(data, default=None, **kwargs):
"""Serialize ke JSON dengan aman."""
try:
return json.dumps(data, **kwargs)
except (TypeError, ValueError) as e:
if default is not None:
return json.dumps(default, **kwargs)
raise
# ===== Validate JSON structure =====
def validate_json_structure(data: dict, schema: dict) -> list:
"""Validasi sederhana JSON berdasarkan schema dict."""
errors = []
for key, expected_type in schema.items():
if key not in data:
errors.append(f"Missing key: '{key}'")
elif not isinstance(data[key], expected_type):
errors.append(
f"Wrong type for '{key}': "
f"expected {expected_type.__name__}, "
f"got {type(data[key]).__name__}"
)
return errors
# Test validation
schema = {
"name": str,
"age": int,
"email": str,
"scores": list,
}
test_data = {"name": "Budi", "age": "25", "scores": [90, 85]}
errors = validate_json_structure(test_data, schema)
if errors:
print(f"Validation errors:")
for err in errors:
print(f" ❌ {err}")
11. Studi Kasus
Configuration Manager
import json
from pathlib import Path
from typing import Any, Optional
from copy import deepcopy
class ConfigManager:
"""JSON-based configuration manager dengan merge dan env support."""
def __init__(self, config_path: str, defaults: dict = None):
self.config_path = Path(config_path)
self.defaults = defaults or {}
self.config = deepcopy(self.defaults)
self._load()
def _load(self):
"""Load config dari file, merge dengan defaults."""
if self.config_path.exists():
try:
file_config = json.loads(self.config_path.read_text())
self._deep_merge(self.config, file_config)
except json.JSONDecodeError as e:
print(f"⚠️ Config parse error: {e}")
def _deep_merge(self, base: dict, override: dict):
"""Deep merge dua dictionary."""
for key, value in override.items():
if key in base and isinstance(base[key], dict) and isinstance(value, dict):
self._deep_merge(base[key], value)
else:
base[key] = value
def get(self, key_path: str, default: Any = None) -> Any:
"""Akses nested config dengan dot notation."""
keys = key_path.split(".")
current = self.config
for key in keys:
if isinstance(current, dict) and key in current:
current = current[key]
else:
return default
return current
def set(self, key_path: str, value: Any):
"""Set nested config dengan dot notation."""
keys = key_path.split(".")
current = self.config
for key in keys[:-1]:
if key not in current:
current[key] = {}
current = current[key]
current[keys[-1]] = value
def save(self):
"""Simpan config ke file."""
self.config_path.parent.mkdir(parents=True, exist_ok=True)
self.config_path.write_text(
json.dumps(self.config, indent=2, ensure_ascii=False),
encoding="utf-8"
)
def __repr__(self):
return json.dumps(self.config, indent=2, ensure_ascii=False)
# Menggunakan
defaults = {
"app": {
"name": "MyApp",
"version": "1.0.0",
"debug": False,
},
"database": {
"host": "localhost",
"port": 5432,
},
"logging": {
"level": "INFO",
"file": "app.log",
}
}
config = ConfigManager("app_config.json", defaults)
print(f"App: {config.get('app.name')}")
print(f"DB Host: {config.get('database.host')}")
print(f"Debug: {config.get('app.debug')}")
config.set("app.debug", True)
config.set("database.host", "db.production.com")
config.save()
print(f"Config saved!")
JSON Data Pipeline
import json
from pathlib import Path
from datetime import datetime
from typing import List, Dict, Any
class JSONDataPipeline:
"""Pipeline untuk memproses JSON data."""
def __init__(self):
self.data: List[Dict] = []
self.transforms: List[callable] = []
self.filters: List[callable] = []
def load_json(self, filepath: str) -> 'JSONDataPipeline':
"""Load data dari JSON file."""
content = Path(filepath).read_text()
loaded = json.loads(content)
if isinstance(loaded, list):
self.data.extend(loaded)
else:
self.data.append(loaded)
return self
def load_jsonl(self, filepath: str) -> 'JSONDataPipeline':
"""Load data dari JSON Lines file."""
for line in Path(filepath).read_text().splitlines():
if line.strip():
self.data.append(json.loads(line))
return self
def filter(self, func: callable) -> 'JSONDataPipeline':
"""Tambahkan filter function."""
self.filters.append(func)
return self
def transform(self, func: callable) -> 'JSONDataPipeline':
"""Tambahkan transform function."""
self.transforms.append(func)
return self
def process(self) -> List[Dict]:
"""Jalankan pipeline."""
result = self.data
# Apply filters
for f in self.filters:
result = [item for item in result if f(item)]
# Apply transforms
for t in self.transforms:
result = [t(item) for item in result]
return result
def save_json(self, data: List[Dict], filepath: str, indent: int = 2):
"""Simpan hasil ke JSON file."""
Path(filepath).write_text(
json.dumps(data, indent=indent, ensure_ascii=False),
encoding="utf-8"
)
print(f" 💾 Saved {len(data)} records to {filepath}")
def save_jsonl(self, data: List[Dict], filepath: str):
"""Simpan hasil ke JSON Lines file."""
lines = [json.dumps(item, ensure_ascii=False) for item in data]
Path(filepath).write_text("\n".join(lines) + "\n")
print(f" 💾 Saved {len(data)} records to {filepath}")
# Menggunakan pipeline
# pipeline = JSONDataPipeline()
# result = (
# pipeline
# .load_json("users.json")
# .filter(lambda u: u.get("age", 0) >= 18)
# .filter(lambda u: u.get("active", True))
# .transform(lambda u: {**u, "processed_at": datetime.now().isoformat()})
# .transform(lambda u: {k: v for k, v in u.items() if k != "password"})
# .process()
# )
# pipeline.save_json(result, "adult_users.json")
12. Quiz: Uji Pemahamanmu!
Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut: