使用提升分配使用变量值初始化类似 JSON 的映射

Using Boost assign to initialize JSON-like map with variant values

本文关键字:JSON 映射 变量值 分配 初始化      更新时间:2023-10-16

我正在寻找一种存储JSON结构的方法

{
    "foo" : "FOO" ,
    "fuu" : "FUU" ,
    "bar" :
    {
        "no"  : "abc" ,
        "yes" : "ABC"
    } ,
    "baa" :
    {
        "no"  : "xyz" ,
        "yes" : "XYZ"   
    }
}

作为C++由boost::assign:::map_list_of初始化的地图。像这样的东西

const std::map<UNKNOWN_TYPE_INVOLVING_VARIANT> = boost::assign::map_list_of
    {
        { "foo" , "FOO" } ,
        { "fuu" , "FUU" } ,
        { "bar" ,
            { "no"  , "abc" } ,
            { "yes" , "ABC" }
        } ,
        { "baa" ,
            { "no"  , "xyz" } ,
            { "yes" , "XYZ" }
        }   
    };

其中一些键映射到字符串,其他键映射到子映射(hello 变体)。请注意,我仅限于C++03,并且我对其他解决方案持开放态度,例如Boost Proto(DSL)。

免责声明:我认为这可能是一个很酷的方法,但遗憾的是我没有意识到(typedefs和那个丑陋的for循环作为见证人)BOOST_METAPARSE_STRING需要 c++11。所以这并不能解决问题中的问题。

以下方法使用 Boost.Metaparse(Docs),从 Boost 1.61 开始应该可用。它使用宏(JSON_MAP_ASSIGNER),允许您直接使用问题中的原始JSON结构(注意重复的括号)。

    Map json = JSON_MAP_ASSIGNER((
    {
        "foo" : "FOO" ,
        "fuu" : "FUU" ,
        "bar" :
        {
            "no"  : "abc" ,
            "yes" : "ABC"
        } ,
        "baa" :
        {
            "no"  : "xyz" ,
            "yes" : "XYZ"   
        }
    }
    ));

这种方法有几个问题:

- 它不能使用运行时字符串,必须在编译时知道。

-编译器可能会因足够大的 json 字符串而阻塞。


宏从"元组"创建一个字符串,解析该字符串,获取一个类型(map_assigner<vector<pair_assigner...> >)并实例化该类型。该实例将分配给地图,并负责构建地图。


完整代码(在Wandbox上运行)

#define BOOST_METAPARSE_LIMIT_STRING_SIZE 128
#include <iostream>
#include <string>
#include <map>
#include<boost/variant.hpp>
#include <boost/mpl/quote.hpp>
#include <boost/mpl/push_back.hpp>
#include <boost/mpl/string.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/at.hpp>
#include <boost/mpl/for_each.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <boost/metaparse/alphanum.hpp>
#include <boost/metaparse/string.hpp>
#include <boost/metaparse/lit_c.hpp>
#include <boost/metaparse/token.hpp>
#include <boost/metaparse/first_of.hpp>
#include <boost/metaparse/build_parser.hpp>
#include <boost/metaparse/entire_input.hpp>
#include <boost/metaparse/foldl_start_with_parser.hpp>
#include <boost/metaparse/transform.hpp>
#include <boost/metaparse/one_of.hpp>
#include <boost/metaparse/last_of.hpp>
#include <boost/metaparse/middle_of.hpp>
#include <boost/metaparse/sequence.hpp>


namespace assign_helper
{
    using namespace boost::metaparse;
    namespace mpl = boost::mpl;
    // map_assigner and pair assigner are the two classes that actually assign to the map
    //BEGIN ASSIGNERS
    template <typename PairSequence>
    struct map_assigner
    {
        template<class K, class V, class C, class A>
        struct do_assign
        {
            do_assign(std::map<K, V, C, A>& val) :map_(val) {};
            std::map<K, V, C, A>& map_;

