【问题标题】:Reading JSON file with C++ and BOOST使用 C++ 和 BOOST 读取 JSON 文件
【发布时间】:2015-01-02 17:32:00
【问题描述】:

HTTP 服务器向我发送这样的 JSON 响应(字符串):

{
    "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\/"
        }
    ]
}

我想将此“远程文件夹树”与本地文件夹树(例如,包含本地文件位置的字符串向量)进行比较,所以我想在 (string, vector ( map(string, string) ) ) (我不知道这是否可能)。

我正在开发一个在本地和远程文件夹之间同步文件的工具,所以我使用 boost 来列出本地文件夹,并且我想将本地列表与远程列表(JSON 响应)进行比较以生成操作(下载本地文件夹中不存在的丢失文件,上传远程文件夹中不存在的文件)。

我在另一个问题上找到了这个函数:

void print(boost::property_tree::ptree const& pt)
{
    using boost::property_tree::ptree;
    ptree::const_iterator end = pt.end();
    for (ptree::const_iterator it = pt.begin(); it != end; ++it)
    {
        std::cout << it->first << ": " << it->second.get_value<std::string>() << std::endl;
        print(it->second);
    }
}

我成功打印了这样的东西:

folders:
:
id: 109
parent_id: 110
name: 2011_pictures
:
id: 110
parent_id: 105
name: Aminos
files:
id: 26
parent_id: 105
name: logo.png
:
id: 5
parent_id: 109
name: me.jpg

我想知道是否可以使用此结果生成 map&lt;string, vector &lt;map&lt;string,string&gt; &gt; &gt;,它将有 2 个键:“文件夹”和“文件”,通过这两个键,我们可以访问包含信息的类型映射向量对于每个对象(文件或文件夹)。如果这是可行的,它将降低任务的复杂性(比较两个文件夹列表)

示例: T["folder"][0]["id"] 将返回 "109" ; T["files"][0]["name"] 将返回 "logo.png"

更新:这个问题很老,但我想给个建议:只要你想在 C++ 下处理 Json,就使用 RAPIDJSON。

【问题讨论】:

  • 您可以使用 boost 中的 property_tree 或 json Spirit 解析器。我相信您会在 SO 上找到一些已回答的问题。

标签: c++ json boost tree directory


【解决方案1】:

因为另一个答案was deemed "very complex"中的数据结构和目标数据结构是suggested to be

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<Folder>;
    using Files   = std::vector<File>;

    Folders folders;
    Files   files;
};

我最终编写了从通用“JSON”到该数据结构的转换(请参阅另一个答案:Reading JSON file with C++ and BOOST)。

但是,如果我们“跳过中间人”并将 JSON 专门解析为所示的 Data 结构,也许 OP 会更高兴。这“简化”了语法,使其仅适用于此类文档:

start    = '{' >> 
           (folders_ >> commasep) ^
           (files_ >> commasep)
         >> '}';

folders_ = prop_key(+"folders") >> '[' >> -(folder_ % ',') >> ']';
files_   = prop_key(+"files")   >> '[' >> -(file_   % ',') >> ']';

folder_  = '{' >> (
                (prop_key(+"id")        >> int_  >> commasep) ^
                (prop_key(+"parent_id") >> int_  >> commasep) ^
                (prop_key(+"path")      >> text_ >> commasep)
            ) >> '}';
file_    = '{' >> (
                (prop_key(+"id")        >> int_  >> commasep) ^
                (prop_key(+"parent_id") >> int_  >> commasep) ^
                (prop_key(+"path")      >> text_ >> commasep) ^
                (prop_key(+"name")      >> text_ >> commasep) ^
                (prop_key(+"hash")      >> text_ >> commasep)
            ) >> '}';

prop_key = lexeme ['"' >> lazy(_r1) >> '"'] >> ':';
commasep = &char_('}') | ',';

这个语法允许

  • 微不足道的空白,
  • 对象内属性的重新排序
  • 和省略的对象属性

好处:

  • 属性值类型的早期检查
  • 编译时间更短
  • 确实减少了代码:减少了 37 个 LoC(不包括 ~22% 的示例 JSON 行)

最后一个好处有一个反面:如果你想读取稍微不同的 JSON,现在你需要搞砸语法,而不是仅仅编写不同的提取/转换。在 37 行代码中,我更喜欢使用 other answer,但我会让你决定。

下面是直接使用这个语法的同一个演示程序:

Live On Coliru

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

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\/"
            }
        ]
    })";

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<Folder>;
    using Files   = std::vector<File>;

    Folders folders;
    Files   files;
};

