Как читать 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.