1. ホーム
  2. python

[解決済み] Pythonでファイル/ストリームから複数のJSON値を遅延して読み込むには?

2022-10-06 15:40:12

質問

Pythonでファイル/ストリームから複数のJSONオブジェクトを一度に読みたいと思います。残念ながら json.load() だけ .read() のみです。これを用いて単一のオブジェクトを読み込んだり、オブジェクトを遅延的に反復処理する方法はないようです。

これを行う方法はあるのでしょうか?標準ライブラリを使用することが理想的ですが、サードパーティのライブラリがあれば、それを代わりに使用したいと思います。

今のところ、私は各オブジェクトを別々の行に置き、その行に json.loads(f.readline()) を使っていますが、本当はこんなことをする必要はないのですが。

使用例

example.py

import my_json as json
import sys

for o in json.iterload(sys.stdin):
    print("Working on a", type(o))

in.txt

{"foo": ["bar", "baz"]} 1 2 [] 4 5 6

セッション例

$ python3.2 example.py < in.txt
Working on a dict
Working on a int
Working on a int
Working on a list
Working on a int
Working on a int
Working on a int

どのように解決するのですか?

もっともっと簡単な解決方法を紹介します。 秘密は、試行錯誤し、失敗し、例外の情報を使って正しくパースすることです。 唯一の制限は、ファイルがシーク可能でなければならないことです。

def stream_read_json(fn):
    import json
    start_pos = 0
    with open(fn, 'r') as f:
        while True:
            try:
                obj = json.load(f)
                yield obj
                return
            except json.JSONDecodeError as e:
                f.seek(start_pos)
                json_str = f.read(e.pos)
                obj = json.loads(json_str)
                start_pos += e.pos
                yield obj

編集:これはPython >=3.5でのみ動作することに気づきました。 それ以前では、失敗するとValueErrorが返され、例えば文字列から位置をパースする必要があります。

def stream_read_json(fn):
    import json
    import re
    start_pos = 0
    with open(fn, 'r') as f:
        while True:
            try:
                obj = json.load(f)
                yield obj
                return
            except ValueError as e:
                f.seek(start_pos)
                end_pos = int(re.match('Extra data: line \d+ column \d+ .*\(char (\d+).*\)',
                                    e.args[0]).groups()[0])
                json_str = f.read(end_pos)
                obj = json.loads(json_str)
                start_pos += end_pos
                yield obj