All Articles

PythonでJSONを扱うとき

 仕事でPythonを使ってサーバーレス開発をやっているのですが、サーバーレスになるとどうしてもREST APIを使ったデータのやり取りが多くなってきます。この時に多用するのがJSON形式のデータを変換する「json」ライブラリです。

jsonライブラリの使い方

import json
from datetime import datetime

person = {
    "name": "jackkitte",
    "age": "秘密だぞ〜",
    "created_at": datetime(2020, 6, 25)
}

json.dumps(person)
# TypeError: datetime.datetime(2020, 6, 25, 0, 0) is not JSON serializable

 上記のようにjsonライブラリをインポートして、json.dumps()関数を使うことでJSON形式の文字列を出力できます。しかしながら上記の場合ですと、dict型のデータの中にdatatime型のオブジェクトがあるためにTypeErrorを起こしています。jsonライブラリではこちらのドキュメントにある型しか対応しておりません(以下の対応表)。
そのためPythonの組込み以外のクラスオブジェクトやインスタンスオブジェクトが含まれるとTypeErrorとなるのです。

Python JSON
dict object
lilst, tuple array
str string
int, floatとintやfloatの派生列挙型 number
True true
False false
None null

 それではこの問題をどう解決すればいいのかと言いますと、jsonライブラリのdefault引数に関数を設定し、その関数に対応していない型が含まれていた場合の処理を記述することで、TypeErrorを回避し正常にJSON形式の文字列を出力することができます。

def support_json_format(obj):
    if isinstance(obj, datetime):
        return obj.isoformat()
    raise TypeError(repr(obj) + " is not JSON serializable")

person = {
    "name": "jackkitte",
    "age": "秘密だぞ〜",
    "created_at": datetime(2020, 6, 25)
}

json.dumps(person, default=support_json_format)
# {"created_at": "2020-06-25T00:00:00", "age": "秘密だぞ〜, "name": "jackkitte"}

ちなみに、AWSのサービスを使ってる時に遭遇したTypeErrorに、DynamoDBのDecimal型とLambdaのinvokeのレスポンスで返ってくるbotocore.response.StreamingBodyオブジェクトがあったので、以下のように対処しました。

from botocore.response import StreamingBody # boto3をpip install
from decimal import Decimal

def support_json_format(obj):
    if isinstance(obj, Decimal):
        return float(obj)
    elif isinstance(obj, datetime):
        return obj.isoformat()
    elif isinstance(obj, StreamingBody):
        data = obj.read()
        return data.decode('utf-8')
    raise TypeError(repr(o) + " is not JSON serializable")