【问题标题】:How to extract multiple JSON objects from one file?如何从一个文件中提取多个 JSON 对象?
【发布时间】:2015-03-10 13:15:36
【问题描述】:

我对 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
 0   20140101      Yes
 1   20140102      No
 2   20140103      No
 …

有没有人知道处理此类问题的一般方法?

【问题讨论】:

标签: python json pandas dataframe parsing


【解决方案1】:

因此,正如在数组中包含数据的几个 cmets 中所提到的,它更简单,但随着数据集大小的增加,该解决方案在效率方面并不能很好地扩展。当您想要访问数组中的随机项时,您真的应该只使用可迭代对象,否则,生成器是要走的路。下面我制作了一个 reader 函数的原型,它分别读取每个 json 对象并返回一个生成器。

基本思想是向读者发出信号,在回车符"\n"(或Windows 的"\r\n")上进行拆分。 Python 可以使用file.readline() 函数来做到这一点。

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

但是,这种方法只有在文件按原样写入时才真正有效——每个对象由换行符分隔。下面我写了一个编写器的例子,它分离了一个 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 文件时的可读性有很大帮助,而且在更有效地使用内存方面也有很大帮助。

如果您在某个时候改变主意并且想要从阅读器中取出列表,Python 允许您将生成器函数放入列表中并自动填充列表。也就是说,只要写

lst = list(json_reader(file))

【讨论】:

  • “当你想访问数组中的随机对象时,你真的应该只使用迭代器”是什么意思?您的意思是“列表”而不是“迭代器”吗?
  • @Clément 我的意思是 Iterable。那是我的错。
  • Iterable 不提供随机访问,AFAIK
【解决方案2】:

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

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

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]

【讨论】:

  • 我也非常喜欢这个答案,除了以下几点:它需要将整个文件读入内存并使用JSONDecoder 的未记录功能。
  • 仅供参考:非空白字符有一个简单的转义:\S。大写变体是小写变体的否定(所以\W = [^\w]\D=[^\d] ecc。)
  • 如果文件有单行多 JSON 文件,这适用于 AWS Lambda。你能更详细地解释它是如何工作的吗?我无法理解 raw_decode 或它如何理解有效 json 何时开始或结束
【解决方案3】:

根据@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

【讨论】:

  • 这太好了,谢谢!如果您正在处理大型数据文件,请增大块大小(对于我来说,在 10MB-2GB 的文件中,大约 4MB 是最快的基准测试)否则您会从 raw_decode 中得到很多虚假异常,从而减慢速度。
【解决方案4】:

使用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 的内容是一个数组,其中包含表示每个元素的字典。

您可以轻松访问它,即:

data[0]["ID"]

【讨论】:

  • 这很酷,但会阻止您将文件用作无休止的流(例如,类似日志的仅附加文件数据)并消耗更多内存。
  • @exa,这是真的,但是如果您需要对此数据流进行仅附加日志记录,也许您应该查看 JSON 以外的格式来传输您的信息,因为 JSON 需要右括号对于所有数据结构,意味着非无限非流格式。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-08-10
  • 2022-06-15
  • 2018-09-22
  • 2020-12-25
  • 1970-01-01
  • 1970-01-01
  • 2018-08-03
相关资源
最近更新 更多