JSON vers Python : dataclass, Pydantic et TypedDict (guide 2026)
Collez votre JSON dans le convertisseur JSON vers Python et copiez les classes qu’il génère. C’est la voie rapide pour passer de JSON à Python : rien à installer ni à téléverser, tout s’exécute dans votre navigateur. Pour un modèle ponctuel ou un coup d’œil rapide à une réponse d’API inconnue, l’affaire est réglée en quelques secondes.
Le problème plus difficile est celui qu’un convertisseur ne peut pas résoudre à votre place. Il infère des types corrects, mais il ne peut pas prendre la décision qui compte le plus : laquelle des trois sorties vous voulez réellement. Un dataclass, un BaseModel de Pydantic v2 et un TypedDict peuvent décrire exactement le même JSON, et pourtant ils diffèrent par la force de leur sûreté de typage, leur comportement à l’exécution et la manière dont vous y chargez les données. Faites le mauvais choix et, soit vous traînez la dépendance de Pydantic pour rien, soit vous supposez qu’un dataclass valide votre entrée alors qu’il n’en fait rien.
Ce guide couvre la matrice de décision pour les trois, la façon dont le convertisseur infère les types, la gestion du camelCase dans chaque mode, comment analyser du JSON avec la classe générée (y compris le piège des objets imbriqués), Pydantic v2 face à v1, et les cas limites à connaître avant de faire confiance au résultat.
Comment convertir JSON en Python
Convertir JSON en Python se fait en trois étapes :
- Collez votre JSON. Déposez un objet, un tableau ou une réponse d’API brute dans la zone de saisie. La conversion s’exécute instantanément et entièrement côté client.
- Choisissez dataclass, Pydantic ou TypedDict. Utilisez le sélecteur Output pour basculer de style, et renommez le
Rootpar défaut en quelque chose de parlant commeUserouApiResponse. - Copiez ou téléchargez. Récupérez le Python généré en un clic et déposez
models.pydirectement dans votre projet.
Voilà pour le chemin transactionnel. Si votre saisie est minifiée ou que vous n’êtes pas certain de sa validité, passez-la d’abord par un formateur JSON pour que le convertisseur lise un JSON propre et bien formé. Le reste de ce guide explique le résultat, afin que vous puissiez corriger les cas qu’un outil ne peut pas inférer seul.
Comment les types JSON se traduisent en Python
Chaque valeur JSON a un équivalent Python, et la correspondance est directe :
| Valeur JSON | Type Python |
|---|---|
"text" | str |
42 | int (précision arbitraire, aucun débordement) |
3.14, 2e3 | float |
true / false | bool |
null | Optional[T] / Optional[Any] |
[1, 2, 3] | List[T] |
{ ... } | une classe nommée |
Prenons une charge utile REST typique :
{ "id": 101, "name": "Ada Lovelace", "email": "ada@example.com", "active": true, "roles": ["admin", "user"] }
En mode dataclass, le convertisseur produit une classe prête à l’emploi :
from dataclasses import dataclass
from typing import List
@dataclass
class Root:
id: int
name: str
email: str
active: bool
roles: List[str]
Une nuance distingue Python. Son int est de précision arbitraire : un snowflake Discord, ou n’importe quel identifiant au-delà de 2^53, reste un simple int sans perte. JavaScript perdrait en précision sur la même valeur, et Rust vous forcerait à choisir entre i64, u64 et f64. Seul un jeton écrit avec un point décimal ou un exposant est inféré comme float. Un champ qui n’est jamais que null ne peut pas être typé à partir du seul échantillon ; il retombe donc sur Optional[Any]. Collez une charge utile représentative avec une valeur renseignée pour obtenir quelque chose de plus précis que Any.
dataclass, Pydantic v2 ou TypedDict : lequel choisir ?
C’est le choix que le convertisseur vous renvoie, et c’est tout l’enjeu de ce guide. En bref : optez pour un dataclass quand vous voulez un conteneur de données interne sans dépendance, pour Pydantic v2 quand vous analysez ou validez une entrée non fiable, et pour TypedDict quand vous avez seulement besoin d’indications de type statiques sur des dictionnaires que vous possédez déjà.
| Dimension | dataclass | Pydantic v2 | TypedDict |
|---|---|---|---|
| Dépendance | Bibliothèque standard | pip install pydantic | Bibliothèque standard (typing) |
| Validation à l’exécution | Non | Oui — valide et convertit | Non — indications seulement |
| Coût à l’exécution | Léger (vrai objet) | Modéré (validation) | Nul (c’est un dict) |
| Analyse auto des imbriqués | Non, manuelle | Oui, model_validate récursif | Non, c’est un dict |
| Alias camelCase | Conserve la clé d’origine | Field(alias=...) | Syntaxe fonctionnelle |
| Idéal pour | Structures internes | Réponses d’API, webhooks, config | Typer du code à base de dict existant |
Les trois cibles se ressemblent presque à s’y méprendre pour une petite structure comme {"id": 101, "name": "Ada Lovelace", "active": true}. Un dataclass généré à partir de votre JSON vous donne un objet de la bibliothèque standard :
from dataclasses import dataclass
@dataclass
class Root:
id: int
name: str
active: bool
La variante Pydantic remplace la base par BaseModel, qui paraît similaire mais gagne sa place à l’exécution :
from pydantic import BaseModel
class Root(BaseModel):
id: int
name: str
active: bool
Et un TypedDict Python issu du JSON décrit la forme d’un simple dict sans rien allouer de nouveau :
from typing import TypedDict
class Root(TypedDict):
id: int
name: str
active: bool
Mêmes champs, trois contrats très différents. L’intérêt de générer les trois à partir d’un seul échantillon, c’est de pouvoir basculer de l’un à l’autre dans l’outil et d’accorder le mode à la tâche : Pydantic quand vous analysez des données que vous ne maîtrisez pas, un dataclass quand les données sont internes, et TypedDict quand vous voulez simplement que mypy comprenne un dict que vous manipulez déjà.
Comment le convertisseur infère les classes
Trois règles couvrent presque tout ce que vous lui donnez : une classe par forme d’objet, une fusion clé par clé pour les tableaux, et la gestion des valeurs manquantes.
Inférence structurelle : une classe nommée par objet
Chaque forme d’objet distincte devient sa propre classe nommée. Les objets imbriqués ne sont pas insérés en ligne ; ils sont extraits dans des définitions séparées et référencées :
{ "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
L’owner imbriqué devient sa propre classe Owner, référencée par le champ. Remarquez l’ordre : la classe enfant est émise avant le parent qui l’utilise, de sorte que le module fonctionne sans from __future__ import annotations. Les formes identiques sont aussi dédupliquées : deux champs de même structure partagent une seule classe au lieu de produire des copies.
Fusion de tableaux et champs Optional
Lorsque vous passez un tableau d’objets, le convertisseur les fusionne clé par clé en un seul type d’élément. Une clé présente dans certains éléments mais absente d’autres devient 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 apparaît dans chaque élément, il reste donc obligatoire. nick est absent du deuxième utilisateur, il devient donc Optional[str]. Voilà précisément pourquoi un objet unique est un échantillon faible : l’outil ne peut marquer un champ Optional que lorsqu’il constate réellement l’absence de la clé quelque part. Donnez-lui un tableau représentatif et l’inférence reflète la forme réelle.
Optional, None et tableaux vides
Quelques valeurs ne portent aucun type par elles-mêmes, et le convertisseur le reconnaît. Une clé absente de certains éléments d’un tableau devient Optional[T]. Un champ qui n’est jamais que null devient Optional[Any], car un null JSON à lui seul ne dit rien du type visé. Un tableau vide, ou dont les éléments ont des types mélangés, retombe sur List[Any]. Rien de tout cela n’est un bogue ; c’est l’outil qui refuse de deviner. Remplacez chaque Any par un type concret dès que vous disposez d’un échantillon qui en révèle un, et rappelez-vous que l’int de précision arbitraire de Python signifie qu’un grand identifiant ne vous force jamais vers un type numérique plus large, contrairement à Rust ou JavaScript.
Gérer les clés camelCase dans chaque mode
Les clés JSON sont souvent en camelCase, alors que les champs Python idiomatiques sont en snake_case. Il n’y a pas de réponse unique ici : chaque mode le résout différemment, et la différence mérite d’être comprise avant de copier le résultat.
dataclass. Un dataclass n’a aucun mécanisme d’alias intégré. Pour que Root(**data) continue de fonctionner, le convertisseur conserve la clé d’origine comme nom de champ chaque fois qu’il s’agit d’un identifiant Python valide : publicRepos reste donc publicRepos. Le forcer en snake_case casserait **data avec une TypeError.
Pydantic v2. Ici, vous obtenez le résultat idiomatique. Les champs sont renommés en snake_case avec un Field(alias=...) qui renvoie vers la clé JSON exacte, plus model_config = ConfigDict(populate_by_name=True) pour que vous puissiez construire le modèle par nom de champ ou par 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")
C’est ce qui permet à Root.model_validate(api_json) de faire l’aller-retour avec de vraies charges utiles en camelCase, tandis que votre code se lit naturellement.
TypedDict. Les clés qui sont de simples identifiants sont utilisées directement. Dès qu’une clé qui n’est pas un identifiant apparaît, le convertisseur bascule vers la syntaxe fonctionnelle TypedDict('User', {...}) pour préserver la clé exacte, ce que couvre la section suivante.
Mots-clés Python et clés non identifiantes
Les clés JSON ne sont que des chaînes : rien n’empêche une API d’envoyer class, from ou first-name. Chacune provoquerait une erreur de syntaxe en tant qu’attribut Python nu, aussi le convertisseur les assainit-il.
Une clé qui est un mot-clé Python reçoit un tiret bas final : class devient class_, from devient from_. En mode Pydantic, elle conserve aussi un Field(alias=...) pour que le modèle lise toujours la clé d’origine telle qu’elle circule :
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")
Les clés comportant des traits d’union, des espaces ou un chiffre en tête sont assainies en identifiants valides dans les modes dataclass et Pydantic. TypedDict est le seul mode capable de conserver la clé littérale, grâce à la forme fonctionnelle : "first-name" survit ainsi intacte :
from typing import TypedDict
Root = TypedDict("Root", {"first-name": str, "2fa": bool})
Le résultat s’exécute toujours, et en mode TypedDict la clé reste, octet pour octet, celle qu’a envoyée l’API.
Analyser du JSON avec la classe générée
Générer la classe n’est que la moitié du travail. C’est en y chargeant le JSON que les trois modes divergent nettement, et qu’une hypothèse répandue se révèle fausse.
dataclass. Pour un objet plat, json.loads suivi de Root(**data) fonctionne directement :
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)
Mais un dataclass ne récursive pas. Si le JSON contient des objets imbriqués, Root(**data) remplit le niveau supérieur et laisse chaque enfant sous forme de simple dict, et non de la classe Owner que vous avez générée. Pour analyser tout un arbre, construisez les enfants vous-même, utilisez une bibliothèque comme dacite ou pydantic.dataclasses, ou basculez l’outil en mode Pydantic. C’est le piège que la plupart des convertisseurs passent sous silence.
Pydantic v2. Un seul appel analyse et valide l’arbre entier :
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)
Une incompatibilité de type lève une ValidationError claire au lieu de passer en silence. C’est là le bénéfice de la dépendance : le modèle est un validateur à l’exécution, donc un appel à model_validate qui aboutit garantit que les données correspondaient, et pas seulement que le vérificateur de types le croyait.
TypedDict. À l’exécution, un TypedDict est un dict ordinaire. Annoter data: Root = json.loads(text) ne sert qu’au vérificateur statique ; rien n’est validé quand le code s’exécute. Si vous avez besoin d’une vraie application des règles, lancez mypy dans votre CI ou passez à Pydantic. Choisir un mode revient à choisir la quantité de garantie que vous voulez à l’exécution ; et quand un contrat doit aller au-delà des types, le guide de validation JSON Schema explique comment faire respecter une structure de bout en bout.
Pydantic v2 face à v1 : ce qui a changé
Le convertisseur émet la syntaxe v2. Bien des bases de code tournent encore en v1, et coller du code v2 dans un projet v1 échoue sur des méthodes renommées plutôt que sur la logique. Voici la correspondance :
| Tâche | Pydantic v2 (sortie de l’outil) | Pydantic v1 |
|---|---|---|
| Analyser un dict | model_validate(data) | parse_obj(data) |
| Analyser du JSON brut | model_validate_json(text) | parse_raw(text) |
| Configurer le modèle | model_config = ConfigDict(...) | class Config interne |
| Sérialiser en dict | model_dump() | dict() |
| Sérialiser en JSON | model_dump_json() | json() |
Le comportement des alias s’est aussi durci : en v2, il faut populate_by_name=True en plus de Field(alias=...) pour construire un modèle par le nom du champ ou par l’alias, d’où sa présence dans la sortie de l’outil. Migrez vers v2 si vous le pouvez, car elle est plus rapide et concentre le développement actuel ; sinon, réécrivez model_validate en parse_obj et ConfigDict en Config interne.
json-to-python face à quicktype, datamodel-code-generator ou l’écriture à la main
Il n’existe pas de meilleure façon unique de générer une classe Python à partir de JSON. Tout dépend d’où vient le JSON et de ce dont vous disposez au départ.
| Approche | Idéal pour | Remarque |
|---|---|---|
| Convertisseur en ligne (cet outil, jsonlint, codeshack) | Conversions ponctuelles, charges utiles sensibles, zéro installation | Infère depuis un échantillon, entièrement côté client |
| quicktype | Sortie multilangage, codegen en pipeline | Aussi piloté par échantillon ; le support Python est plus mince sur les alias et le choix du mode |
| datamodel-code-generator | Générer du Pydantic depuis un Schema JSON ou OpenAPI | L’entrée est un schéma, pas un échantillon, donc plus fiable quand vous en avez un |
| Écrire les classes à la main | Charges utiles minuscules, apprentissage de la syntaxe | Contrôle total, mais fastidieux et sujet à la dérive |
La distinction à intégrer : un convertisseur en ligne et quicktype infèrent tous deux les types à partir d’un échantillon de JSON, tandis que datamodel-code-generator lit un JSON Schema ou une spécification OpenAPI comme source de vérité. Servez-vous des outils à échantillon pour l’exploration et les cas ponctuels ; tournez-vous vers celui piloté par schéma quand un contrat existe déjà. Si votre base de code est en TypeScript plutôt qu’en Python, la même approche par échantillon s’applique avec le convertisseur JSON vers TypeScript, et le guide des interfaces JSON vers TypeScript traite ce versant en profondeur. Pour l’angle de la validation à l’exécution dans un langage compilé fortement typé, le guide des structs JSON vers Rust offre un contraste utile : serde est le Pydantic de Rust.
Pièges courants lors de la génération de Python depuis du JSON
Les classes générées sont un point de départ. Guettez ces pièges avant de vous fier au résultat avec des données réelles.
- Un seul échantillon révèle rarement toutes les formes.
Optionalne peut être inféré qu’à partir d’éléments de tableau qui diffèrent réellement. Collez un tableau représentatif pour que l’optionalité soit exacte, et non devinée à partir d’un objet chanceux. - Un dataclass ne valide pas.
Root(**data)se contente d’affecter les champs ; il ne vérifie aucun type et ne récursive nulle part. Si vous avez besoin d’un contrôle, utilisez Pydantic. - Le JSON imbriqué casse
Root(**data). Seul le niveau supérieur est rempli ; les enfants restent des dict. Passez aumodel_validatede Pydantic, ou utilisez dacite. - Un champ toujours
nulldevientOptional[Any]. Traitez-le comme un espace réservé, pas comme un champ optionnel ordinaire, et donnez-lui un vrai type dès que vous en connaissez un. - Sortie v2 dans un projet v1.
model_validatesera indéfini. Migrez vers v2 ou réécrivez-le enparse_obj. - Une date laissée en
strne fera pas de calcul de dates. Passez le champ endatetime, ou, avec Pydantic, utilisez un champdatetimepour qu’il analyse l’ISO 8601 à votre place.
Questions fréquentes
Dois-je utiliser un dataclass, Pydantic ou TypedDict pour du JSON ?
Utilisez un dataclass pour un conteneur de données interne sans dépendance. Utilisez Pydantic v2 quand vous analysez ou validez une entrée non fiable, car il impose les types à l’exécution. Utilisez TypedDict pour ajouter des indications statiques à un dict que vous possédez déjà, sans coût à l’exécution. Quand les données échappent à votre contrôle, choisissez Pydantic par défaut.
Pydantic valide-t-il le JSON à l’exécution ?
Oui. Root.model_validate(data) valide et convertit les types pendant l’exécution du code, en levant une ValidationError sur une entrée incorrecte. Un dataclass ne fait ni l’un ni l’autre : Root(**data) se contente d’affecter les champs. Un TypedDict non plus ; c’est une indication pour les vérificateurs statiques comme mypy, et il se comporte comme un simple dict à l’exécution.
Comment typer un champ que le JSON omet parfois ?
Typez-le en Optional[T]. Quand une clé n’apparaît que dans certains éléments d’un tableau, le convertisseur la marque automatiquement Optional. Avec Pydantic, donnez au champ une valeur par défaut comme = None pour qu’une clé absente ne déclenche pas d’erreur de validation. Un champ de dataclass peut avoir une valeur par défaut de la même façon, avec field(default=None).
Pourquoi mon dataclass n’a-t-il pas réussi à analyser du JSON imbriqué ?
Parce que Root(**data) ne récursive pas. Il remplit les champs du niveau supérieur et laisse les objets imbriqués sous forme de simples dict, au lieu des classes enfants que vous avez générées. Pour analyser tout l’arbre en un seul appel, passez au model_validate de Pydantic, ou ajoutez la récursion avec dacite ou pydantic.dataclasses.
Quel type Python obtient un grand entier JSON ?
int. Les entiers Python sont de précision arbitraire, donc un snowflake ou un identifiant Discord au-delà de 2^53 reste un simple int sans perte. Vous ne rencontrez jamais la perte de précision de JavaScript, et vous n’avez jamais à choisir entre i64 et u64 comme Rust vous y oblige.
Comment les clés JSON en camelCase sont-elles gérées dans chaque mode ?
Un dataclass conserve la clé d’origine, pour que Root(**data) fonctionne toujours. Pydantic renomme les champs en snake_case avec Field(alias="camelKey") plus populate_by_name=True, ce qui lui permet de faire l’aller-retour avec de vraies données d’API. TypedDict emploie la syntaxe fonctionnelle TypedDict('Name', {...}) pour préserver toute clé qui n’est pas un identifiant valide.
Comment gérer une clé JSON qui est un mot-clé Python comme class ?
Le convertisseur ajoute un tiret bas final, transformant class en class_ pour que le code soit valide. En mode Pydantic, il ajoute aussi Field(alias="class") qui relie le champ à la clé d’origine telle qu’elle circule, de sorte que le nom est licite et que le modèle lit toujours la charge utile réelle.
Mon JSON est-il privé quand j’utilise un convertisseur JSON vers Python en ligne ?
Oui. La conversion s’exécute à 100 % dans votre navigateur avec JavaScript. Votre JSON (jetons, identifiants et données clients compris) ne quitte jamais la page et n’est jamais envoyé à un serveur. Il fonctionne même hors ligne une fois la page chargée. Quand vous êtes prêt, essayez le convertisseur JSON vers Python sur votre propre charge utile.