Python i JSON to naturalne połączenie.
Niezależnie od tego, czy budujesz REST API z FastAPI
czy Django,
przetwarzasz potoki danych, czy po prostu wczytujesz plik konfiguracyjny — z JSON-em będziesz pracować nieustannie.
Dobra wiadomość: biblioteka standardowa Pythona ma wszystko, czego potrzebujesz, w module
json.
Bez pip install.
Cztery funkcje, których naprawdę używasz
Moduł json udostępnia cztery funkcje do codziennej pracy:
json.loads(str)— parsuje łańcuch JSON do obiektu Pythonajson.dumps(obj)— konwertuje obiekt Pythona do łańcucha JSONjson.load(file)— parsuje JSON bezpośrednio z obiektu plikujson.dump(obj, file)— zapisuje obiekt Pythona jako JSON do pliku
Litera s w loads / dumps oznacza string (łańcuch).
Wersje bez s pracują z obiektami plików. Łatwe do zapamiętania, gdy znasz tę zasadę.
json.loads() — Parsowanie łańcucha JSON
import json
json_string = '{"name": "Alice", "age": 30, "active": true, "score": 98.5}'
user = json.loads(json_string)
print(user["name"]) # Alice
print(user["age"]) # 30
print(user["active"]) # True
print(type(user)) # <class 'dict'>Zwróć uwagę na mapowanie typów: JSON true staje się Pythonowym True,
JSON false staje się False, JSON null staje się None.
Obiekty JSON stają się słownikami dict, tablice JSON stają się listami list.
json.dumps() — Serializacja do łańcucha JSON
import json
user = {
"name": "Bob",
"age": 25,
"roles": ["admin", "editor"],
"active": True,
"extra": None
}
# Kompaktowy (dobry do transmisji sieciowej)
compact = json.dumps(user)
print(compact)
# {"name": "Bob", "age": 25, "roles": ["admin", "editor"], "active": true, "extra": null}
# Sformatowany (dobry do logów i czytelności)
pretty = json.dumps(user, indent=2)
print(pretty)
# {
# "name": "Bob",
# "age": 25,
# "roles": [
# "admin",
# "editor"
# ],
# "active": true,
# "extra": null
# }Zauważ odwrotne mapowanie typów: Python True → JSON true,
Python None → JSON null. Python obsługuje to automatycznie.
Wczytywanie JSON z pliku
To prawdopodobnie najczęstszy przypadek użycia — wczytanie pliku konfiguracyjnego lub pliku z danymi podczas uruchamiania:
import json
# Odczyt i parsowanie w jednym kroku
with open("config.json", "r", encoding="utf-8") as f:
config = json.load(f)
print(config["database"]["host"]) # localhost
print(config["database"]["port"]) # 5432Zawsze podawaj encoding="utf-8" przy otwieraniu plików JSON. JSON jest zdefiniowany jako UTF-8
przez RFC 8259,
a jego pominięcie może powodować problemy w systemie Windows, gdzie domyślne kodowanie to czasem cp1252.
Zapisywanie JSON do pliku
import json
results = {
"timestamp": "2024-01-15T09:30:00Z",
"total": 1523,
"processed": 1521,
"failed": 2,
"errors": [
{"id": 42, "reason": "missing field"},
{"id": 99, "reason": "invalid format"}
]
}
with open("results.json", "w", encoding="utf-8") as f:
json.dump(results, f, indent=2)
print("Results saved to results.json")Prawidłowa obsługa błędów
json.loads() zgłasza wyjątek
json.JSONDecodeError
(podklasa ValueError), gdy dane wejściowe nie są poprawnym JSON-em. Zawsze obsługuj go podczas parsowania
danych, nad którymi nie masz kontroli:
import json
def safe_parse(json_str):
try:
return json.loads(json_str)
except json.JSONDecodeError as e:
print(f"Invalid JSON at line {e.lineno}, column {e.colno}: {e.msg}")
return None
data = safe_parse('{"name": "Alice"}') # działa poprawnie
bad = safe_parse('not json at all') # wypisuje błąd, zwraca None
also_bad = safe_parse('{"key": }') # wypisuje błąd z informacją o pozycjiJSONDecodeError podaje dokładną linię i kolumnę, w której parsowanie się nie powiodło —
przydatne przy debugowaniu dużych plików JSON.
Przydatne opcje dumps()
import json
data = {
"z_key": 1,
"a_key": 2,
"price": 9.999999999
}
# Sortowanie kluczy alfabetycznie (świetne dla powtarzalnego wyjścia / diffów)
print(json.dumps(data, sort_keys=True, indent=2))
# {
# "a_key": 2,
# "price": 9.999999999,
# "z_key": 1
# }
# Zachowanie znaków spoza ASCII (domyślnie: escape do \uXXXX)
data2 = {"city": "Münich", "greeting": "こんにちは"}
print(json.dumps(data2, ensure_ascii=False))
# {"city": "Münich", "greeting": "こんにちは"}
# Z ensure_ascii=True (domyślnie):
print(json.dumps(data2))
# {"city": "M\u00fcnich", "greeting": "\u3053\u3093\u306b\u3061\u306f"}ensure_ascii=False to opcja, którą zawsze dodaję przy zapisywaniu plików JSON
zawierających tekst spoza ASCII. Wersja z escape jest technicznie poprawnym JSON-em, ale o wiele
trudniejsza do odczytania w edytorze tekstu.
Serializacja niestandardowych obiektów
Domyślnie json.dumps() nie potrafi serializować instancji niestandardowych klas ani obiektów
datetime.
Masz dwie opcje: dziedziczenie po
json.JSONEncoder
lub wcześniejsza konwersja do słownika:
import json
from datetime import datetime, date
# Opcja 1: niestandardowa klasa kodera
class AppEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, (datetime, date)):
return obj.isoformat()
return super().default(obj)
data = {"name": "Alice", "created_at": datetime(2024, 1, 15, 9, 30)}
print(json.dumps(data, cls=AppEncoder, indent=2))
# {
# "name": "Alice",
# "created_at": "2024-01-15T09:30:00"
# }
# Opcja 2: parametr default= (prostszy dla jednorazowych konwersji)
print(json.dumps(data, default=str, indent=2)) # konwertuje nieznane typy na strPraktyczny wzorzec: wczytywanie pliku konfiguracyjnego
Oto wzorzec z prawdziwego świata, którego używam w niemal każdym projekcie Pythona — moduł ładujący konfigurację z rozsądnymi wartościami domyślnymi:
import json
import os
from pathlib import Path
DEFAULTS = {
"database": {"host": "localhost", "port": 5432},
"debug": False,
"log_level": "INFO"
}
def load_config(path="config.json"):
config = DEFAULTS.copy()
config_path = Path(path)
if config_path.exists():
with open(config_path, "r", encoding="utf-8") as f:
try:
user_config = json.load(f)
# Głębokie scalanie: ustawienia użytkownika nadpisują domyślne
for key, value in user_config.items():
if isinstance(value, dict) and key in config:
config[key].update(value)
else:
config[key] = value
except json.JSONDecodeError as e:
print(f"Warning: config.json is invalid ({e.msg}), using defaults")
return config
config = load_config()
print(config["database"]["host"]) # localhost (lub nadpisana wartość)Podsumowanie
Moduł json Pythona obejmuje wszystko, czego potrzebujesz, bez żadnych zależności.
Kluczowe zasady: używaj loads()/dumps() dla łańcuchów, load()/dump()
dla plików, zawsze obsługuj JSONDecodeError przy parsowaniu danych zewnętrznych i dodawaj
ensure_ascii=False, gdy Twoje dane zawierają znaki spoza alfabetu łacińskiego.
Do debugowania danych JSON JSON Formatter i
JSON Validator mogą zaoszczędzić Ci dużo czasu.