スクリプトのお勉強

小ネタ: 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が使えることに気がついたブログ

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

執筆者:

関連記事

seaborn + Pandas + Python によるグラフ描画(その1: 準備編)

グラフ描画してみよう 今現在の仕事で、グラフ描画する可能性があるので、少し練習してみようと思います。 題材について 突然ですが、私は確定拠出型年金に加入しています。証券会社はSBI証券 にしています。 …

pipenv + Apache + Django起動設定

仕事でwebアプリケーションを作成しています。 Djangoで作成し、webサーバをApache、環境をpipenvで設定したpython3環境上で動かす予定です。 Apacheが起動するまでに、苦労 …

IPS/IDS(Suricata)のインストール

とても遅まきながら、暇なのでIPS/IDSをこのサイトに組み込んでみます。 Suricata SuricataというOSSのIPSがあるそうなのでインストールしてみます。 環境 CentOS7(7.9 …

fastapi + SQLAlchemy で CRUDアプリケーションを作ってみる

概要 勉強用に、PythonでPostgresqlを制御しようと思います。の続きです。 前回でPostgreSQLと、データベース/テーブルまでは用意したので、今回はAPIを作成しようと思います。 実 …

Pythonでコマンド非同期起動

はじめに 小ネタです。 作成するプログラムの要件で、コマンドを起動して、そのコマンドが「継続」している/していないことを確認する、という要件があります。 具体的には、pingコマンドを普通に打つと、コ …

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