PythonでJSONファイル・文字列の読み込み・書き込み

Pythonの標準ライブラリのjsonモジュールを使うとJSON形式のファイルや文字列をパースできます。整形してファイルや文字列として出力・保存することもできます。 基本的な使い方を説明します。 データ分析ライブラリpandasのpandas.DataFrameとして読み書きする場合は

ここでは、以下のようにモジュールをインポートします。いずれも標準ライブラリに含まれているので追加でインストールする必要はない。

import json
from collections import OrderedDict
import pprint

pprintは結果を見やすくするためにインポートしています。JSONの処理自体には使わない。

以下の内容について説明します。

JSON文字列を辞書に変換: json.loads() 順番を保持: 引数object_pairs_hook バイト列を変換 JSONファイルを辞書として読み込み: json.load() 読み込んだ辞書の値の取得・変更・削除・追加 文字列として整形して出力: json.dumps() 区切り文字を指定: 引数separators インデントを指定: 引数indent キーでソート: 引数sort_keys Unicodeエスケープ指定: 引数ensure_ascii ファイルとして保存: json.dump() JSONファイルの新規作成・更新(修正・追記) JSONファイル・文字列を扱う上での注意点 Unicodeエスケープ 引用符

JSON文字列を辞書に変換: json.loads()

JSON文字列を辞書に変換するにはjson.loads()関数を使います。 以下のように第一引数に文字列を指定すると辞書に変換されます。

s = r'{"C": "\u3042", "A": {"i": 1, "j": 2}, "B": [{"X": 1, "Y": 10}, {"X": 2, "Y": 20}]}'

print(s)
# {"C": "\u3042", "A": {"i": 1, "j": 2}, "B": [{"X": 1, "Y": 10}, {"X": 2, "Y": 20}]}

d = json.loads(s)

pprint.pprint(d, width=40)
# {'A': {'i': 1, 'j': 2},
#  'B': [{'X': 1, 'Y': 10},
#        {'X': 2, 'Y': 20}],
#  'C': 'あ'}

print(type(d))
# <class 'dict'>

文字列の\u3042はUnicodeエスケープシーケンス。JSONでは全角文字などの非ASCII文字がUnicodeエスケープされている場合があります。 例ではraw文字列を使ってバックスラッシュを\ではなく\で記述しています。なお、ややこしいですが、Unicodeエスケープシーケンスとraw文字列で無効化するエスケープシーケンスは別物。

json.loads()とこのあと説明するjson.load()では、特になにも設定しなくてもUnicodeエスケープシーケンスが対応する文字列に変換されます。 読み込んだ辞書の値の取得や変更については後述します。

順番を保持: 引数object_pairs_hook

Python3.6までは言語仕様として辞書(dict型オブジェクト)は要素の順序を保持していない(保持している実装もある)。 このため、変換された辞書では元のJSON文字列での順番が保持されない場合があります。 引数object_pairs_hookに順序付き辞書であるOrderedDict型を指定すると、返り値がOrderedDict型となり、順序が保持されたまま変換されます。OrderedDictをインポートしておく必要があるので気をつけてください。

od = json.loads(s, object_pairs_hook=OrderedDict)

pprint.pprint(od)
# OrderedDict([('C', 'あ'),
#              ('A', OrderedDict([('i', 1), ('j', 2)])),
#              ('B',
#               [OrderedDict([('X', 1), ('Y', 10)]),
#                OrderedDict([('X', 2), ('Y', 20)])])])

OrderedDictについては

バイト列を変換

Python3.6から、json.loads()の第一引数として文字列だけでなくバイト列(bytes型)を指定できるようになりました。

b = s.encode()

print(b)
# b'{"C": "\\u3042", "A": {"i": 1, "j": 2}, "B": [{"X": 1, "Y": 10}, {"X": 2, "Y": 20}]}'

print(type(b))
# <class 'bytes'>

