Reading JSON file with C++ and BOOST

后端 未结 3 1385
青春惊慌失措
青春惊慌失措 2020-12-02 01:33

An HTTP server sends me a JSON response (a string) like this :

{
    \"folders\" :
    [{
            \"id\" : 109,
            \"parent_id\" : 110,
                 


        
3条回答
  •  旧巷少年郎
    2020-12-02 02:02

    Disclaimer: The sample below is not a full blown JSON parser. Consider using a library that supports your needs. You can see a more evolved JSON parser here https://github.com/sehe/spirit-v2-json

    A quick-and-dirty Spirit grammar (assuming you don't need too much conformance) would be:

        text_   = '"' >> raw [*('\\' >> char_ | ~char_('"'))] >> '"'; // ¹
        value_  = null | bool | text_ | double_ | object_ | array_; // ²
        member_ = text_ >> ':' >> value_;
        object_ = '{' >> -(member_ % ',') >> '}';
        array_  = '[' >> -(value_  % ',') >> ']';
    
        // ¹ as a bonus I added utf8 escape decoding in the full sample
        // ² as another bonus I threw in the missing `null` and `bool` types
    

    Which translates into C++ types without further effort using an AST like:

    using text   = std::string;
    using value  = boost::make_recursive_variant<
            null,
            bool,
            text,                                      // "string" (roughly!)
            double,                                    // number
            std::map, // object
            std::vector     // array
        >::type;
    using member = std::pair;
    using object = std::map;
    using array  = std::vector;
    

    If you have two qd_json::value objects, you can just compare them:

    qd_json::value local_tree, remote_tree;
    if (local_tree == remote_tree)
    {
        std::cout << "the tree is unchanged\n";
    }
    

    Here's a demo program:

    Updated Demo

    The demonstration was updated to show you how to get to the "user-friendly" data structure you suggested in the edit of the question:

    int main() {
        auto json = qd_json::parse(sample);
    
        // extract into user friendly datastructure from the question:
        auto extracted = Data::extract_from(json);
    
        for (auto& e : extracted.folders) std::cout << "folder:\t" << e.id << "\t" << e.path << "\n";
        for (auto& e : extracted.files)   std::cout << "file:\t"   << e.id << "\t" << e.path << "\t" << e.name << "\n";
    }
    

    Live On Coliru

    #include 
    #include 
    #include 
    #include 
    
    namespace qi = boost::spirit::qi;
    
    static std::string const sample = R"(
        {
            "folders" :
            [{
                    "id" : 109,
                    "parent_id" : 110,
                    "path" : "\/1\/105\/110\/"
                },
                {
                    "id" : 110,
                    "parent_id" : 105,
                    "path" : "\/1\/105\/"
                }
            ],
    
            "files" :
            [{
                    "id" : 26,
                    "parent_id" : 105,
                    "name" : "picture.png",
                    "hash" : "md5_hash",
                    "path" : "\/1\/105\/"
                },
                {
                    "id" : 25,
                    "parent_id" : 110,
                    "name" : "another_picture.jpg",
                    "hash" : "md5_hash",
                    "path" : "\/1\/105\/110\/"
                }
            ]
        })";
    
    namespace qd_json { // quick and dirty JSON handling
        struct null {
            bool operator==(null) const { return true; }
        };
    
        inline static std::ostream& operator<<(std::ostream& os, null) { return os << "null"; }
    
        using text   = std::string;
        using value  = boost::make_recursive_variant<
                null,
                text,                                      // "string" (roughly!)
                double,                                    // number
                std::map, // object
                std::vector,    // array
                bool
            >::type;
        using member = std::pair;
        using object = std::map;
        using array  = std::vector;
    
        template 
        struct grammar : qi::grammar
        {
            grammar() : grammar::base_type(value_) {
                using namespace qi;
    
                text_   = '"' >> raw [*('\\' >> char_ | ~char_('"'))] >> '"';
                null_   = "null" >> attr(null{});
                bool_   = "true" >> attr(true) | "false" >> attr(false);
                value_  = null_ | bool_ | text_ | double_ | object_ | array_;
                member_ = text_ >> ':' >> value_;
                object_ = '{' >> -(member_ % ',') >> '}';
                array_  = '[' >> -(value_  % ',') >> ']';
    
                ////////////////////////////////////////
                // Bonus: properly decoding the string:
                text_   = lexeme [ '"' >> *ch_ >> '"' ];
    
                ch_ = +(
                        ~char_("\"\\")) [ _val += _1 ] |
                           qi::lit("\x5C") >> (               // \ (reverse solidus)
                           qi::lit("\x22") [ _val += '"'  ] | // "    quotation mark  U+0022
                           qi::lit("\x5C") [ _val += '\\' ] | // \    reverse solidus U+005C
                           qi::lit("\x2F") [ _val += '/'  ] | // /    solidus         U+002F
                           qi::lit("\x62") [ _val += '\b' ] | // b    backspace       U+0008
                           qi::lit("\x66") [ _val += '\f' ] | // f    form feed       U+000C
                           qi::lit("\x6E") [ _val += '\n' ] | // n    line feed       U+000A
                           qi::lit("\x72") [ _val += '\r' ] | // r    carriage return U+000D
                           qi::lit("\x74") [ _val += '\t' ] | // t    tab             U+0009
                           qi::lit("\x75")                    // uXXXX                U+XXXX
                                >> _4HEXDIG [ append_utf8(qi::_val, qi::_1) ]
                        );
    
                BOOST_SPIRIT_DEBUG_NODES((text_)(value_)(member_)(object_)(array_)(null_)(bool_))
            }
        private:
            qi::rule            text_, ch_;
            qi::rule            null_;
            qi::rule            bool_;
            qi::rule value_;
            qi::rule member_;
            qi::rule object_;
            qi::rule array_;
    
            struct append_utf8_f {
                template  struct result { typedef void type; };
                template 
                void operator()(String& to, Codepoint codepoint) const {
                    auto out = std::back_inserter(to);
                    boost::utf8_output_iterator convert(out);
                    *convert++ = codepoint;
                }
            };
            boost::phoenix::function append_utf8;
            qi::uint_parser _4HEXDIG;
        };
    
        template ::type>
        value parse(Range const& input) {
            grammar g;
    
            It first(boost::begin(input)), last(boost::end(input));
            value parsed;
            bool ok = qi::phrase_parse(first, last, g, qi::space, parsed);
    
            if (ok && (first == last))
                return parsed;
    
            throw std::runtime_error("Remaining unparsed: '" + std::string(first, last) + "'");
        }
    
    }
    
    #include 
    #include 
    #include 
    
    struct Data {
        struct Folder { int id, parent_id; std::string path; };
        struct File   { int id, parent_id; std::string path, name, md5_hash; };
    
        using Folders = std::vector;
        using Files   = std::vector;
    
        Folders folders;
        Files   files;
    
        static Data extract_from(qd_json::value const& json) {
            using namespace boost::adaptors;
    
            return {
                boost::copy_range(arr(obj(json).at("folders")) | transformed(obj) | transformed(&Data::extract_folder)),
                boost::copy_range  (arr(obj(json).at("files"))   | transformed(obj) | transformed(&Data::extract_file)),
            };
        }
     private:
        static Folder extract_folder(qd_json::object const& obj) {
            return {
                id   (obj.at("id")),
                id   (obj.at("parent_id")),
                text (obj.at("path"))
            };
        }
        static File extract_file(qd_json::object const& obj) {
            return {
                id   (obj.at("id")),
                id   (obj.at("parent_id")),
                text (obj.at("path")),
                text (obj.at("name")),
                text (obj.at("hash")),
            };
        }
    
        static int             id  (qd_json::value const&v) { return boost::get(v); };
        static std::string     text(qd_json::value const&v) { return boost::get(v); };
        static qd_json::array  arr (qd_json::value const&v) { return boost::get(v); };
        static qd_json::object obj (qd_json::value const&v) { return boost::get(v); };
    };
    
    int main()
    {
        auto json = qd_json::parse(sample);
    
        // compare json documents
        qd_json::value clone = json;
        assert(json == clone);
    
        // extract into user friendly datastructure from the question:
        auto extracted = Data::extract_from(json);
    
        for (auto& e : extracted.folders) std::cout << "folder:\t" << e.id << "\t" << e.path << "\n";
        for (auto& e : extracted.files)   std::cout << "file:\t"   << e.id << "\t" << e.path << "\t" << e.name << "\n";
    }
    

    Output:

    folder: 109 /1/105/110/
    folder: 110 /1/105/
    file:   26  /1/105/ picture.png
    file:   25  /1/105/110/ another_picture.jpg
    

提交回复
热议问题