我有一个文件,其中包含一个JSON对象的数组。 该文件超过1GB,因此我无法一次性将其加载到内存中。 我需要解析每个单独的对象。 我尝试使用ijson,但这将把整个数组作为一个对象加载,实际上与简单的json.load()
做的事情相同。
是否还有其他方法可以实现?
编辑:没关系,只需使用ijson.items()
并将前缀参数设置为"item"
。
您可以仅解析JSON文件一次,以查找每个一级分隔符(即属于顶级对象的逗号)的位置,然后将文件分成由这些位置指示的部分。例如:
{"a": [1, 2, 3], "b": "Hello, World!", "c": {"d": 4, "e": 5}}
^ ^ ^ ^ ^
| | | | |
level-2 | quoted | level-2
| |
level-1 level-1
在这里我们想要找到一级逗号, 分隔包含在顶层对象中的对象。我们可以使用一个生成器来解析JSON流并跟踪进入和退出嵌套对象。当它遇到未被引用的一级逗号时,会产生相应的位置。
def find_sep_pos(stream, *, sep=','):
level = 0
quoted = False # handling strings in the json
backslash = False # handling quoted quotes
for pos, char in enumerate(stream):
if backslash:
backslash = False
elif char in '{[':
level += 1
elif char in ']}':
level -= 1
elif char == '"':
quoted = not quoted
elif char == '\\':
backslash = True
elif char == sep and not quoted and level == 1:
yield pos
应用于上述示例数据,这将给出 list(find_sep_pos(example)) == [15, 37]
。
然后我们可以根据分隔符位置将文件分割成对应的部分,并通过 json.loads
分别加载每个部分:
import itertools as it
import json
with open('example.json') as fh:
# Iterating over `fh` yields lines, so we chain them in order to get characters.
sep_pos = tuple(find_sep_pos(it.chain.from_iterable(fh)))
fh.seek(0) # reset to the beginning of the file
stream = it.chain.from_iterable(fh)
opening_bracket = next(stream)
closing_bracket = dict(('{}', '[]'))[opening_bracket]
offset = 1 # the bracket we just consumed adds an offset of 1
for pos in sep_pos:
json_str = (
opening_bracket
+ ''.join(it.islice(stream, pos - offset))
+ closing_bracket
)
obj = json.loads(json_str) # this is your object
next(stream) # step over the separator
offset = pos + 1 # adjust where we are in the stream right now
print(obj)
# The last object still remains in the stream, so we load it here.
obj = json.loads(opening_bracket + ''.join(stream))
print(obj)
"foo\",\"bar"
。 - chepner