Как использовать модуль 'json' для чтения по одному объекту JSON одновременно?

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

Использование Python 3 и json модуль, как я могу прочитать один объект JSON за один раз из файла в память?

Данные находятся в текстовом файле. Вот пример подобной записи. Фактические записи содержат много вложенных словарей и списков.

Запись в читаемом формате:

{
    "results": {
      "__metadata": {
        "type": "DataServiceProviderDemo.Address"
      },
      "Street": "NE 228th",
      "City": "Sammamish",
      "State": "WA",
      "ZipCode": "98074",
      "Country": "USA"
    }
  }
}

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

{"results": { "__metadata": {"type": "DataServiceProviderDemo.Address"},"Street": "NE 228th","City": "Sammamish","State": "WA","ZipCode": "98074","Country": "USA" } } }{"results": { "__metadata": {"type": "DataServiceProviderDemo.Address"},"Street": "NE 228th","City": "Sammamish","State": "WA","ZipCode": "98074","Country": "USA" } } }{"results": { "__metadata": {"type": "DataServiceProviderDemo.Address"},"Street": "NE 228th","City": "Sammamish","State": "WA","ZipCode": "98074","Country": "USA" } } }

3 ответа

Решение

Вообще говоря, помещение в файл нескольких объектов JSON делает этот файл недействительным, нарушает JSON. Тем не менее, вы все еще можете анализировать данные в кусках, используя JSONDecoder.raw_decode() метод.

Следующее приведёт к завершенным объектам, так как парсер их найдет:

from json import JSONDecoder
from functools import partial


def json_parse(fileobj, decoder=JSONDecoder(), buffersize=2048):
    buffer = ''
    for chunk in iter(partial(fileobj.read, buffersize), ''):
         buffer += chunk
         while buffer:
             try:
                 result, index = decoder.raw_decode(buffer)
                 yield result
                 buffer = buffer[index:]
             except ValueError:
                 # Not enough data to decode, read more
                 break

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

Используйте это так:

with open('yourfilename', 'r') as infh:
    for data in json_parse(infh):
        # process object

Используйте это только в том случае, если ваши объекты JSON записаны в файл вплотную, без переносов между ними. Если у вас есть новые строки, и каждый объект JSON ограничен одной строкой, у вас есть документ JSON Lines, и в этом случае вы можете использовать Загрузка и анализ файла JSON с несколькими объектами JSON в Python.

Вот небольшая модификация решения Martijn Pieters, которая будет обрабатывать строки JSON, разделенные пробелами.

def json_parse(fileobj, decoder=json.JSONDecoder(), buffersize=2048, 
               delimiters=None):
    remainder = ''
    for chunk in iter(functools.partial(fileobj.read, buffersize), ''):
        remainder += chunk
        while remainder:
            try:
                stripped = remainder.strip(delimiters)
                result, index = decoder.raw_decode(stripped)
                yield result
                remainder = stripped[index:]
            except ValueError:
                # Not enough data to decode, read more
                break

Например, если data.txt содержит строки JSON, разделенные пробелом:

{"business_id": "1", "Accepts Credit Cards": true, "Price Range": 1, "type": "food"} {"business_id": "2", "Accepts Credit Cards": true, "Price Range": 2, "type": "cloth"} {"business_id": "3", "Accepts Credit Cards": false, "Price Range": 3, "type": "sports"}

затем

In [47]: list(json_parse(open('data')))
Out[47]: 
[{u'Accepts Credit Cards': True,
  u'Price Range': 1,
  u'business_id': u'1',
  u'type': u'food'},
 {u'Accepts Credit Cards': True,
  u'Price Range': 2,
  u'business_id': u'2',
  u'type': u'cloth'},
 {u'Accepts Credit Cards': False,
  u'Price Range': 3,
  u'business_id': u'3',
  u'type': u'sports'}]

Если ваши документы JSON содержат список объектов, и вы хотите читать по одному объекту по одному, вы можете использовать итерационный JSON-анализатор ijson для этой работы. Он будет только читать больше содержимого из файла, когда ему нужно будет декодировать следующий объект.

Обратите внимание, что вы должны использовать его с библиотекой YAJL, иначе вы скорее всего не увидите увеличения производительности.

Тем не менее, если ваш файл не очень большой, чтение его полностью в память и последующий синтаксический анализ с помощью обычного модуля JSON, вероятно, все еще будет лучшим вариантом.

Другие вопросы по тегам