如何自动修复无效的JSON字符串?

35

我从2gis API获得了以下JSON字符串。

{
    "api_version": "1.3",
    "response_code": "200",
    "id": "3237490513229753",
    "lon": "38.969916127827",
    "lat": "45.069889625267",
    "page_url": null,
    "name": "ATB",
    "firm_group": {
        "id": "3237499103085728",
        "count": "1"
    },
    "city_name": "Krasnodar",
    "city_id": "3237585002430511",
    "address": "Turgeneva,   172/1",
    "create_time": "2008-07-22 10:02:04 07",
    "modification_time": "2013-08-09 20:04:36 07",
    "see_also": [
        {
            "id": "3237491513434577",
            "lon": 38.973110606808,
            "lat": 45.029031222211,
            "name": "Advance",
            "hash": "5698hn745A8IJ1H86177uvgn94521J3464he26763737242Cf6e654G62J0I7878e",
            "ads": {
                "sponsored_article": {
                    "title": "Center "ADVANCE"",
                    "text": "Business.English."
                },
                "warning": null
            }
        }
    ]
}

但是Python无法识别它:

json.loads(firm_str)

期望分隔符为逗号,位于第1行第3646列(第3645个字符)

看起来是引号的问题: "title": "Center "ADVANCE""

如何在Python中自动修正它?


4
这是一个编码问题,而不是JSON问题。 - Gijs
1
我认为问题在于字符串中有用双引号括起来的双引号。尝试使用"title": "bla 'ADVANCE'""title": 'bla "ADVANCE"'代替。应该可以构建一个正则表达式来查找这些内容... - tobias_k
@tobias_k,你能建议什么正则表达式吗?请注意,这样未转义的引号可能出现在任何文本值中。 - Anton Barycheuski
@tobias_k,我认为没有一个通用的模式。像gnibbler描述的情况可能也存在,但很少见。 - Anton Barycheuski
4
请考虑,如果API的后续版本修复了这个漏洞会发生什么。确保您使用的任何解决方法在他们修复漏洞时不会在您的代码中引入新的漏洞。 - John La Rooy
显示剩余7条评论
11个回答

47

Michael的回答给了我一个想法……虽然不是很好看,但至少在你的示例上似乎行得通:尝试解析JSON字符串,如果失败了,在异常字符串1中查找失败的字符并替换它。

while True:
    try:
        result = json.loads(s)   # try to parse...
        break                    # parsing worked -> exit loop
    except Exception as e:
        # "Expecting , delimiter: line 34 column 54 (char 1158)"
        # position of unexpected character after '"'
        unexp = int(re.findall(r'\(char (\d+)\)', str(e))[0])
        # position of unescaped '"' before that
        unesc = s.rfind(r'"', 0, unexp)
        s = s[:unesc] + r'\"' + s[unesc+1:]
        # position of correspondig closing '"' (+2 for inserted '\')
        closg = s.find(r'"', unesc + 2)
        s = s[:closg] + r'\"' + s[closg+1:]
print result

你可能希望添加一些额外的检查来防止进入无限循环(例如,最多重复与字符串中的字符数相同次数)。另外,正如@gnibbler指出的那样,如果错误的代码“”后面实际上跟着逗号,则仍然无法工作。

更新:现在这似乎工作得相当好(虽然还不完美),即使未转义的"后面跟着逗号或闭括号,此时它可能会收到关于语法错误的投诉(预期属性名称等)并跟踪回到上一个"。它也会自动转义相应的结束"(如果有的话)


1)异常的str"Expecting,delimiter:line XXX column YYY(char ZZZ)",其中ZZZ是发生错误的字符串位置。但是请注意,此消息可能取决于Python版本、json模块、操作系统或区域设置,因此必须相应地调整此解决方案。


