ネストされたJSONオブジェクトからPython Dataclassへの変換

ネストされたサブオブジェクトを含むJSONオブジェクトから複数のPython dataclassを生成する方法を学びます。命名、合成、デシリアライゼーションのコツを含みます。

Dataclasses

詳細な説明

DataclassによるネストされたJSON構造の処理

実際のJSONはフラットであることはまれです。APIはオブジェクトの中にオブジェクトを返し、各ネストレベルはPythonでは別のdataclassにマッピングされます。

JSONの例

{
  "id": 1,
  "name": "Acme Corp",
  "address": {
    "street": "123 Main St",
    "city": "Springfield",
    "geo": {
      "lat": 39.7817,
      "lng": -89.6501
    }
  }
}

生成されるPython

from dataclasses import dataclass

@dataclass
class Geo:
    lat: float
    lng: float

@dataclass
class Address:
    street: str
    city: str
    geo: Geo

@dataclass
class Company:
    id: int
    name: str
    address: Address

なぜ別々のDataclassにするのか?

各ネストされたオブジェクトを独自のdataclassに抽出することで:

  1. 再利用性Geo は座標が登場するすべての場所(店舗、イベント、配送先)で再利用できます。
  2. 可読性 — 深くネストされたdictは理解しにくいですが、名前付きクラスは自己文書化されます。
  3. テスト容易性AddressCompany から独立してインスタンス化しテストできます。

デシリアライゼーション

json.loads() はネストされたdictを返しますが、dataclassインスタンスは返しません。再帰的な構築ステップが必要です:

import json

data = json.loads(raw_json)
company = Company(
    id=data["id"],
    name=data["name"],
    address=Address(
        street=data["address"]["street"],
        city=data["address"]["city"],
        geo=Geo(**data["address"]["geo"]),
    ),
)

dacitecattrs などのライブラリでこれを自動化できます:

from dacite import from_dict
company = from_dict(data_class=Company, data=data)

宣言順序

Pythonでは、参照されるクラスはそれを使用するクラスの前に定義する必要があります(または from __future__ import annotations を使用)。コンバーターはリーフクラスの Geo を最初に、次に Address、最後に Company を配置します。

ユースケース

サードパーティAPIが住所やジオロケーションのサブオブジェクトを含む深くネストされた企業データを返し、mypyバリデーションによる各レベルへの型安全なアクセスが必要な場合に使用します。

試してみる — JSON to Python Converter

フルツールを開く