如何从一个文件中提取多个 JSON 对象?

How to extract multiple JSON objects from one file?

我对 Json 文件很陌生。如果我有一个包含多个 json 对象的 json 文件,例如:

{"ID":"12345","Timestamp":"20140101", "Usefulness":"Yes",
 "Code":[{"event1":"A","result":"1"},…]}
{"ID":"1A35B","Timestamp":"20140102", "Usefulness":"No",
 "Code":[{"event1":"B","result":"1"},…]}
{"ID":"AA356","Timestamp":"20140103", "Usefulness":"No",
 "Code":[{"event1":"B","result":"0"},…]}
…

我想将所有 "Timestamp" 和 "Usefulness" 提取到一个数据框中:

    Timestamp    Usefulness
 0   20140101      Yes
 1   20140102      No
 2   20140103      No
 …

有谁知道处理此类问题的一般方法吗?

使用 json 数组,格式为:

[
{"ID":"12345","Timestamp":"20140101", "Usefulness":"Yes",
  "Code":[{"event1":"A","result":"1"},…]},
{"ID":"1A35B","Timestamp":"20140102", "Usefulness":"No",
  "Code":[{"event1":"B","result":"1"},…]},
{"ID":"AA356","Timestamp":"20140103", "Usefulness":"No",
  "Code":[{"event1":"B","result":"0"},…]},
...
]

然后将其导入您的 python 代码

import json

with open('file.json') as json_file:

    data = json.load(json_file)

现在数据的内容是一个数组,其中包含代表每个元素的字典。

您可以轻松访问它,即:

data[0]["ID"]

因此,正如在包含数组中的数据的几个评论中提到的那样,该解决方案在效率方面随​​着数据集大小的增加而无法很好地扩展。当你想访问数组中的随机项时,你真的应该只使用可迭代对象,否则,生成器是可行的方法。下面我制作了一个 reader 函数的原型,它单独读取每个 json 对象和 returns 一个生成器。

基本思想是指示 reader 在回车符 "\n" 上拆分(或 "\r\n" 表示 Windows)。 Python 可以用 file.readline() 函数做到这一点。

import json
def json_reader(filename):
    with open(filename) as f:
        for line in f:
            yield json.loads(line)

但是,此方法仅在按原样编写文件时才真正起作用——每个对象由换行符分隔。下面我写了一个 writer 的例子,它分离了一个 json 对象数组并将每个对象保存在一个新行上。

def json_writer(file, json_objects):
    with open(file, "w") as f:
        for jsonobj in json_objects:
            jsonstr = json.dumps(jsonobj)
            f.write(jsonstr + "\n")

您也可以使用 file.writelines() 和列表理解来执行相同的操作:

...
    json_strs = [json.dumps(j) + "\n" for j in json_objects]
    f.writelines(json_strs)
...

如果您想附加数据而不是写入新文件,只需将 open(file, "w") 更改为 open(file, "a")

最后我发现这不仅对我尝试在文本编辑器中打开 json 文件时的可读性有很大帮助,而且对更有效地使用内存也有很大帮助。

关于这一点,如果您在某个时候改变主意并且想要一个 reader 之外的列表,Python 允许您将生成器函数放在列表中并填充列表自动地。也就是说,只要写

lst = list(json_reader(file))

更新:我写了一个不需要一次读取整个文件的解决方案。它对于 Whosebug 的答案来说太大了,但可以在这里找到 jsonstream.

您可以使用 json.JSONDecoder.raw_decode 来解码任意大的“堆叠”字符串 JSON(只要它们可以放入内存中)。 raw_decode 一旦它有一个有效的对象就停止,returns 最后一个位置不是解析对象的一部分。它没有记录,但您可以将此位置传回 raw_decode 并从该位置再次开始解析。不幸的是,Python json 模块不接受带有前缀空格的字符串。所以我们需要搜索以找到文档的第一个 none-空白部分。

from json import JSONDecoder, JSONDecodeError
import re

NOT_WHITESPACE = re.compile(r'[^\s]')

def decode_stacked(document, pos=0, decoder=JSONDecoder()):
    while True:
        match = NOT_WHITESPACE.search(document, pos)
        if not match:
            return
        pos = match.start()
        
        try:
            obj, pos = decoder.raw_decode(document, pos)
        except JSONDecodeError:
            # do something sensible if there's some error
            raise
        yield obj

s = """

{"a": 1}  


   [
1
,   
2
]


"""

for obj in decode_stacked(s):
    print(obj)

打印:

{'a': 1}
[1, 2]

根据@dunes 的回答添加了流媒体支持:

import re
from json import JSONDecoder, JSONDecodeError

NOT_WHITESPACE = re.compile(r"[^\s]")


def stream_json(file_obj, buf_size=1024, decoder=JSONDecoder()):
    buf = ""
    ex = None
    while True:
        block = file_obj.read(buf_size)
        if not block:
            break
        buf += block
        pos = 0
        while True:
            match = NOT_WHITESPACE.search(buf, pos)
            if not match:
                break
            pos = match.start()
            try:
                obj, pos = decoder.raw_decode(buf, pos)
            except JSONDecodeError as e:
                ex = e
                break
            else:
                ex = None
                yield obj
        buf = buf[pos:]
    if ex is not None:
        raise ex