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

Posted

技术标签:

【中文标题】如何自动修复无效的 JSON 字符串?【英文标题】:How do I automatically fix an invalid JSON string? 【发布时间】:2013-09-02 02:54:09 【问题描述】:

从 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": "中心 "ADVANCE""

如何在 Python 中自动修复它?

【问题讨论】:

这是编码问题,不是 JSON 问题。 编码正确。不要注意奇怪的字符 你能把它隔离成一个具体的小例子吗?移除碎片,直到剩下断裂的部分。 我认为问题在于用双引号分隔的字符串中有双引号。请改用"title": "bla 'ADVANCE'""title": 'bla "ADVANCE"'。应该可以构建一个正则表达式来找到那些...... 考虑如果更高版本的 API 修复了这个错误会发生什么。确保当他们修复他们的代码时,您使用的任何解决方法都不会导致您的代码出现新错误。 【参考方案1】:

answer by @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 您能否更具体地说明您期望“更完整的解决方案”是什么样子?在哪些情况下我的解决方案不起作用? (我相信有很多,但哪些与您相关?) 我主要指的是一个库来处理这些类型的怪事,但也是一个更灵活的解决方案来解决这种无法处理的极端情况问题。一个例子是带有一个额外双引号的字符串。 顺便说一句,我真的只是想看看那里是否还有其他东西,否则我只会将奖励积分奖励给这个答案。 从 Python 3.5 开始,您可以使用 JsonDecodeError.pos 获取位置。【参考方案2】:

如果这正是 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\"",

【讨论】:

我需要自动修复这个问题。 你应该首先写信给2gis,告诉他们他们没有返回正确的JSON。 你可以尝试解析 JSON,如果失败,jsonString.replace('""', '\""')。这可能会奏效。但它可能会在其他输入上失败。没有正确的解决方案。【参考方案3】:

唯一真正确定的解决方案是要求 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
            
        
    ]

请记住,如果您的需求不能完全满足,您可以轻松自定义功能。

【讨论】:

【参考方案4】:

上面的想法很好,但我有问题。我的 json Sting 只包含一个额外的双引号。 因此,我对上面给出的代码进行了修复。

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)

【讨论】:

很好,但不是从文本中删除所有",为什么不用一些占位符字符替换它们(在字符串中没有找到),然后用转义后的引号替换它们正确的" 已被放回? 这将是一个正确的方法。【参考方案5】:

需要对JSON字符串中的双引号进行转义,如下:

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

要以编程方式修复它,最简单的方法是修改您的 JSON 解析器,以便您了解错误的上下文,然后尝试修复它。

【讨论】:

【参考方案6】:

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

It's Python Package (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)

【讨论】:

【参考方案7】:

在https://fix-json.com 的源代码中,我找到了一个解决方案,但它很脏,看起来像个黑客。适应python就行了

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

【讨论】:

【参考方案8】:

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

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

【讨论】:

【参考方案9】:

修复 #1

如果您从某个网站获取它,请确保您使用的是相同的字符串。就我而言,我正在做 .replace('\\"','"') 。因此,数据不再是 json。如果你也做了什么。像这样,请修复它。

修复 #2

尝试在键名的所有位置添加一些字符。会好的。

【讨论】:

以上是关于如何自动修复无效的 JSON 字符串?的主要内容,如果未能解决你的问题,请参考以下文章

修复 SQL 中的无效 json

无法修复 JSONDecodeError:无效的控制字符

如何修复 Invariant Violation:元素类型无效:预期为字符串(对于内置组件)

如何修复元素类型无效:期望字符串(用于内置组件)或类/函数(用于复合组件)但得到:对象

如何修复来自 Google Ads API 的无效 JSON 负载错误

序列化为 JSON 字符串时如何修复 OutOfMemoryException?