jsonboostdescribe

Describe for third party's type


I use boost::json library to generate json string, but with 3rd party type, BOOST_DESCRIBE_CLASS can't understand. I can use friend function for classes that use 3rd party styles but it is too much. any idea to handle 1 time only and use everywhere? Example:

class CTest
{
public:
    Imath::V3d v;

    BOOST_DESCRIBE_CLASS(CTest, (), (v), (), ())
};

Solution

  • I had to guess, so I initially set out to work on a generic transformation function.

    You would indeed use tag_invoke:

    namespace bd = boost::describe;
    
    template <class T, //
              class Bd = bd::describe_bases<T, bd::mod_any_access>,
              class Md = bd::describe_members<T, bd::mod_any_access>,
              class En = std::enable_if_t<!std::is_union<T>::value>>
    void tag_invoke(json::value_from_tag, json::value& v, T const& obj) {
        json::object bases, members;
    
        boost::mp11::mp_for_each<Bd>([&, first = true](auto const& D) mutable {
            using B = typename std::decay_t<decltype(D)>::type;
            bases.emplace(boost::core::demangle(typeid(B).name()),
                          json::value_from(static_cast<B const&>(obj)));
        });
    
        boost::mp11::mp_for_each<Md>([&](auto D) {
            members.emplace(D.name, json::value_from(obj.*D.pointer));
        });
    
        json::object result;
        if (bases.size())
            result.emplace("bases", std::move(bases));
        if (members.size())
            result.emplace("members", std::move(members));
    
        v = std::move(result);
    }
    

    Here it is in demonstration:

    Live On Coliru

    class CTest
    {
    public:
        int v = 42;
        BOOST_DESCRIBE_CLASS(CTest, (), (v), (), ())
    };
    
    struct X { int m1 = 1; };
    struct Y { int m2 = 2; };
    BOOST_DESCRIBE_STRUCT(X, (), (m1))
    BOOST_DESCRIBE_STRUCT(Y, (), (m2))
    
    class Z : public X , public Y {
        int m1 = 3, m2 = 4;
        BOOST_DESCRIBE_CLASS(Z, (X, Y), (), (), (m1, m2))
    };
    
    int main() 
    {
        std::cout << json::value_from(CTest{}) << "\n";
        std::cout << json::value_from(Z{}) << "\n";
    }
    

    Prints

    {"members":{"v":42}}
    {"bases":{"X":{"members":{"m1":1}},"Y":{"members":{"m2":2}}},"members":{"m1":3,"m2":4}}
    

    Third Party Types

    Third-party types can be transformed in just the same way. Let's imagine a third-party header contains stuff like:

    namespace Imath {
        struct V3d {
            std::tuple<float, float, float> coords;
            std::string meta;
        };
    };
    

    You can't edit the header. But you can add this in your own file after including that:

    namespace boost::json {
        void tag_invoke(json::value_from_tag, json::value& v,
                        Imath::V3d const& v3d) {
            v = json::object{
                {"x", std::get<0>(v3d.coords)},
                {"y", std::get<1>(v3d.coords)},
                {"z", std::get<2>(v3d.coords)},
                {"meta", v3d.meta},
            };
        }
    } // namespace json
    

    For ADL it's also fine to put the tag_invoke overload into the Imath namespace (or rather, the namespace declaring V3d), but you might prefer the boost::json namespace as it specifically expects overloads of this customization point.

    Now the output becomes:

    Live On Coliru

    class CTest
    {
      public:
        Imath::V3d v{{11, 22, 33}, "This is very nifty"};
        BOOST_DESCRIBE_CLASS(CTest, (), (v), (), ())
    };
    
    struct X { int m1 = 1; };
    struct Y { int m2 = 2; };
    BOOST_DESCRIBE_STRUCT(X, (), (m1))
    BOOST_DESCRIBE_STRUCT(Y, (), (m2))
    
    class Z : public X , public Y {
        int m1 = 3, m2 = 4;
        BOOST_DESCRIBE_CLASS(Z, (X, Y), (), (), (m1, m2))
    };
    
    int main() 
    {
        std::cout << json::value_from(CTest{}) << "\n";
        std::cout << json::value_from(Z{}) << "\n";
    }
    

    Printing:

    {"members":{"v":{"x":1.1E1,"y":2.2E1,"z":3.3E1,"meta":"This is very nifty"}}}
    {"bases":{"X":{"members":{"m1":1}},"Y":{"members":{"m2":2}}},"members":{"m1":3,"m2":4}}