Получение объектов JSON из текстового файла (с использованием Python)

У меня есть тысячи текстовых файлов, содержащих несколько объектов JSON, но, к сожалению, между объектами нет разделителя. Объекты хранятся в виде словарей, а некоторые из их полей сами являются объектами. Каждый объект может иметь переменное количество вложенных объектов. Конкретно, объект может выглядеть так:

{field1: {}, field2: "some value", field3: {}, ...} 

и сотни таких объектов объединяются без разделителя в текстовом файле. Это означает, что я не могу использовать json.load() или json.loads() .

Любое предложение о том, как я могу решить эту проблему. Есть ли известный парсер для этого?

Это расшифровывает ваш «список» объектов 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) 

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

 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 

Как насчет чего-то вроде этого:

 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 ) 

Решение

Насколько я знаю }{ не отображается в действительном 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"}'] 

как доказано в примере, который я упомянул .

Почему вы не загружаете файл в виде строки, замените все} {на}, {и окружаете все это с помощью []? Что-то вроде:

 re.sub('\}\s*?\{', '\}, \{', string_read_from_a_file) 

Или просто замените строку, если вы уверены, что у вас всегда есть} {без пробелов между ними.

В случае, если вы ожидаете, что} {произойдет также в строках, вы также можете разделить} {и оценить каждый фрагмент с помощью json.load, если вы получите сообщение об ошибке, фрагмент не был завершен, и вам нужно добавить следующий первый и т. д.

Как насчет чтения файла, увеличивающего счетчик каждый раз, когда {найден и уменьшает его, когда вы сталкиваетесь с}. Когда ваш счетчик достигнет 0, вы узнаете, что вы пришли к концу первого объекта, поэтому отправьте это через json.load и начните считать снова. Затем просто повторите до завершения.

 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') ''' 

Предположим, вы добавили [к началу текста в файле и использовали версию json.load (), которая, обнаружив ошибку поиска {вместо ожидаемой запятой (или попадающей в конец файла) , выплюнуть только что законченный объект?

Замените файл этим нежелательным:

 $ sed -i -e 's;}{;}, {;g' foo 

Сделайте это на лету в Python:

 junkJson.replace('}{', '}, {')