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

Modified: | Tags: Python, JSON

Pythonの標準ライブラリのjsonモジュールを使うと、JSON形式のファイルや文字列をパースして辞書(dict)などのオブジェクトとして読み込んだり、JSONに相当するオブジェクトを整形してJSON形式のファイルや文字列として出力・保存したりできる。

すべての引数について触れているわけではないので詳細は公式ドキュメントを参照されたい。

JSON形式の文字列やファイルをpandas.DataFrameとして読み書きする場合は以下の記事を参照。

また、JSON形式のWeb APIを取得するにはRequestsが便利。

以降のサンプルコードでは以下のようにjsonモジュールをインポートしている。標準ライブラリに含まれているので追加でインストールする必要はない。

import json

JSON文字列をPythonオブジェクトに変換: json.loads()

JSON形式の文字列を辞書などのPythonオブジェクトに変換するにはjson.loads()を使う。

json.loads()の第一引数に文字列を指定するとPythonオブジェクト(ここでは辞書)に変換される。

s = '{"A": {"X": 1, "Y": 1.0, "Z": "abc"}, "B": [true, false, null, NaN, Infinity]}'

d = json.loads(s)
print(d)
# {'A': {'X': 1, 'Y': 1.0, 'Z': 'abc'}, 'B': [True, False, None, nan, inf]}

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

JSONとPythonオブジェクトはデフォルトで以下のように対応している。

JSON Python
object dict
array list
string str
number (int) int
number (real) float
true True
false False
null None

JSONの仕様から外れるので表には記載されていないが、上の例のようにNaNInfinityも対応するfloatの値(非数nan、無限大inf)に変換される。

true, false, null, NaN, Infinityは大文字小文字が異なっているとエラーになるので注意。例えばTrueなどは変換されない。

s = '{"A": True}'

# d = json.loads(s)
# JSONDecodeError: Expecting value: line 1 column 7 (char 6)

例は省略するが、json.loads()の第一引数には文字列だけでなくバイト列(bytes)もそのまま指定できる(Python3.6以降)。

JSONファイルをPythonオブジェクトとして読み込み: json.load()

JSONファイルを辞書などのPythonオブジェクトとして読み込むにはjson.load()を使う。

組み込み関数open()などで取得できるファイルオブジェクトを第一引数に指定する。そのほかの使い方はjson.loads()と同じ。

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

with open('data/src/test.json') as f:
    print(f.read())
# {"A": {"X": 1, "Y": 1.0, "Z": "abc"}, "B": [true, false, null, NaN, Infinity]}

json.load()で読み込みできる。

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

print(d)
# {'A': {'X': 1, 'Y': 1.0, 'Z': 'abc'}, 'B': [True, False, None, nan, inf]}

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

PythonオブジェクトをJSON文字列として整形して出力: json.dumps()

辞書などのPythonオブジェクトをJSON形式の文字列に変換するにはjson.dumps()を使う。

第一引数に辞書を指定すると、JSON形式の文字列が返される。

d = {'A': {'X': 1, 'Y': 1.0, 'Z': 'abc'}, 'B': [True, False, None]}

s = json.dumps(d)
print(s)
# {"A": {"X": 1, "Y": 1.0, "Z": "abc"}, "B": [true, false, null]}

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

PythonオブジェクトとJSONはデフォルトで以下のように対応している。

Python JSON
dict object
list, tuple array
str string
int, floatintfloat の派生列挙型 number
True true
False false
None null

デフォルトでは全角文字などの非ASCII文字はUnicodeエスケープされて出力される。引数ensure_asciiFalseとすると、Unicodeエスケープされない。後述。

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

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

d = {'A': {'X': 1, 'Y': 1.0, 'Z': 'abc'}, 'B': [True, False, None]}
print(json.dumps(d, separators=(',', ':')))
# {"A":{"X":1,"Y":1.0,"Z":"abc"},"B":[true,false,null]}

print(json.dumps(d, separators=(' / ', '->')))
# {"A"->{"X"->1 / "Y"->1.0 / "Z"->"abc"} / "B"->[true / false / null]}

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

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

d = {'A': {'X': 1, 'Y': 1.0, 'Z': 'abc'}, 'B': [True, False, None]}
print(json.dumps(d, indent=4))
# {
#     "A": {
#         "X": 1,
#         "Y": 1.0,
#         "Z": "abc"
#     },
#     "B": [
#         true,
#         false,
#         null
#     ]
# }

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

キーでソート: 引数sort_keys

引数sort_keysTrueとすると、辞書の要素がキーによってソートされる。デフォルトはソートされずそのまま。

d = {'B': {'Y': 2, 'X': 1}, 'A': [3, 1, 2]}
print(json.dumps(d))
# {"B": {"Y": 2, "X": 1}, "A": [3, 1, 2]}

print(json.dumps(d, sort_keys=True))
# {"A": [3, 1, 2], "B": {"X": 1, "Y": 2}}

非数nanと無限大infの扱い: 引数allow_nan

デフォルトでは非数nanと無限大infはそれぞれNaN, Infinityに変換される。引数allow_nanFalseとすると、naninfが含まれているとエラーになる。

d = {'A': [float('nan'), float('inf')]}
print(json.dumps(d))
# {"A": [NaN, Infinity]}

# print(json.dumps(d, allow_nan=False))
# ValueError: Out of range float values are not JSON compliant

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

