2

I'm looking for a way to store the JSON structure

{
    "foo" : "FOO" ,
    "fuu" : "FUU" ,

    "bar" :
    {
        "no"  : "abc" ,
        "yes" : "ABC"
    } ,

    "baa" :
    {
        "no"  : "xyz" ,
        "yes" : "XYZ"   
    }
}

as a map in C++ initialized by boost::assign:::map_list_of. Something like this

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" }
        }   
    };

Where some keys map to strings and others to sub maps (hello variant). Note that I'm limited to C++03 and I'm open to other solutions e.g. Boost Proto (DSL).

Olumide
  • 5,397
  • 10
  • 55
  • 104
  • 1
    I think [this](http://melpon.org/wandbox/permlink/mgP7ZSdiVVCTuUc0) is a cool solution, but sadly I realized too late that BOOST_METAPARSE_STRING requires c++11. – llonesmiz Jan 30 '16 at 12:47
  • Cool! Worth more than just a comment IMO. – Olumide Jan 30 '16 at 13:14

2 Answers2

2

Disclaimer: I thought this could be a cool approach, but sadly I didn't realize (with the typedefs and that ugly for loop as witnesses) that BOOST_METAPARSE_STRING requires c++11. So this doesn't solve the problem in the question.

The following approach uses Boost.Metaparse(Docs) that should be available starting with Boost 1.61. It uses a macro (JSON_MAP_ASSIGNER) that allows you to use directly the original JSON structure in the question(Note the duplicated parentheses).

    Map json = JSON_MAP_ASSIGNER((
    {
        "foo" : "FOO" ,
        "fuu" : "FUU" ,

        "bar" :
        {
            "no"  : "abc" ,
            "yes" : "ABC"
        } ,

        "baa" :
        {
            "no"  : "xyz" ,
            "yes" : "XYZ"   
        }
    }
    ));

This approach has several problems:

-It can't use a runtime string, it must be known at compile time.

-The compiler will probably choke with a large enough json string.


The macro creates a string from the "tuple", parses that string, obtaining a type (map_assigner<vector<pair_assigner...> >) and instantiates that type. That instance is assigned to a map, and that takes care of building the map.


Full Code (Running on 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);

}
llonesmiz
  • 155
  • 2
  • 11
  • 20
1

Here is an approach based on Boost.Proto that does work with c++03. It's a slight variation from this example in the documentation.

It can be used like this (sadly not as pretty as the other solution):

// 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 builds a tree like this:

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)
    )
)

and applies different transforms according to the expression matched. There are four distinct cases:

  • function(map_list_of,terminal,terminal): This is the easiest case. In the example occurs when matching map_list_of("foo","FOO").It simply inserts a pair (key,value) into your map and returns a reference.

  • function(function,terminal,terminal): It occurs when matching any pair of terminals that is not the first(map_list_of("foo","FOO")("fuu","FUU")). Inserts a pair (key,value) into the map returned from recursively applying the transform to the first child.

  • function(map_list_of,terminal,function): This would occur if there were a nested map in the first pair of parentheses. It creates the nested map and then inserts it with the key terminal in your original map.

  • function(function,terminal,function): This occurs when matching a parenthesized group with a nested map ( map_list_of("foo", "FOO")("fuu", "FUU")("bar", map_list_of("no", "abc")("yes", "ABC")) ). Creates the nested map and inserts it in the bar key.

Full Code (Running on Coliru)

#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;
}
llonesmiz
  • 155
  • 2
  • 11
  • 20