db = json.loads(b)

pprint.pprint(db, width=40)
# {'A': {'i': 1, 'j': 2},
#  'B': [{'X': 1, 'Y': 10},
#        {'X': 2, 'Y': 20}],
#  'C': 'あ'}

print(type(db))
# <class 'dict'>

以前のバージョンではバイト列は指定できないので、decode()メソッドで文字列に変換してjson.loads()に渡します。

sb = b.decode()

print(sb)
# {"C": "\u3042", "A": {"i": 1, "j": 2}, "B": [{"X": 1, "Y": 10}, {"X": 2, "Y": 20}]}

print(type(sb))
# <class 'str'>

dsb = json.loads(sb)

pprint.pprint(dsb, width=40)
# {'A': {'i': 1, 'j': 2},
#  'B': [{'X': 1, 'Y': 10},
#        {'X': 2, 'Y': 20}],
#  'C': 'あ'}

print(type(dsb))
# <class 'dict'>

最終的にjson.loads()に渡すのであれば特に気にしなくてもいいが、decode()メソッドの第一引数encodingに'unicode-escape'を指定すると、Unicodeエスケープシーケンスが変換された文字列を取得できます。

sb_u = b.decode('unicode-escape')

print(sb_u)
# {"C": "あ", "A": {"i": 1, "j": 2}, "B": [{"X": 1, "Y": 10}, {"X": 2, "Y": 20}]}

print(type(sb_u))
# <class 'str'>

dsb_u = json.loads(sb_u)

pprint.pprint(dsb_u, width=40)
# {'A': {'i': 1, 'j': 2},
#  'B': [{'X': 1, 'Y': 10},
#        {'X': 2, 'Y': 20}],
#  'C': 'あ'}

print(type(dsb_u))
# <class 'dict'>

json.loads()の出力は同じ。 Unicodeエスケープについての詳細は

JSONファイルを辞書として読み込み: json.load()

JSONファイルを辞書として読み込むにはjson.load()関数を使います。 第一引数にファイルオブジェクトを指定する以外はjson.loads()と同じ。 ファイルオブジェクトは組み込み関数open()などで取得できます。

json.loads()の例と同じ文字列が書き込まれたファイルを使います。

with open('data/src/test.json') as f:
    print(f.read())
# {"C": "\u3042", "A": {"i": 1, "j": 2}, "B": [{"X": 1, "Y": 10}, {"X": 2, "Y": 20}]}

json.load()でファイルから読み込み。

with open('data/src/test.json') as f:
    df = json.load(f)

pprint.pprint(df, width=40)
# {'A': {'i': 1, 'j': 2},
#  'B': [{'X': 1, 'Y': 10},
#        {'X': 2, 'Y': 20}],
#  'C': 'あ'}

print(type(df))
# <class 'dict'>

読み込んだ辞書の値の取得・変更・削除・追加

json.load(), json.loads()でJSONファイル・文字列の内容を辞書として取得できます。 辞書オブジェクトの操作を例とともに示す。 なお、順序付き辞書OrderedDictは辞書dictのサブクラスなので、以下の操作がそのまま使えます。 値の取得 辞書の値は[キー]で指定して取得できます。ネストした要素の値は[キー][キー]のように連続して記述すれば問題ありません。

pprint.pprint(d, width=40)
# {'A': {'i': 1, 'j': 2},
#  'B': [{'X': 1, 'Y': 10},
#        {'X': 2, 'Y': 20}],
#  'C': 'あ'}

print(d['A'])
# {'i': 1, 'j': 2}

print(d['A']['i'])
# 1

辞書のリストが要素となっている場合は、[インデックス]で指定します。

print(d['B'])
# [{'X': 1, 'Y': 10}, {'X': 2, 'Y': 20}]

print(d['B'][0])
# {'X': 1, 'Y': 10}

print(d['B'][0]['X'])
# 1

