Обработка ленивого JSON в Python – «Ожидание названия свойства»

Используя модуль Pythons (2.7) 'json', я хочу обработать различные каналы JSON. К сожалению, некоторые из этих каналов не соответствуют стандартам JSON – в некоторых некоторых ключах не завертываются двойные речевые метки («). Это приводит к ошибке Python.

Прежде чем писать фрагмент кода с уродливым адским кодом, чтобы разобрать и восстановить входящие данные, я подумал, что спрошу – есть ли способ разрешить Python либо проанализировать этот искаженный JSON, либо «восстановить» данные, чтобы он был действительный JSON?

Рабочий пример

import json >>> json.loads('{"key1":1,"key2":2,"key3":3}') {'key3': 3, 'key2': 2, 'key1': 1} 

Неисправный пример

 import json >>> json.loads('{key1:1,key2:2,key3:3}') Traceback (most recent call last): File "<stdin>", line 1, in <module> File "C:\Python27\lib\json\__init__.py", line 310, in loads return _default_decoder.decode(s) File "C:\Python27\lib\json\decoder.py", line 346, in decode obj, end = self.raw_decode(s, idx=_w(s, 0).end()) File "C:\Python27\lib\json\decoder.py", line 362, in raw_decode obj, end = self.scan_once(s, idx) ValueError: Expecting property name: line 1 column 1 (char 1) 

Я написал небольшой REGEX, чтобы исправить JSON, исходящий от этого конкретного провайдера, но я предвижу, что это будет проблемой в будущем. Ниже я придумал.

 >>> import re >>> s = '{key1:1,key2:2,key3:3}' >>> s = re.sub('([{,])([^{:\s"]*):', lambda m: '%s"%s":'%(m.group(1),m.group(2)),s) >>> s '{"key1":1,"key2":2,"key3":3}' 

6 Solutions collect form web for “Обработка ленивого JSON в Python – «Ожидание названия свойства»”

Вы пытаетесь использовать парсер JSON для анализа того, что не является JSON. Лучше всего заставить создателя фидов исправить их.

Я понимаю, что это не всегда возможно. Возможно, вы сможете исправить данные с помощью регулярных выражений, в зависимости от того, насколько они нарушены:

 j = re.sub(r"{\s*(\w)", r'{"\1', j) j = re.sub(r",\s*(\w)", r',"\1', j) j = re.sub(r"(\w):", r'\1":', j) 

Другой вариант – использовать модуль demjson, который может анализировать json в нестрогом режиме.

Регулярные выражения, отмеченные Ned и cheeseinvert, не учитываются, когда совпадение находится внутри строки.

См. Следующий пример (с использованием решения soninvert):

 >>> fixLazyJsonWithRegex ('{ key : "a { a : b }", }') '{ "key" : "a { "a": b }" }' 

Проблема в том, что ожидаемый результат:

 '{ "key" : "a { a : b }" }' 

Поскольку токены JSON являются подмножеством токенов python, мы можем использовать модуль tokenize python.

Пожалуйста, исправьте меня, если я ошибаюсь, но следующий код исправит ленивую строку json во всех случаях:

 import tokenize import token from StringIO import StringIO def fixLazyJson (in_text): tokengen = tokenize.generate_tokens(StringIO(in_text).readline) result = [] for tokid, tokval, _, _, _ in tokengen: # fix unquoted strings if (tokid == token.NAME): if tokval not in ['true', 'false', 'null', '-Infinity', 'Infinity', 'NaN']: tokid = token.STRING tokval = u'"%s"' % tokval # fix single-quoted strings elif (tokid == token.STRING): if tokval.startswith ("'"): tokval = u'"%s"' % tokval[1:-1].replace ('"', '\\"') # remove invalid commas elif (tokid == token.OP) and ((tokval == '}') or (tokval == ']')): if (len(result) > 0) and (result[-1][1] == ','): result.pop() # fix single-quoted strings elif (tokid == token.STRING): if tokval.startswith ("'"): tokval = u'"%s"' % tokval[1:-1].replace ('"', '\\"') result.append((tokid, tokval)) return tokenize.untokenize(result) 

Таким образом, чтобы разобрать json-строку, вы можете захотеть инкапсулировать вызов fixLazyJson, когда json.loads завершится с ошибкой (чтобы избежать штрафных санкций за хорошо сформированный json):

 import json def json_decode (json_string, *args, **kwargs): try: json.loads (json_string, *args, **kwargs) except: json_string = fixLazyJson (json_string) json.loads (json_string, *args, **kwargs) 

