【问题标题】:Parse a substring as JSON using QJsonDocument使用 QJsonDocument 将子字符串解析为 JSON
【发布时间】:2013-04-13 18:18:26
【问题描述】:

我有一个字符串,其中 包含(不是 is)JSON 编码数据,如下例所示:

foo([1, 2, 3], "some more stuff")
    |        |
  start     end   (of JSON-encoded data)

我们在应用程序中使用的完整语言嵌套了 JSON 编码的数据,而该语言的其余部分是微不足道的(只是递归的东西)。在递归解析器中从左到右解析这样的字符串时,我知道什么时候遇到 JSON 编码的值,比如这里的 [1, 2, 3] 从索引 4 开始。解析这个子字符串后,我需要知道结束位置才能继续解析字符串的其余部分。

我想将此子字符串传递给经过良好测试的 JSON 解析器,例如 Qt5 中的QJsonDocument。但是在读取the documentation 时,不可能只将子字符串解析为JSON,这意味着只要解析的数据结束(在此处使用] 之后)控件就会返回而不报告解析错误。另外,我需要知道结束位置才能继续解析我自己的东西(这里剩余的字符串是, "some more stuff"))。

为此,我曾经使用自定义 JSON 解析器,它通过引用获取当前位置并在完成解析后更新它。但由于它是业务应用程序的安全关键部分,我们不想再坚持使用我自己制作的解析器。我的意思是有QJsonDocument,所以为什么不使用它。 (我们已经在使用 Qt5。)

作为一种变通方法,我正在考虑这种方法:

  • QJsonDocument从当前位置开始解析子字符串(不是有效的JSON)
  • 错误报告意外字符,这是 JSON 之外的某个位置
  • QJsonDocument 再次解析,但这次是具有正确结束位置的子字符串

第二个想法是编写一个“JSON 结束扫描器”,它获取整个字符串、一个起始位置并返回 JSON 编码数据的结束位置。这也需要解析,因为不匹配的括号/括号可以出现在字符串值中,但与完全手工制作的 JSON 解析器相比,编写(和使用)这样的类应该更容易(也更安全)。

有人有更好的主意吗?

