JSON naar Python: dataclass, Pydantic & TypedDict (gids 2026)
Plak je JSON in de JSON naar Python-converter en kopieer de klassen die eruit rollen. Dat is de snelle route om JSON naar Python om te zetten: niets te installeren, niets geüpload, alles draait in je browser. Voor een eenmalig model of een snelle blik op een onbekende API-respons ben je in seconden klaar.
Het lastigere probleem lost een converter niet voor je op. Hij leidt de juiste types af, maar hij kan de keuze die er het meest toe doet niet maken: welke van de drie uitvoer je eigenlijk wilt. Een dataclass, een Pydantic v2 BaseModel en een TypedDict kunnen exact dezelfde JSON beschrijven, en toch verschillen ze in de sterkte van de type-safety, het gedrag tijdens runtime en hoe je er data in laadt. Kies verkeerd en je sleept ofwel de dependency van Pydantic voor niets mee, ofwel ga je ervan uit dat een dataclass je invoer valideert terwijl hij dat stiekem niet doet.
Deze gids behandelt de beslissingsmatrix voor alle drie, hoe de converter types afleidt, hoe elke modus met camelCase omgaat, hoe je JSON inleest met de klasse die je genereert (inclusief de valkuil met geneste objecten waar mensen intrappen), Pydantic v2 versus v1, en de randgevallen die het waard zijn te kennen voordat je de uitvoer vertrouwt.
Zo zet je JSON om naar Python
JSON omzetten naar Python gaat in drie stappen:
- Plak je JSON. Zet een object, array of ruwe API-respons in het invoervak. De conversie draait direct en volledig client-side.
- Kies dataclass, Pydantic of TypedDict. Gebruik de Output-schakelaar om van stijl te wisselen en hernoem de standaard
Rootnaar iets betekenisvols alsUserofApiResponse. - Kopieer of download. Pak de gegenereerde Python met één klik en zet
models.pyrechtstreeks in je project.
Dat is de hele snelle route. Is je invoer geminificeerd of weet je niet zeker of hij geldig is, haal hem dan eerst door een JSON-formatter zodat de converter schone, correct gevormde JSON te lezen krijgt. De rest van deze gids legt de uitvoer uit, zodat je de gevallen kunt oplossen die een tool niet zelf kan afleiden.
Hoe JSON-types overeenkomen met Python-types
Elke JSON-waarde heeft een Python-tegenhanger, en de overeenkomst is vrij direct:
| JSON-waarde | Python-type |
|---|---|
"text" | str |
42 | int (willekeurige precisie, geen overflow) |
3.14, 2e3 | float |
true / false | bool |
null | Optional[T] / Optional[Any] |
[1, 2, 3] | List[T] |
{ ... } | een benoemde klasse |
Neem een typische REST-payload:
{ "id": 101, "name": "Ada Lovelace", "email": "ada@example.com", "active": true, "roles": ["admin", "user"] }
In dataclass-modus produceert de converter een klasse die je meteen kunt gebruiken:
from dataclasses import dataclass
from typing import List
@dataclass
class Root:
id: int
name: str
email: str
active: bool
roles: List[str]
Op één punt is Python anders. Zijn int heeft willekeurige precisie, dus een Discord-snowflake of elk ID boven 2^53 blijft een gewone int zonder verlies. JavaScript zou op dezelfde waarde precisie kwijtraken, en Rust zou je laten kiezen tussen i64, u64 en f64. Alleen een token met een decimale punt of een exponent wordt afgeleid als float. Een veld dat altijd alleen maar null is, kun je niet typeren op basis van het voorbeeld alleen, dus valt het terug op Optional[Any]; plak een representatieve payload met een ingevulde waarde om iets scherpers dan Any te krijgen.
dataclass vs Pydantic v2 vs TypedDict — welke moet je gebruiken?
Dit is de keuze die de converter aan jou teruggeeft, en waar deze gids om draait. Kort samengevat: grijp naar een dataclass als je een interne datahouder zonder dependencies wilt, naar Pydantic v2 als je niet-vertrouwde invoer inleest of valideert, en naar TypedDict als je alleen statische type-hints wilt over dicts die je al hebt.
| Dimensie | dataclass | Pydantic v2 | TypedDict |
|---|---|---|---|
| Dependency | Standaardbibliotheek | pip install pydantic | Standaardbibliotheek (typing) |
| Runtime-validatie | Nee | Ja — valideert en converteert | Nee — alleen hints |
| Runtime-kosten | Licht (echt object) | Iets (validatie) | Nul (het is een dict) |
| Geneste objecten automatisch inlezen | Nee, handmatig | Ja, model_validate gaat recursief | Nee, het is een dict |
| camelCase-aliassen | Behoudt originele sleutel | Field(alias=...) | Functionele syntaxis |
| Meest geschikt voor | Interne structuren | API-responses, webhooks, config | Typering van legacy dict-code |
De drie doelen zien er bijna identiek uit voor een kleine vorm als {"id": 101, "name": "Ada Lovelace", "active": true}. Een JSON-naar-Python-dataclass geeft je een object uit de standaardbibliotheek:
from dataclasses import dataclass
@dataclass
class Root:
id: int
name: str
active: bool
De JSON-naar-Pydantic-versie zet BaseModel ervoor in de plaats, wat er vergelijkbaar uitziet maar zich tijdens runtime terugbetaalt:
from pydantic import BaseModel
class Root(BaseModel):
id: int
name: str
active: bool
En een Python-TypedDict uit JSON beschrijft de vorm van een gewone dict zonder iets nieuws te alloceren:
from typing import TypedDict
class Root(TypedDict):
id: int
name: str
active: bool
Dezelfde velden, drie heel verschillende contracten. De waarde van het genereren van alle drie uit één voorbeeld is dat je in de tool tussen ze kunt wisselen en de modus op de klus kunt afstemmen: Pydantic wanneer je data inleest die je niet in de hand hebt, een dataclass wanneer de data intern is, en TypedDict wanneer je alleen wilt dat mypy een dict begrijpt die je toch al doorgeeft.
Hoe de converter klassen afleidt
Drie regels dekken bijna alles wat je erin stopt: één klasse per objectvorm, sleutel-voor-sleutel samenvoegen bij arrays, en zorgvuldige omgang met ontbrekende waarden.
Structurele inferentie: één benoemde klasse per object
Elke afzonderlijke objectvorm wordt een eigen benoemde klasse. Geneste objecten worden niet inline gezet; ze worden gelicht naar aparte, gerefereerde definities:
{ "repo": "pydantic", "owner": { "login": "samuelcolvin", "id": 100 } }
from dataclasses import dataclass
@dataclass
class Owner:
login: str
id: int
@dataclass
class Root:
repo: str
owner: Owner
De geneste owner wordt een eigen Owner-klasse, waarnaar het veld verwijst. Let op de volgorde: de kindklasse komt vóór de ouder die haar gebruikt, zodat de module draait zonder from __future__ import annotations. Identieke vormen worden ook ontdubbeld, dus twee velden met dezelfde structuur delen één klasse in plaats van kopieën te maken.
Arrays samenvoegen en Optional-velden
Wanneer je een array met objecten meegeeft, voegt de converter ze sleutel voor sleutel samen tot één elementtype. Een sleutel die in sommige elementen aanwezig is maar in andere ontbreekt, wordt Optional:
{ "users": [{ "id": 1, "nick": "x" }, { "id": 2 }] }
from typing import List, Optional, TypedDict
class User(TypedDict):
id: int
nick: Optional[str]
class Root(TypedDict):
users: List[User]
id komt in elk element voor, dus blijft het verplicht. nick ontbreekt bij de tweede user, dus wordt het Optional[str]. Dit is precies waarom één object een zwak voorbeeld is: de tool kan een veld alleen als Optional markeren als het de sleutel ergens daadwerkelijk mist. Voer een representatieve array in en de inferentie weerspiegelt de echte vorm.
Optional, None en lege arrays
Een paar waarden dragen op zichzelf geen type, en de converter is daar eerlijk over. Een sleutel die in sommige array-items ontbreekt, wordt Optional[T]. Een veld dat altijd alleen maar null is, wordt Optional[Any], want JSON null zegt op zichzelf niets over het bedoelde type. Een lege array of een array met gemengde elementtypes valt terug op List[Any]. Geen van deze is een bug; het is de tool die weigert te gokken. Vervang elke Any door een concreet type zodra je een voorbeeld hebt dat er een laat zien, en onthoud dat de willekeurige precisie van Python’s int betekent dat een groot ID je nooit in een breder numeriek type dwingt zoals in Rust of JavaScript wel gebeurt.
camelCase-sleutels in elke modus afhandelen
JSON-sleutels zijn vaak camelCase, terwijl idiomatische Python-velden snake_case zijn. Er is hier geen eenduidig juist antwoord, dus elke modus lost het anders op, en het verschil is de moeite waard om te begrijpen voordat je de uitvoer kopieert.
dataclass. Een dataclass heeft geen ingebouwd aliasmechanisme. Om Root(**data) werkend te houden, behoudt de converter de originele sleutel als veldnaam zodra die een geldige Python-identifier is, dus publicRepos blijft publicRepos. Het naar snake_case forceren zou **data breken met een TypeError.
Pydantic v2. Hier krijg je het idiomatische resultaat. Velden worden hernoemd naar snake_case met een Field(alias=...) die terugverwijst naar de exacte JSON-sleutel, plus model_config = ConfigDict(populate_by_name=True) zodat je het model kunt opbouwen op veldnaam of op alias:
from pydantic import BaseModel, ConfigDict, Field
class Root(BaseModel):
model_config = ConfigDict(populate_by_name=True)
login: str
public_repos: int = Field(alias="publicRepos")
created_at: str = Field(alias="createdAt")
Dat is wat Root.model_validate(api_json) echte camelCase-payloads volledig laat verwerken (round-trip) terwijl je code natuurlijk leesbaar blijft.
TypedDict. Gewone identifier-sleutels worden direct gebruikt. Zodra er een sleutel verschijnt die geen geldige identifier is, schakelt de converter over op de functionele syntaxis TypedDict('User', {...}) om de exacte sleutel te behouden, wat de volgende sectie behandelt.
Python-keywords en niet-identifier-sleutels
JSON-sleutels zijn gewoon strings, dus niets weerhoudt een API ervan om class, from of first-name te sturen. Elk daarvan zou als kaal Python-attribuut een syntaxfout zijn, dus schoont de converter ze op.
Een sleutel die een Python-keyword is, krijgt een underscore achteraan: class wordt class_, from wordt from_. In Pydantic-modus houdt hij ook een Field(alias=...) aan, zodat het model nog steeds de originele wire-sleutel leest:
from pydantic import BaseModel, ConfigDict, Field
class Root(BaseModel):
model_config = ConfigDict(populate_by_name=True)
class_: str = Field(alias="class")
from_: str = Field(alias="from")
Sleutels met koppeltekens, spaties of een cijfer vooraan worden in dataclass- en Pydantic-modus opgeschoond tot geldige identifiers. TypedDict is de enige modus die de letterlijke sleutel kan behouden, via de functionele vorm, zodat "first-name" onaangeroerd blijft:
from typing import TypedDict
Root = TypedDict("Root", {"first-name": str, "2fa": bool})
Het resultaat draait altijd, en in TypedDict-modus blijft de sleutel byte voor byte wat de API stuurde.
JSON inlezen met de gegenereerde klasse
De klasse genereren is het halve werk. JSON erin laden is waar de drie modi scherp uiteenlopen, en waar één populaire aanname misgaat.
dataclass. Voor een plat object werkt json.loads plus Root(**data) meteen:
import json
from dataclasses import dataclass
from typing import List
@dataclass
class Root:
id: int
name: str
roles: List[str]
data = json.loads('{"id": 101, "name": "Ada Lovelace", "roles": ["admin"]}')
root = Root(**data)
print(root.name, root.roles)
Maar een dataclass gaat niet recursief. Heeft de JSON geneste objecten, dan vult Root(**data) het bovenste niveau en laat elk kind een gewone dict, niet de Owner-klasse die je genereerde. Om een hele boom in te lezen, bouw je de kinderen zelf, gebruik je een bibliotheek als dacite of pydantic.dataclasses, of zet je de tool op Pydantic-modus. Dit is de valkuil die de meeste converters nooit noemen.
Pydantic v2. Eén aanroep leest en valideert de hele boom:
import json
from pydantic import BaseModel
class Root(BaseModel):
id: int
name: str
root = Root.model_validate(json.loads('{"id": 101, "name": "Ada Lovelace"}'))
print(root.name)
Een typemismatch gooit een duidelijke ValidationError in plaats van er stil doorheen te glippen. Dat is de opbrengst voor de dependency: het model is een runtime-validator, dus een model_validate-aanroep die terugkeert is een garantie dat de data klopte, niet slechts het geloof van een typechecker dat dat zo was.
TypedDict. Tijdens runtime is een TypedDict een gewone dict. De annotatie data: Root = json.loads(text) is puur voor de statische checker; er wordt niets gevalideerd wanneer de code draait. Heb je echte afdwinging nodig, draai dan mypy in CI of stap over op Pydantic. Een modus kiezen is eigenlijk kiezen hoeveel runtime-garantie je wilt, en wanneer een contract verder moet gaan dan types, laat de gids over JSON Schema-validatie zien hoe je de structuur van begin tot eind afdwingt.
Pydantic v2 vs v1 — wat er veranderde
De converter geeft v2-syntaxis uit. Genoeg codebases draaien nog v1, en v2-uitvoer in een v1-project plakken faalt op hernoemde methoden, niet op logica. Zo staan de methoden tegenover elkaar:
| Taak | Pydantic v2 (tool-uitvoer) | Pydantic v1 |
|---|---|---|
| Een dict inlezen | model_validate(data) | parse_obj(data) |
| Ruwe JSON inlezen | model_validate_json(text) | parse_raw(text) |
| Het model configureren | model_config = ConfigDict(...) | interne class Config |
| Serialiseren naar dict | model_dump() | dict() |
| Serialiseren naar JSON | model_dump_json() | json() |
Het aliasgedrag werd ook strenger: in v2 heb je populate_by_name=True naast Field(alias=...) nodig om een model op te bouwen op de veldnaam óf de alias, en daarom neemt de tool het op. Upgrade naar v2 als het kan, want het is sneller en het mikpunt van de huidige ontwikkeling; anders herschrijf je model_validate terug naar parse_obj en ConfigDict terug naar een interne Config.
json-to-python vs quicktype vs datamodel-code-generator vs handmatig
Er is geen enkele beste manier om een Python-klasse uit JSON te genereren. Het hangt af van waar de JSON leeft en wat je uitgangspunt is.
| Aanpak | Meest geschikt voor | Opmerking |
|---|---|---|
| Online converter (deze tool, jsonlint, codeshack) | Eenmalige conversies, gevoelige payloads, geen installatie | Leidt af uit een voorbeeld, volledig client-side |
| quicktype | Uitvoer in meerdere talen, codegen in een pipeline | Ook op voorbeeld gebaseerd; de Python-ondersteuning is magerder qua aliassen en moduskeuze |
| datamodel-code-generator | Pydantic genereren uit een JSON-Schema of OpenAPI | Invoer is een schema, geen voorbeeld — gezaghebbender als je er een hebt |
| Klassen met de hand schrijven | Kleine payloads, de syntaxis leren | Volledige controle, maar arbeidsintensief en snel uit de pas |
Het onderscheid dat telt: een online converter en quicktype leiden beide types af uit een voorbeeld van JSON, terwijl datamodel-code-generator een JSON-Schema of OpenAPI-spec als bron van waarheid leest. Gebruik de voorbeeld-tools voor verkenning en eenmalig werk; grijp naar de schema-gedreven tool wanneer er al een contract bestaat. Is je codebase TypeScript in plaats van Python, dan geldt dezelfde voorbeeldgedreven aanpak met de JSON naar TypeScript-converter, en de gids voor JSON naar TypeScript-interfaces behandelt die kant diepgaand. Voor de invalshoek van runtime-validatie in een sterk getypeerde, gecompileerde taal vormt de gids voor JSON naar Rust-structs een nuttig contrast: serde is de Pydantic van Rust.
Veelvoorkomende valkuilen bij het genereren van Python uit JSON
Gegenereerde klassen zijn een startpunt. Let op het volgende voordat je met live data op de uitvoer vertrouwt.
- Eén voorbeeld onthult zelden elke vorm.
Optionalkan alleen worden afgeleid uit array-elementen die daadwerkelijk verschillen. Plak een representatieve array zodat de optionaliteit klopt en geen gok is op basis van één toevallig object. - Een dataclass valideert niet.
Root(**data)kent alleen velden toe; het controleert geen types en gaat nergens recursief in. Heb je afdwinging nodig, gebruik dan Pydantic. - Geneste JSON breekt
Root(**data). Alleen het bovenste niveau wordt gevuld; kinderen blijven dicts. Stap over opmodel_validatevan Pydantic, of gebruik dacite. - Een veld dat altijd
nullis, wordtOptional[Any]. Behandel het als een placeholder, niet als een normale optional, en geef het een echt type zodra je er een kent. - v2-uitvoer in een v1-project.
model_validateis dan niet gedefinieerd. Upgrade naar v2 of herschrijf het alsparse_obj. - Een datum die
strblijft, kan geen datumberekeningen doen. Zet het veld opdatetime, of gebruik in Pydantic eendatetime-veld zodat het ISO 8601 voor je inleest.
Veelgestelde vragen
Moet ik een dataclass, Pydantic of TypedDict gebruiken voor JSON?
Gebruik een dataclass voor een interne datahouder zonder dependencies. Gebruik Pydantic v2 wanneer je niet-vertrouwde invoer inleest of valideert, want het dwingt types af tijdens runtime. Gebruik TypedDict om statische hints toe te voegen aan een dict die je al hebt, zonder runtime-kosten. Komt de data van buiten je controle, kies dan standaard voor Pydantic.
Valideert Pydantic JSON tijdens runtime?
Ja. Root.model_validate(data) valideert en converteert types terwijl de code draait, en gooit een ValidationError bij ongeldige invoer. Een dataclass doet geen van beide — Root(**data) kent alleen velden toe. Een TypedDict doet dat evenmin; het is een hint voor statische checkers als mypy en gedraagt zich tijdens runtime als een gewone dict.
Hoe typeer ik een veld dat JSON soms weglaat?
Typeer het als Optional[T]. Wanneer een sleutel maar in sommige array-elementen voorkomt, markeert de converter het automatisch als Optional. Geef het veld in Pydantic een standaardwaarde als = None, zodat een ontbrekende sleutel geen validatiefout veroorzaakt. Een dataclass-veld kan op dezelfde manier een standaardwaarde krijgen met field(default=None).
Waarom lukte het mijn dataclass niet om geneste JSON in te lezen?
Omdat Root(**data) niet recursief gaat. Het vult de velden op het bovenste niveau en laat geneste objecten als gewone dicts in plaats van de kindklassen die je genereerde. Om de hele boom in één aanroep in te lezen, stap je over op model_validate van Pydantic, of voeg je recursie toe met dacite of pydantic.dataclasses.
Welk Python-type wordt een groot JSON-integer?
int. Python-integers hebben willekeurige precisie, dus een snowflake- of Discord-ID boven 2^53 blijft een gewone int zonder verlies. Je loopt nooit tegen het precisieverlies van JavaScript aan, en je hoeft nooit te kiezen tussen i64 en u64 zoals Rust je dwingt.
Hoe worden camelCase-JSON-sleutels in elke modus afgehandeld?
Een dataclass behoudt de originele sleutel, zodat Root(**data) blijft werken. Pydantic hernoemt velden naar snake_case met Field(alias="camelKey") plus populate_by_name=True, zodat het echte API-data heen en weer kan verwerken. TypedDict gebruikt de functionele syntaxis TypedDict('Name', {...}) om elke sleutel te behouden die geen geldige identifier is.
Hoe ga ik om met een JSON-sleutel die een Python-keyword is, zoals class?
De converter voegt een underscore achteraan toe, waardoor class verandert in class_ zodat de code geldig is. In Pydantic-modus voegt hij ook Field(alias="class") toe die het veld terugkoppelt aan de originele wire-sleutel, zodat de naam legaal is en het model nog steeds de echte payload leest.
Is mijn JSON privé bij het gebruik van een online JSON-naar-Python-converter?
Ja. De conversie draait 100% in je browser met JavaScript. Je JSON — inclusief tokens, ID’s en klantgegevens — verlaat de pagina nooit en wordt nooit naar een server gestuurd. Het werkt zelfs offline zodra de pagina is geladen. Als je zover bent, probeer de JSON-naar-Python-converter op je eigen payload.