Единственная проблема, которую я вижу при установке lazy json, заключается в том, что если json неверен, ошибка, вызванная вторым json.loads, не будет ссылаться на строку и столбец из исходной строки, а на измененную.

В качестве заключительной заметки я просто хочу отметить, что было бы легко обновить любой из методов для принятия файлового объекта вместо строки.

БОНУС: Кроме того, людям обычно нравится включать комментарии C / C ++, когда json используется для файлов конфигурации, в этом случае вы можете либо удалять комментарии с использованием обычного выражения , либо использовать расширенную версию, и исправить строку json за один проход :

 import tokenize import token from StringIO import StringIO def fixLazyJsonWithComments (in_text): """ Same as fixLazyJson but removing comments as well """ result = [] tokengen = tokenize.generate_tokens(StringIO(in_text).readline) sline_comment = False mline_comment = False last_token = '' for tokid, tokval, _, _, _ in tokengen: # ignore single line and multi line comments if sline_comment: if (tokid == token.NEWLINE) or (tokid == tokenize.NL): sline_comment = False continue # ignore multi line comments if mline_comment: if (last_token == '*') and (tokval == '/'): mline_comment = False last_token = tokval continue # fix unquoted strings if (tokid == token.NAME): if tokval not in ['true', 'false', 'null', '-Infinity', 'Infinity', 'NaN']: tokid = token.STRING tokval = u'"%s"' % tokval # fix single-quoted strings elif (tokid == token.STRING): if tokval.startswith ("'"): tokval = u'"%s"' % tokval[1:-1].replace ('"', '\\"') # remove invalid commas elif (tokid == token.OP) and ((tokval == '}') or (tokval == ']')): if (len(result) > 0) and (result[-1][1] == ','): result.pop() # detect single-line comments elif tokval == "//": sline_comment = True continue # detect multiline comments elif (last_token == '/') and (tokval == '*'): result.pop() # remove previous token mline_comment = True continue result.append((tokid, tokval)) last_token = tokval return tokenize.untokenize(result) 

Расширившись по предложению Неда, мне было полезно следующее:

 j = re.sub(r"{\s*'?(\w)", r'{"\1', j) j = re.sub(r",\s*'?(\w)", r',"\1', j) j = re.sub(r"(\w)'?\s*:", r'\1":', j) j = re.sub(r":\s*'(\w+)'\s*([,}])", r':"\1"\2', j) 

В аналогичном случае я использовал ast.literal_eval . AFAIK, это не сработает, только когда в JSON появится константа null (соответствующая Python None ).

Учитывая, что вы знаете о null/None , вы можете:

 import ast decoded_object= ast.literal_eval(json_encoded_text) 

В дополнение к предложениям Neds и cheeseinvert добавление (?!/) Должно избегать указанной проблемы с URL-адресами

 j = re.sub(r"{\s*'?(\w)", r'{"\1', j) j = re.sub(r",\s*'?(\w)", r',"\1', j) j = re.sub(r"(\w)'?\s*:(?!/)", r'\1":', j) j = re.sub(r":\s*'(\w+)'\s*([,}])", r':"\1"\2', j) j = re.sub(r",\s*]", "]", j) 
Interesting Posts

Как заменить только часть соответствия на python re.sub

Как я могу перестроить mod_wsgi для использования python 2.7.3?

Установка easy_install … для установки lxml

Как передать обратный вызов python на вызов функции c #

Функция find () в стиле MATLAB в Python

Python CSV – индекс списка вне диапазона

Группируйте по часам и заговорам в Боке

Как выбрать столбцы DataFrame на основе частичного совпадения?

Django Formsets – form.is_valid () – ложное предупреждение о получении формы

Как печатать все значения переменных при отладке Python с помощью pdb без указания каждой переменной?

Stem как клиент python tor – застрял на дескрипторах загрузки

Неправильный XML, созданный SUDS

ERROR openerp.addons.website.models.ir_http: 500 Внутренняя ошибка сервера: после выхода из режима odoo

Как издеваться над datetime.now () python в методе класса для модульного тестирования?

«Os.environ» в настройках django.py не может получить переменные системной среды с apache и wsgi

Python - лучший язык программирования в мире.