スクリプトのお勉強

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

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

執筆者:

関連記事

gradleのcommandLineでリダイレクト

gradleというビルドツールがあります。なぜかRPMを作成するのに使ってます。Ansibleも使ってるんですがね。。 それはともかく、ここの通りなのですが、例えばls -lRの出力を、プロジェクトデ …

Pipenvでライブラリ(*.whl)をキャッシュする方法

仕事でWebアプリケーションを作成した場合、ガンガンとライブラリのバージョンを上げたりはしない場合があります。 そして、長期間放置されるタイプのWebアプリケーションの場合、使用しているライブラリが、 …

pipenv + Apache + Django起動設定

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

svelteをチュートリアルしてみる

年末の休みは、svelteのチュートリアルをしてみました。なんとなくやはりそう?なので。 svelteとは Webアプリのフロントエンド(UI)用フレームワークです。要するにReactみたいなもんです …

言語別ログイン機能パスワード保存処理方針

ちょっと前に、ログイン機能を作成した際、パスワードを暗号化するか、という議論を目にしたことがありました。 昔だと、「パスワードを暗号化しない」方で実装していましたが、最近はセキュリティが当たり前になっ …

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