Как я могу проанализировать первый объект 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}');