2012-02-14 73 views
1

我使用JsonCpp来解析C++中的JSON。我可以使用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

通过缓冲字符逐字符和迭代手动检查:

  • 的字母字符的存在
    • 字符串(小心的是"可以与\转义,虽然)的外
    • 不是nulltruefalse
    • 不是部分0或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字节)并建立在字节顺序(您的机器上)以便将它们轻松转换为/从二进制转换为二进制。我相信开销相当小(假设{"name":""}已经是11个字节,每个条目4字节+ 4字节)。

+0

生产者只知道一个完整的JSON对象。分割完全取决于能够通过网络传输的内容。 – 2012-02-14 10:06:55

+0

(我不希望我们的协议有一个消息长度字段,但实际上,我们_do_必须在每JSON消息,看起来像'鸽子开始定点:JSON:',所以我们可以建立它变成唉也许。在下一个版本中:D) – 2012-02-14 12:00:45

+0

我能够最终调整格式,生成包含长度标题的协议的替代版本;然而,为了向后兼容,我仍然需要为现有格式解决这个问题。 – 2012-02-16 17:04:47

0

只要看看外籍人士或其他流XML解析器。如果没有,jsoncpp的逻辑应该是相似的。 (如果需要,要求开发商这个库,以提高流阅读。)

换句话说,从我的观点:

  1. 如果一些网络(未JSON)数据包丢失它不是JSON解析器的问题,只是使用更可靠的协议或发明自己的。只有然后将JSON转移到它上面。

  2. 如果JSON解析器报告错误,并且在最后解析的标记上发生了此错误(没有更多数据在数据流中但期望) - 累积数据并重试(此任务应由库本身完成)。

    虽然有时它可能不会报告错误。例如,当您传送123456并且只收到123时。但是这不符合您的情况,因为您不会在单个JSON数据包中传输原始数据。

  3. 如果流包含有效的数据包,然后半收到的数据包,一些回调应该呼吁每个有效的数据包。

  4. 如果JSON解析器报告错误,这真的无效JSON,流应重新如有必要,关闭和打开。

+0

我也在寻找流式json解析器,并尝试jsoncpp。现在,我将包含数据包长度的uint32放在我的json数据包中,我不喜欢这个解决方案,因为当我混合这样的二进制数据json时,它对测试稍微不方便。 – Sergey 2012-12-27 09:45:35

相关问题