Как я могу проанализировать первый объект JSON в потоке в JS

У меня есть поток объектов JSON, как у JSON-RPC через TCP или WebSockets. Там нет префикса длины или разделителя, потому что 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 всегда будет возвращать ровно одну отправку (что отлично работает для JSON-RPC через HTTP, но не через TCP или WebSockets, хотя, возможно, это может работать в локальных тестах, конечно). Другие фактически не обрабатывают JSON-RPC, потому что они добавляют требования к пробелам (некоторые из которых даже не применимы для JSON-RPC).

Я мог бы написать проверку разделителя, которая уравновешивает скобки и кавычки (обрабатывая экранирование и цитирование, конечно), или просто писать парсер JSON с нуля (или переносить один с другого языка или изменять http://code.google.com/p/json-sans-eval/), но я не могу поверить, что никто этого не делал раньше.

EDIT: я сам сделал две версии: http://pastebin.com/fqjKYiLw на основе json-sans-eval и http://pastebin.com/8H4QT82b на основе рекурсивного парсера регрессионного спуска в Crockford json_parse.js. Я бы предпочел использовать что-то, что было проверено и использовано другими людьми, а не само кодирование, поэтому я оставляю этот вопрос открытым.

Ответ 1

Спустя месяц поиска альтернатив и не найдя ничего полезного, я решил закодировать кучу различных реализаций и проверить их, и я пошел с моей модификацией рекурсивного спуска парсера Crockford (как описано в вопросе, здесь).

Это был не самый быстрый, но он был более чем достаточно быстрым в каждом испытании, которое я сделал. Что еще более важно, он ловит явно ошибочный JSON, когда это не двусмысленно с неполным JSON, намного лучше, чем большинство других альтернатив. Самое главное, это требовало очень немногих и довольно простых изменений от хорошо известной и протестированной кодовой базы, что делает меня более уверенным в ее правильности.

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

Ответ 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}');