太棒了。这并不能解决我所有的问题,但是这是一个不错的开端。 - eandersson
@eandersson能否更具体地解释一下您对“更完整的解决方案”的期望?我的解决方案在哪些情况下不起作用?(我确信有很多情况,但哪些情况对您来说是相关的?) - tobias_k
我主要是指一个处理这些奇怪情况的库,但也需要一个更灵活的解决方案来修复这种库无法处理的极端情况问题。例如,一个带有单个额外双引号的字符串。 - eandersson
顺便说一句,我只是想看看是否还有其他的东西,否则我就会把奖励点数授予这个答案。 - eandersson
从Python 3.5开始,您可以使用JsonDecodeError.pos来获取位置。 - Capi Etheriel
如果JSON中存在其他类型的问题,这将无限运行,我不建议在没有跟踪它进行多少次迭代并在某个阈值处停止的情况下使用它。 - Tomer Gal

9
如果API确实返回的就是这个内容,那么它们的API存在问题。这不是有效的JSON格式。特别是在这个位置上:
"ads": {
            "sponsored_article": {
                "title": "Образовательный центр "ADVANCE"", <-- here
                "text": "Бизнес.Риторика.Английский язык.Подготовка к школе.Подготовка к ЕГЭ."
            },
            "warning": null
        }

ADVANCE周围的双引号没有被转义。您可以使用http://jsonlint.com/之类的工具进行验证。

这是"未被转义的问题,如果您得到的是这种数据,则源数据存在问题。他们需要解决这个问题。

Parse error on line 4:
...азовательный центр "ADVANCE"",         
-----------------------^
Expecting '}', ':', ',', ']'

这是解决问题的方法:
"title": "Образовательный центр \"ADVANCE\"",

我需要自动修复这个问题。 - Anton Barycheuski
9
你应该先写信给2gis,告诉他们他们没有返回正确的JSON。 - Joe
你可以尝试通过解析JSON,如果失败了,就用jsonString.replace('""', '\""')。这可能有效,但在其他输入上可能会失败。没有正确的解决方案。 - Joe

6

唯一真正明确的解决方案是要求2gis修复他们的API。

与此同时,可能需要修复JSON中字符串内部错误编码的双引号。如果每个键值对后面都跟着一个换行符(从发布的数据中看起来似乎是这样),则以下功能将完成任务:

def fixjson(badjson):
    s = badjson
    idx = 0
    while True:
        try:
            start = s.index( '": "', idx) + 4
            end1  = s.index( '",\n',idx)
            end2  = s.index( '"\n', idx)
            if end1 < end2:
                end = end1
            else:
                end = end2
            content = s[start:end]
            content = content.replace('"', '\\"')
            s = s[:start] + content + s[end:]
            idx = start + len(content) + 6
        except:
            return s

请注意以下一些假设:
该函数试图转义属于键值对的值字符串内的双引号字符。
假定要转义的文本是从序列后开始的。
": "

并在序列之前结束

",\n

或者

"\n

将发布的JSON传递给函数会导致返回此值。
{
    "api_version": "1.3",
    "response_code": "200",
    "id": "3237490513229753",
    "lon": "38.969916127827",
    "lat": "45.069889625267",
    "page_url": null,
    "name": "ATB",
    "firm_group": {
        "id": "3237499103085728",
        "count": "1"
    },
    "city_name": "Krasnodar",
    "city_id": "3237585002430511",
    "address": "Turgeneva,   172/1",
    "create_time": "2008-07-22 10:02:04 07",
    "modification_time": "2013-08-09 20:04:36 07",
    "see_also": [
        {
            "id": "3237491513434577",
            "lon": 38.973110606808,
            "lat": 45.029031222211,
            "name": "Advance",
            "hash": "5698hn745A8IJ1H86177uvgn94521J3464he26763737242Cf6e654G62J0I7878e",
            "ads": {
                "sponsored_article": {
                    "title": "Center \"ADVANCE\"",
                    "text": "Business.English."
                },
                "warning": null
            }
        }
    ]
}

请记住,如果您的需求没有得到完全满足,您可以轻松地自定义此功能。