デフォルトでは全角文字などの非ASCII文字がUnicodeエスケープされて出力される。引数ensure_asciiFalseとすると、Unicodeエスケープされずそのまま出力される。

d = {'A': 'あいうえお', 'B': 'abc'}
print(json.dumps(d))
# {"A": "\u3042\u3044\u3046\u3048\u304a", "B": "abc"}

print(json.dumps(d, ensure_ascii=False))
# {"A": "あいうえお", "B": "abc"}

PythonオブジェクトをJSONファイルとして保存: json.dump()

辞書などのPythonオブジェクトをJSONファイルとして保存するにはjson.dump()を使う。

第二引数にファイルオブジェクトを指定する。そのほかの使い方はjson.dumps()と同じ。第一引数に辞書などを指定し、indentなど整形のための他の引数も同様に指定できる。

書き込みモード'w'open()でファイルを開いて指定する。

d = {
    'A': {'X': 1, 'Y': 1.0, 'Z': 'abc'},
    'B': [True, False, None, float('nan'), float('inf')]
}

with open('data/temp/test.json', 'w') as f:
    json.dump(d, f, indent=2)

出力されたファイルの中身は以下の通り。

with open('data/temp/test.json') as f:
    print(f.read())
# {
#   "A": {
#     "X": 1,
#     "Y": 1.0,
#     "Z": "abc"
#   },
#   "B": [
#     true,
#     false,
#     null,
#     NaN,
#     Infinity
#   ]
# }

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

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

JSONファイルを新規作成・更新(修正・追記)する流れを例とともに説明する。

JSONファイルの新規作成

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

d_new = {'A': 100, 'B': 'abc', 'C': [True, False]}

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

出力されたファイルの中身は以下の通り。

with open('data/temp/test_new.json') as f:
    print(f.read())
# {
#   "A": 100,
#   "B": "abc",
#   "C": [
#     true,
#     false
#   ]
# }

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

open()でパスを指定し、json.load()で既存のJSONファイルを読み込み。

with open('data/temp/test_new.json') as f:
    d_update = json.load(f)

print(d_update)
# {'A': 100, 'B': 'abc', 'C': [True, False]}

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

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

print(d_update)
# {'A': 200, 'C': [True, False], 'D': 'new value'}

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

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

出力されたファイルの中身は以下の通り。

with open('data/temp/test_new_update.json') as f:
    print(f.read())
# {
#   "A": 200,
#   "C": [
#     true,
#     false
#   ],
#   "D": "new value"
# }

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

jsonモジュールを使っている場合は特に気にしなくても適切に処理されるが、jsonモジュールを使わずにJSONファイル・文字列を処理するときに注意しなければならない点を挙げる。

引用符の扱い

JSONではキーや文字列を囲む引用符はダブルクォート"でなければならない。引用符がシングルクォート'になっていると、json.load(), json.loads()でエラーになるので注意。

例えば、辞書オブジェクトをstr()で文字列に変換すると引用符としてシングルクォート'が使われるので、json.loads()でエラーとなる。

d = {'A': 100, 'B': 'abc', 'C': [True, False]}

s = str(d)
print(str(d))
# {'A': 100, 'B': 'abc', 'C': [True, False]}

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

json.dumps()で文字列に変換すると、引用符としてダブルクォート"が使われる。json.loads()で正しく処理される。

s = json.dumps(d)
print(s)
# {"A": 100, "B": "abc", "C": [true, false]}

print(json.dumps(d))
# {"A": 100, "B": "abc", "C": [true, false]}

非ASCII文字列のUnicodeエスケープ処理

例えば、Web APIが返すJSONはセキュリティのため全角文字などの非ASCII文字がUnicodeエスケープされている場合がある。

ファイルの読み込み

Unicodeエスケープされたテキストファイルをopen()で読み込むときは、引数encoding'unicode-escape'を指定しないとUnicodeエスケープされたままの文字列となる。JSONファイルを読み込んだときに日本語が文字化けしたようになるのはこれが原因。

with open('data/src/test_u.json') as f:
    print(f.read())
# {"A": "\u3042\u3044\u3046\u3048\u304a", "B": "abc"}

with open('data/src/test_u.json', encoding='unicode-escape') as f:
    print(f.read())
# {"A": "あいうえお", "B": "abc"}

json.load()は、Unicodeエスケープシーケンス\uXXXXを適切に処理して、JSONファイルを辞書などのPythonオブジェクトとして読み込む。

with open('data/src/test_u.json') as f:
    print(json.load(f))
# {'A': 'あいうえお', 'B': 'abc'}

バイト列のデコード

Unicodeエスケープされたバイト列を文字列にデコードする場合も同様。decode()メソッドの引数encoding'unicode-escape'を指定しないとUnicodeエスケープされたままの文字列となる。

s = r'{"A": "\u3042\u3044\u3046\u3048\u304a", "B": "abc"}'
b = s.encode()
print(b)
# b'{"A": "\\u3042\\u3044\\u3046\\u3048\\u304a", "B": "abc"}'

print(b.decode())
# {"A": "\u3042\u3044\u3046\u3048\u304a", "B": "abc"}

print(b.decode(encoding='unicode-escape'))
# {"A": "あいうえお", "B": "abc"}

json.loads()は、Unicodeエスケープシーケンス\uXXXXを適切に処理して、バイト列を辞書などのPythonオブジェクトに変換する。

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

Unicodeエスケープについての詳細は以下の記事を参照。

関連カテゴリー

関連記事