如何在JS中解析流上的第一个JSON对象

时间:2012-03-22 20:15:54

标签: javascript json

我有一个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。我仍然宁愿使用经过其他人测试和使用的东西,而不是自己编写代码,所以我将这个问题保持开放。

3 个答案:

答案 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);