JSON を Python に変換:dataclass・Pydantic・TypedDict ガイド(2026)
JSON を JSON to Python 変換ツール に貼り付け、生成されたクラスをコピーする。JSON を Python に変換する最短ルートはこれだ。インストール不要、アップロードもなし、すべてブラウザ内で走る。使い捨てのモデルが欲しいときや、見慣れない API レスポンスをざっと確認したいときなら、数秒で片が付く。
難しいのは、変換ツールが代わりに解いてくれない問題のほうだ。ツールは正しい型を推論できるが、一番肝心な判断だけは下せない。3 つの出力のうち、自分が本当に欲しいのはどれか、という判断だ。dataclass、Pydantic v2 の BaseModel、そして TypedDict は、まったく同じ JSON を表現できる。それでいて、型安全性の強さ、実行時の振る舞い、データの読み込み方はそれぞれ違う。選び方を間違えれば、意味もなく Pydantic の依存を抱え込むか、あるいは dataclass が入力を検証してくれると思い込んで、実際には何も検証されていない、という羽目になる。
このガイドでは、3 つすべての決定マトリクス、変換ツールがどう型を推論するか、各モードでの camelCase の扱い、生成したクラスで JSON をパースする方法(多くの人がはまるネストの罠も含む)、Pydantic v2 と v1 の違い、そして出力を信頼する前に知っておくべきエッジケースを扱う。
JSON を Python に変換する方法
JSON を Python に変換するのは 3 ステップだ。
- JSON を貼り付ける。 オブジェクト、配列、あるいは生の API レスポンスを入力ボックスに落とし込む。変換は即座に、完全にクライアントサイドで走る。
- dataclass・Pydantic・TypedDict を選ぶ。 Output のトグルで出力スタイルを切り替え、デフォルトの
RootをUserやApiResponseのような意味のある名前に変える。 - コピーまたはダウンロードする。 生成された Python をワンクリックで取得し、
models.pyをそのままプロジェクトに落とし込む。
トランザクション的な流れはこれで全部だ。入力が minify されている、あるいは妥当か自信がない場合は、まず JSON整形ツール に通し、変換ツールがきれいで整形済みの JSON を読めるようにする。ツールが独力では推論できないケースを自分の手で直すには、出力の読み方を知っておく必要がある。
JSON の型は Python にどう対応するか
すべての JSON 値には Python の対応物があり、その対応は気持ちいいほど素直だ。
| JSON 値 | Python 型 |
|---|---|
"text" | str |
42 | int(任意精度、オーバーフローなし) |
3.14, 2e3 | float |
true / false | bool |
null | Optional[T] / Optional[Any] |
[1, 2, 3] | List[T] |
{ ... } | 名前付きクラス |
典型的な REST ペイロードを見てみよう。
{ "id": 101, "name": "Ada Lovelace", "email": "ada@example.com", "active": true, "roles": ["admin", "user"] }
dataclass モードでは、変換ツールはそのまま使えるクラスを生成する。
from dataclasses import dataclass
from typing import List
@dataclass
class Root:
id: int
name: str
email: str
active: bool
roles: List[str]
ひとつだけ Python を際立たせる細部がある。Python の int は任意精度なので、Discord の snowflake や 2^53 を超えるどんな ID も、精度を失わずただの int のままでいられる。同じ値に対して、JavaScript なら精度が落ちるし、Rust なら i64・u64・f64 のどれかを選ばされる。float と推論されるのは、小数点や指数付きで書かれたトークンだけだ。ずっと null のままのフィールドは、サンプルだけからは型を決められないので Optional[Any] に落ちる。値の入った代表的なペイロードを貼れば、Any よりも鋭い型が得られる。
dataclass vs Pydantic v2 vs TypedDict — どれを使うべきか
これこそ、変換ツールが君に投げ返してくる選択だ。手短に言えば、依存ゼロの内部データホルダーが欲しいなら dataclass、信頼できない入力をパースまたは検証するなら Pydantic v2、すでに手元にある dict に静的な型ヒントを付けたいだけなら TypedDict に手を伸ばせばいい。
| 観点 | dataclass | Pydantic v2 | TypedDict |
|---|---|---|---|
| 依存 | 標準ライブラリ | pip install pydantic | 標準ライブラリ(typing) |
| 実行時検証 | なし | あり — 検証して型変換する | なし — ヒントのみ |
| 実行時コスト | 軽い(実体のあるオブジェクト) | いくらか(検証コスト) | ゼロ(正体は dict) |
| ネストの自動パース | なし、手動 | あり、model_validate が再帰する | なし、正体は dict |
| camelCase エイリアス | 元のキーを保持 | Field(alias=...) | functional 構文 |
| 向いている用途 | 内部データ構造 | API レスポンス・webhook・設定 | 既存の dict コードへの型付け |
{"id": 101, "name": "Ada Lovelace", "active": true} のような小さな形なら、3 つのターゲットはほとんど同じに見える。JSON から生成する dataclass は、標準ライブラリのオブジェクトをくれる。
from dataclasses import dataclass
@dataclass
class Root:
id: int
name: str
active: bool
Pydantic 版は BaseModel に差し替わる。見た目は似ているが、実行時にその存在価値を示す。
from pydantic import BaseModel
class Root(BaseModel):
id: int
name: str
active: bool
そして JSON から作る TypedDict は、新しいものを何も割り当てずに、素の dict の形を記述する。
from typing import TypedDict
class Root(TypedDict):
id: int
name: str
active: bool
フィールドは同じ、しかし契約はまるで違う 3 つだ。同じサンプルから 3 つとも生成できることの価値は、ツール上で切り替えながら、仕事に合わせてモードを選べる点にある。自分の管理下にないデータをパースするなら Pydantic、データが内部のものなら dataclass、すでに引き回している dict を mypy に理解させたいだけなら TypedDict、という具合だ。
変換ツールはどうクラスを推論するか
与えたものはほぼ 3 つのルールで説明できる。オブジェクトの形ごとに 1 クラス、配列はキー単位でのマージ、そして欠損値の慎重な扱いだ。
構造の推論 — オブジェクトごとに名前付きクラス 1 つ
異なるオブジェクトの形は、それぞれ独立した名前付きクラスになる。ネストしたオブジェクトはインライン展開されず、別個の定義として引き上げられ、参照される。
{ "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
ネストした owner は独自の Owner クラスになり、フィールドから参照される。順序に注目してほしい。子クラスは、それを使う親より先に出力される。だからこのモジュールは from __future__ import annotations なしで動く。同一の形は重複排除もされるので、同じ構造を持つ 2 つのフィールドは、コピーを作らずに 1 つのクラスを共有する。
配列のマージと Optional フィールド
オブジェクトの配列を渡すと、変換ツールはそれらをキー単位でマージし、1 つの要素型にまとめる。一部の要素にはあるが他の要素には欠けているキーは 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 はすべての要素に現れるので、必須のままだ。nick は 2 人目のユーザーにないので、Optional[str] になる。単一のオブジェクトが弱いサンプルなのは、まさにこれが理由だ。ツールがフィールドを Optional と印を付けられるのは、キーがどこかで実際に欠けているのを目にしたときだけ。代表的な配列を与えれば、推論は本当の形を反映する。
Optional、None、空配列
いくつかの値は、それ単体では型を持たない。そして変換ツールはそのことに正直だ。一部の配列要素で欠けているキーは Optional[T] になる。ずっと null のままのフィールドは Optional[Any] になる。JSON の null だけでは、意図された型について何もわからないからだ。空の配列や、要素の型が混在した配列は List[Any] に落ちる。どれもバグではない。ツールが推測を控えているだけだ。それぞれの Any は、具体的な型を示すサンプルが手に入り次第、実際の型に置き換えればいい。なお、Python の任意精度 int のおかげで、大きな ID があっても、Rust や JavaScript のようにより広い数値型へ追い込まれることは決してない。
各モードでの camelCase キーの扱い
JSON のキーはしばしば camelCase だが、Python らしいフィールドは snake_case だ。ここに唯一の正解はなく、各モードはこれを別々のやり方で解決する。その違いは、出力をコピーする前に理解しておく価値がある。
dataclass。 dataclass には組み込みのエイリアス機構がない。Root(**data) を動かし続けるため、変換ツールは、キーが妥当な Python 識別子である限り、それをフィールド名としてそのまま保持する。だから publicRepos は publicRepos のままだ。無理に snake_case にすれば、**data は TypeError で壊れてしまう。
Pydantic v2。 ここでは Python らしい結果が得られる。フィールドは snake_case に改名され、Field(alias=...) で正確な JSON キーへ対応づけられる。さらに model_config = ConfigDict(populate_by_name=True) が付き、フィールド名でもエイリアスでもモデルを構築できる。
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")
これがあるからこそ、Root.model_validate(api_json) は本物の camelCase ペイロードを往復させられる一方で、コードは自然に読める。
TypedDict。 ただの識別子キーはそのまま使われる。識別子でないキーが現れた途端、変換ツールは正確なキーを保つために functional な TypedDict('User', {...}) 構文へ切り替える。これは次の節で扱う。
Python の予約語と識別子でないキー
JSON のキーはただの文字列なので、API が class、from、first-name を送ってくるのを止めるものは何もない。これらはどれも、素の Python 属性として書けば構文エラーになる。だから変換ツールがそれらを無害化する。
Python の予約語であるキーには、末尾にアンダースコアが付く。class は class_ に、from は from_ になる。Pydantic モードではさらに Field(alias=...) も保持され、モデルは元のワイヤ上のキーを読み続けられる。
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")
ハイフン、スペース、あるいは先頭が数字のキーは、dataclass と Pydantic の各モードで妥当な識別子へ無害化される。リテラルのキーをそのまま保てるのは TypedDict だけだ。functional な形を使うので、"first-name" は手つかずで生き残る。
from typing import TypedDict
Root = TypedDict("Root", {"first-name": str, "2fa": bool})
結果は常に動作し、TypedDict モードではキーは API が送ってきたものとバイト単位で一致したまま残る。
生成したクラスで JSON をパースする
クラスを生成するのは仕事の半分だ。そこに JSON を読み込む段になると、3 つのモードははっきり分かれる。そして、よくある思い込みが一つ、ここで裏切られる。
dataclass。 平坦なオブジェクトなら、json.loads に Root(**data) を足すだけで直接動く。
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)
しかし dataclass は再帰しない。JSON にネストしたオブジェクトがあると、Root(**data) はトップレベルを埋めるだけで、子はどれも生成した Owner クラスではなく素の dict のまま残る。ツリー全体をパースするには、子を自分で組み立てるか、dacite や pydantic.dataclasses のようなライブラリを使うか、ツールを Pydantic モードに切り替えるかだ。これは、ほとんどの変換ツールが決して触れない落とし穴だ。
Pydantic v2。 一度の呼び出しでツリー全体をパースし、検証する。
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)
型の不一致は、静かに通り抜けるのではなく、明快な ValidationError を送出する。これが依存の見返りだ。モデルは実行時のバリデータなので、model_validate の呼び出しが値を返したという事実は、データが一致したことの保証になる。型チェッカーがそう信じているだけの静的な型付けとは、そこが違う。
TypedDict。 実行時には、TypedDict はただの dict だ。data: Root = json.loads(text) という注釈は、純粋に静的チェッカーのためのもの。コードが走るときには何も検証されない。本当に強制したいなら、CI で mypy を回すか、Pydantic へ移るかだ。モードを選ぶことは、要するに実行時の保証をどれだけ欲しいかを選ぶことでもある。そして契約が型を超える必要があるときは、JSON Schema 検証ガイド が、構造を端から端まで強制する方法を扱っている。
Pydantic v2 と v1 — 何が変わったか
変換ツールが出力するのは v2 の構文だ。いまだ v1 で動いているコードベースは多く、v2 の出力を v1 のプロジェクトに貼ると、ロジックではなく改名されたメソッドのところで失敗する。対応関係はこうだ。
| タスク | Pydantic v2(ツールの出力) | Pydantic v1 |
|---|---|---|
| dict をパース | model_validate(data) | parse_obj(data) |
| 生の JSON をパース | model_validate_json(text) | parse_raw(text) |
| モデルを設定 | model_config = ConfigDict(...) | 内部 class Config |
| dict へシリアライズ | model_dump() | dict() |
| JSON へシリアライズ | model_dump_json() | json() |
エイリアスの振る舞いも厳しくなった。v2 では、フィールド名でもエイリアスでもモデルを構築するために、Field(alias=...) と並べて populate_by_name=True が必要になる。ツールがそれを含めているのはこのためだ。実践的なルールは短い。可能なら v2 にアップグレードすること。より速く、いまの開発が向かう先でもあるからだ。そうでなければ、model_validate を parse_obj へ、ConfigDict を内部 Config へ書き戻せばいい。
json-to-python vs quicktype vs datamodel-code-generator vs 手書き
JSON から Python クラスを生成する唯一最良の方法はない。JSON がどこにあるか、そして何を出発点にできるかで変わる。
| アプローチ | 向いている用途 | 備考 |
|---|---|---|
| オンライン変換ツール(本ツール、jsonlint、codeshack) | 使い捨ての変換、機密ペイロード、インストール不要 | サンプルから推論、完全にクライアントサイド |
| quicktype | 多言語出力、パイプラインでのコード生成 | 同じくサンプル駆動。ただし Python はエイリアスやモード選択のサポートが薄い |
| datamodel-code-generator | JSON Schema や OpenAPI から Pydantic を生成 | 入力はサンプルではなくスキーマ。手元にあればより権威がある |
| 手書きでクラスを書く | 小さなペイロード、構文の学習 | 完全に掌握できるが、退屈でずれやすい |
身につける価値のある区別はこうだ。オンライン変換ツールと quicktype はどちらも JSON の サンプル から型を推論するが、datamodel-code-generator は JSON Schema や OpenAPI の仕様を信頼できる情報源として読む。探索や使い捨てにはサンプル系のツールを使い、契約がすでに存在するならスキーマ駆動のものに手を伸ばせばいい。コードベースが Python ではなく TypeScript なら、同じサンプル駆動のアプローチが JSON to TypeScript 変換ツール で使えるし、その側面は JSON to TypeScript インターフェースガイド が深く掘り下げている。強い型付けのコンパイル言語における実行時検証の観点なら、JSON to Rust 構造体ガイド がよい対照になる。serde は Rust の Pydantic だ。
JSON から Python を生成するときのよくある落とし穴
生成されたクラスは出発点にすぎない。本番のデータで出力を頼りにする前に、次の点に気をつけてほしい。
- 1 つのサンプルで全部の形が見えることはめったにない。
Optionalは、実際に食い違う配列要素からしか推論できない。代表的な配列を貼れば、オプショナル性は 1 つのたまたまのオブジェクトからの推測ではなく、正確になる。 - dataclass は検証しない。
Root(**data)はフィールドを代入するだけ。型のチェックもしないし、何にも再帰しない。強制が必要なら Pydantic を使う。 - ネストした JSON は
Root(**data)を壊す。 埋まるのはトップレベルだけで、子は dict のまま残る。Pydantic のmodel_validateに切り替えるか、dacite を使う。 - ずっと
nullのフィールドはOptional[Any]になる。 これは普通のオプショナルではなくプレースホルダーとして扱い、型がわかり次第、実際の型を与える。 - v1 プロジェクトに v2 の出力。
model_validateは未定義になる。v2 にアップグレードするか、parse_objに書き換える。 strのままの日付では日付演算ができない。 フィールドをdatetimeに切り替えるか、Pydantic ならdatetimeフィールドを使えば、ISO 8601 を代わりにパースしてくれる。
よくある質問
dataclass、Pydantic、TypedDict、JSON にはどれを使うべき?
依存ゼロの内部データホルダーには dataclass を使う。信頼できない入力をパースまたは検証するなら Pydantic v2 を使う。実行時に型を強制してくれるからだ。すでに手元にある dict に静的なヒントを付けたいだけで、実行時コストを避けたいなら TypedDict を使う。データが自分の管理の外から来るなら、既定で Pydantic にする。
Pydantic は実行時に JSON を検証する?
する。Root.model_validate(data) はコードの実行中に型を検証して変換し、不正な入力には ValidationError を送出する。dataclass はどちらもしない。Root(**data) はフィールドを代入するだけだ。TypedDict もどちらもしない。mypy のような静的チェッカーのためのヒントであり、実行時にはただの dict として振る舞う。
JSON が時々省くフィールドはどう型付けする?
Optional[T] として型付けする。キーが一部の配列要素にしか現れないとき、変換ツールは自動でそれを Optional と印を付ける。Pydantic では、そのフィールドに = None のような既定値を与えれば、キーが欠けても検証エラーにならない。dataclass のフィールドも field(default=None) で同じように既定値を持てる。
dataclass でネストした JSON のパースに失敗するのはなぜ?
Root(**data) が再帰しないからだ。トップレベルのフィールドは埋めるが、ネストしたオブジェクトは、生成した子クラスではなく素の dict のまま残る。ツリー全体を一度の呼び出しでパースするには、Pydantic の model_validate に切り替えるか、dacite や pydantic.dataclasses で再帰を足す。
大きな JSON 整数はどんな Python 型になる?
int だ。Python の整数は任意精度なので、snowflake や 2^53 を超える Discord ID も、精度を失わずただの int のまま残る。JavaScript のような精度の脱落に出くわすことはないし、Rust に強いられるように i64 と u64 のどちらかを選ぶ必要もない。
camelCase の JSON キーは各モードでどう扱われる?
dataclass は元のキーを保持するので Root(**data) がそのまま動く。Pydantic はフィールドを snake_case に改名し、Field(alias="camelKey") に populate_by_name=True を足すので、本物の API データを往復させられる。TypedDict は、妥当な識別子でないキーを保つために functional な TypedDict('Name', {...}) 構文を使う。
class のような Python 予約語の JSON キーはどう扱う?
変換ツールは末尾にアンダースコアを足し、class を class_ に変えてコードを妥当にする。Pydantic モードではさらに Field(alias="class") を足してフィールドを元のワイヤ上のキーへ対応づけるので、名前は合法でありながら、モデルは本物のペイロードを読み続けられる。
オンラインの JSON to Python 変換ツールを使うとき、私の JSON は非公開?
非公開だ。変換は 100% ブラウザ内で JavaScript によって走る。トークン、ID、顧客データを含め、君の JSON はページを離れず、サーバーに送られることも決してない。ページさえ読み込まれれば、オフラインでも動く。準備ができたら、自分のペイロードで JSON to Python 変換ツールを試して みてほしい。