2015-06-12 2 views
6

У меня есть файл, который содержит поток JSON словарей, как это:Как обращаться с огромным потоком словарей JSON?

{"menu": "a"}{"c": []}{"d": [3, 2]}{"e": "}"} 

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

for d in getobjects(f): 
    handle_dict(d) 

Дело в том, что было бы идеально, если итерация произошло только на корневом уровне. Есть ли парсер Python, который будет обрабатывать все причуды JSON? Меня интересует решение, которое будет работать с файлами, которые не помещаются в ОЗУ.

+0

Я бы попытался разделить на '} {' или с регулярным выражением на '} \ s * {'. Оба не допускаются в JSON за пределами строк. Если у вас есть эти внутренние строки, это будет намного сложнее. –

+0

Я не могу быть уверен, что у меня его нет. – d33tah

+0

Посмотрите на парсер JSON с потоковым API. Используя Google, я столкнулся с https://pypi.python.org/pypi/ijson/ См. Особенно пример с географическими объектами. –

ответ

5

Я думаю, что JSONDecoder.raw_decode может быть тем, что вы ищете. Возможно, вам придется выполнить форматирование строк, чтобы получить его в идеальном формате в зависимости от новых строк и т. Д., Но с небольшой работой вы, вероятно, сможете что-то сделать. См. Этот пример.

import json 
jstring = '{"menu": "a"}{"c": []}{"d": [3, 2]}{"e": "}"}' 
substr = jstring 
decoder = json.JSONDecoder() 

while len(substr) > 0: 
    data,index = decoder.raw_decode(substr) 
    print data 
    substr = substr[index:] 

дает выход:

{u'menu': u'a'} 
{u'c': []} 
{u'd': [3, 2]} 
{u'e': u'}'} 
+0

Как бы вы могли заставить его работать с 'sys.stdin'? – d33tah

+0

Заменить эту строку: 'jstring = '{" menu ":" a "} {" c ": []} {" d ": [3, 2]} {" e ":"} "}' 'с чем-то например 'jstring = sys.stdin.read()' – Brien

+0

Это огромный файл, считывающий все это в память, это не вариант. – d33tah

0

Вот частичное решение, но оно продолжает замедление, как вход поступает:

#!/usr/bin/env pypy 

import json 
import cStringIO 
import sys 

def main(): 
    BUFSIZE = 10240 
    f = sys.stdin 
    decoder = json.JSONDecoder() 
    io = cStringIO.StringIO() 

    do_continue = True 
    while True: 
     read = f.read(BUFSIZE) 
     if len(read) < BUFSIZE: 
      do_continue = False 
     io.write(read) 
     try: 
      data, offset = decoder.raw_decode(io.getvalue()) 
      print(data) 
      rest = io.getvalue()[offset:] 
      if rest.startswith('\n'): 
       rest = rest[1:] 
      io = cStringIO.StringIO() 
      io.write(rest) 
     except ValueError, e: 
      #print(e) 
      #print(repr(io.getvalue())) 
      continue 
     if not do_continue: 
      break 

if __name__ == '__main__': 
    main() 
2

Здесь вы идете: испытанный решение, основанное на ответ от @Brien

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

Если вы запускаете его как автономный, он запускает три тестовых примера. (В блоке if __name__ == "__main__")

Конечно, чтобы прочитать это со стандартного ввода, вы просто передадите sys.stdin в качестве аргумента входного файла.

import json 


_DECODER = json.JSONDecoder() 

_DEFAULT_CHUNK_SIZE = 4096 
_MB = (1024 * 1024) 
_LARGEST_JSON_OBJECT_ACCEPTED = 16 * _MB # default to 16 megabytes 

def json_objects_from_file(input_file, 
      chunk_size=_DEFAULT_CHUNK_SIZE, 
      max_size=_LARGEST_JSON_OBJECT_ACCEPTED): 
    """ 
    Read an input file, and yield up each JSON object parsed from the file. 

    Allocates minimal memory so should be suitable for large input files. 
    """ 
    buf = '' 
    while True: 
     temp = input_file.read(chunk_size) 
     if not temp: 
      break 

     # Accumulate more input to the buffer. 
     # 
     # The decoder is confused by leading white space before an object. 
     # So, strip any leading white space if any. 
     buf = (buf + temp).lstrip() 
     while True: 
      try: 
       # Try to decode a JSON object. 
       x, i = _DECODER.raw_decode(buf) 
       # If we got back a dict, we got a whole JSON object. Yield it. 
       if type(x) == dict: 
        # First, chop out the JSON from the buffer. 
        # Also strip any leading white space if any. 
        buf = buf[i:].lstrip() 
        yield x 
      except ValueError: 
       # Either the input is garbage or we got a partial JSON object. 
       # If it's a partial, maybe appending more input will finish it, 
       # so catch the error and keep handling input lines. 

       # Note that if you feed in a huge file full of garbage, this will grow 
       # very large. Blow up before reading an excessive amount of data. 

       if len(buf) >= max_size: 
        raise ValueError("either bad input or too-large JSON object.") 
       break 
    buf = buf.strip() 
    if buf: 
     if len(buf) > 70: 
      buf = buf[:70] + '...' 
     raise ValueError('leftover stuff from input: "{}"'.format(buf)) 

if __name__ == "__main__": 
    from StringIO import StringIO 

    jstring = '{"menu":\n"a"}{"c": []\n}\n{\n"d": [3,\n 2]}{\n"e":\n "}"}' 
    f = StringIO(jstring) 
    correct = [{u'menu': u'a'}, {u'c': []}, {u'd': [3, 2]}, {u'e': u'}'}] 

    result = list(json_objects_from_file(f, chunk_size=3)) 
    assert result == correct 

    f = StringIO(' ' * (17 * _MB)) 
    correct = [] 

    result = list(json_objects_from_file(f, chunk_size=_MB)) 
    assert result == correct 

    f = StringIO('x' * (17 * _MB)) 
    correct = "ok" 

    try: 
     result = list(json_objects_from_file(f, chunk_size=_MB)) 
    except ValueError: 
     result = correct 
    assert result == correct 
Смежные вопросы