5
上述想法不错,但我遇到了问题。我的JSON字符串中只有一个多余的双引号。 因此,我对上面给出的代码进行了修复。
jsonStr如下:
{
    "api_version": "1.3",
    "response_code": "200",
    "id": "3237490513229753",
    "lon": "38.969916127827",
    "lat": "45.069889625267",
    "page_url": null,
    "name": "ATB",
    "firm_group": {
        "id": "3237499103085728",
        "count": "1"
    },
    "city_name": "Krasnodar",
    "city_id": "3237585002430511",
    "address": "Turgeneva,   172/1",
    "create_time": "2008-07-22 10:02:04 07",
    "modification_time": "2013-08-09 20:04:36 07",
    "see_also": [
        {
            "id": "3237491513434577",
            "lon": 38.973110606808,
            "lat": 45.029031222211,
            "name": "Advance",
            "hash": "5698hn745A8IJ1H86177uvgn94521J3464he26763737242Cf6e654G62J0I7878e",
            "ads": {
                "sponsored_article": {
                    "title": "Center "ADVANCE",
                    "text": "Business.English."
                },
                "warning": null
            }
        }
    ]
}

修复方法如下:
import json, re
def fixJSON(jsonStr):
    # Substitue all the backslash from JSON string.
    jsonStr = re.sub(r'\\', '', jsonStr)
    try:
        return json.loads(jsonStr)
    except ValueError:
        while True:
            # Search json string specifically for '"'
            b = re.search(r'[\w|"]\s?(")\s?[\w|"]', jsonStr)

            # If we don't find any the we come out of loop
            if not b:
                break

            # Get the location of \"
            s, e = b.span(1)
            c = jsonStr[s:e]

            # Replace \" with \'
            c = c.replace('"',"'")
            jsonStr = jsonStr[:s] + c + jsonStr[e:]
        return json.loads(jsonStr)

这段代码同样适用于问题说明中提到的JSON字符串。


或者你也可以这样做:

def fixJSON(jsonStr):
    # First remove the " from where it is supposed to be.
    jsonStr = re.sub(r'\\', '', jsonStr)
    jsonStr = re.sub(r'{"', '{`', jsonStr)
    jsonStr = re.sub(r'"}', '`}', jsonStr)
    jsonStr = re.sub(r'":"', '`:`', jsonStr)
    jsonStr = re.sub(r'":', '`:', jsonStr)
    jsonStr = re.sub(r'","', '`,`', jsonStr)
    jsonStr = re.sub(r'",', '`,', jsonStr)
    jsonStr = re.sub(r',"', ',`', jsonStr)
    jsonStr = re.sub(r'\["', '\[`', jsonStr)
    jsonStr = re.sub(r'"\]', '`\]', jsonStr)

    # Remove all the unwanted " and replace with ' '
    jsonStr = re.sub(r'"',' ', jsonStr)

    # Put back all the " where it supposed to be.
    jsonStr = re.sub(r'\`','\"', jsonStr)

    return json.loads(jsonStr)

1
不错,但是不要从文本中删除所有的 ",为什么不用一些占位符字符(在字符串中没有其他地方找到)来替换它们,然后在正确的 " 放回之后再将它们替换回转义引号呢? - tobias_k
这将是一个适当的方式来做。 - theBuzzyCoder

4

我制作了一个jsonfixer来解决这样的问题。

它是Python包(2.7)(一个未完成的命令行工具)

只需查看https://github.com/half-pie/half-json

from half_json.core import JSONFixer
f = JSONFixer(max_try=100)
new_s = s.replace('\n', '')
result = f.fix(new_s)
d = json.loads(result.line)
# {u'name': u'ATB', u'modification_time': u'2013-08-09 20:04:36 07', u'city_id': u'3237585002430511', u'see_also': [{u'hash': u'5698hn745A8IJ1H86177uvgn94521J3464he26763737242Cf6e654G62J0I7878e', u'ads': {u'warning': None, u'sponsored_article': {u'ADVANCE': u',                    ', u'text': u'Business.English.', u'title': u'Center '}}, u'lon': 38.973110606808, u'lat': 45.029031222211, u'id': u'3237491513434577', u'name': u'Advance'}], u'response_code': u'200', u'lon': u'38.969916127827', u'firm_group': {u'count': u'1', u'id': u'3237499103085728'}, u'create_time': u'2008-07-22 10:02:04 07', u'city_name': u'Krasnodar', u'address': u'Turgeneva,   172/1', u'lat': u'45.069889625267', u'id': u'3237490513229753', u'api_version': u'1.3', u'page_url': None}

