1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Python】jsonモジュールの使い方

Last updated at Posted at 2022-03-11

Pythonの標準ライブラリ、jsonモジュールの基本操作をまとめます。

環境
Python 3.8.10
Ubuntu 20.04.3

基本操作

json.load

対象ファイルからjsonオブジェクトを受け取る。返り値は辞書型

with open('jsonファイルのパス', 'r') as f:
    map_obj = json.load(f)
    print(type(map_obj) # <class 'dict'>

json.loads

文字列からjsonオブジェクトを受け取る。返り値は辞書型

str = '{"a": 1, "b": 2, "c": 3}'
map_obj = json.loads(str)
print(type(map_obj) 
# >> <class 'dict'>

json.dump

辞書型の値をjsonに変換してファイル出力する。第一引数には変換するデータ、第2引数には、出力先のファイルを指定する。

str = '{"a": 1, "b": 2, "c": 3}'
with open('jsonファイルのパス', 'w') as f:
    json.dump(str, f)

整形したい場合は、第三引数に指定する

str = '{"a": 1, "b": 2, "c": 3}'
with open('jsonファイルのパス', 'w') as f:
    json.dump(str, f, indent=2)

json.dumps

辞書型の値をjsonに変換しm、文字列として出力する。

map = {a: 1, b: 2, c: 3}
print(json.dump(str)) # '{"a": 1, "b": 2, "c": 3}'

エンコード、デコードのカスタマイズ

Pythonのjsonモジュールでエンコード・デコードできるデータ型は以下に限られている。

JSON ⇔ Python

  • object ⇔dict
  • array ⇔ list
  • string ⇔ str
  • number(int) ⇔ int
  • number(real) ⇔ float
  • true ⇔ True
  • false ⇔ False
  • nukk ⇔ None

そのため、他のデータ型を変換したい場合は、独自の関数を作成し、エンコーダーとデコーダーに渡してあげる必要がある。

エンコードのカスタマイズ(datetime型)

import json
from datetime import datetime

profiles = [
    {'Name': 'Tarou', 'Age': 15, 'Gender': 'male', 'birth_day': datetime(2000, 9, 11)},
    {'Name': 'Sayaka', 'Age': 20, 'Gender': 'female', 'birth_day': datetime(2000, 3, 11)},
]

def main():
    # dampsのclsにDatetimeJSONEncoderを渡す。
    # こうすることでデフォルトの方式ではなく
    # DatetimeJSONEncoderに定義したdefaultにしたがってエンコードされる。
    json_data = json.dumps(profiles, indent=4, cls=DatetimeJSONEncoder)
    print(json_data)

class DatetimeJSONEncoder(json.JSONEncoder):
    """JSONEncoderのdefaultを上書きする"""
    def default(self, o):
        if isinstance(o, datetime):
            # datetime型の時は、isoformatで文字列を変える。toordinalとかでも良い。
            return {'_type': 'datetime', 'value': o.isoformat()}
        else:
            # datetime型ではないときは、通常の方法の、defaultでエンコードする。
            # super()はjson.JSONEncoderと書いても良い。
            return super().default(o) 

if __name__ == '__main__':
    main()

出力結果

[
    {
        "Name": "Tarou",
        "Age": 15,
        "Gender": "male",
        "birth_day": {
            "_type": "datetime",
            "value": "2000-09-11T00:00:00"
        }
    },
    {
        "Name": "Sayaka",
        "Age": 20,
        "Gender": "female",
        "birth_day": {
            "_type": "datetime",
            "value": "2000-03-11T00:00:00"
        }
    }
]

受け取ったdatetime型を返す時に、'_type'プロパティを与えているのは、複数のデータ型に対応するため、およびデコードするための前準備です。

return {'_type': 'datetime', 'value': o.isoformat()}

というのも、、、エンコードするときは、データ型が分かります。
しかし、jsonからデコードするとき、jsonはただの文字列なので、データ型が判断できません。そのため、デコードするときに備えて、データの型を保存しておく必要があります。

エンコードのカスタマイズ(クラス)

import json

class Person():
    def __init__(self, name, age=0):
        self.name = name
        self.age = age

def main():
    bob = Person("Bob", 23)
    bob_json = json.dumps(bob, indent=4, cls=PersonEncoder)
    print(bob_json)

class PersonEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, Person):
            # classの情報を辞書型で取得するために、__dict__を仕様
            # あまり使わない方がよい、という記事もあり、ベストな書き方かは不明
            return {'_type': 'Person', 'value': o.__dict__}
        else:
            return json.JSONEncoder.default(o) 

if __name__ == '__main__':
    main()

出力結果

{
    "_type": "Person",
    "value": {
        "name": "Bob",
        "age": 23
    }
}

datetime型の時とほとんど同じなので、特別な解説は不要かと思います。

デコードのカスタマイズ(datetime型)

import json
from datetime import datetime

profiles_json = '''[
    {
        "Name": "Tarou",
        "Age": 15,
        "Gender": "male",
        "birth_day": {
            "_type": "datetime",
            "value": "2000-09-11T00:00:00"
        }
    },
    {
        "Name": "Sayaka",
        "Age": 20,
        "Gender": "female",
        "birth_day": {
            "_type": "datetime",
            "value": "2000-03-11T00:00:00"
        }
    }
]'''

def main():
    profiles = json.loads(profiles_json, cls=DatetimeDecoder)
    print(profiles[0])
    print(f'birth_day: {profiles[0]["birth_day"]}')
    print(f'type of birth_day: {type(profiles[0]["birth_day"])}')


class DatetimeDecoder(json.JSONDecoder):
    """json.JSONDecorderのobject_hookを上書きする"""
    # __init__で何をやっているかはいまいち分かっていない。
    def __init__(self, *args, **kwargs):
        json.JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs)

    def object_hook(self, o):
        if '_type' not in o:
            # _typeが定義されていない。つまり、jsonモジュールで変換がサポートされている型はそのまま返す。
            return o
        type = o['_type']
        if type == 'datetime':
            # _typeがdatetimeの場合、datetimeに変換して返す。。
            return datetime.fromisoformat(o['value'])

