Получение объектов JSON из текстового файла (с использованием Python)
У меня есть тысячи текстовых файлов, содержащих несколько объектов JSON, но, к сожалению, между объектами нет разделителя. Объекты хранятся в виде словарей, а некоторые из их полей сами являются объектами. Каждый объект может иметь переменное количество вложенных объектов. Конкретно, объект может выглядеть так:
{field1: {}, field2: "some value", field3: {}, ...}
и сотни таких объектов объединены без разделителя в текстовом файле. Это означает, что я не могу использовать json.load()
и json.loads()
.
Любое предложение о том, как я могу решить эту проблему. Есть ли известный парсер для этого?
Ответы
Ответ 1
Это расшифровывает ваш "список" объектов JSON из строки:
from json import JSONDecoder
def loads_invalid_obj_list(s):
decoder = JSONDecoder()
s_len = len(s)
objs = []
end = 0
while end != s_len:
obj, end = decoder.raw_decode(s, idx=end)
objs.append(obj)
return objs
Бонус здесь заключается в том, что вы играете хорошо с парсером. Следовательно, он постоянно сообщает вам, где он нашел ошибку.
<сильные > Примеры
>>> loads_invalid_obj_list('{}{}')
[{}, {}]
>>> loads_invalid_obj_list('{}{\n}{')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "decode.py", line 9, in loads_invalid_obj_list
obj, end = decoder.raw_decode(s, idx=end)
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/decoder.py", line 376, in raw_decode
obj, end = self.scan_once(s, idx)
ValueError: Expecting object: line 2 column 2 (char 5)
Чистое решение (добавлено позже)
import json
import re
#shameless copy paste from json/decoder.py
FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL
WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS)
class ConcatJSONDecoder(json.JSONDecoder):
def decode(self, s, _w=WHITESPACE.match):
s_len = len(s)
objs = []
end = 0
while end != s_len:
obj, end = self.raw_decode(s, idx=_w(s, end).end())
end = _w(s, end).end()
objs.append(obj)
return objs
<сильные > Примеры
>>> print json.loads('{}', cls=ConcatJSONDecoder)
[{}]
>>> print json.load(open('file'), cls=ConcatJSONDecoder)
[{}]
>>> print json.loads('{}{} {', cls=ConcatJSONDecoder)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/__init__.py", line 339, in loads
return cls(encoding=encoding, **kw).decode(s)
File "decode.py", line 15, in decode
obj, end = self.raw_decode(s, idx=_w(s, end).end())
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/decoder.py", line 376, in raw_decode
obj, end = self.scan_once(s, idx)
ValueError: Expecting object: line 1 column 5 (char 5)
Ответ 2
Себастьян Бласк имеет правильную идею, но нет причин использовать регулярные выражения для такого простого изменения.
objs = json.loads("[%s]"%(open('your_file.name').read().replace('}{', '},{')))
Или, более разборчиво
raw_objs_string = open('your_file.name').read() #read in raw data
raw_objs_string = raw_objs_string.replace('}{', '},{') #insert a comma between each object
objs_string = '[%s]'%(raw_objs_string) #wrap in a list, to make valid json
objs = json.loads(objs_string) #parse json
Ответ 3
Решение
Насколько я знаю, }{
не отображается в действительном JSON, поэтому следующее должно быть совершенно безопасным при попытке получить строки для отдельных конкатенированных объектов (txt
- это содержимое вашего файла). Для этого не требуется импорт (даже модуля re
):
retrieved_strings = map(lambda x: '{'+x+'}', txt.strip('{}').split('}{'))
или если вы предпочитаете понимание списков (как отметил Дэвид Цвиккер в комментариях), вы можете использовать его так:
retrieved_strings = ['{'+x+'}' for x in txt.strip('{}').split('}{'))]
Это приведет к тому, что retrieved_strings
будет списком строк, каждый из которых будет содержать отдельный объект JSON. См. Доказательство здесь: http://ideone.com/Purpb
Пример
Следующая строка:
'{field1:"a",field2:"b"}{field1:"c",field2:"d"}{field1:"e",field2:"f"}'
будет преобразован в:
['{field1:"a",field2:"b"}', '{field1:"c",field2:"d"}', '{field1:"e",field2:"f"}']
как доказано в примере, о котором я говорил.
Ответ 4
Как насчет чего-то вроде этого:
import re
import json
jsonstr = open('test.json').read()
p = re.compile( '}\s*{' )
jsonstr = p.sub( '}\n{', jsonstr )
jsonarr = jsonstr.split( '\n' )
for jsonstr in jsonarr:
jsonobj = json.loads( jsonstr )
print json.dumps( jsonobj )
Ответ 5
Почему вы не загружаете файл как строку, замените все} {на}, {и окружаете все это с помощью []? Что-то вроде:
re.sub('\}\s*?\{', '\}, \{', string_read_from_a_file)
Или простая строка замените, если вы уверены, что у вас всегда есть} {без пробелов между ними.
Если вы ожидаете, что} {произойдет и в строках, вы также можете разбить} {и оценить каждый фрагмент с помощью json.load, в случае, если вы получите ошибку, фрагмент не был завершен, и вам нужно добавить рядом с первым и т.д.
Ответ 6
Как прочитывать файл, увеличивая счетчик каждый раз, когда {найден и уменьшает его, когда вы сталкиваетесь с}. Когда ваш счетчик достигнет 0, вы узнаете, что вы пришли к концу первого объекта, поэтому отправьте это через json.load и начните считать снова. Затем просто повторите до завершения.
Ответ 7
import json
file1 = open('filepath', 'r')
data = file1.readlines()
for line in data :
values = json.loads(line)
'''Now you can access all the objects using values.get('key') '''
Ответ 8
Предположим, вы добавили [к началу текста в файле и использовали версию json.load(), которая, когда обнаружила ошибку нахождения {вместо ожидаемой запятой (или попала в конец файл), выплюнуть только что законченный объект?
Ответ 9
Замените файл этим нежелательным файлом в нем:
$ sed -i -e 's;}{;}, {;g' foo
Сделайте это на лету в Python:
junkJson.replace('}{', '}, {')