如使用JSON解析QJsonDocument一个子(Parse a substring as JSO

2019-08-22 19:09发布

我有一个包含 (未IS)JSON编码的数据,像在本实施例中的字符串:

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

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

我想这个字符串传递给像一个屡试不爽的JSON解析器QJsonDocument在QT5。 但是,随着阅读的文档 ,也不可能只分析一个字符串作为JSON,这意味着只要解析数据结束(消耗后]控制返回而不报告语法错误,在这里)。 另外,我需要知道终点位置继续分析我自己的东西(这里剩下的字符串是, "some more stuff")

要做到这一点,我用一个自定义的JSON解析器通过参考和分析整理后的更新所花费的当前位置。 但由于它是一个商业应用程序的安全性关键部分,我们并不想坚持我自己制作的解析器了。 我的意思是有QJsonDocument ,那么为什么不使用它。 (我们已经使用QT5。)

作为变通,我想这种做法的:

  • QJsonDocument解析从当前位置开始的子串(这是没有有效的JSON)
  • 错误报告意外字符,这已经超出了JSON一些位置
  • QJsonDocument再次解析,但这次用正确的结束位置的子

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

没有任何人有一个更好的主意吗?

Answer 1:

我滚[*]基于快速解析器http://www.ietf.org/rfc/rfc4627.txt使用灵奇。

它实际上并没有解析成一个AST,但它分析所有的有效载荷JSON,这实际上是比这里需要更多一点的。

将样品在这里 (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)不影响有效性这个答案(见注释)一致性快捷方式。

[*]从技术上来说,这更是一个分析器存根的,因为它并没有转化成别的东西。 它基本上是一个词法分析器承担的工作太多了:)


样品的全码:

// #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;
}


文章来源: Parse a substring as JSON using QJsonDocument