Как читать JSON из сокета в python? (Инкрементальный парсинг JSON с)

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

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

Итак, мои вопросы: есть ли (простой и элегантный) решение? Есть ли другая библиотека json, которая может анализировать данные постепенно? Стоит ли писать самому?

Edit: это XBMC JSONRPC api. Нет конвертов с сообщениями, и я не контролирую формат. Каждое сообщение может быть в одной строке или несколько строк. Я мог бы написать простой парсер, который нуждается только в функции getc в некоторой форме и кормить его с помощью s.recv(1), но это не так, как очень pythonic решение, и я немного ленив, чтобы сделать это: -)

6 ответов


то, что вы хотите(ed), - это ijson, инкрементный парсер json. Он доступен здесь: https://pypi.python.org/pypi/ijson/ . Использование должно быть простым, как (копирование с этой страницы):

import ijson.backends.python as ijson

for item in ijson.items(file_obj):
    # ...

(для тех, кто предпочитает что-то автономное - в том смысле, что он полагается только на стандартную библиотеку: вчера я написал небольшую обертку вокруг json - но только потому, что я не знал об ijson. Это, вероятно, гораздо меньше эффективный.)

редактировать: поскольку я узнал, что на самом деле (цитонизированная версия) мой подход был намного эффективнее, чем ijson, я упаковал его как независимую библиотеку-см. здесь также для некоторых грубых тестов:http://pietrobattiston.it/jsaone


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


предполагая, что это сокет stream (TCP), вам нужно реализовать свой собственный механизм кадрирования сообщений (или использовать существующий протокол более высокого уровня, который делает это). Один простой способ-определить каждое сообщение как 32-разрядное поле целочисленной длины, за которым следует столько байтов данных.

отправитель: возьмите длину пакета JSON, упакуйте его в 4 байты с struct модуль, отправьте его в сокет, затем отправьте пакет JSON.

Receiver: повторное чтение из сокета, пока у вас не будет по крайней мере 4 байта данных, используйте struct.unpack распаковать длина. Читайте из сокета, пока у вас не будет по крайней мере столько данных, и это ваш пакет JSON; все, что осталось, - это длина следующего сообщения.

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

другой, немного больше стандартного, метод DJB это Netstrings протокол; он очень похож на систему, предложенную выше, но с текстовыми кодировками вместо двоичных; он напрямую поддерживается такими фреймворками, как Twisted.


Если вы получаете JSON из потока HTTP, используйте Content-Length заголовок для получения длины данных JSON. Например:

import httplib
import json

h = httplib.HTTPConnection('graph.facebook.com')
h.request('GET', '/19292868552')
response = h.getresponse()
content_length = int(response.getheader('Content-Length','0'))

# Read data until we've read Content-Length bytes or the socket is closed
data = ''
while len(data) < content_length or content_length == 0:
    s = response.read(content_length - len(data))
    if not s:
        break
    data += s

# We now have the full data -- decode it
j = json.loads(data)
print j

у вас есть контроль над json? Попробуйте записать каждый объект в одну строку. Затем выполните вызов readline для сокета как описано здесь.

infile = sock.makefile()

while True:
    line = infile.readline()
    if not line: break
    # ...
    result = json.loads(line)

просматривая документы XBMC JSON RPC, я думаю, вам нужна существующая библиотека JSON-RPC - вы можете взглянуть на: http://www.freenet.org.nz/dojo/pyjson/

Если это не подходит по какой-либо причине, мне кажется, что каждый запрос и ответ содержатся в объекте JSON (а не в свободном примитиве JSON, который может быть строкой, массивом или числом), поэтому конверт, который вы ищете, является" {... } 'это определяет объект JSON.

Я бы, поэтому попробуйте что-то вроде (псевдокод):

while not dead:
    read from the socket and append it to a string buffer
    set a depth counter to zero
    walk each character in the string buffer:
        if you encounter a '{':
            increment depth
        if you encounter a '}':
            decrement depth
            if depth is zero:
                remove what you have read so far from the buffer
                pass that to json.loads()

вы можете найти JSON-RPC полезным для этой ситуации. Это протокол удаленного вызова процедур, который должен позволить вам вызывать методы, предоставляемые XBMC JSON-RPC. Вы можете найти спецификацию на Trac.