            template <typename Pair>
            void operator()(Pair) const
            {
                Pair::template assign(map_);
            }
        };
        //This does the actual work when an instance of map_assigner is assigned to a map
        template<class K, class V, class C, class A>
        operator std::map<K, V, C, A>() const
        {
            //Creates a map...
            std::map<K, V, C, A> m;
            //...and assigns each of the pairs
            mpl::for_each<PairSequence>(do_assign<K, V, C, A>(m));
            return m;
        }
    };
    //if it's a "normal" pair (string,string)
    template <typename First, typename Second>
    struct pair_assigner
    {
        template <typename K, typename V, typename C, typename A>
        static void assign(std::map<K, V, C, A>& m)
        {
            m[mpl::c_str<First>::type::value] = mpl::c_str<Second>::type::value;
        }
    };
    //if it's a pair that has a map as its second param
    template <typename First, typename NestedSequence>
    struct pair_assigner<First, map_assigner<NestedSequence> >
    {
        template <typename K, typename V, typename C, typename A>
        static void assign(std::map<K, V, C, A>& m)
        {
            std::map<K, V, C, A> nested_m = map_assigner<NestedSequence>();
            m[mpl::c_str<First>::type::value] = nested_m;
        }
    };
    //END ASSIGNERS
    //BEGIN AST-BUILD HELPERS
    //This is "called" with a mpl::vector<string,char<':'>,value> (value can be either string or another map)
    //and "creates" a pair_assigner<string,value>
    template <typename Sequence>
    struct create_pair_assigner : pair_assigner<typename mpl::at_c<Sequence, 0>::type, typename mpl::at_c<Sequence, 2>::type>
    {};
    //This is "called" with a char<letter>
    //and "creates" a string<letter> which can be appended to
    template <typename Letter>
    struct create_string : string<Letter::value>
    {};
    //This is "called" with an Elem
    //and "creates" a vector<Elem> which can be appended to
    template <typename Elem>
    struct create_vector : mpl::vector1<Elem>
    {};
    //This is "called" with a vector/string and an Elem/char<letter> and appends the latter to the former
    template <typename State, typename Elem>
    struct append : mpl::push_back<State, Elem>
    {};
    //END AST BUILD HELPERS
    //BEGIN TOKENS
    typedef token < lit_c < '(' > > paren_open;
    typedef token < lit_c < ')' > > paren_close;
    typedef token < lit_c < '{' > > brace_open;
    typedef token < lit_c < '}' > > brace_close;
    typedef token < lit_c < ',' > > comma;
    typedef token < lit_c < ':' > > colon;
    typedef lit_c < '"' > quotation;
    //END TOKENS
    //BEGIN RULES
    //This parses and returns a letter
    typedef one_of<alphanum, lit_c<'_'>, lit_c<' '> > word_letter;
    //This parses a letter optionally followed by more and returns a string
    typedef foldl_start_with_parser<
        word_letter,
        transform<word_letter, mpl::quote1<create_string> >,
        mpl::quote2<append> > word;
    //This parses a quoted string and returns a string without quotes
    typedef token<middle_of<quotation, word, quotation> > literal;
    //This is a recursive rule and needs to be forward declared
    struct json_body;
    //A value is either a string or a map
    typedef one_of< literal, json_body > value;
    //This parses `string ':' (string|map)` and returns pair_assigner<string,string> or pair_assigner<string,map>
    typedef transform<sequence<literal, colon, value>, mpl::quote1<create_pair_assigner> > json_pair;
    //This parses a pair optionally followed by groups of (',' pair) 
    //and returns a map_assigner<vector<pair_assigner...> >
    typedef transform<
        foldl_start_with_parser<
        last_of<comma, json_pair>,
        transform<json_pair, mpl::quote1<create_vector> >,
        mpl::quote2<append>
        >,
        mpl::quote1<map_assigner> >pair_sequence;
    //This parses `'{' pair_sequence '}'` and returns pair_sequence
    struct json_body : middle_of<brace_open, pair_sequence, brace_close>
    {};
    //This parses `'(' json_body ')'` and returns json_body
    //the parentheses are an artifact due to the tuple used to stringize the json text to avoid problems with commas
    typedef middle_of<paren_open, json_body, paren_close> json;
    //END RULES
    typedef build_parser<entire_input<json> > assigner;
}
//Creates a map_assigner and instantiates it
#define CREATE_ASSIGNER(TEXT) assign_helper::assigner::apply<BOOST_METAPARSE_STRING(TEXT)>::type()

#define JSON_MAP_ASSIGNER(TUPLE) CREATE_ASSIGNER(BOOST_PP_STRINGIZE(TUPLE))


typedef boost::make_recursive_variant<std::string, std::map<std::string, boost::recursive_variant_> >::type Value;
typedef std::map<std::string, Value> Map;
struct printer : boost::static_visitor<void>
{
    printer(int indent) :indent(indent) {}
    void operator()(const std::string& val) const
    {
        std::cout << std::string(indent, ' ') << val << std::endl;
    }
    void operator()(const Map& val) const
    {
        for (Map::const_iterator it = val.begin(), end = val.end(); it != end; ++it)
        {
            std::cout << std::string(indent, ' ') << it->first << std::endl;
            boost::apply_visitor(printer(indent + 4), it->second);
            std::cout << std::string(indent, ' ') << std::endl;
        }
    }

