のーずいだんぷ

主に自分用メモですが、もしかしたら誰かの役に立つかもしれません

<python>json.load()でjsonを読み込めない(jsonlinesを読み込む方法)

はじめに

自然言語処理100本ノックの3章20番の問題を解いているときに詰まったエラー。

Pythonでjson形式の外部ファイルを読み込む場合、多くの場合json.load(fp)を使うと思う。

最初そのやり方を試みたところ次のようなエラーが出力された。

json.decoder.JSONDecodeError: Extra data: line 2 column 1 (char 27837)

正直エラーメッセージからはよくわからない部分が多かったのだが、APIリファレンスを調べると解決策が見つかった。

対策

今回は原因より先に対策。

json.load()はjsonモジュールにloadメソッドが直接定義されているのだが、他にもJSONDecoder()クラスがある。 このクラスメソッド、JSONDecoder.raw_decode()で使うとなんとかなりそうとのこと、実際うまく行った。

raw_decode()とは?

Python3のリファレンスに次のように説明がある。

docs.python.org

s (str インスタンスで JSON 文書で始まるもの) から JSON 文書をデコードし、Python 表現と s の文書の終わるところのインデックスからなる 2 要素のタプルを返します。 このメソッドは後ろに余分なデータを従えた文字列から JSON 文書をデコードするのに使えます。

つまり不完全なjson形式のファイルに使用することが出来るのだ。

正確には不完全なjsonファイルではなく、jsonlines形式のファイルを想定指定のだと思う。

jsonlinesファイルで実験してみる

次のようなjsonが\n区切りで複数行並んだファイルを用意する。

{"1": "test"}
{"2": "stage"}
{"3": "dev"}
{"4": "prod"}

json.loadでデコードしてみる。

>>> import json
>>> with open('jsonlines.json', 'r') as f:
...     res = json.load(f)
... 
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "/Users/******/.pyenv/versions/3.7.5/lib/python3.7/json/__init__.py", line 296, in load
    parse_constant=parse_constant, object_pairs_hook=object_pairs_hook, **kw)
  File "/Users/******/.pyenv/versions/3.7.5/lib/python3.7/json/__init__.py", line 348, in loads
    return _default_decoder.decode(s)
  File "/Users/*****/.pyenv/versions/3.7.5/lib/python3.7/json/decoder.py", line 340, in decode
    raise JSONDecodeError("Extra data", s, end)
json.decoder.JSONDecodeError: Extra data: line 2 column 1 (char 14)

先ほどと同じエラーが発生した。

JSONDecoder.raw_decode()でデコードしてみる

>>> res = []
>>> decoder = json.JSONDecoder()
>>> with open('jsonlines.json', 'r') as f:
...     line = f.readline()
...     while line:
...             res.append(decoder.raw_decode(line))
...             line = f.readline()
... 
>>> for i, d in enumerate(res):
...     print("{}: {}".format(i, d))
... 
0: ({'1': 'test'}, 13)
1: ({'2': 'stage'}, 14)
2: ({'3': 'dev'}, 12)
3: ({'4': 'prod'}, 13)

無事読み込めた。

原因

結果を見たところ、複数のjsonデータが区切り文字無しで連続していたようだった。

つまりjsonlinesもどきのような状態になっていた。

おわりに

こうやって見ると色々知らないメソッドがあると感じた。

たまにはリファレンスをざっと眺めてみるのも大事かもしれない。