如何从python中的socket读取JSON? (JSON的增量解析)

时间:2011-09-07 16:44:13

标签: python json sockets

我打开了一个套接字,我想从中读取一些json数据。问题是标准库中的json模块只能从字符串中解析(load只读取整个文件并在其中调用loads)它甚至在模块内部一直看起来一切都取决于参数是字符串。

这是套接字的真正问题,因为你永远无法将它全部读取为字符串,并且在实际解析它之前你不知道要读取多少字节。

所以我的问题是:是否有(简单而优雅)的解决方法?是否有另一个可以递增地解析数据的json库?值得自己写一下吗?

编辑:这是XBMC jsonrpc api。没有消息信封,我无法控制格式。每条消息可以在一行或多行上。 我可以编写一些简单的解析器,它只需要某种形式的getc函数并使用s.recv(1)来提供它,但这不是一个非常pythonic的解决方案而且我有点懒,这样做: - )

7 个答案:

答案 0 :(得分:4)

编辑:鉴于您没有定义协议,这没有用,但它可能在其他环境中很有用。


假设它是一个流(TCP)套接字,您需要实现自己的消息成帧机制(或使用现有的更高级别协议)。一种直接的方法是将每条消息定义为32位整数长度字段,然后是那么多字节的数据。

Sender:获取JSON数据包的长度,用struct模块将其打包成4个字节,在套接字上发送,然后发送JSON数据包。

Receiver:重复读取套接字,直到您有至少4个字节的数据,使用struct.unpack解压缩长度。从套接字读取,直到你拥有至少那么多的数据,这是你的JSON数据包;遗留下来的是下一条消息的长度。

如果在某个时候您想要在同一个套接字上发送由JSON以外的内容组成的消息,您可能希望在长度和数据有效负载之间发送消息类型代码;恭喜你,你已经发明了另一种协议。

另一种稍微更标准的方法是DJB的Netstrings协议;它与上面提出的系统非常相似,但是使用文本编码的长度而不是二进制;它由Twisted等框架直接支持。

答案 1 :(得分:3)

如果您从HTTP流获取JSON,请使用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

答案 2 :(得分:3)

你想要的(ed)是ijson,一个增量的json解析器。 它可以在这里找到:https://pypi.python.org/pypi/ijson/。用法应该很简单(从该页面复制):

import ijson.backends.python as ijson

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

(对于那些喜欢自足的人 - 从某种意义上说它只依赖于标准库:我昨天写了一个围绕json的小包装 - 但仅仅因为我不知道ijson。它可能更少高效。)

编辑:因为我发现事实上(一个cythonized版本)我的方法比ijson更有效,我把它打包成一个独立的库 - 在这里也看一些粗略的基准:http://pietrobattiston.it/jsaone

答案 3 :(得分:2)

你有控制json吗?尝试将每个对象写为一行。然后在套接字上执行readline调用described here

infile = sock.makefile()

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

答案 4 :(得分:0)

略读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()

答案 5 :(得分:0)

您可能会发现JSON-RPC对这种情况很有用。它是一个远程过程调用协议,应该允许您调用XBMC JSON-RPC公开的方法。您可以在Trac上找到规范。

答案 6 :(得分:0)

res = str(s.recv(4096), 'utf-8') # Getting a response as string
res_lines = res.splitlines() # Split the string to an array
last_line = res_lines[-1] # Normally, the last one is the json data
pair = json.loads(last_line)

https://github.com/A1vinSmith/arbitrary-python/blob/master/sockets/loopHost.py