当然ながら、別の変数に代入することもできます。

value = d['B'][1]['Y']
print(value)
# 20

存在しないキーを指定するとエラーになるが、get()メソッドを使うと存在しないキーを指定しても問題ありません。

# print(d['D'])
# KeyError: 'D'

print(d.get('D'))
# None

値の変更 [キー]で指定して新たな値を代入できます。

d['C'] = 'ん'
pprint.pprint(d, width=40)
# {'A': {'i': 1, 'j': 2},
#  'B': [{'X': 1, 'Y': 10},
#        {'X': 2, 'Y': 20}],
#  'C': 'ん'}

要素の削除 pop()メソッドでキーを指定して要素(キーと値のペア)を削除できます。

d.pop('C')
pprint.pprint(d, width=40)
# {'A': {'i': 1, 'j': 2},
#  'B': [{'X': 1, 'Y': 10},
#        {'X': 2, 'Y': 20}]}

pop()のほかdel文でも削除できます。

要素の追加 [キー]で新たなキー(存在しないキー)を指定して値を代入すると、新たなキーと値が要素として追加されます。

d['C'] = 'あ'
pprint.pprint(d, width=40)
# {'A': {'i': 1, 'j': 2},
#  'B': [{'X': 1, 'Y': 10},
#        {'X': 2, 'Y': 20}],
#  'C': 'あ'}

他の辞書を連結したり、複数の要素をまとめて追加したい場合はupdate()メソッドを使います。

そのほかの辞書操作 辞書をforループでまわしたり、キーの存在を確認したりしたい場合は、

またOrderedDictの場合に、任意の位置に新たな要素を追加したり、要素を順番を並べ替えたりする方法については

辞書関連の記事は以下から。

文字列として整形して出力: json.dumps()

辞書をJSON形式の文字列として出力するにはjson.dumps()を使います。 第一引数に辞書を指定すると、JSON形式の文字列が取得できます。

pprint.pprint(d, width=40)
# {'A': {'i': 1, 'j': 2},
#  'B': [{'X': 1, 'Y': 10},
#        {'X': 2, 'Y': 20}],
#  'C': 'あ'}

sd = json.dumps(d)

print(sd)
# {"A": {"i": 1, "j": 2}, "B": [{"X": 1, "Y": 10}, {"X": 2, "Y": 20}], "C": "\u3042"}

print(type(sd))
# <class 'str'>

OrderedDict型もそのまま指定できます。

pprint.pprint(od)
# OrderedDict([('C', 'あ'),
#              ('A', OrderedDict([('i', 1), ('j', 2)])),
#              ('B',
#               [OrderedDict([('X', 1), ('Y', 10)]),
#                OrderedDict([('X', 2), ('Y', 20)])])])

sod = json.dumps(od)

print(sod)
# {"C": "\u3042", "A": {"i": 1, "j": 2}, "B": [{"X": 1, "Y": 10}, {"X": 2, "Y": 20}]}

print(type(sod))
# <class 'str'>

引数を設定することによって整形できます。

区切り文字を指定: 引数separators

デフォルトではキーと値の間が:、要素間が,で区切られます。 引数separatorsでそれぞれの区切り文字をタプルで指定でき、空白を無くして切り詰めたり、まったく違う文字にしたりできます。

print(json.dumps(d, separators=(',', ':')))
# {"A":{"i":1,"j":2},"B":[{"X":1,"Y":10},{"X":2,"Y":20}],"C":"\u3042"}

print(json.dumps(d, separators=(' / ', '-> ')))
# {"A"-> {"i"-> 1 / "j"-> 2} / "B"-> [{"X"-> 1 / "Y"-> 10} / {"X"-> 2 / "Y"-> 20}] / "C"-> "\u3042"}

インデントを指定: 引数indent

引数indentをにインデント幅(文字数)を指定すると、要素ごとに改行・インデントされます。