BOOST_FUSION_ADAPT_STRUCT(Data::Folder, (int,id)(int,parent_id)(std::string,path))
BOOST_FUSION_ADAPT_STRUCT(Data::File,   (int,id)(int,parent_id)(std::string,path)(std::string,name)(std::string,md5_hash))
BOOST_FUSION_ADAPT_STRUCT(Data,         (Data::Folders,folders)(Data::Files,files))

namespace folder_info { // adhoc JSON parser

    template <typename It, typename Skipper = qi::space_type>
    struct grammar : qi::grammar<It, Data(), Skipper>
    {
        grammar() : grammar::base_type(start) {
            using namespace qi;

            start    = '{' >> 
                       (folders_ >> commasep) ^
                       (files_ >> commasep)
                     >> '}';

            folders_ = prop_key(+"folders") >> '[' >> -(folder_ % ',') >> ']';
            files_   = prop_key(+"files")   >> '[' >> -(file_   % ',') >> ']';

            folder_  = '{' >> (
                            (prop_key(+"id")        >> int_  >> commasep) ^
                            (prop_key(+"parent_id") >> int_  >> commasep) ^
                            (prop_key(+"path")      >> text_ >> commasep)
                        ) >> '}';
            file_    = '{' >> (
                            (prop_key(+"id")        >> int_  >> commasep) ^
                            (prop_key(+"parent_id") >> int_  >> commasep) ^
                            (prop_key(+"path")      >> text_ >> commasep) ^
                            (prop_key(+"name")      >> text_ >> commasep) ^
                            (prop_key(+"hash")      >> text_ >> commasep)
                        ) >> '}';

            prop_key = lexeme ['"' >> lazy(_r1) >> '"'] >> ':';
            commasep = &char_('}') | ',';

            ////////////////////////////////////////
            // Bonus: properly decoding the string:
            text_   = '"' >> *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((files_)(folders_)(file_)(folder_)(start)(text_))
        }
    private:
        qi::rule<It, Data(),            Skipper> start;
        qi::rule<It, Data::Files(),     Skipper> files_;
        qi::rule<It, Data::Folders(),   Skipper> folders_;
        qi::rule<It, Data::File(),      Skipper> file_;
        qi::rule<It, Data::Folder(),    Skipper> folder_;
        qi::rule<It, void(const char*), Skipper> prop_key;

        qi::rule<It, std::string()> text_, ch_;
        qi::rule<It> commasep;

        struct append_utf8_f {
            template <typename...> struct result { typedef void type; };
            template <typename String, typename Codepoint>
            void operator()(String& to, Codepoint codepoint) const {
                auto out = std::back_inserter(to);
                boost::utf8_output_iterator<decltype(out)> convert(out);
                *convert++ = codepoint;
            }
        };
        boost::phoenix::function<append_utf8_f> append_utf8;
        qi::uint_parser<uint32_t, 16, 4, 4> _4HEXDIG;
    };

    template <typename Range, typename It = typename boost::range_iterator<Range const>::type>
    Data parse(Range const& input) {
        grammar<It> g;

        It first(boost::begin(input)), last(boost::end(input));
        Data 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) + "'");
    }
}

int main()
{
    auto parsed = folder_info::parse(sample);

    for (auto& e : parsed.folders) 
        std::cout << "folder:\t" << e.id << "\t" << e.path << "\n";
    for (auto& e : parsed.files) 
        std::cout << "file:\t"   << e.id << "\t" << e.path << "\t" << e.name << "\n";
}

输出:

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

【讨论】:

  • 您好,非常感谢您的努力,这非常令人印象深刻,但实际上,在我的 JSON 中还有文件夹或文件对象的其他信息(如创建日期、修改日期、所有者、大小等...),但对我来说最重要的是我在示例中放入的那些(名称、pid、id 等),那么您认为您的代码适用于这种情况吗?我将等待您的回答,因为我将不得不使用“打印”功能的结果,并尝试提取有关文件或文件夹的信息并将其放入向量中。再次感谢您,祝您有美好的一天!
  • 哦,好吧。我很惊讶你当时接受了这个答案,因为另一个答案完全一样,除了它确实接受并忽略“其他”JSON内容。您是否错过了定义extract_from 的更新?它使用完全相同的数据结构 - 您在问题中建议的那个。
  • 总而言之,the other answer 在这里是明显的胜利,因为关注点的分离可以避免所有意外的复杂性。没有这些缺点
  • 只使用 typedefs。查看类似 wikipedia:c++11 的页面,了解该版本尚不支持的任何 c++11 构造。
  • @Aminos 这个可能不明显:template &lt;typename,typename&gt; struct result { typedef void type; }; will work in c++03
