Могу ли я использовать JsonCpp для частичной проверки ввода JSON? - PullRequest
2 голосов
/ 14 февраля 2012

Я использую JsonCpp для анализа JSON в C ++.

например

Json::Reader r;
std::stringstream ss;
ss << "{\"name\": \"sample\"}";

Json::Value v;
assert(r.parse(ss, v));         // OK
assert(v["name"] == "sample");  // OK

Но мой фактический ввод - это целый поток сообщений JSON, которые могут приходить кусками любого размера;все, что я могу сделать, это заставить JsonCpp попытаться проанализировать мой ввод, символ за символом, сожрать полные сообщения JSON, когда мы их обнаружим:

Json::Reader r;
std::string input = "{\"name\": \"sample\"}{\"name\": \"aardvark\"}";

for (size_t cursor = 0; cursor < input.size(); cursor++) {  
    std::stringstream ss;
    ss << input.substr(0, cursor);

    Json::Value v;
    if (r.parse(ss, v)) {
        std::cout << v["name"] << " ";
        input.erase(0, cursor);
    }
} // Output: sample aardvark

Это уже немного неприятно, но становится все хуже,Мне также нужно иметь возможность повторной синхронизации, когда часть ввода отсутствует (по любой причине).

Теперь это не должно быть без потерь, но я хочу предотвратить потенциально возможный ввод, такой как следующийнавсегда прервал анализатор:

{"name": "samp{"name": "aardvark"}

Передача этого ввода в JsonCpp не удастся, но эта проблема не исчезнет, ​​когда мы получим больше символов в буфер;этот второй name просто недействителен непосредственно после ", который предшествует ему;буфер никогда не может быть завершен для представления действительного JSON.

Однако, если бы мне сказали, что фрагмент наверняка станет недействительным со второго n символа, я мог бы отбросить все в буфере до этой точкии затем просто дождитесь следующего {, чтобы рассмотреть начало нового объекта как повторную синхронизацию с максимальным усилием.


Итак, есть ли способ, которым я могу попросить JsonCpp сказать мнедействительно ли неполный фрагмент JSON уже гарантирует, что полный «объект» будет синтаксически недействительным?

То есть:

{"name": "sample"}   Valid        (Json::Reader::parse == true)
{"name": "sam        Incomplete   (Json::Reader::parse == false)
{"name": "sam"LOL    Invalid      (Json::Reader::parse == false)

Я хотел бы различать два состояния отказа.

Могу ли я использовать JsonCpp для достижения этой цели, или мне придется написать свой собственный «частичный валидатор» JSON, создав конечный автомат, который считает, какие символы «допустимы» на каждом шаге входной строки?Я бы не стал изобретать велосипед ...

Ответы [ 3 ]

3 голосов
/ 14 февраля 2012

Это, безусловно, зависит от того, действительно ли вы контролируете пакеты (и, следовательно, производителя) или нет. Если вы это сделаете, самый простой способ - указать границы в заголовке:

+---+---+---+---+-----------------------
| 3 | 16|132|243|endofprevious"}{"name":...
+---+---+---+---+-----------------------

Заголовок прост:

  • 3 обозначает количество границ
  • 16, 132 и 243 указывают положение каждой границы, которое соответствует открывающей скобке нового объекта (или списка)

, а затем приходит сам буфер.

После получения такого пакета могут быть проанализированы следующие записи:

  • previous + current[0:16]
  • current[16:132]
  • current[132:243]

И current[243:] сохраняется для следующего пакета (хотя вы всегда можете попытаться проанализировать его, если он завершен).

Таким образом, пакеты автоматически синхронизируются, и нечеткого обнаружения со всеми вытекающими отсюда ошибками.

Обратите внимание, что в пакете могут быть границы 0. Это просто означает, что один объект достаточно большой, чтобы охватить несколько пакетов, и вам просто нужно накапливать на данный момент.

Я бы порекомендовал сделать представление чисел «фиксированным» (например, по 4 байта каждое) и установить порядок байтов (порядок вашей машины), чтобы легко преобразовать их в / из двоичного кода. Я полагаю, что издержки довольно минимальны (4 байта + 4 байта на запись, учитывая, что {"name":""} уже 11 байтов).

3 голосов
/ 14 февраля 2012

Перебирая символьно-символьный буфер и вручную проверяя:

  • наличие буквенных символов
    • вне строки (следя за тем, чтобы " могло бытьхотя с \ *)
    • не является частью null, true или false
    • , а не e или E внутри, что выглядит как числовой литералс показателем
  • наличие цифры вне строки, но сразу после "

... не является всеобъемлющим, но ядумаю, что он охватывает достаточно случаев, чтобы достаточно надежно прервать анализ в точке или достаточно близко к точке усечения сообщения.

Он правильно принимает:

{"name": "samL
{"name": "sam0
{"name": "sam", 0
{"name": true

в качестве допустимых фрагментов JSON, ноловит:

{"name": "sam"L
{"name": "sam"0
{"name": "sam"true

как неприемлемое.

Следовательно, все следующие входные данные приведут к успешному анализу завершающего конечного объекта:

1. {"name": "samp{"name": "aardvark"}
   //            ^ ^
   //            A B    - B is point of failure.
   //                     Stripping leading `{` and scanning for the first
   //                      free `{` gets us to A. (*)
   {"name": "aardvark"}

2. {"name": "samp{"0": "abc"}
   //            ^ ^
   //            A B    - B is point of failure.
   //                     Stripping and scanning gets us to A.
   {"0": "abc"}

3. {"name":{ "samp{"0": "abc"}
   //      ^      ^ ^
   //      A      B C   - C is point of failure.
   //                     Stripping and scanning gets us to A.
   { "samp{"0": "abc"}
   //     ^ ^
   //     B C           - C is still point of failure.
   //                     Stripping and scanning gets us to B.
   {"0": "abc"}

Моя реализация проходитнекоторые довольно тщательные юнит-тесты.Тем не менее, мне интересно, можно ли улучшить сам подход, не взорвавшись по сложности.


* Вместо того, чтобы искать ведущий "{", у меня фактически есть строка часового, добавленная к каждому сообщению, котороеделает деталь «зачистки и сканирования» еще более надежной.

0 голосов
/ 27 декабря 2012

Просто посмотрите на expat или другие потоковые парсеры xml. Логика jsoncpp должна быть похожей, если ее нет. (Попросите разработчиков этой библиотеки улучшить потоковое чтение, если это необходимо.)

Другими словами, и с моей точки зрения:

  1. Если некоторые из ваших сетевых (не JSON) пакетов потеряны, это не проблема парсера JSON, просто используйте более надежный протокол или придумайте свой собственный. И только потом перенесите JSON поверх него.

  2. Если анализатор JSON сообщает об ошибках и эта ошибка произошла на последнем проанализированном токене (в потоке больше нет данных, но ожидается) - накапливайте данные и повторите попытку (эту задачу должна выполнить сама библиотека).

    Иногда он может не сообщать об ошибках, хотя. Например, когда вы переводите 123456 и только 123 получено. Но это не соответствует вашему случаю, поскольку вы не передаете примитивные данные в одном пакете JSON.

  3. Если поток содержит действительные пакеты, за которыми следуют полу-принятые пакеты, для каждого действительного пакета должен быть вызван некоторый обратный вызов.

  4. Если анализатор JSON сообщает об ошибках и это действительно недопустимый JSON, поток должен быть закрыт и, если необходимо, открыт снова.

...