gpt4 book ai didi

c++ - 使用 QJsonDocument 将子字符串解析为 JSON

转载 作者:塔克拉玛干 更新时间:2023-11-03 00:40:18 26 4
gpt4 key购买 nike

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

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

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

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

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

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

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

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

有没有人有更好的主意?

最佳答案

我基于 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 文档(包括多行)。

几点说明:

  • 我制作了基于 Iterator 的解析器,因此它很可能很容易与 Qt 字符串一起使用(?)
  • 如果您想禁止多行片段,请将 skipper 从 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;
}

关于c++ - 使用 QJsonDocument 将子字符串解析为 JSON,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15991232/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com