Home > Back-end >  BOOST_DEFINE_ENUM_CLASS and json
BOOST_DEFINE_ENUM_CLASS and json

Time:05-05

In the documentation for boost describe, under the heading "Automatic Conversion to JSON", it shows how to implement "a universal tag_invoke overload that automatically converts an annotated struct to a Boost.JSON value". The example supports BOOST_DESCRIBE_STRUCT, how would I implement something similar for BOOST_DEFINE_ENUM_CLASS?

Here is my naive attempt to adapt the example to support BOOST_DEFINE_ENUM_CLASS alongside BOOST_DESCRIBE_STRUCT:

#include <boost/describe.hpp>
#include <boost/mp11.hpp>
#include <boost/json.hpp>
#include <type_traits>
#include <vector>
#include <map>

namespace app {
    template<class T,
        class D1 = boost::describe::describe_members<T, boost::describe::mod_public | boost::describe::mod_protected>,
        class D2 = boost::describe::describe_members<T, boost::describe::mod_private>,
        class En = std::enable_if_t<boost::mp11::mp_empty<D2>::value> >

    void tag_invoke(boost::json::value_from_tag const&, boost::json::value& v, T const& t) {
        auto& obj = v.emplace_object();
        boost::mp11::mp_for_each<D1>([&](auto D) {
            obj[D.name] = boost::json::value_from(t.*D.pointer);
        });
    }

    struct A {
        int x;
        int y;
    };
    BOOST_DESCRIBE_STRUCT(A, (), (x, y))

    struct B {
        std::vector<A> v;
        std::map<std::string, A> m;
    };
    BOOST_DESCRIBE_STRUCT(B, (), (v, m))

    BOOST_DEFINE_ENUM_CLASS(E1, v1, v2, v3)
    struct C {
        int x;
        E1 e1;
    };
    BOOST_DESCRIBE_STRUCT(C, (), (x, e1))

} // namespace app

#include <iostream>

void main() {
    app::A a{ 1, 2 };
    std::cout << boost::json::value_from(a) << std::endl;
    app::B b{ { { 1, 2 }, { 3, 4 } }, { { "k1", { 5, 6 } }, { "k2", { 7, 8 } } } };
    std::cout << boost::json::value_from(b) << std::endl;
    app::C c{ 1, app::E1::v1 };
    //std::cout << boost::json::value_from(c) << std::endl;
}

What do I need to do in order to enable boost::json::value_from() for c?

Edit: Okey dokes, the code below does the trick, but I got to make it more templatey:

void tag_invoke(boost::json::value_from_tag const&, boost::json::value& v, E1 const& t) {
    v = boost::describe::enum_to_string(t, "x");
}

CodePudding user response:

The documentation gives this example (slightly modified to suit our JSON needs later):

template <class E> char const* enum_to_string(E e) {
    char const* r = nullptr;

    boost::mp11::mp_for_each<boost::describe::describe_enumerators<E>>(
        [&](auto D) {
            if (e == D.value)
                r = D.name;
        });

    return r;
}

You can use that to implement the value_from customization point:

static inline void tag_invoke(json::value_from_tag, json::value& jv, E1 e) {
    auto name = enum_to_string(e);

    jv = name
        ? name
        : std::to_string(static_cast<std::underlying_type_t<E1>>(e));
}

Given suitable SFINAE you can template that on any enum type, as long as you keep in mind to limit the ADL applicability to your own enums to avoid interfering with other libraries.

Live demo:

Live On Compiler Explorer

#include <boost/describe.hpp>
#include <boost/json.hpp>
#include <boost/json/src.hpp> // for online demo
#include <boost/mp11.hpp>
#include <map>
#include <type_traits>
#include <vector>

namespace app {
    namespace json = boost::json;

    template<class T,
        class D1 = boost::describe::describe_members<T, boost::describe::mod_public | boost::describe::mod_protected>,
        class D2 = boost::describe::describe_members<T, boost::describe::mod_private>,
        class En = std::enable_if_t<boost::mp11::mp_empty<D2>::value> >

    void tag_invoke(json::value_from_tag const&, json::value& v, T const& t) {
        auto& obj = v.emplace_object();
        boost::mp11::mp_for_each<D1>([&](auto D) {
            obj[D.name] = json::value_from(t.*D.pointer);
        });
    }

    struct A {
        int x;
        int y;
    };
    BOOST_DESCRIBE_STRUCT(A, (), (x, y))

    struct B {
        std::vector<A> v;
        std::map<std::string, A> m;
    };
    BOOST_DESCRIBE_STRUCT(B, (), (v, m))

    BOOST_DEFINE_ENUM_CLASS(E1, v1, v2, v3)

    template <class E> char const* enum_to_string(E e) {
        char const* r = nullptr;

        boost::mp11::mp_for_each<boost::describe::describe_enumerators<E>>(
            [&](auto D) {
                if (e == D.value)
                    r = D.name;
            });

        return r;
    }

    static inline void tag_invoke(json::value_from_tag, json::value& jv, E1 e) {
        auto name = enum_to_string(e);

        jv = name
            ? name
            : std::to_string(static_cast<std::underlying_type_t<E1>>(e));
    }

    struct C {
        int x;
        E1 e1;
    };
    BOOST_DESCRIBE_STRUCT(C, (), (x, e1))

} // namespace app

#include <iostream>

int main() {
    app::A a{ 1, 2 };
    std::cout << boost::json::value_from(a) << std::endl;
    app::B b{ { { 1, 2 }, { 3, 4 } }, { { "k1", { 5, 6 } }, { "k2", { 7, 8 } } } };
    std::cout << boost::json::value_from(b) << std::endl;
    app::C c{ 1, app::E1::v1 };
    std::cout << boost::json::value_from(c) << std::endl;
}

Prints

{"x":1,"y":2}
{"v":[{"x":1,"y":2},{"x":3,"y":4}],"m":{"k1":{"x":5,"y":6},"k2":{"x":7,"y":8}}}
{"x":1,"e1":"v1"}
  • Related