如何从文本文件中获取多个未用分隔符分隔的JSON对象?
我有成千上万的文本文件,这些文件里包含了多个JSON对象,但不幸的是,这些对象之间没有任何分隔符。
这些对象是以字典的形式存储的,有些字段本身也是对象。每个对象可能会有不同数量的嵌套对象。具体来说,一个对象可能长这样:
{field1: {}, field2: "some value", field3: {}, ...}
而在一个文本文件中,这样的对象成百上千地连在一起,没有任何分隔符。这就意味着我不能使用 json.load()
或者 json.loads()
来处理它们。
有没有什么建议可以解决这个问题?有没有已知的解析器可以做到这一点?
9 个回答
4
Sebastian Blask的回答提到了一个不错的思路,但对于这么简单的修改,使用正则表达式其实没必要。
objs = json.loads("[%s]"%(open('your_file.name').read().replace('}{', '},{')))
或者,写得更清楚一些
raw_objs_string = open('your_file.name').read() #read in raw data
raw_objs_string = raw_objs_string.replace('}{', '},{') #insert a comma between each object
objs_string = '[%s]'%(raw_objs_string) #wrap in a list, to make valid json
objs = json.loads(objs_string) #parse json
4
解决方案
据我所知,}{
这个组合在有效的 JSON 中是不会出现的,所以当你想从连接在一起的字符串中提取出单独的对象时,下面的方法是完全安全的(txt
是你文件的内容)。这不需要任何导入(甚至不需要导入 re
模块)来实现:
retrieved_strings = map(lambda x: '{'+x+'}', txt.strip('{}').split('}{'))
或者如果你喜欢用列表推导式(正如 David Zwicker 在评论中提到的),你可以这样使用:
retrieved_strings = ['{'+x+'}' for x in txt.strip('{}').split('}{'))]
这样做的结果是 retrieved_strings
会变成一个字符串列表,每个字符串都包含一个单独的 JSON 对象。你可以在这里查看证明:http://ideone.com/Purpb
示例
下面这个字符串:
'{field1:"a",field2:"b"}{field1:"c",field2:"d"}{field1:"e",field2:"f"}'
会被转换成:
['{field1:"a",field2:"b"}', '{field1:"c",field2:"d"}', '{field1:"e",field2:"f"}']
这在我提到的 示例中得到了证明。
23
这段代码可以把你的一串字符串中的“列表”解码成JSON对象:
from json import JSONDecoder
def loads_invalid_obj_list(s):
decoder = JSONDecoder()
s_len = len(s)
objs = []
end = 0
while end != s_len:
obj, end = decoder.raw_decode(s, idx=end)
objs.append(obj)
return objs
这里的好处是你和解析器相处得很好。因此,它会准确地告诉你错误出现的地方。
示例
>>> loads_invalid_obj_list('{}{}')
[{}, {}]
>>> loads_invalid_obj_list('{}{\n}{')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "decode.py", line 9, in loads_invalid_obj_list
obj, end = decoder.raw_decode(s, idx=end)
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/decoder.py", line 376, in raw_decode
obj, end = self.scan_once(s, idx)
ValueError: Expecting object: line 2 column 2 (char 5)
干净的解决方案(后面添加的)
import json
import re
#shameless copy paste from json/decoder.py
FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL
WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS)
class ConcatJSONDecoder(json.JSONDecoder):
def decode(self, s, _w=WHITESPACE.match):
s_len = len(s)
objs = []
end = 0
while end != s_len:
obj, end = self.raw_decode(s, idx=_w(s, end).end())
end = _w(s, end).end()
objs.append(obj)
return objs
示例
>>> print json.loads('{}', cls=ConcatJSONDecoder)
[{}]
>>> print json.load(open('file'), cls=ConcatJSONDecoder)
[{}]
>>> print json.loads('{}{} {', cls=ConcatJSONDecoder)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/__init__.py", line 339, in loads
return cls(encoding=encoding, **kw).decode(s)
File "decode.py", line 15, in decode
obj, end = self.raw_decode(s, idx=_w(s, end).end())
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/decoder.py", line 376, in raw_decode
obj, end = self.scan_once(s, idx)
ValueError: Expecting object: line 1 column 5 (char 5)