概要
前回 dataclass を使った JSON <-> オブジェクトの変換方法を紹介しました
今回はその中でもクセのある辞書データが配列内に直接置かれている場合を紹介したいと思います
また配列内にある辞書データはデータ構造がデータごとに異なる場合を想定しています
環境
- macOS 11.7.1
- Python 3.10.2
- dataclass-wizard 0.22.2
サンプルコード
from dataclasses import dataclass
from dataclass_wizard import JSONWizard
from typing import (List,
Optional)
@dataclass
class Framework():
sinatra: str
flask: str
@dataclass
class Lang():
ruby: str
python: str
@dataclass
class Profile():
lang: Optional[List[Lang]] = None
framework: Optional[List[Framework]] = None
@dataclass
class UserData(JSONWizard):
name: str
profile: List[Profile]
if __name__ == "__main__":
data = {
"name": "hawksnowlog",
"profile": [
{
"lang": [
{
"ruby": "3.1.2",
"python": "3.10.2",
}
]
},
{
"framework": [
{
"sinatra": "3.0.0",
"flask": "1.0.0",
}
]
},
]
}
result = UserData.from_dict(data)
print(result)
print(result.name)
print(result.profile[0].lang[0].ruby)
print(result.profile[1].framework[0].sinatra)
ちょっと解説
今回は profile というフィールドが配列でこの中にデータ構造の異なる辞書データが複数個入ることを想定しています
ポイントは Profile クラスでここにデータ構造が異なるデータをデフォルト None ですべて定義する点です
配列の場合順番にオブジェクトに変換されます 最初は lang 情報が変換されます
Profile クラスに渡されるデータは lang 情報だけなので必ず他のフィールドはデフォルト値で初期化しておく必要があります
lang 変換時には framework フィールドの情報は渡されないので None が設定されます
つまり配列内の最初のデータは lang がオブジェクトに変換され framework が None として変換されます
配列の2つ目の値は framework になります
framework が Profile クラスに渡されて変換されます
同様に framework 情報のみが渡されるので lang は None として変換されます
このように配列それぞれのデータごとに変換が行われるので配列内で管理するデータ構造分定義する必要がありまたそれぞれのフィールドはデフォルト値を必ず設定する必要があります
tuple でもいけます
あとから気づいたんですが tuple を使ってもいけます
この場合は tuple に定義する順番は配列内の順番と同じである必要があります
None もないのでこちらのほうが綺麗かもしれません
from dataclasses import dataclass
from dataclass_wizard import JSONWizard
from typing import List
@dataclass
class Framework():
sinatra: str
flask: str
@dataclass
class Lang():
ruby: str
python: str
@dataclass
class Frameworks():
framework: List[Framework]
@dataclass
class Langs():
lang: List[Lang]
@dataclass
class UserData(JSONWizard):
name: str
profile: tuple[Langs, Frameworks]
if __name__ == "__main__":
data = {
"name": "hawksnowlog",
"profile": [
{
"lang": [
{
"ruby": "3.1.2",
"python": "3.10.2",
}
]
},
{
"framework": [
{
"sinatra": "3.0.0",
"flask": "1.0.0",
}
]
},
]
}
result = UserData.from_dict(data)
print(result)
print(result.name)
print(result.profile[0].lang[0].ruby)
print(result.profile[1].framework[0].sinatra)
更に配列の順番が固定であればオブジェクトから直接参照できるようにできる
例えば今回のように 1 番目が lang 2 番目が framework のように配列に格納されている順番が保証されているのであればわざわざ profile からインデックスで参照せずに直接 lang と framework を参照してもいいと思います
その場合は __post_init__
と組み合わせるといい感じになると思います
from dataclasses import dataclass
from dataclass_wizard import JSONWizard
from typing import (List,
Optional)
@dataclass
class Framework():
sinatra: str
flask: str
@dataclass
class Lang():
ruby: str
python: str
@dataclass
class Frameworks():
framework: List[Framework]
@dataclass
class Langs():
lang: List[Lang]
@dataclass
class Profile():
lang: List[Lang]
framework: List[Framework]
@dataclass
class UserData(JSONWizard):
name: str
profile: tuple[Langs, Frameworks]
p: Optional[Profile] = None
def __post_init__(self):
self.p = Profile(self.profile[0].lang,
self.profile[1].framework)
if __name__ == "__main__":
data = {
"name": "hawksnowlog",
"profile": [
{
"lang": [
{
"ruby": "3.1.2",
"python": "3.10.2",
}
]
},
{
"framework": [
{
"sinatra": "3.0.0",
"flask": "1.0.0",
}
]
},
]
}
result = UserData.from_dict(data)
print(result)
print(result.name)
print(result.profile[0].lang[0].ruby)
print(result.profile[1].framework[0].sinatra)
print(result.p.lang[0].ruby)
print(result.p.framework[0].sinatra)
最後に
オブジェクトに変換する場合は JSON の構造に合わせて型を定義する必要があるのでうまく行かない場合は型定義がちゃんと JSON から抽出できているかを確認しましょう
0 件のコメント:
コメントを投稿