この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
API GatewayとLambdaでWebAPIを作ったとき、リクエストJSONのバリデーションを行うと思います。 その方法はいくつかあります。
- API Gatewayでやる
- Lambdaコードでやる(自前 or ライブラリ)
たとえば、Pythonであれば、下記のライブラリがあります。
そんなJSONバリデーションですが、Pythonでisinstance()
を使ったときに、bool型なのにint型と判定されるケースに遭遇したので、起こったことやどうすればよかったのかをまとめてみました。
ハマったこと
たとえば
次のようなJSONがあります。TODOリストをイメージしています。
{
"taskId": "test001",
"title": "あああ",
"message": "てすとてすと",
"deadlineTimestamp": 123456789,
"isCompleted": False
}
このJSONをisinstance()
を使って次のようにバリデーションしていました。(本当は文字数とかも確認してますがここでは省略しています)
def validate1(data):
task_id = data['taskId']
title = data['title']
message = data['message']
deadline_timestamp = data['deadlineTimestamp']
is_completed = data['isCompleted']
if not isinstance(task_id, str):
return False
if not isinstance(title, str):
return False
if not isinstance(message, str):
return False
if not isinstance(deadline_timestamp, int):
return False
if not isinstance(is_completed, bool):
return False
return True
何が起こったか
上記メソッドのテストを書いているときに気づいたのです。次のJSONがバリデーションOKになることを。
{
"taskId": "test001",
"title": "あああ",
"message": "てすとてすと",
"deadlineTimestamp": True,
"isCompleted": False
}
簡単に再現してみる
isinstance()
にbool型を与えて、int型か?を確認すると、True
になりました。
>>> isinstance(True, int)
True
>>> isinstance(False, int)
True
ほかは想定通りになります。
>>> isinstance(True, str)
False
>>> isinstance(123, int)
True
>>> isinstance(123, str)
False
どうしてこうなった
isinstance()
の仕様でした。
Return True if the object argument is an instance of the classinfo argument, or of a (direct, indirect or virtual) subclass thereof.
初めて知ったのですが、PythonのBool型は、Integer(整数)のサブクラスとして実装されています。
Python の Bool 型は整数のサブクラスとして実装されています。
つまり、PythonのBool型は整数のサブクラスのため、isinstance(True, int)
は「int型のサブクラスにBool型があるのでTrue
になる」と理解しました。
では、どうするか
type()を使う
オブジェクトの判定であればisinstance()
が推奨されていますが、今回のようにJSONバリデーションをする場合はちょっと嬉しくないです。
そこでtype()
を使えばと考えました。
引数が1つだけの場合、object の型を返します。
オブジェクトの型の判定には、 isinstance() 組み込み関数を使うことが推奨されます。これはサブクラスを考慮するからです。
実際にやってみた
type()
を使えば、型情報が返ってきます。コレを元にif文の条件式を書けばOKです。
>>> type(True)
<class 'bool'>
>>> type(False)
<class 'bool'>
>>> type(123)
<class 'int'>
たとえば
冒頭のバリデーション関数をtype()
を使うようにしてみました。
def validate2(data):
task_id = data['taskId']
title = data['title']
message = data['message']
deadline_timestamp = data['deadlineTimestamp']
is_completed = data['isCompleted']
if type(task_id) != str:
return False
if type(title) != str:
return False
if type(message) != str:
return False
if type(deadline_timestamp) != int:
return False
if type(is_completed) != bool:
return False
return True
さいごに
発見したのは偶然ですが気づけてよかったです。気をつけたいと思いました。