スクリプトのお勉強

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

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

執筆者:

関連記事

Python3 – VCR.py でネットワーク系テストを簡単に作成する

1. 始めに python3で実装すると、モックテストをしたくなります。モックを使って、比較的簡単にテストできるからです。 問題はネットワーク系テスト モックテストで問題になるのは、外部に依存するテス …

gradleのcommandLineでリダイレクト

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

Nuxt.jsのFormで入力/確認/完了フォームを作成してみた(その1)

背景 今回は、Webアプリケーションの、フロントエンド系のお話です。ほとんどの場合、バックエンドなのですが、時々フロントエンドもするんですよね。。 私の派遣先では、入力フォームを以下のように分ける要望 …

Python3/ある日付から日付までの月/日/時間ごとの時刻を算出する

小ネタです。 要するに、時刻A と 時刻B を指定したときの、各時間間隔での時刻取得したかったです。 当初はdateutilを使う方法でなく、自前で実装しようと思ったのですが、面倒なことに気づきました …

Markdown to HTML(grip)

1.はじめに 最近の文書はほとんどMarkdownで書くのですが、それをHTMLに変換する方法を調べました。 ただし、私がMarkdownと思っている文法は「github-flavored-markd …

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