我有一个JSON对象流,就像TCP或WebSockets上的JSON-RPC一样。没有长度前缀或分隔符,因为JSON是自我分隔的。所以,当我从流中读到时,我可能会得到这样的结果:
{"id":1,"result":{"answer":23},"error":null}
{"id":2,"result":{"answer":42},"error":null}
{"id":3,"result":{"answ
我需要逐个解析每个JSON对象。我不能用JSON.parse做到这一点,因为它只会在最后为无关数据抛出语法错误。
当然,通过这个例子我可以逐行进行,但我不能依赖看起来那样的空白; JSON-RPC可以很容易看起来像这样:
{
"id": 1,
"result": {
"answer": 23
},
"error":null
}
或者这个:
{"id":1,"result":{"answer":23},"error":null}{"id":2,"result":{"answer":42},"error":null}
对于大多数其他语言的解析器,显而易见的答案是这样的(以Python为例):
buf = ''
decoder = json.JSONDecoder()
def onReadReady(sock):
buf += sock.read()
obj, index = decoder.raw_decode(buf)
buf = buf[index:]
if obj:
dispatch(obj)
但我在JS中找不到类似的东西。我查看了我能找到的每个JS解析器,它们都有效地等同于JSON.parse。
我尝试查看各种JSON-RPC框架,看看他们如何处理这个问题,而他们却没有。他们中的许多人都认为recv总是会返回一个send(这对于JSON-RPC通过HTTP工作正常,但不适用于TCP或WebSockets - 当然,它可能在本地测试中起作用)。其他人实际上并没有处理JSON-RPC,因为他们在空格上添加了一些要求(其中一些甚至对JSON-RPC都不起作用)。
我可以写一个分隔括号和引号的分隔符检查(当然是处理转义和引用),或者只是从头开始编写JSON解析器(或者从另一种语言编写端口1,或修改http://code.google.com/p/json-sans-eval/),但是我不敢相信没有人这样做过。
编辑:我自己制作了两个版本,http://pastebin.com/fqjKYiLw基于json-sans-eval,http://pastebin.com/8H4QT82b基于Crockford的参考递归下降解析器json_parse.js。我仍然宁愿使用经过其他人测试和使用的东西,而不是自己编写代码,所以我将这个问题保持开放。答案 0 :(得分:15)
经过一个月的搜索替代方案而没有找到任何有用的东西,我决定编写一堆不同的实现并测试它们,然后我修改了Crockford的参考递归下降解析器(如问题中所述) ,available here)。
这不是最快的,但在我做的每一次测试中它都足够快。更重要的是,它可以捕获明显错误的JSON,当它与不完整的JSON不一致时,比大多数其他替代方案要好得多。最重要的是,它需要从众所周知且经过验证的代码库中进行非常简单的更改,这使我对其正确性更有信心。
尽管如此,如果有人知道一个比我更好的图书馆(并且只是被许多项目使用而不仅仅是我认为是一个重要的资格),我很想知道它。
答案 1 :(得分:2)
这是一个简单的JSON对象分隔符。它假定您收到一系列JSON对象(不是数组)并且格式正确。
function JSONObjectSepaator() {
this.onObject = function (JSONStr) {};
this.reset = function () {
this.brace_count = 0;
this.inString = false;
this.escaped = false;
this.buffer = "";
};
this.receive = function (S) {
var i;
var pos=0;
for (i = 0; i < S.length; i++) {
var c = S[i];
if (this.inString) {
if (this.escaped) {
this.escaped = false;
} else {
if (c == "\\") {
this.escaped = true;
} else if (c == "\"") {
this.inString = false;
}
}
} else {
if (c == "{") {
this.brace_count++;
} else if (c == "}") {
this.brace_count--;
if (this.brace_count === 0) {
this.buffer += S.substring(pos,i+1);
this.onObject(this.buffer);
this.buffer = "";
pos=i+1;
}
} else if (c == "\"") {
this.inString = true;
}
}
}
this.buffer += S.substring(pos);
};
this.reset();
return this;
}
要使用它,您可以这样做:
var separator = new JSONObjectSepaator();
separator.onObject = function (o) {
alert("Object received: "+o);
};
separator.receive('{"id":1,"result":{"answer":23},"error":null, "x');
separator.receive('x":"\\\""}{"id":2,"result":{"answer":42},"error":null}{"id":');
separator.receive('3,"result":{"answer":43},"err{or":3}');
答案 2 :(得分:0)
务实回答:使用python
pos = 0
try:
while not pos == len(str(body)):
# raw_decode will parse as much of the line as possible and return how much was left
j, json_len = decoder.raw_decode(str(body)[pos:])
pos += json_len
# "j" holds your object
我正在编写一些代码来解析从流中保存的大量 JSON 文件,每个流中有 1 到 50 个 JSON 对象。 这是一个 JavaScript hack 但它适用于我的目的(依赖于包含“坏”JSON 位置的错误消息。如果错误字符是“{”那么我知道这可能是开始一个新对象):
let contents = ""; // your JSON string
let startIndex = 0;
let endIndex = contents.length;
do {
let obj;
try {
let str = contents.slice(startIndex, endIndex);
obj = JSON.parse(str);
startIndex = endIndex;
endIndex = contents.length;
console.log("successfully parsed", obj);
}
catch (e) {
let match = e.toString().match(/^SyntaxError: Unexpected token { in JSON at position (\d+)$/);
if (match && match.length == 2) {
endIndex = parseInt(match[1]) + startIndex;
}
else throw e;
}
} while (startIndex < endIndex);