并在https://github.com/half-pie/half-json/blob/master/tests/test_cases.py#L76-L80中进行测试案例。

    line = '{"title": "Center "ADVANCE"", "text": "Business.English."}'
    ok, newline, _ = JSONFixer().fix(line)
    self.assertTrue(ok)
    self.assertEqual('{"title": "Center ","ADVANCE":", ","text": "Business.English."}', newline)

谢谢您创建这个包!我在我的项目中使用了它,并发现它非常有帮助。非常感谢您的努力! - abdullah.cu

2

在JSON字符串中,需要转义双引号,如下所示:

"title": "Образовательный центр \"ADVANCE\"",

为了以编程方式修复它,最简单的方法是修改您的JSON解析器,使其具有一些错误上下文,然后尝试修复它。


1
def fix_json(jsonStr):
    # Remove all empty spaces to make things easier bellow
    jsonStr = jsonStr.replace('" :','":').replace(': "',':"').replace('"\n','"').replace('" ,','",').replace(', "',',"')
    # First remove the " from where it is supposed to be.
    jsonStr = re.sub(r'\\"', '"', jsonStr)
    jsonStr = re.sub(r'{"', '{`', jsonStr)
    jsonStr = re.sub(r'"}', '`}', jsonStr)
    jsonStr = re.sub(r'":"', '`:`', jsonStr)
    jsonStr = re.sub(r'":\[', '`:[', jsonStr)
    jsonStr = re.sub(r'":\{', '`:{', jsonStr)
    jsonStr = re.sub(r'":([0-9]+)', '`:\\1', jsonStr)
    jsonStr = re.sub(r'":([null|true|false])', '`:\\1', jsonStr)
    jsonStr = re.sub(r'","', '`,`', jsonStr)
    jsonStr = re.sub(r'",\[', '`,[', jsonStr)
    jsonStr = re.sub(r'",\{', '`,{', jsonStr)
    jsonStr = re.sub(r',"', ',`', jsonStr)
    jsonStr = re.sub(r'\["', '[`', jsonStr)
    jsonStr = re.sub(r'"\]', '`]', jsonStr)
    # Backslash all double quotes (")
    jsonStr = re.sub(r'"','\\"', jsonStr)
    # Put back all the " where it is supposed to be.
    jsonStr = re.sub(r'\`','\"', jsonStr)
    return jsonStr

它基于上面@theBuzzyCoder的代码,感谢伙计提供的思路。


运行得很顺利。在函数的开头添加了jsonStr = jsonStr[1:-1] if jsonStr[0] == '"' and jsonStr[-1]=='"' else jsonStr来去除多余的双引号。 - Ilya

0

修复 #1

如果你从某个网站获取了它,请确保你正在使用相同的字符串。在我的情况下,我正在执行 .replace('\\"','"')。因此,数据不再是json。如果你也做了类似的事情,请修复它。

修复 #2

尝试在所有地方添加一些字符,而不是键名。这样就没问题了。


0

https://fix-json.com 的资源中我找到了一个解决方案,但它非常肮脏且看起来像是一种hack。只需将其适应于Python即可。

jsString.match(/:.*"(.*)"/gi).forEach(function(element){
   var filtered = element.replace(/(^:\s*"|"(,)?$)/gi, '').trim();
   jsString = jsString.replace(filtered, filtered.replace(/(\\*)\"/gi, "\\\""));
});

0

它不完美也很丑,但它对我有帮助。

def get_json_info(info_row: str, type) -> dict:
    try:
        info = json.loads(info_row)
    except JSONDecodeError:
        data = {
        }
        try:

            for s in info_row.split('","'):
                if not s:
                    continue
                key, val = s.split(":", maxsplit=1)
                key = key.strip().lstrip("{").strip('"')
                val: str = re.sub('"', '\\"', val.lstrip('"').strip('\"}'))
                data[key] = val
        except ValueError:
            print("ERROR:", info_row)
        info = data
    return info

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接