print(json.dumps(d, indent=4))
# {
#     "A": {
#         "i": 1,
#         "j": 2
#     },
#     "B": [
#         {
#             "X": 1,
#             "Y": 10
#         },
#         {
#             "X": 2,
#             "Y": 20
#         }
#     ],
#     "C": "\u3042"
# }

デフォルトはindent=Noneで改行なし。indent=0とすると、インデントはなしで改行だけされます。

キーでソート: 引数sort_keys

引数sort_keys=Trueとすると、辞書の要素がキーによってソートされます。

print(json.dumps(od))
# {"C": "\u3042", "A": {"i": 1, "j": 2}, "B": [{"X": 1, "Y": 10}, {"X": 2, "Y": 20}]}

print(json.dumps(od, sort_keys=True))
# {"A": {"i": 1, "j": 2}, "B": [{"X": 1, "Y": 10}, {"X": 2, "Y": 20}], "C": "\u3042"}

Unicodeエスケープ指定: 引数ensure_ascii

これまでの例のように、デフォルトでは全角文字などの非ASCII文字がUnicodeエスケープされて出力されます。 引数ensure_ascii=Falseとすると、Unicodeエスケープされません。

print(json.dumps(od, ensure_ascii=False))
# {"C": "あ", "A": {"i": 1, "j": 2}, "B": [{"X": 1, "Y": 10}, {"X": 2, "Y": 20}]}

ファイルとして保存: json.dump()

辞書をJSON形式のファイルとして保存するにはjson.dump()を使います。 第二引数にファイルオブジェクトを指定する以外はjson.dumps()と同じ。第一引数に辞書を指定し、indentなど他の引数も同様に使えます。 open()でファイルを開く際に書き込みモード'w'を指定する必要があるので気をつけてください。

with open('data/dst/test2.json', 'w') as f:
    json.dump(d, f, indent=4)

結果を確認。

with open('data/dst/test2.json') as f:
    print(f.read())
# {
#     "A": {
#         "i": 1,
#         "j": 2
#     },
#     "B": [
#         {
#             "X": 1,
#             "Y": 10
#         },
#         {
#             "X": 2,
#             "Y": 20
#         }
#     ],
#     "C": "\u3042"
# }

既存ファイルのパスを指定すると上書き、存在しないファイルのパスを指定すると新規作成になります。ただし、ファイルの直上までのディレクトリ(フォルダ)は存在していなければエラー(FileNotFoundError例外)となります。

JSONファイルの新規作成・更新(修正・追記)

JSONファイルを新規作成・更新(修正・追記)する流れを例とともに説明します。 JSONファイルの新規作成 辞書オブジェクトを作成。

d_new = {'A': 100, 'B': 'abc', 'C': 'あいうえお'}

open()でパスを指定、json.dump()で適宜整形して保存。

with open('data/dst/test_new.json', 'w') as f:
    json.dump(d_new, f, indent=2, ensure_ascii=False)

結果を確認。

with open('data/dst/test_new.json') as f:
    print(f.read())
# {
#   "A": 100,
#   "B": "abc",
#   "C": "あいうえお"
# }

JSONファイルの更新(修正・追記)

open()でパスを指定しjson.load()で既存のJSONファイルを読み込み。元の並びを保持したい場合は、引数object_pairs_hook=OrderedDictとします。

with open('data/dst/test_new.json') as f:
    d_update = json.load(f, object_pairs_hook=OrderedDict)

print(d_update)
# OrderedDict([('A', 100), ('B', 'abc'), ('C', 'あいうえお')])

辞書オブジェクトを適宜更新。

d_update['A'] = 200
d_update.pop('B')
d_update['D'] = 'new value'

print(d_update)
# OrderedDict([('A', 200), ('C', 'あいうえお'), ('D', 'new value')])

open()でパスを指定しjson.dump()で適宜整形して保存。便宜上、例では新しいパスを指定しているが、元のファイルパスを指定すると上書きとなります。

