我可以使用JsonCpp来部分验证JSON输入吗?

时间:2012-02-14 00:32:47

标签: c++ json validation jsoncpp

我正在使用JsonCpp来解析C ++中的JSON。

e.g。

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字符时肯定会变为无效,我可以将缓冲区中的所有内容丢弃到该点,然后只需等待下一个{{1}考虑新对象的开始,作为尽力而为的重新同步。


那么,有没有办法可以让JsonCpp告诉我JSON的不完整片段是否已经保证完整的“对象”在语法上是无效的?

那是:

{

我想区分两种失败状态。

我可以使用JsonCpp来实现这一点,还是我必须通过构造一个状态机来编写我自己的JSON“部分验证器”,该状态机在输入字符串的每一步中考虑哪些字符是“有效的”?我宁愿不重新发明轮子......

3 个答案:

答案 0 :(得分:3)

逐个字符地迭代缓冲区并手动检查:

  • 字母字符的存在
    • 字符串之外(注意"可以使用\进行转义)
    • 不属于nulltruefalse
    • 不是eE里面看起来像带有指数的数字文字
  • 字符串外部的数字,但紧接"
  • 之后

...并不是无所不包,但我认为它涵盖了足够的案例,可以在消息截断点或合理接近消息截断点时相当可靠地破解解析。

它正确接受:

{"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"}

我的实现通过了一些非常彻底的单元测试。不过,我想知道这种方法本身是否可以在没有复杂性爆炸的情况下得到改善。


*我没有寻找领先的"{",而是在每条消息前面都有一个前哨字符串,这使得“剥离和扫描”部分更加可靠。

答案 1 :(得分:3)

这当然取决于你是否真正控制了数据包(以及生产者)。如果这样做,最简单的方法是在标题中指明边界:

+---+---+---+---+-----------------------
| 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个字节)。

答案 2 :(得分:0)

只需查看expat或其他流式xml解析器即可。如果不是,jsoncpp的逻辑应该是相似的。 (如果需要,请该库的开发人员改进流读取。)

换句话说,从我的观点来看:

  1. 如果您的某些网络(而不是JSON)数据包丢失,则不会出现JSON解析器问题,只需使用更可靠的协议或创建自己的协议即可。然后才将JSON转移到它上面。

  2. 如果JSON解析器报告错误并且此错误发生在最后解析的令牌上(流中没有更多数据但是预期) - 累积数据并再次尝试(此任务应由库本身完成)。

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

  3. 如果流包含有效数据包后跟半数据包,则应为每个有效数据包调用一些回调。

  4. 如果JSON解析器报告错误并且它确实是无效的JSON,则应关闭该流并在必要时再次打开。