スクリプトのお勉強

小ネタ: Python のdataclass でNone or “”を使わないJSONデータを定義する

投稿日:2022年12月18日 更新日:

PythonをREST APIのクライアントとして作成する際、dataclassesを使用しようと思いました。型が見た瞬間分かるし、しっかりしているからです。

でも、そのREST API は””やNoneのデータを受け付けない仕様でした。なので、JSONとして送信する際は、削除する必要があります。

作成に何故か苦労したので、その方法を書いておきます。

環境

  • WSL2(ubuntu 22.04 LTS)
  • Python: 3.10.4

dataclassとは

「dataclassとはデータを保持のためのclassである」とのことです。

ただ、実はそれほどdataclassには思い入れがなく、一つ前はgolangを書いていたので、当然データを先に定義するだろうと思いこんで使っていたに過ぎません。

でもまぁ先にデータ定義しても悪いことはないので、使ってみようと思いました。

Noneや””が入っていていてはいけない

WebUIのFormからvalidateした結果、入力しないデータは””で入ってきます。もちろん必須であれば””をvalidateで弾いてもいいですが、Optionalな値だとします。

そして、Formの内容を結果的にJSONとして送信するのですが、サーバー側の仕様で、Noneや””が入ってるとinvalidでエラーになります。

サーバ側を変えてもらってもいいですが、送信すること自体意味がないのでクライアント側から送信しない方針がいいだろうと考えました。ここらへんはいろいろな事情によるでしょうけど。

意外とやりにくかった

簡単に実装できるかなと思ったのですが、意外に方法がわからず四苦八苦しました。

結局こうなった

サンプルコードは以下です。

from dataclasses import asdict, dataclass
from typing import List, Optional


@dataclass
class Five():
    data3: Optional[str] = None
    data4: Optional[str] = None

@dataclass
class Four():
    five: List[Five]
    data1: Optional[str] = None
    data2: Optional[str] = None

@dataclass
class Three():
    four: Four

@dataclass
class Two():
    data_test1: Optional[str] = None
    data_test2: Optional[str] = None

@dataclass
class Top():
    two: Two
    three: Three

# Dictで設定する方法
inited = {
    "two": {
        "data_test1": "data_test1"
    },
    "three": {
        "four": {
            "five": [{
                "data3": "data3"
            }],
            "data2": "data2"
        }
    }
}

instance = Top(**inited)
print(repr(instance))
print(asdict(instance))

# dataclassesだけで実現する方法
top = Top(two=Two(data_test1="data_test1", data_test2=""), three=Three(four=Four(five=[Five(data3="data3")], data2="data2")))
print(repr(top))
# None or ""の場合は辞書から除く
print(asdict(top, dict_factory=lambda tuples: {tuple[0]: tuple[1] for tuple in tuples if tuple[1]}))

実行結果

上記を実行した結果は以下のとおりです。

op(two={'data_test1': 'data_test1'}, three={'four': {'five': [{'data3': 'data3'}], 'data2': 'data2'}})
{'two': {'data_test1': 'data_test1'}, 'three': {'four': {'five': [{'data3': 'data3'}], 'data2': 'data2'}}}
Top(two=Two(data_test1='data_test1', data_test2=''), three=Three(four=Four(five=[Five(data3='data3', data4=None)], data1=None, data2='data2')))
{'two': {'data_test1': 'data_test1'}, 'three': {'four': {'five': [{'data3': 'data3'}], 'data2': 'data2'}}}

主なところ

実現する上で、主なところは以下です。

  • フィールドをOptional[str] = Noneにする
  • dict_factoryを指定してNoneや””を無視する

いろいろ検索しましたが、あまりちゃんと載っておらず、苦労しました。

上記でやりたいことができました。

参考

  • https://gist.github.com/xhiroga/6c5a8ac4749d25f5ecbffcfad9e750e4
    まんまのスニペット。
  • https://www.lifewithpython.com/2022/09/python-dataclasses-dict-factory.html
    dict_factoryが使えることに気がついたブログ

-スクリプトのお勉強
-,

執筆者:

関連記事

Vue.jsをbetter jQueryとして使う!FastAPI+Vue.jsと生成AIで作る住所録アプリ

昔あるところに。。 昔、jquery全盛で、Vue.jsが出てきたすぐに「これはjqueryを駆逐できるかも」と思ってjqueryの実装をVue.jsに変えようとしたことがあります。 結局全部を変えた …

Ruby(Rails)で最小限にREST APIを立ち上げる

簡単にREST APIを立ち上げたいなーと思うときが仕事であります。別にRubyでなくてもいいのですが、一応前提がRubyであることにします。 仕事で必要になるには 現在の仕事を鑑みると、最低限以下の …

PythonでAESを使用して暗号/復号する

1.つづき Pythonで、PKCS#12の公開鍵で暗号、秘密鍵で復号するプログラムの続きです。 今回はAESで暗号化/復号を行い、通信しながらRSA暗号/復号,AES暗号/復号を組み合わせたいと思い …

Djangoアプリサンプル – 画像ファイルアップロード + 顔モザイク(統合編)

顔モザイク Djangoアプリ 前々回 前回 を統合して、Djangoアプリを作成してみようと思います。 前提インストール 前回、ubuntu 18を前提に記述しましたが、CentOS7(CentOS …

Python3でMongoDBを使ってみる(MongoEngine)

仕事している過程で、ふと疑問に思うことがありました。非同期についてです。 非同期問題を解決する方法はたくさんあると思いますが、MongoDBで解決できるのかが気になったので試してみました。 非同期問題 …

google オプトアウト Click here to opt-out.