with open('data/dst/test_new_update.json', 'w') as f:
    json.dump(d_update, f, indent=2, ensure_ascii=False)

結果を確認。

with open('data/dst/test_new_update.json') as f:
    print(f.read())
# {
#   "A": 200,
#   "C": "あいうえお",
#   "D": "new value"
# }

JSONファイル・文字列を扱う上での注意点

jsonモジュールを使っている場合は特に気にしなくてもうまいこと処理してくれるが、jsonモジュールを使わずに処理するときに注意しなければならない点を挙げる。

Unicodeエスケープ

特にWeb APIで取得できるJSONはセキュリティのため全角文字などの非ASCII文字がUnicodeエスケープされている場合があります。 json.load(), json.loads()ではUnicodeエスケープシーケンス\uXXXXを適切に処理してくれるが、テキストファイルとして読み込む場合はエンコーディングに'unicode-escape'を設定しないとUnicodeエスケープされたままの文字列となります。 JSONファイルを読み込んだときに日本語が文字化けしたようになっている場合はこれが原因。 エンコーディングはopen()関数の引数で指定します。

with open('data/src/test.json') as f:
    print(f.read())
# {"C": "\u3042", "A": {"i": 1, "j": 2}, "B": [{"X": 1, "Y": 10}, {"X": 2, "Y": 20}]}

with open('data/src/test.json', encoding='unicode-escape') as f:
    print(f.read())
# {"C": "あ", "A": {"i": 1, "j": 2}, "B": [{"X": 1, "Y": 10}, {"X": 2, "Y": 20}]}

上でも書いたが、Unicodeエスケープされたバイト列を文字列にデコードする場合も注意が必要。 Python標準ライブラリのurllib.request.urlopen()はバイト列を返すので、Web APIにアクセスして取得したバイト列をエンコーディングを指定せずにデコードするとUnicodeエスケープされたままの文字列となります。 エンコーディングはdecode()メソッドの引数で指定します。

print(b)
# b'{"C": "\\u3042", "A": {"i": 1, "j": 2}, "B": [{"X": 1, "Y": 10}, {"X": 2, "Y": 20}]}'

print(b.decode())
# {"C": "\u3042", "A": {"i": 1, "j": 2}, "B": [{"X": 1, "Y": 10}, {"X": 2, "Y": 20}]}

print(b.decode(encoding='unicode-escape'))
# {"C": "あ", "A": {"i": 1, "j": 2}, "B": [{"X": 1, "Y": 10}, {"X": 2, "Y": 20}]}

なお、バイト列の場合も最終的にjson.loads()で辞書に変換するのであれば、json.loads()の内部でUnicodeエスケープシーケンスが処理されるので特に問題ない。 Unicodeエスケープについての詳細は

引用符

JSONではキーや文字列を囲む引用符はダブルクォート"でなければならない。 json.dump(), json.dumps()では適切に処理してくれるが、独自に文字列を生成して引用符がシングルクォート'になっていると、json.load(), json.loads()でエラーとなります。 例えば、辞書オブジェクトをstr()で文字列に変換すると引用符としてシングルクォート'が使われるので、json.loads()でエラーとなります。

d = {"A": 100, "B": 'abc', "C": 'あいうえお'}

print(str(d))
# {'A': 100, 'B': 'abc', 'C': 'あいうえお'}

# print(json.loads(str(d)))
# JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)

json.dumps()で文字列に変換すれば問題ありません。

print(json.dumps(d))
# {"A": 100, "B": "abc", "C": "\u3042\u3044\u3046\u3048\u304a"}

print(json.loads(json.dumps(d)))
# {'A': 100, 'B': 'abc', 'C': 'あいうえお'}

print(type(json.loads(json.dumps(d))))
# <class 'dict'>
Last Updated: 6/26/2019, 10:34:03 PM