2012-02-14 4 views
1

Я использую JsonCpp для анализа JSON в C++.Могу ли я использовать JsonCpp для частичного подтверждения ввода JSON?

например.

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-частичный валидатор, построив конечный автомат, который считает, какие символы «действительны» на каждом шаге через входную строку? Я бы предпочел не изобретать колесо ...

ответ

2

Итерации через буфер символов по-характер и ручную проверку для:

  • присутствия алфавитных символов
    • вне струны (быть осторожным, чтобы " можно избежать с \, хотя)
    • не является частью 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"} 

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


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

2

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

+---+---+---+---+----------------------- 
| 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 байтов).

+0

Производитель знает только полный объект JSON. Сегментация сводится к тому, что можно было передать по сети. –

+0

(Я действительно хочу, чтобы у нашего протокола было поле длины сообщения. В начале каждого сообщения JSON у нас есть дозорный сигнал, который выглядит как «PIGEON: JSON:', поэтому мы могли бы построить его в этом. в следующей версии: D) –

+0

I _was_ в состоянии настроить формат в конце, создавая сверхсовременную версию нашего протокола, которая включает в себя заголовок длины; однако мне все еще нужно было решить эту проблему для существующего формата по соображениям обратной совместимости. –

0

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

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

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

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

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

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

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

+0

Я также ищу потоковый json-парсер и собираюсь попробовать jsoncpp. На данный момент я добавляю свои json-пакеты с длиной uint32, содержащей длину пакета, и мне не нравится это решение, так как он немного неудобен для тестирования, когда я смешиваю такие двоичные данные json. – Sergey

Смежные вопросы