JSONをPythonに変換
「JSONをPythonに変換」は、サンプルのドキュメントからデータクラス、TypedDict、Pydanticモデルをブラウザ内で生成します。3つは異なる問いに答えます。データクラスは依存関係なしにデータを記述し、TypedDictはjson.loadsが既に返した辞書を記述し、Pydanticモデルは境界でそれを検証します。
ドキュメントが途中で終わっています。切れている可能性があります。
^
JSONをPythonに変換はどう動きますか?
形は、結果をどう使うつもりかで選んでください。3つは入れ替え可能ではなく、間違ったものを選ぶことが、生成されたPythonで最もよくある問題です。
データクラス、TypedDict、Pydanticモデル
@dataclassは、属性アクセス、等価性、有用なreprを備えた本物のオブジェクトを、サードパーティの依存なしで与えます。ただし検証は何もしないので、intと注釈したフィールドでも、構築時に渡された文字列をそのまま保持します。TypedDictは、json.loadsが既に返した素の辞書に注釈を付けるだけなので、何も構築する必要がなく、型チェッカーはキーの打ち間違いを依然として捕捉します。パース済みのJSONをそのまま受け渡す場合には、これが正解です。PydanticのBaseModelは、構築時に実際に検証と型変換を行います。APIクライアントが境界で求める振る舞いですが、依存関係と実行時の作業のコストが掛かります。
フィールドの順序はスタイルではなく正しさの問題
デフォルト値を持つデータクラスのフィールドは、持たないフィールドより前に置けません。Pythonは、最初に使うときではなく、importの時点でTypeErrorを投げるからです。省略可能なフィールドには=Noneが付くため、このジェネレーターは必須のフィールドをすべて先に出力します。ここでJSONのキー順を保つジェネレーターは、importに失敗するモジュールを出力することになります。
Optionalは「Noneになり得る」の意味であり、「省略できる」の意味ではない
特にPythonでは、ここでつまずく人が多いです。Optional[int]はoptional(省略可能)と読めて、実際の意味はnullable(nullになり得る)だからです。データクラスでは、一部のレコードで欠落するフィールドはOptional[int] = Noneとして出力されます。nullになり得るかは注釈が、欠落するかはデフォルト値が担います。TypedDictでは2つは本当に別の概念で、存在しないキーはNotRequired[int]、nullの値はOptional[int]になります。
改名したキーはエイリアスを残す
Pythonの慣習はsnake_caseですが、JavaScriptのサービスが出すJSONはたいてい違います。キーは改名され、元の名前は記録されます。データクラスやTypedDictではコメントとして、Pydanticでは本物のField(alias=…)としてです。Pydanticでは、記録するだけでなく動作が変わります。
前方参照はクォートする
ネストしたクラスは、それを使うクラスより先に出力されますが、注釈は文字列として書きます。手で並べ替えても、モジュールがimportできる状態が保たれます。
サンプル
[
{ "userId": 1, "team": "ops" },
{ "userId": 2 }
]生成結果
from dataclasses import dataclass
from typing import Optional
@dataclass
class User:
user_id: int # JSON key: userId
team: Optional[str] = NoneJSONをPythonに変換が対応するオプションとエッジケースは?
| パラメータ | 型 | デフォルト | 動作とエッジケース |
|---|---|---|---|
| 形状 | dataclass / TypedDict / pydantic | dataclass | 依存関係のないオブジェクト用のdataclass; json.loadsが返すdictを注釈するTypedDict; 境界で値を検証したい場合のpydantic。 |
| snake_case | 真偽値 | on | キーをPythonの規約に名前を変更します。pydanticでは実際のエイリアスになるため、パースはワイヤ形式と一致します。それ以外の場所ではコメントとして記録され、キーは自分でマッピングする必要があります。 |
| X | None | 真偽値 | off | Optional[X]の代わりにPython 3.10+の共用構文を使用します。より短く、typingのインポートを削除しますが、Python 3.9以下では構文エラーになります。 |
| string | Python型 | str | 検出されたフォーマットはdatetimeやUUIDにマッピングされません: json.loadsは文字列を返し、何かがそれをパースしない限り、datetimeとして注釈をつけることは嘘になります。 |
| 整数 / 数値 | Python型 | int / float | サンプルに小数部分があるかどうかで分けられます。Pythonの整数は任意精度なので、ここにint64の区別はありません。 |
| 任意フィールド | アノテーション | Optional[T] = None | TypedDictでは、存在しないキーはNotRequired[T]になり、これはPythonが区別するもので、他の2つの形式ではできません。 |
| フィールド順序 | 必須を先頭 | 強制 | デフォルト値のないdataclassフィールドは、デフォルト値のあるフィールドの後に続けることはできません。必須フィールドはJSONキーの順序に関係なく最初に出力されます。 |
| ネストしたオブジェクト | クラス | クォート付き参照 | 独自のクラスに昇格し、使用前に定義され、引用符付きの前方参照で参照されるため、モジュールは並べ替えを失いません。 |
よくある質問
データクラス、TypedDict、Pydanticのどれを選ぶべきですか?
データが信頼の境界を越える場合——HTTPレスポンス、Webhook、誰かがアップロードしたファイル——にはPydanticを使ってください。3つのうち、実際に何かを検証するのはこれだけです。既にパースした辞書に注釈を付け、社内で受け渡すだけなら、実行時のコストがゼロのTypedDictを使います。本物のオブジェクトが欲しく、依存関係を増やしたくない場合はデータクラスです。ただし、注釈は保証ではなく、型チェッカーのためのドキュメントだと受け入れてください。
フィールドの順序がJSONと違うのはなぜですか?
デフォルト値を持つデータクラスのフィールドは、持たないフィールドより後に置けないからです。Pythonは、あなたのコードが1行も実行される前に、モジュールがimportされる時点でTypeErrorを投げます。省略可能なフィールドには=Noneが付くので、必須のフィールドの後ろへ全部移動されます。TypedDictとPydanticにこのルールはなく、元の順序が保たれます。
Optional[int]はキーが欠落していてもよいという意味ですか?
いいえ。そしてこれが、Pythonの型付けで最もよくある誤読です。Optional[int]が意味するのは、値がNoneかもしれないことです。キーが存在しなくてもよいかは別の問いで、データクラスではデフォルト値が、TypedDictではNotRequiredが表現します。このジェネレーターは両者を区別します。一部のレコードで欠落するキーにはデフォルト値を付け、一部のレコードでnullになる値にはOptionalを付けます。
タイムスタンプがdatetimeではなくstrになるのはなぜですか?
json.loadsは文字列を返します。パースする処理がない限り、datetimeと注釈するのは嘘になります。Pydanticなら注釈をdatetimeに変えればISO 8601をパースしてくれます。Pydanticを選ぶ良い理由のひとつです。データクラスやTypedDictでは、注釈を変えても実行時には何も変わらず、読む人を皆誤導します。
生成されたTypedDictをPython 3.9で使えますか?
はい。importがtypingではなくtyping_extensionsからになっているのは、そのためです。NotRequiredが標準ライブラリに入ったのは3.11からですが、typing_extensionsはTypedDictが存在するすべてのバージョンへバックポートしています。逆に、X | Noneオプションを有効にするには3.10が必要です。
JSONはアップロードされますか?
いいえ。パース、推論、生成はすべてブラウザの中で行われます。Networkパネルで確認できます。サンプルからクラスを生成するのにサーバーは必要なく、貼り付けられるサンプルは、たいてい統合相手のシステムからの実際のペイロードです。