【解决方案2】:

免责声明:下面的示例不是完整的 JSON 解析器。考虑使用支持您需求的库。你可以在这里看到一个更进化的 JSON 解析器https://github.com/sehe/spirit-v2-json

一种简单粗暴的 Spirit 语法(假设您不需要太多的一致性)是:

    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

使用 AST 无需进一步努力即可转换为 C++ 类型,例如:

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

如果您有两个 qd_json::value 对象,您可以比较它们:

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

这是一个演示程序:

更新演示

更新了演示,向您展示了如何获得您在问题编辑中建议的“用户友好”数据结构:

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 <boost/fusion/adapted/std_pair.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <map>

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<text, boost::recursive_variant_>, // object
            std::vector<boost::recursive_variant_>,    // array
            bool
        >::type;
    using member = std::pair<text, value>;
    using object = std::map<text, value>;
    using array  = std::vector<value>;

    template <typename It, typename Skipper = qi::space_type>
    struct grammar : qi::grammar<It, value(), Skipper>
    {
        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<It, text()>            text_, ch_;
        qi::rule<It, null()>            null_;
        qi::rule<It, bool()>            bool_;
        qi::rule<It, value(),  Skipper> value_;
        qi::rule<It, member(), Skipper> member_;
        qi::rule<It, object(), Skipper> object_;
        qi::rule<It, array(),  Skipper> array_;

        struct append_utf8_f {
            template <typename...> struct result { typedef void type; };
            template <typename String, typename Codepoint>
            void operator()(String& to, Codepoint codepoint) const {
                auto out = std::back_inserter(to);
                boost::utf8_output_iterator<decltype(out)> convert(out);
                *convert++ = codepoint;
            }
        };
        boost::phoenix::function<append_utf8_f> append_utf8;
        qi::uint_parser<uint32_t, 16, 4, 4> _4HEXDIG;
    };

    template <typename Range, typename It = typename boost::range_iterator<Range const>::type>
    value parse(Range const& input) {
        grammar<It> 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 <boost/range/algorithm.hpp>
#include <boost/range/adaptors.hpp>
#include <boost/range/algorithm_ext/push_back.hpp>

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<Folder>;
    using Files   = std::vector<File>;

    Folders folders;
    Files   files;

    static Data extract_from(qd_json::value const& json) {
        using namespace boost::adaptors;

        return {
            boost::copy_range<Folders>(arr(obj(json).at("folders")) | transformed(obj) | transformed(&Data::extract_folder)),
            boost::copy_range<Files>  (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<double>(v); };
    static std::string     text(qd_json::value const&v) { return boost::get<qd_json::text>(v); };
    static qd_json::array  arr (qd_json::value const&v) { return boost::get<qd_json::array>(v); };
    static qd_json::object obj (qd_json::value const&v) { return boost::get<qd_json::object>(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";
}

输出:

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

【讨论】:

  • 谢谢你的回答,但是很抱歉我使用它太复杂了,我没有编译成功,最后我决定做的是使用“我在顶部发布的“打印”功能,并尝试获取每个文件的“路径”行,然后用文件夹名称替换 ID(编号)......这比将结果放在“非常复杂”中更容易数据结构和算法思路。
  • 思考总是最难的部分 :) 无论如何,你没有指定太多(没有代码,没有算法),我只是不推荐“JSON”的 Boost Property Tree。为了好玩:here's how "very complex" it would be 使用我的数据结构打印该文本:see it Live On Coliru
  • demonstration was updated 向您展示如何访问您在问题编辑中建议的 user-friendly 数据结构,但我建议改为 using map&lt;Id, File&gt; etc. so you could lookup by e.g. id or name . (哦,作为奖励,我实现了字符串(utf8)转义解码)。
  • 有了一个完全跳过“复杂”通用 JSON 结构的 contrasting answer,我想我又获得了 ridiculously-comprehensive 徽章 :) /cc @MooingDuck
  • @Aminos 这是相同的example JSON with ignorable content(来自其他答案 cmets)在没有任何调整的情况下被解析(除了忽略extract_from 中的错误)Live On Coliru
【解决方案3】:
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-08-19
  • 2020-09-15
  • 2014-02-21
  • 1970-01-01
相关资源
最近更新 更多