if __name__ == '__main__':
    main()

出力結果

{'Name': 'Tarou', 'Age': 15, 'Gender': 'male', 'birth_day': datetime.datetime(2000, 9, 11, 0, 0)}
birth_day: 2000-09-11 00:00:00
type of birth_day: <class 'datetime.datetime'>

datetime型でしっかり取得できているのが分かります。先ほども述べましたが、jsonは単なる文字列ではあるので、_typeのような形で型情報がなければ、このように正しいデータ型を取り出すのは難しいです。

デコードのカスタマイズ(クラス)

import json

def main():
    bob_json = '{"_type": "Person","value": {"name": "Bob","age": 23}}'
    person = json.loads(bob_json, cls=PersonDecoder)
    print(f'type: {type(person)}')
    print(f'name: {person.name}')
    print(f'age: {person.age}')

class Person():
    def __init__(self, name, age=0):
        self.name = name
        self.age = age

class PersonDecoder(json.JSONDecoder):
    """json.JSONDecorderのobject_hookを上書きする"""
    # __init__で何をやっているかはいまいち分かっていない。
    def __init__(self, *args, **kwargs):
        json.JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs)

    def object_hook(self, o):
        if '_type' not in o:
            # _typeが定義されていない。つまり、jsonモジュールで変換がサポートされている型はそのまま返す。
            return o
        type = o['_type']
        if type == 'Person':
            # _typeがPersonクラスのものは、クラスのインスタンスとして返す。
            return Person(**o['value'])

if __name__ == '__main__':
    main()

出力結果

type: <class '__main__.Person'>
name: Bob
age: 23

datetime型の変換とほぼ一緒です。

エンコードとデコート、datetime型とクラスすべて合わせる

分かりやすくするためにdatetimeとクラスを分けて書いていたのですが、一緒に書けます。

class MyEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, date):
            return {'_type': 'date', 'value': o.isoformat()}
        elif isinstance(o, Person):
            return {'_type': 'Person', 'value': o.__dict__}
        else:
            return json.JSONEncoder.default(self, o) 

class MyDecoder(json.JSONDecoder):
    def __init__(self, *args, **kwargs):
        json.JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs)

    def object_hook(self, o):
        if '_type' not in o:
            return o
        type = o['_type']
        if type == 'date':
            return date.fromisoformat(o['value'])
        elif type == 'Person':
            return Person(**o['value'])

if文にほかの条件も書いてあげれば、jsonがデフォルトではサポートしていないデータ型も、柔軟に変換できるようになります。試してみてください。
他にももっといい書き方があると思いますので、分かる方がいたら教えてください!

参考

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?