Разбор нестандартной точки с запятой разделяет "JSON"

57
3

У меня есть нестандартный файл "JSON" для разбора. Каждый элемент разделяется точкой с запятой, а не разделяется запятой. Я не могу просто заменить ; с , потому что может быть какое-то значение, содержащее ; , например. "Привет, мир". Как я могу разобрать это в той же структуре, что и JSON, как правило, анализирует его?

{
"client" : "someone";
"server" : ["s1"; "s2"];
"timestamp" : 1000000;
"content" : "hello; world";
...
}

спросил(а) 2016-08-12T22:05:00+03:00 4 года, 1 месяц назад
1
Решение
108

Используйте модуль tokenize Python, чтобы преобразовать текстовый поток в один с запятой, а не точкой с запятой. Токенизатор Python с удовольствием обрабатывает вход JSON, даже включая точки с запятой. Токенизатор представляет строки в виде целых жетонов, а "сырые" точки с запятой находятся в потоке в виде одиночных токенов. token.OP для замены:

import tokenize
import json

corrected = []

with open('semi.json', 'r') as semi:
for token in tokenize.generate_tokens(semi.readline):
if token[0] == tokenize.OP and token[1] == ';':
corrected.append(',')
else:
corrected.append(token[1])

data = json.loads(''.join(corrected))

Это предполагает, что формат становится действительным JSON после того, как вы заменили точки с запятой запятыми; например, никаких конечных запятых до закрытия ] или } разрешено, хотя вы даже можете отслеживать добавленную последнюю запятую и удалять ее снова, если следующий токен не-новой строки является закрывающей скобкой.

Демо-версия:

>>> import tokenize
>>> import json
>>> open('semi.json', 'w').write('''\
... {
... "client" : "someone";
... "server" : ["s1"; "s2"];
... "timestamp" : 1000000;
... "content" : "hello; world"
... }
... ''')
>>> corrected = []
>>> with open('semi.json', 'r') as semi:
... for token in tokenize.generate_tokens(semi.readline):
... if token[0] == tokenize.OP and token[1] == ';':
... corrected.append(',')
... else:
... corrected.append(token[1])
...
>>> print ''.join(corrected)
{
"client":"someone",
"server":["s1","s2"],
"timestamp":1000000,
"content":"hello; world"
}
>>> json.loads(''.join(corrected))
{u'content': u'hello; world', u'timestamp': 1000000, u'client': u'someone', u'server': [u's1', u's2']}

tokenize.NL токен, но его можно было восстановить, обратив внимание на токены tokenize.NL и (lineno, start) и (lineno, end) позиции, которые являются частью каждого токена. Поскольку пробелы вокруг токенов не имеют значения для парсера JSON, я не беспокоился об этом.

ответил(а) 2016-08-12T22:24:00+03:00 4 года, 1 месяц назад
40

Pyparsing упрощает запись строкового трансформатора. Напишите выражение для строки, которую нужно изменить, и добавьте действие синтаксического анализа (обратный вызов времени разбора), чтобы заменить согласованный текст на то, что вы хотите. Если вам нужно избегать некоторых случаев (например, цитируемых строк или комментариев), включите их в сканер, но просто оставьте их без изменений. Затем, чтобы фактически преобразовать строку, вызовите scanner.transformString.

(Из вашего примера было непонятно, есть ли у вас ";" после последнего элемента в одном из ваших списков в скобках, поэтому я добавил термин для их устранения, так как конечный "," в списке в скобках также недействителен JSON).

sample = """
{
"client" : "someone";
"server" : ["s1"; "s2"];
"timestamp" : 1000000;
"content" : "hello; world";
}"""

from pyparsing import Literal, replaceWith, Suppress, FollowedBy, quotedString
import json

SEMI = Literal(";")
repl_semi = SEMI.setParseAction(replaceWith(','))
term_semi = Suppress(SEMI + FollowedBy('}'))
qs = quotedString

scanner = (qs | term_semi | repl_semi)
fixed = scanner.transformString(sample)
print(fixed)
print(json.loads(fixed))


печатает:

{
"client" : "someone",
"server" : ["s1", "s2"],
"timestamp" : 1000000,
"content" : "hello; world"}
{'content': 'hello; world', 'timestamp': 1000000, 'client': 'someone', 'server': ['s1', 's2']}

ответил(а) 2016-08-13T17:40:00+03:00 4 года, 1 месяц назад
41

Используя простой персональный автомат, вы можете преобразовать этот текст в действительный JSON. Основная вещь, которую нам нужно обработать, - определить текущее "состояние" (будь то экранирование символа, строка, список, словарь и т.д.) И заменить ";" by ',', когда в определенном состоянии.

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

Я пытался прокомментировать столько, сколько мог:

def filter_characters(text):
# we use this dictionary to match opening/closing tokens
STATES = {
'"': '"', "'": "'",
"{": "}", "[": "]"
}

# these two variables represent the current state of the parser
escaping = False
state = list()

# we iterate through each character
for c in text:
if escaping:
# if we are currently escaping, no special treatment
escaping = False
else:
if c == "\\":
# character is a backslash, set the escaping flag for the next character
escaping = True
elif state and c == state[-1]:
# character is expected closing token, update state
state.pop()
elif c in STATES:
# character is known opening token, update state
state.append(STATES[c])
elif c == ';' and state == ['}']:
# this is the delimiter we want to change
c = ','
yield c

assert not state, "unexpected end of file"

def filter_text(text):
return ''.join(filter_characters(text))

Тестирование с помощью:

{
"client" : "someone";
"server" : ["s1"; "s2"];
"timestamp" : 1000000;
"content" : "hello; world";
...
}

Возврат:

{
"client" : "someone",
"server" : ["s1"; "s2"],
"timestamp" : 1000000,
"content" : "hello; world",
...
}

ответил(а) 2016-08-12T22:38:00+03:00 4 года, 1 месяц назад
41

Вы можете сделать некоторые странные вещи и получить его (возможно) правильно.

Поскольку строки на JSON не могут иметь контрольные символы, такие как \t, вы можете заменить каждый ; to \t, поэтому файл будет правильно проанализирован, если ваш парсер JSON сможет загружать нестандартные JSON (такие как Python's).

После этого вам нужно только преобразовать ваши данные обратно в JSON, чтобы вы могли заменить все эти \t, to ; и используйте обычный анализатор JSON, чтобы, наконец, загрузить правильный объект.

Некоторые примеры кода в Python:

data = '''{
"client" : "someone";
"server" : ["s1"; "s2"];
"timestamp" : 1000000;
"content" : "hello; world"
}'''

import json
dec = json.JSONDecoder(strict=False).decode(data.replace(';', '\t,'))
enc = json.dumps(dec)
out = json.loads(dec.replace('\\t,' ';'))

ответил(а) 2016-08-12T22:30:00+03:00 4 года, 1 месяц назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема