iMind Developers Blog

iMind開発者ブログ

Pythonでjsonの読み書き

概要

Pythonの標準ライブラリでdictionaryをjsonに変換して出力する。

また出力したjsonを読み込む。

バージョン情報

  • Python 3.7.4

サンプルデータ

本ページのサンプルコードでは下記のdictionaryを出力する。

obj = {
    'foo': 'hoge',
    'bar': [1, 2, 3, 4, 5],
    'baz': {'x': '日本語', 'y': 'テスト'}
}

dictionaryをjson文字列に変換

dictionaryをjson文字列に変換してみる。

json.dumpsを使う。

import json

json.dumps(obj)

    #=> '{"foo": "hoge", "bar": [1, 2, 3, 4, 5], "baz": {"x": "\\u65e5\\u672c\\u8a9e", "y": "\\u30c6\\u30b9\\u30c8"}}'

文字列のエスケープをしない

デフォルトでは上の例のように日本語がAscii文字にエスケープされる。

これをさせたくない場合は、ensure_asciiにFalseを設定する。

import json

json.dumps(obj, ensure_ascii=False)

    #=> '{"foo": "hoge", "bar": [1, 2, 3, 4, 5], "baz": {"x": "日本語", "y": "テスト"}}'

読みやすい形式で出力する

indentに数値を設定すると、指定値分だけインデントされた読みやすいjsonを返してくれる。

下記は2文字インデントするよう指定した例。

json.dumps(obj, indent=2, ensure_ascii=False)

    #=> {
    #=>   "foo": "hoge",
    #=>   "bar": [
    #=>     1,
    #=>     2,
    #=>     3,
    #=>     4,
    #=>     5
    #=>   ],
    #=>   "baz": {
    #=>     "x": "日本語",
    #=>     "y": "テスト"
    #=>   }
    #=> }

ファイルに出力

ファイルに出力する場合は wt でファイルをopenしてjson.dumpで出力する。

with open('example.json', 'wt') as fp:
    json.dump(obj, fp)

出力されたファイル

{"foo": "hoge", "bar": [1, 2, 3, 4, 5], "baz": {"x": "日本語", "y": "テスト"}}

gzip圧縮してファイル出力する

サイズが大きいファイルを扱う場合は、gzip.openで圧縮して出力することも多い。

import gzip

with gzip.open('example.json.gz', 'wt') as fp:
    json.dump(obj, fp)

pythonのgzipはあまり速くないのでネイティブなコマンドを呼ぶこともある。

import subprocess

with open('example.json', 'wt') as fp: 
    json.dump(obj, fp)
subprocess.call(['gzip', 'example.json'])

基本型以外のキーを無視する

下記のようにdictioonaryのキーに基本型(str, int, float, bool or None)以外のキーが入っていた場合はエラーになる。

import io
sio = io.StringIO()

obj2 = { 
     'foo': 1,
    sio: 'hoge' 
}

json.dumps(obj2)

    #=> TypeError: keys must be str, int, float, bool or None, not MyClass

この時、skipkeysにTrueが指定されていると自動で型の合わないキーは無視してくれる。

json.dumps(obj2, skipkeys=True)
    #=> '{"foo": 1}'

独自クラスを値に設定する

下記のような独自クラスが入ったdictionaryがあった場合。

class MyClass:
    def __init__(self, i):
        self.i = i

obj3 = {
    'my': MyClass()
}

json.dumps(obj3, skipkeys=True)

MyClassはシリアライズできないので下記のようなエラーになる。

TypeError: Object of type MyClass is not JSON serializable

JSONEncoderを自作して、MyClassだったら独自にエンコードする処理を入れれば動作する。

class MyClass:
    def __init__(self, i):
        self.i = i

obj3 = {
    'my': MyClass(3)
}

class MyEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, MyClass):
            return {'__MyClass__': obj.i}
        return json.JSONEncoder.default(self, obj)

json.dumps(obj3, cls=MyEncoder)

    #=> '{"my": {"__MyClass__": 3}}'

デコードしやすいように __MyClass__ というキー名を入れている。

出力されたjsonをロードする場合はJSONDecorderを自作してキーが __MyClass__ だったら個別の処理を入れるなどの処置が必要。

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

    def object_hook(self, dic):
        if '__MyClass__' in dic:
            return MyClass(dic['__MyClass__'])
        return dic

json.loads('{"my": {"__MyClass__": 3}}', cls=MyDecoder)
    #=> {'my': <__main__.MyClass at 0x7f30a02be490>}

Pythonにはpickle等のObjectをそのまま保存できる機能があるのでわざわざjsonでこんなことをするケースはあまりなさそうだけど。

json文字列の読込み

json文字列をPythonのobjectに変換する場合はloadsを使う。

json.loads('{"foo":1,"bar":2}') 
    #=> {'foo': 1, 'bar': 2}

jsonファイルの読込み

jsonファイルから読み込む。

with open('example.json', 'rt') as fp:
    obj = json.load(fp)

parse_float / parse_int

json.loadの処理にはfloatやintのパースに関する指定が用意されている。

下記はfloatが渡されたらDecimalに変換するコード。

import decimal

json.loads(
    '{"foo":1.0,"bar":2.0}',
    parse_float=decimal.Decimal)

    #=> {'foo': Decimal('1.0'), 'bar': Decimal('2.0')}

例えばfloatでは溢れるような数値を指定した場合。

何も指定しないと値が丸められてしまう。

json.loads( 
    '{"foo":1.000000000000000001,"bar":2.0}')

    #=> {'foo': 1.0, 'bar': 2.0}

decimalを指定するとこの問題は起こらない。

json.loads( 
    '{"foo":1.000000000000000001,"bar":2.0}',
    parse_float=decimal.Decimal)

    #=> {'foo': Decimal('1.000000000000000001'), 'bar': Decimal('2.0')}

strにすることもできる。

json.loads( 
    '{"foo":1.7,"bar":2.3}',
    parse_float=lambda x: int(round(float(x))))

    #=> {'foo': '1.000000000000000001', 'bar': '2.0'}

lambdaで変換処理を書くことも可能。下記はfloatにしてからroundしてintにしている。

json.loads( 
    '{"foo":1.7,"bar":2.3}',
    parse_float=lambda x: int(round(float(x))))

    #=> {'foo': 2, 'bar': 2}

parse_intは整数型に対して動作する。

下記はintもfloatにしてしまう例。

json.loads( 
    '{"foo":1,"bar":2}', 
    parse_int=float)  

parse_constant

parse_constantは'-Infinity', 'Infinity', 'NaN'に対して動作する。

Infinity, NaNが送られてきた場合、未指定であればそれぞれPythonのinf, nanに変換される。

json.loads( 
    '{"foo":null,"bar":Infinity, "baz": NaN}')

    #=> {'foo': None, 'bar': inf, 'baz': nan}

InfinityやNanをそれぞれ別の値に変換する例。

def convert(x):
    if x == 'Infinity':
        return '無限ホンダ'
    elif x == 'NaN':
        return 'カレー'
    else:
        return x

json.loads(
    '{"foo":null,"bar":Infinity, "baz": NaN}',
    parse_constant=convert)

    #=> {'foo': None, 'bar': '無限ホンダ', 'baz': 'カレー'}

改定履歴

Author: Masato Watanabe, Date: 2019-10-24, 記事投稿