概要
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, 記事投稿