    int indent;
};
void print(const Value& val)
{
    boost::apply_visitor(printer(0), val);
}
int main()
{
    Map json = JSON_MAP_ASSIGNER((
    {
        "foo" : "FOO" ,
        "fuu" : "FUU" ,
        "bar" :
        {
            "no"  : "abc" ,
            "yes" : "ABC"
        } ,
        "baa" :
        {
            "no"  : "xyz" ,
            "yes" : "XYZ"   
        }
    }
    ));
    print(json);
}

这是一个基于 Boost.Proto 的方法,它适用于 c++03。这与文档中的此示例略有不同。

它可以像这样使用(可悲的是不如其他解决方案漂亮):

// Initialize a map:
    Map json =
        map_list_of
            ("foo", "FOO")
            ("fuu", "FUU")
            ("bar", 
                    map_list_of
                        ("no", "abc")
                        ("yes", "ABC")
            )
            ("baa",
                    map_list_of
                        ("no", "xyz")
                        ("yes", "XYZ")
            )
        ;

Proto 构建了一棵像这样的树:

function(
    function(
        function(
            function(
                terminal(map_list_of_tag)
              , terminal(foo)
              , terminal(FOO)
            )
          , terminal(fuu)
          , terminal(FUU)
        )
      , terminal(bar)
      , function(
            function(
                terminal(map_list_of_tag)
              , terminal(no)
              , terminal(abc)
            )
          , terminal(yes)
          , terminal(ABC)
        )
    )
  , terminal(baa)
  , function(
        function(
            terminal(map_list_of_tag)
          , terminal(no)
          , terminal(xyz)
        )
      , terminal(yes)
      , terminal(XYZ)
    )
)

并根据匹配的表达式应用不同的转换。有四种不同的情况:

  • function(map_list_of,terminal,terminal):这是最简单的情况。在示例中,当匹配map_list_of("foo","FOO") 时发生。它只是将一对(键,值)插入到映射中并返回引用。

  • function(function,terminal,terminal) :当匹配任何一对不是第一个(map_list_of("foo","FOO") ("fuu","FUU"))的终端时,就会发生这种情况。将一对(键,值)插入到从递归应用转换到第一个子项返回的映射中。

  • function(map_list_of,terminal,function):如果第一对括号中有嵌套映射,就会发生这种情况。它会创建嵌套映射,然后将其与密钥终端一起插入原始映射中。

  • function(function,terminal,function) :当将括号组与嵌套映射匹配时会发生这种情况(map_list_of("foo", "FOO")("fuu", "FUU") ("bar", map_list_of("no", "abc")("yes", "ABC")) )。创建嵌套映射并将其插入bar键中。

完整代码(在科利鲁上运行)

#include <map>
#include <string>
#include <iostream>
#include <boost/variant.hpp>
#include <boost/proto/core.hpp>
#include <boost/proto/transform.hpp>
#include <boost/type_traits/add_reference.hpp>
#include <boost/mpl/bool.hpp>
namespace proto = boost::proto;
using proto::_;
using boost::mpl::true_;
using boost::mpl::false_;
struct map_list_of_tag
{};

