c++boostboost-spiritqi

How to parse number after finding some word


I want to parse undermentioned JSON and extract from it productionYear value

    auto data = {
        "cars" : [
           {
              "name" : "BMW",
              "engine" : 3.0
           },
           {
              "name" : "Citroen",
              "engine" : 3.6
           },
           {
              "name" : "Ferrari",
              "engine" : 4.2
           }
        ],
        "productionYear" : 1999
    }

and I have the following rule for that:

    using boost::spirit::ascii::string;
    using boost::spirit::omit;
    using boost::spirit::qi::lit;
    using boost::spirit::qi::char_;

    boost::spirit::qi::rule<std::string::iterator, int()> production_;
    production_ = omit[*(char_ - "productionYear") >>
                     lit('"') >> lit(' ') >> lit(':') >> lit(' ')] >> int_;

    int year;
    auto it = data.begin();

    if (boost::spirit::qi::parse(it, data.end(), production_, year))
    {
        std::cout << "Parse finished with succeded status"
                  << std::distance(data.begin(), it);
    }

The parser fails with last iterator position: 0

Can anybody tell me what am I doing wrong ?


Solution

  • DISCLAIMER

    Do not do this. Use a JSON parser. There are plenty. Your grammar is brittle in a gazillion ways. (You will stop on productionYear inside another value, as partial match in a longer key, as a property of nested/sibling objects, UNICODE escapes, escapes. You will not allow valid JSON optional whitespace, etc.).

    See here for what it takes to parse JSON in Spirit, to a reasonable extent: https://github.com/sehe/spirit-v2-json/blob/master/json.cpp

    *(char_ - "productionYear") 
    

    Parses any text up to (but not including) productionYear.

    This means that the next character never matches '"' (because it's 'p').

    A straight forward fix is

    #include <boost/spirit/include/qi.hpp>
    
    namespace qi = boost::spirit::qi;
    
    int main() {
        std::string const data = R"({
            "cars": [{
                "name": "BMW",
                "engine": 3.0
            },
            {
                "name": "Citroen",
                "engine": 3.6
            },
            {
                "name": "Ferrari",
                "engine": 4.2
            }],
            "productionYear" : 1999
        })";
    
        boost::spirit::qi::rule<std::string::const_iterator, int()> production_;
        production_ = qi::omit[+(qi::char_ - "productionYear") >> "productionYear\"" >> ' ' >> ':' >> ' '] >> qi::int_;
    
        int year;
        auto it = data.begin(), last = data.end();
    
        if (qi::parse(it, last, production_, year)) {
            std::cout << "Parsed: " << year << "\n";
        } else {
            std::cout << "Parsed failed\n";
        }
    
        if (it != last)
            std::cout << "Remaining input: '" << std::string(it, last) << "'\n";
    }
    

    Live On Coliru

    Output:

    Parsed: 1999
    Remaining input: '
        }'