Python en JSON zijn een natuurlijk duo. Of je nu een REST API bouwt met FastAPI of Django, datapipelines verwerkt of simpelweg een configuratiebestand leest — je werkt voortdurend met JSON. Het goede nieuws: de standaardbibliotheek van Python heeft alles wat je nodig hebt in de json-module. Geen pip install nodig.

De Vier Functies die Je Echt Gebruikt

De json-module geeft je vier functies voor dagelijks gebruik:

  • json.loads(str) — parseer een JSON-string naar een Python-object
  • json.dumps(obj) — converteer een Python-object naar een JSON-string
  • json.load(file) — parseer JSON rechtstreeks vanuit een bestandsobject
  • json.dump(obj, file) — schrijf een Python-object als JSON naar een bestand

De s in loads / dumps staat voor string. De varianten zonder s werken met bestandsobjecten. Makkelijk te onthouden als je de regel eenmaal kent.

json.loads() — Een JSON-string Parsen

python
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'>

Let op de type-mapping: JSON true wordt Python True, JSON false wordt Python False, JSON null wordt Python None. JSON-objecten worden Python dict, JSON-arrays worden Python list.

json.dumps() — Serialiseren naar een JSON-string

python
import json

user = {
    "name": "Bob",
    "age": 25,
    "roles": ["admin", "editor"],
    "active": True,
    "extra": None
}

# Compact (handig voor netwerktransmissie)
compact = json.dumps(user)
print(compact)
# {"name": "Bob", "age": 25, "roles": ["admin", "editor"], "active": true, "extra": null}

# Opgemaakte uitvoer (handig voor logs en menselijke inspectie)
pretty = json.dumps(user, indent=2)
print(pretty)
# {
#   "name": "Bob",
#   "age": 25,
#   "roles": [
#     "admin",
#     "editor"
#   ],
#   "active": true,
#   "extra": null
# }

Let op de omgekeerde type-mapping: Python True → JSON true, Python None → JSON null. Python regelt dit automatisch.

JSON Lezen uit een Bestand

Dit is waarschijnlijk het meest voorkomende gebruik — een configuratie- of databestand inlezen bij het opstarten:

python
import json

# Lees en parseer in één stap
with open("config.json", "r", encoding="utf-8") as f:
    config = json.load(f)

print(config["database"]["host"])  # localhost
print(config["database"]["port"])  # 5432

Geef altijd encoding="utf-8" op bij het openen van JSON-bestanden. JSON is gespecificeerd als UTF-8 door RFC 8259, en het weglaten hiervan kan problemen veroorzaken op Windows waar de standaardcodering soms cp1252 is.

JSON Schrijven naar een Bestand

python
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")

Fouten Correct Afhandelen

json.loads() gooit een json.JSONDecodeError (een subklasse van ValueError) wanneer de invoer geen geldige JSON is. Vang dit altijd op wanneer je data parseert die je niet beheert:

python
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"}')   # works fine
bad  = safe_parse('not json at all')     # prints error, returns None
also_bad = safe_parse('{"key": }')       # prints error with position info

JSONDecodeError geeft je de exacte regel en kolom waar het parsen mislukte, wat handig is bij het debuggen van grote JSON-bestanden.

Handige dumps()-opties

python
import json

data = {
    "z_key": 1,
    "a_key": 2,
    "price": 9.999999999
}

# Sorteer sleutels alfabetisch (handig voor reproduceerbare uitvoer / diffs)
print(json.dumps(data, sort_keys=True, indent=2))
# {
#   "a_key": 2,
#   "price": 9.999999999,
#   "z_key": 1
# }

# Zorg dat niet-ASCII-tekens bewaard blijven (standaard: escaped naar \uXXXX)
data2 = {"city": "Münich", "greeting": "こんにちは"}
print(json.dumps(data2, ensure_ascii=False))
# {"city": "Münich", "greeting": "こんにちは"}

# Met ensure_ascii=True (standaard):
print(json.dumps(data2))
# {"city": "M\u00fcnich", "greeting": "\u3053\u3093\u306b\u3061\u306f"}

ensure_ascii=False voeg ik altijd toe wanneer ik JSON-bestanden schrijf die niet-ASCII-tekst bevatten. De escaped versie is technisch geldige JSON maar veel moeilijker te lezen in een teksteditor.

Aangepaste Objecten Serialiseren

Standaard kan json.dumps() geen instanties van aangepaste klassen of datetime-objecten serialiseren. Je hebt twee opties: subklasse json.JSONEncoder, of converteer eerst naar een dict:

python
import json
from datetime import datetime, date

# Optie 1: aangepaste encoder-klasse
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"
# }

# Optie 2: default=-parameter (eenvoudiger voor eenmalige conversies)
print(json.dumps(data, default=str, indent=2))  # converteert alles onbekends naar str

Een Praktisch Patroon: Configuratiebestand Laden

Hier is een praktijkpatroon dat ik in bijna elk Python-project gebruik — een configuratielader die een JSON-configuratiebestand leest met verstandige standaardwaarden:

python
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)
                # Diepe merge: gebruikersinstellingen overschrijven standaardwaarden
                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 (or overridden value)

Samenvatting

De json-module van Python dekt alles wat je nodig hebt zonder externe afhankelijkheden. De kernregels: gebruik loads()/dumps() voor strings, load()/dump() voor bestanden, handel altijd JSONDecodeError af bij het parsen van externe data, en voeg ensure_ascii=False toe wanneer je data niet-Latijnse tekens bevat. Voor het debuggen van JSON-data kunnen de JSON Formatter en JSON Validator je veel tijd besparen.