web-dev-qa-db-ja.com

無効なJSON文字列を自動的に修正するにはどうすればよいですか?

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で自動的に修正するにはどうすればよいですか?

24

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が指摘するように、誤った_"_の後に実際にコンマが続いている場合も、これは機能しません。

更新:これは動作するようですprettyエスケープされていない場合でも_"_この場合、コンマまたは閉じ角かっこが続きます。この場合、その後に構文エラーに関するエラーが表示され(予期されるプロパティ名など)、最後の_"_までたどる可能性があります。また、対応する終了_"_が自動的にエスケープされます(存在する場合)。


1) 例外のstr"Expecting , delimiter: line XXX column YYY (char ZZZ)"です。ここで、ZZZはエラーが発生した文字列内の位置です。ただし、このメッセージはPythonのバージョン、jsonモジュール、OS、またはロケールに依存する可能性があるため、このソリューションはそれに応じて調整する必要がある場合があります。

32
tobias_k

これが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\"",
7
atorres757

唯一確実で確実な解決策は、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
Paolo

次のように、JSON文字列の二重引用符をエスケープする必要があります。

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

プログラムで修正するには、最も簡単な方法は、JSONパーサーを変更してエラーのコンテキストを取得し、修復を試みることです。

2

上記のアイデアは良いですが、私はそれに問題がありました。私のjson Stingには、追加の二重引用符が1つだけ含まれていました。そこで、上記のコードを修正しました。

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
theBuzzyCoder

私はこのような問題を解決するためにjsonfixerを作成します。

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-L8 のテストケース

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

完璧ではなく醜いですが、私には役立ちます

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

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, "\\\""));
});
0
Frost