Как автоматически исправить недопустимую строку JSON?

Из API 2gis я получил следующую строку 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 (char 3645)

Похож на проблему с кавычками в:    "title": "Центр" ADVANCE ""

Как я могу исправить это автоматически в Python?

Ответ 1

Ответ от @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 модуля, ОС или локали, и, следовательно, это решение, возможно, потребуется соответствующим образом адаптировать.

Ответ 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\"",

Ответ 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, как показано ниже:

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

Чтобы исправить это программно, самым простым способом было бы изменить ваш парсер JSON, чтобы у вас был некоторый контекст для ошибки, а затем попытайтесь его исправить.

Ответ 5

Вышеупомянутая идея хороша, но у меня была проблема с этим. Мой 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)

Ответ 6

В источниках https://fix-json.com я нашел решение, но оно очень грязное и похоже на взлом. Просто приспособьте его к питону

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

Ответ 7

это не идеально и некрасиво, но это помогает мне

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