使用Pythons(2.7)'json'模块我正在寻找处理各种JSON提要。不幸的是,其中一些feed不符合JSON标准 - 特别是一些键没有包含在双重语音标记(“)中。这导致Python出错。
在编写一个丑陋的代码片段来解析和修复传入的数据之前,我想我会问 - 有没有办法允许Python解析这个格式错误的JSON或“修复”数据,以便它会是有效的JSON吗?
工作示例
import json
>>> json.loads('{"key1":1,"key2":2,"key3":3}')
{'key3': 3, 'key2': 2, 'key1': 1}
破碎的例子
import json
>>> json.loads('{key1:1,key2:2,key3:3}')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\Python27\lib\json\__init__.py", line 310, in loads
return _default_decoder.decode(s)
File "C:\Python27\lib\json\decoder.py", line 346, in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
File "C:\Python27\lib\json\decoder.py", line 362, in raw_decode
obj, end = self.scan_once(s, idx)
ValueError: Expecting property name: line 1 column 1 (char 1)
我已经编写了一个小的REGEX来修复来自这个特定提供商的JSON,但是我认为这是未来的一个问题。以下是我提出的建议。
>>> import re
>>> s = '{key1:1,key2:2,key3:3}'
>>> s = re.sub('([{,])([^{:\s"]*):', lambda m: '%s"%s":'%(m.group(1),m.group(2)),s)
>>> s
'{"key1":1,"key2":2,"key3":3}'
答案 0 :(得分:33)
您正在尝试使用JSON解析器来解析非JSON的内容。最好的办法是让饲料的创建者来修复它们。
我明白并非总是可行。您可以使用正则表达式修复数据,具体取决于它的破坏程度:
j = re.sub(r"{\s*(\w)", r'{"\1', j)
j = re.sub(r",\s*(\w)", r',"\1', j)
j = re.sub(r"(\w):", r'\1":', j)
答案 1 :(得分:17)
另一种选择是使用demjson模块,它可以在非严格模式下解析json。
答案 2 :(得分:11)
当匹配在字符串中时,Ned和cheeseinvert指出的正则表达式不会考虑。
请参阅以下示例(使用cheeseinvert的解决方案):
>>> fixLazyJsonWithRegex ('{ key : "a { a : b }", }')
'{ "key" : "a { "a": b }" }'
问题是预期的输出是:
'{ "key" : "a { a : b }" }'
由于JSON令牌是python令牌的子集,我们可以使用python的tokenize module。
如果我错了,请纠正我,但以下代码将修复所有情况下的懒惰json字符串:
import tokenize
import token
from StringIO import StringIO
def fixLazyJson (in_text):
tokengen = tokenize.generate_tokens(StringIO(in_text).readline)
result = []
for tokid, tokval, _, _, _ in tokengen:
# fix unquoted strings
if (tokid == token.NAME):
if tokval not in ['true', 'false', 'null', '-Infinity', 'Infinity', 'NaN']:
tokid = token.STRING
tokval = u'"%s"' % tokval
# fix single-quoted strings
elif (tokid == token.STRING):
if tokval.startswith ("'"):
tokval = u'"%s"' % tokval[1:-1].replace ('"', '\\"')
# remove invalid commas
elif (tokid == token.OP) and ((tokval == '}') or (tokval == ']')):
if (len(result) > 0) and (result[-1][1] == ','):
result.pop()
# fix single-quoted strings
elif (tokid == token.STRING):
if tokval.startswith ("'"):
tokval = u'"%s"' % tokval[1:-1].replace ('"', '\\"')
result.append((tokid, tokval))
return tokenize.untokenize(result)
因此,为了解析json字符串,您可能希望在json.loads失败时封装对fixLazyJson的调用(以避免对格式良好的json进行性能惩罚):
import json
def json_decode (json_string, *args, **kwargs):
try:
json.loads (json_string, *args, **kwargs)
except:
json_string = fixLazyJson (json_string)
json.loads (json_string, *args, **kwargs)
我在修复lazy json时遇到的唯一问题是,如果json格式错误,第二个json.loads引发的错误将不会引用原始字符串中的行和列,而是修改后的行。 / p>
作为最后一点,我只想指出,更新任何接受文件对象而不是字符串的方法都是直截了当的。
BONUS:除此之外,人们通常喜欢在使用json时包含C / C ++注释 配置文件,在这种情况下,您可以remove comments using a regular expression,或使用扩展版本并在一次传递中修复json字符串:
import tokenize
import token
from StringIO import StringIO
def fixLazyJsonWithComments (in_text):
""" Same as fixLazyJson but removing comments as well
"""
result = []
tokengen = tokenize.generate_tokens(StringIO(in_text).readline)
sline_comment = False
mline_comment = False
last_token = ''
for tokid, tokval, _, _, _ in tokengen:
# ignore single line and multi line comments
if sline_comment:
if (tokid == token.NEWLINE) or (tokid == tokenize.NL):
sline_comment = False
continue
# ignore multi line comments
if mline_comment:
if (last_token == '*') and (tokval == '/'):
mline_comment = False
last_token = tokval
continue
# fix unquoted strings
if (tokid == token.NAME):
if tokval not in ['true', 'false', 'null', '-Infinity', 'Infinity', 'NaN']:
tokid = token.STRING
tokval = u'"%s"' % tokval
# fix single-quoted strings
elif (tokid == token.STRING):
if tokval.startswith ("'"):
tokval = u'"%s"' % tokval[1:-1].replace ('"', '\\"')
# remove invalid commas
elif (tokid == token.OP) and ((tokval == '}') or (tokval == ']')):
if (len(result) > 0) and (result[-1][1] == ','):
result.pop()
# detect single-line comments
elif tokval == "//":
sline_comment = True
continue
# detect multiline comments
elif (last_token == '/') and (tokval == '*'):
result.pop() # remove previous token
mline_comment = True
continue
result.append((tokid, tokval))
last_token = tokval
return tokenize.untokenize(result)
答案 3 :(得分:6)
根据Ned的建议,以下内容对我有所帮助:
j = re.sub(r"{\s*'?(\w)", r'{"\1', j)
j = re.sub(r",\s*'?(\w)", r',"\1', j)
j = re.sub(r"(\w)'?\s*:", r'\1":', j)
j = re.sub(r":\s*'(\w+)'\s*([,}])", r':"\1"\2', j)
答案 4 :(得分:1)
在类似的情况下,我使用了ast.literal_eval
。 AFAIK,只有当常量null
(对应于Python None
)出现在JSON中时才会起作用。
鉴于您了解null/None
困境,您可以:
import ast
decoded_object= ast.literal_eval(json_encoded_text)
答案 5 :(得分:0)
除了Neds和cheeseinvert建议之外,添加(?!/)
应该避免提到的网址问题
j = re.sub(r"{\s*'?(\w)", r'{"\1', j)
j = re.sub(r",\s*'?(\w)", r',"\1', j)
j = re.sub(r"(\w)'?\s*:(?!/)", r'\1":', j)
j = re.sub(r":\s*'(\w+)'\s*([,}])", r':"\1"\2', j)
j = re.sub(r",\s*]", "]", j)