【问题讨论】:

  • 好问题!虽然不知道答案:(
  • 我想我只是要基于ietf.org/rfc/rfc4627.txt写一个简单的结构解析器
  • 注意:“JSON 结束扫描器”只需要在从当前位置开始存在有效 JSON 的情况下报告一个有意义的值。如果不是,我仍然想在字符串上运行 QJsonDocument 解析器以获得良好的错误消息。所以扫描仪可能只计算开括号和右括号,甚至不区分它们。唯一的困难是不在字符串中计算它们。请注意,\" 不会终止字符串。我想这是足够的信息来编写这样的扫描仪。问题是是否有更好的解决方案来做到这一点(更简单、更快、更安全……)。

标签: c++ json qt parsing qt5


【解决方案1】:

我使用 Spirit Qi 基于http://www.ietf.org/rfc/rfc4627.txt 滚动了一个快速解析器[*]。

它实际上并没有解析成 AST,但它解析了所有 JSON 有效负载,这实际上比这里需要的要多一点。

示例here (http://liveworkspace.org/code/3k4Yor$2) 输出:

Non-JSON part of input starts after valid JSON: ', "some more stuff")'

根据OP给出的测试:

const std::string input("foo([1, 2, 3], \"some more stuff\")");

// set to start of JSON
auto f(begin(input)), l(end(input));
std::advance(f, 4);

bool ok = doParse(f, l); // updates f to point after the start of valid JSON

if (ok) 
    std::cout << "Non-JSON part of input starts after valid JSON: '" << std::string(f, l) << "'\n";

我已经测试了其他几个涉及更多的 JSON 文档(包括多行)。

几点说明:

  • 我制作了基于迭代器的解析器,因此它可能很容易与 Qt 字符串一起工作(?)
  • 如果您想禁止多行片段,请将跳过器从 qi::space 更改为 qi::blank
  • 有一个关于数字解析的一致性快捷方式(请参阅 TODO),它不会影响此答案的有效性(请参阅评论)。

[*] 从技术上讲,这更像是一个解析器 stub,因为它不会翻译成其他东西。它基本上是一个词法分析器,承担了太多的工作:)


示例完整代码:

// #define BOOST_SPIRIT_DEBUG
#include <boost/spirit/include/qi.hpp>

namespace qi = boost::spirit::qi;

template <typename It, typename Skipper = qi::space_type>
    struct parser : qi::grammar<It, Skipper>
{
    parser() : parser::base_type(json)
    {
        // 2.1 values
        value = qi::lit("false") | "null" | "true" | object | array | number | string;

        // 2.2 objects
        object = '{' >> -(member % ',') >> '}';
        member = string >> ':' >> value;

        // 2.3 Arrays
        array = '[' >> -(value % ',') >> ']';

        // 2.4.  Numbers
        // Note out spirit grammar takes a shortcut, as the RFC specification is more restrictive:
        //
        // However non of the above affect any structure characters (:,{}[] and double quotes) so it doesn't
        // matter for the current purpose. For full compliance, this remains TODO:
        //
        //    Numeric values that cannot be represented as sequences of digits
        //    (such as Infinity and NaN) are not permitted.
        //     number = [ minus ] int [ frac ] [ exp ]
        //     decimal-point = %x2E       ; .
        //     digit1-9 = %x31-39         ; 1-9
        //     e = %x65 / %x45            ; e E
        //     exp = e [ minus / plus ] 1*DIGIT
        //     frac = decimal-point 1*DIGIT
        //     int = zero / ( digit1-9 *DIGIT )
        //     minus = %x2D               ; -
        //     plus = %x2B                ; +
        //     zero = %x30                ; 0
        number = qi::double_; // shortcut :)

        // 2.5 Strings
        string = qi::lexeme [ '"' >> *char_ >> '"' ];

        static const qi::uint_parser<uint32_t, 16, 4, 4> _4HEXDIG;

        char_ = ~qi::char_("\"\\") |
               qi::char_("\x5C") >> (       // \ (reverse solidus)
                   qi::char_("\x22") |      // "    quotation mark  U+0022
                   qi::char_("\x5C") |      // \    reverse solidus U+005C
                   qi::char_("\x2F") |      // /    solidus         U+002F
                   qi::char_("\x62") |      // b    backspace       U+0008
                   qi::char_("\x66") |      // f    form feed       U+000C
                   qi::char_("\x6E") |      // n    line feed       U+000A
                   qi::char_("\x72") |      // r    carriage return U+000D
                   qi::char_("\x74") |      // t    tab             U+0009
                   qi::char_("\x75") >> _4HEXDIG )  // uXXXX                U+XXXX
               ;

        // entry point
        json = value;

        BOOST_SPIRIT_DEBUG_NODES(
                (json)(value)(object)(member)(array)(number)(string)(char_));
    }

  private:
    qi::rule<It, Skipper> json, value, object, member, array, number, string;
    qi::rule<It> char_;
};

template <typename It>
bool tryParseAsJson(It& f, It l) // note: first iterator gets updated
{
    static const parser<It, qi::space_type> p;

    try
    {
        return qi::phrase_parse(f,l,p,qi::space);
    } catch(const qi::expectation_failure<It>& e)
    {
        // expectation points not currently used, but we could tidy up the grammar to bail on unexpected tokens
        std::string frag(e.first, e.last);
        std::cerr << e.what() << "'" << frag << "'\n";
        return false;
    }
}

int main()
{
#if 0
    // read full stdin
    std::cin.unsetf(std::ios::skipws);
    std::istream_iterator<char> it(std::cin), pte;
    const std::string input(it, pte);

    // set up parse iterators
    auto f(begin(input)), l(end(input));
#else
    const std::string input("foo([1, 2, 3], \"some more stuff\")");

    // set to start of JSON
    auto f(begin(input)), l(end(input));
    std::advance(f, 4);
#endif

    bool ok = tryParseAsJson(f, l); // updates f to point after the end of valid JSON

    if (ok) 
        std::cout << "Non-JSON part of input starts after valid JSON: '" << std::string(f, l) << "'\n";
    return ok? 0 : 255;
}

【讨论】:

  • 非常感谢。是的,QString 有一个迭代器接口,但 qi 似乎有问题......我目前正在尝试解决它;)可能是因为 QString 使用了自定义字符类型,即 QChar,这可能会让 qi 不高兴。迭代器本身是QChar*,所以处理迭代器不是问题。子字符串构造不适用于QString(f,l),但不适用于QString(f, l-f)(长度参数)。 (但是您的代码在使用 std::string 时没有问题。谢谢!)
  • @leemes 如果你愿意给我一个使用 Qt 的隔离测试应用程序,我可能会尝试在这里了解 Qt :) - 该死的 example 用于解析 into QString
  • 我发现了问题,遗憾的是它的内心深处:它试图将字符类型转换为 int,QChar 不支持 :( 它在 boost/spirit/home/support/char_class.hpp,第 785 行.
  • 出于好奇,我决定制作 JSON 解析器 UNICODE aware 并解析为实际的 AST 树 (a beauty if I may say so myself)。往返测试检查出来(尽管排序不稳定,所以第一个测试报告了一个假阴性;使用list&lt;pair&lt;K,V&gt;&gt; 而不是map&lt;K,V&gt; 来防止这种情况发生)。见my github
  • 现在我也可以在线演示它live了:http://coliru..../view?id=079b418....
猜你喜欢
  • 1970-01-01
  • 2015-02-15
  • 2011-03-14
  • 2015-10-23
  • 2022-12-10
  • 2012-10-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多