きり丸の技術日記

技術検証したり、資格等をここに残していきます。

PythonでJSONを一部項目を無視して比較したい(I want to compare JSON in Python while ignoring certain items by deepdiff)

PythonでJSONを比較したいという記事を過去に書きました。しかし、日付項目等々の固定しづらい項目を無視して比較するというユースケースが達成できていませんでした。

今回の記事では、一部項目を無視して比較する方法を記載します。

前提

  • Python
    • 3.11
  • DeepDiff
    • 6.3.0

対応

DeepDiffを使用します。

導入

DeepDiffをインストールしてください。

pip install deepdiff
pipenv install deepdiff

比較

DeepDiffを使うには、DeepDiffのインスタンスを元に比較します。過去の記事と同様に、json.loadsメソッドを使ってJSONをDict型に変換します。

今回の場合は日付項目のcreatetime, updatetimeを無視するようにするため、DeepDiffのインスタンス作成時にexclude_pathsを渡します。

import json

from deepdiff import DeepDiff

def test_if_ignore_column_has_diff_then_True():
    # GIVEN
    json_data_1 = """
{
"name": "John",
"createtime": "2222-01-01T00:11:00Z",
"updatetime": "2222-04-10T15:30:00Z"
}
"""

    json_data_2 = """
{
"name": "John",
"createtime": "3333-01-01T00:11:00Z",
"updatetime": "3333-04-11T08:00:00Z"
}
"""
    dict_1 = json.loads(json_data_1)
    dict_2 = json.loads(json_data_2)

    # WHEN
    actual = DeepDiff(dict_1, dict_2, exclude_paths=["createtime", "updatetime"])

    # THEN
    assert actual == {}

ソースコード

終わりに

なかなかJSONの特定の項目を無視する方法が見つかりませんでした。

DeepDiff自体にはオプションが大量にあったため、細かい比較をする際にも役立ちそうです。2023年4月19日時点で、1.6KのStarもあることからPythonでのJSON比較のスタンダードなのかもしれません。

参考情報

類似情報

GitHubのPRをブランチ名でフィルタしたい(filtering pr by branch name in github)

基本的には公式ヘルプ読んでください。

環境

  • GitHub
    • 2022/11/04時点

対応

base:ブランチ名で特定のブランチに対するPRをフィルタリングできます。fromではなく、toのブランチ名を指しています。ブランチを切るときのbaseとして考えると、意味合いは正しいのですが、混乱しました。

base:{ブランチ名}
base:develop

逆にfromブランチでフィルタしたい場合は、head:ブランチ名を使用します。

head:{ブランチ名}
head:feature

なお、baseheadも前方一致で検索できます。

※ View advanced search syntaxから該当のヘルプページに遷移できます。

終わりに

地味ですが、重宝します。

検索するとすぐにひっかかるのですが一番最初に検索されるページは概要で、詳細ページに遷移しなかったのでブランチ名での検索はできないものだと勘違いしていました。

参考情報

Pythonでメソッド呼び出し元がNoneパラメータを渡さないようにする(doesn't call none parameter in Python)

メソッド呼び出し元で引数が指定されていない場合は、デフォルト値を与えられます。キーワード引数を使用すると、任意のパラメータに値を与えられます。

ただし、意図的にメソッド呼び出し元でNoneを渡してしまった場合、デフォルト値を使用できません。

今回の記事では、パラメータの値がNoneの場合はメソッドに渡さず、値が含まれている場合はメソッドに渡します。

前提

  • Python
    • 3.11

対応

辞書型をパラメータに使用します。辞書型で**を使用するとキーワード引数に展開されて使用できます。

class DictDomain:
    @staticmethod
    def action(a: str = "1", b: str = "2", c: str = "3"):
        print(a, b, c)


def test_param_method():
    dict = {"a": "a", "b": "b", "c": None}
    DictDomain.action(**dict)
    # print文の結果
    # a b None

辞書型で値がNoneの時にキーを削除する処理をします。

def test_param_method():
    dict = {"a": "a", "b": "b", "c": None}
    all_key_has_value = {k: v for k, v in dict.items() if v is not None}
    DictDomain.action(**all_key_has_value)
    # print文の結果
    # キーワードに渡されていないので、デフォルトの3が出力される
    # a b 3

ただ私が慣れていないこともあり、非常に読みづらいので必要な項目だけ辞書型に絞ったほうが分かりやすいと思います。

def test_param_method():
    dict = {"c": None}
    all_key_has_value = {k: v for k, v in dict.items() if v is not None}
    DictDomain.action("a"="a", "b"="b", **all_key_has_value)

注意点

辞書型を動的に加工するので静的解析が効きません。具体的には定義していないキーワードを指定していても、静的解析が効かなくなるのでTypoをした場合に気付くタイミングが遅れてしまいます。画像はVSCodeの警告の有無を表現しています。

import pytest

def test_undifined_parameter():
    with pytest.raises(TypeError):
        param = {"d": 1}
        DictDomain.action(**param)
        param2 = {k: v for k, v in param.items() if v is not None}
        DictDomain.action(**param2)

ソースコード

終わりに

Factory_boyを使用してデータのセットアップをしていましたが、NotNullで定義している項目に対してNoneを設定するとValidationErrorが発生します。このエラーを回避しようとしましたがFactory_boy側で回避する方法が見つからなかったため、呼び出し側で対処するようにしました。

テストコードなので次のコードのように単純化しようとしましたが、項目が多かったのと共通化したかったので、今回の方式を取ろうとしています。

if c:
    DictDomain.action("a"="a", "b"="b", "c"=c)
else:
    DictDomain.action("a"="a", "b"="b")

非常に使い道のうすい使い方だと思っていますが、ぜひ使ってみてください。

参考情報

類似情報