web-dev-qa-db-fra.com

Comment corriger automatiquement une chaîne JSON non valide?

De l'API 2gis, j'ai obtenu la chaîne JSON suivante.

{
    "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
            }
        }
    ]
}

Mais Python ne le reconnaît pas:

json.loads(firm_str)

Attente, délimiteur: ligne 1 colonne 3646 (caractère 3645)

Cela ressemble à un problème avec les guillemets dans: "titre": "Centre" ADVANCE ""

Comment puis-je résoudre ce problème automatiquement en Python?

18
Anton Barycheuski

Le réponse de @Michael m'a donné une idée ... pas très jolie, mais cela semble fonctionner, du moins dans votre exemple: essayez d'analyser la chaîne JSON, et si elle échoue, cherchez le caractère où il a échoué dans la chaîne d'exception1 et remplacer ce personnage.

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

Vous voudrez peut-être ajouter quelques vérifications supplémentaires pour éviter que cela ne se termine par une boucle infinie (par exemple, autant de répétitions que de caractères dans la chaîne). En outre, cela ne fonctionnera toujours pas si un " incorrect est en réalité suivi d'une virgule, comme l'a souligné @gnibbler.

Update: Cela semble fonctionner (joli} _ bien maintenant (même si ce n'est toujours pas parfait), même si le " non échappé est suivi d'une virgule, ou d'un crochet de fermeture, car dans ce cas, se plaindre d’une erreur de syntaxe après celle-ci (nom de la propriété attendue, etc.) et retrouver le dernier ". Il échappe également automatiquement le " de fermeture correspondant (en supposant qu'il en existe un).


1) La variable str de l'exception est "Expecting , delimiter: line XXX column YYY (char ZZZ)", où ZZZ est la position dans la chaîne où l'erreur s'est produite. Notez cependant que ce message peut dépendre de la version de Python, du module json, du système d'exploitation ou des paramètres régionaux, de sorte que cette solution devra peut-être être adaptée en conséquence.

26
tobias_k

Si c'est exactement ce que l'API renvoie, il y a un problème avec leur API. C'est JSON invalide. Surtout autour de cette zone:

"ads": {
            "sponsored_article": {
                "title": "Образовательный центр "ADVANCE"", <-- here
                "text": "Бизнес.Риторика.Английский язык.Подготовка к школе.Подготовка к ЕГЭ."
            },
            "warning": null
        }

Les guillemets autour d’ADVANCE ne sont pas épargnés. Vous pouvez le savoir en utilisant quelque chose comme http://jsonlint.com/ pour le valider.

Ceci est un problème avec le " non échappé, les données sont mauvaises à la source si c'est ce que vous obtenez. Ils ont besoin de le réparer.

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

Cela corrige le problème:

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

La seule solution réelle et définitive consiste à demander à 2gis de réparer son API.

En attendant, il est possible de réparer les guillemets doubles échappés par JSON mal codé à l'intérieur des chaînes. Si chaque paire clé-valeur est suivie d'une nouvelle ligne (comme cela semble provenir des données publiées), la fonction suivante fera le travail:

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

S'il vous plaît, notez que certaines hypothèses faites:

La fonction tente d'échapper aux guillemets doubles à l'intérieur de chaîne de valeur appartenant à des paires clé-valeur.

On suppose que le texte à échapper commence après la séquence

": "

et se termine avant la séquence

",\n

ou

"\n

En transmettant le code JSON publié à la fonction, cette valeur renvoyée est renvoyée.

{
    "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
            }
        }
    ]
}

N'oubliez pas que vous pouvez facilement personnaliser la fonction si vos besoins ne sont pas entièrement satisfaits.

4
Paolo

Vous devez échapper aux guillemets doubles dans les chaînes JSON, comme suit:

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

Pour résoudre ce problème par programme, le moyen le plus simple consiste à modifier votre analyseur JSON afin que vous ayez un contexte pour l'erreur, puis tentez de le réparer.

2
Michael Foukarakis

L’idée ci-dessus est bonne mais j’ai eu un problème avec ça. Mon json Sting ne comportait qu’une seule double citation en plus… .. Donc, j’ai corrigé le code ci-dessus. 

Le jsonStr était

{
    "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
            }
        }
    ]
}

Le correctif est le suivant: 

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)

Ce code fonctionne également pour la chaîne JSON mentionnée dans l'instruction problem


OU vous pouvez aussi faire ceci:

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
theBuzzyCoder

ce n'est pas parfait et moche mais ça m'aide

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
0
madjardi

Dans les sources de https://fix-json.com j'ai trouvé une solution, mais elle est très sale et ressemble à un hack. Il suffit de l'adapter au python

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