// A simple callable function object that inserts a
// (key,value) pair into a map.
struct insert
  : proto::callable
{
    template<typename Sig>
    struct result;
    template<typename This, typename Map, typename Key, typename Value>
    struct result<This(Map, Key, Value)>
      : boost::add_reference<Map>
    {};
    template<typename Map, typename Key, typename Value>
    Map &operator()(Map &map, Key const &key, Value const &value) const
    {
        map.insert(typename Map::value_type(key, value));
        return map;
    }
};
struct nest
  : proto::callable
{
    template<typename Sig>
    struct result;
    template<typename This, typename Map, typename Key, typename Value>
    struct result<This(Map, Key, Value)>
      : boost::add_reference<Map>
    {};
    template<typename Map, typename Key, typename Value>
    Map& operator()(Map& map, Key const &key, Value const &value) const
    {
        map[key]=Map();//assign a map to the variant at key
        map[key]=value;//assign a map_list_of_expr to a map
        return boost::get<Map&>(map[key]); //proto seems to need that this return value be a reference
    }
};
// Work-arounds for Microsoft Visual C++ 7.1
#if BOOST_WORKAROUND(BOOST_MSVC, == 1310)
#define MapListOf(x) proto::call<MapListOf(x)>
#define _value(x) call<proto::_value(x)>
#endif
// The grammar for valid map-list expressions, and a
// transform that populates the map.
struct MapListOf
  : proto::or_<
        proto::when<
            // map_list_of(a,b)
            proto::function<
                proto::terminal<map_list_of_tag>
              , proto::terminal<_>
              , proto::terminal<_>
            >
          , insert(
                proto::_data //the map you are assigning to
              , proto::_value(proto::_child1) //a char const [N] key
              , proto::_value(proto::_child2) //a char const [N] value
            )
        >
      , proto::when<
            // map_list_of(a,map)
            proto::function<
                proto::terminal<map_list_of_tag>
              , proto::terminal<_>
              , MapListOf
            >
          , insert(
                proto::_data //the map you are assigning to
              , proto::_value(proto::_child1)
              , nest(
                  proto::_data, //the map you are assigning to
                  proto::_value(proto::_child1), //a char const [N] key
                  proto::_child2 //a proto expression (map_list_of_expr) that represents a nested map
                )
            )
        >
      , proto::when<
            // map_list_of(a,b)(c,d)...
            proto::function<
                MapListOf
              , proto::terminal<_>
              , proto::terminal<_>
            >
          , insert(
                MapListOf(proto::_child0) //evaluates the transform recursively
              , proto::_value(proto::_child1) //a char const [N] key
              , proto::_value(proto::_child2) //a char const [N] value
            )
        >
      , proto::when<
            // map_list_of(a,b)(c,map)...
            proto::function<
                MapListOf
              , proto::terminal<_>
              , MapListOf
            >
          , insert(
                MapListOf(proto::_child0) //evaluates the transform recursively
              , proto::_value(proto::_child1) //a char const [N] key
              , nest(
                  proto::_data, //the map you are assigning to
                  proto::_value(proto::_child1), //a char const [N] key
                  proto::_child2 //a proto expression that represents a nested map
                )
            )
        >
    >
{};
#if BOOST_WORKAROUND(BOOST_MSVC, == 1310)
#undef MapListOf
#undef _value
#endif
template<typename Expr>
struct map_list_of_expr;
struct map_list_of_dom
  : proto::domain<proto::pod_generator<map_list_of_expr>, MapListOf>
{};
// An expression wrapper that provides a conversion to a
// map that uses the MapListOf
template<typename Expr>
struct map_list_of_expr
{
    BOOST_PROTO_BASIC_EXTENDS(Expr, map_list_of_expr, map_list_of_dom)
    BOOST_PROTO_EXTENDS_FUNCTION()
    template<typename Key, typename Value, typename Cmp, typename Al>
    operator std::map<Key, Value, Cmp, Al> () const
    {
        BOOST_MPL_ASSERT((proto::matches<Expr, MapListOf>));
        std::map<Key, Value, Cmp, Al> map;
        return MapListOf()(*this, 0, map);
    }
};
map_list_of_expr<proto::terminal<map_list_of_tag>::type> const map_list_of = {{{}}};
typedef boost::make_recursive_variant<std::string, std::map<std::string, boost::recursive_variant_> >::type Value;
typedef std::map<std::string, Value> Map;
struct printer : boost::static_visitor<void>
{
    printer(int indent) :indent(indent) {}
    void operator()(const std::string& val) const
    {
        std::cout << std::string(indent, ' ') << val << std::endl;
    }
    void operator()(const Map& val) const
    {
        for (Map::const_iterator it = val.begin(), end = val.end(); it != end; ++it)
        {
            std::cout << std::string(indent, ' ') << it->first << std::endl;
            boost::apply_visitor(printer(indent + 4), it->second);
            std::cout << std::string(indent, ' ') << std::endl;
        }
    }

    int indent;
};
void print(const Value& val)
{
    boost::apply_visitor(printer(0), val);
}
int main()
{
    // Initialize a map:
    Map json =
        map_list_of
            ("foo", "FOO")
            ("fuu", "FUU")
            ("bar", 
                    map_list_of
                        ("no", "abc")
                        ("yes", "ABC")
            )
            ("baa",
                    map_list_of
                        ("no", "xyz")
                        ("yes", "XYZ")
            )
        ;
    print(json);
    return 0;
}