【问题标题】:How do I automatically fix an invalid JSON string?如何自动修复无效的 JSON 字符串?
【发布时间】: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 修复了这个错误会发生什么。确保当他们修复他们的代码时,您使用的任何解决方法都不会导致您的代码出现新错误。

标签: python json escaping


【解决方案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

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2022-01-04
              • 1970-01-01
              • 2015-04-23
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多