Home > Enterprise >  How to convert a struct that includes a pointer member to JSON with Boost Describe
How to convert a struct that includes a pointer member to JSON with Boost Describe

Time:01-10

In the documentation of Boost Describe, there is an example for automatic conversion of a struct to JSON (https://www.boost.org/doc/libs/1_78_0/libs/describe/doc/html/describe.html#example_to_json). However, when adding a pointer as a member of the struct, the code example does not work. I guess that the tag_invoke function needs to be modified in order to handle the pointer.

I tried the following, based on the example:

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

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 &&
                                !std::is_union<T>::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); }); 
}

int b = 0;

struct A {
  int *i;
};

BOOST_DESCRIBE_STRUCT(A, (), (i));

A a{&b};

}

int main() {
  std::cout << boost::json::value_from(app::a)
            << std::endl;

  return 0;
}

CodePudding user response:

If your pointers are to user-defined types, you can "just" overload for the pointer:

template <class T>
void tag_invoke(boost::json::value_from_tag const&, boost::json::value& v, T const* p) {
    if (p)
        boost::json::value_from(*p, v);
    else
        v.emplace_null();
}

This works because of ADL:

Live On Coliru

#include <boost/describe.hpp>
#include <boost/json/src.hpp>
#include <boost/mp11.hpp>
#include <iostream>

namespace app {
    struct MyInt {
        int value;
    };

    template <class T>
    void tag_invoke(boost::json::value_from_tag const&, boost::json::value& v, T const* p) {
        if (p)
            boost::json::value_from(*p, v);
        else
            v.emplace_null();
    }

    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 && !std::is_union<T>::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); });
    }

    MyInt b{42};

    struct A {
        MyInt* i;
    };

    BOOST_DESCRIBE_STRUCT(MyInt, (), (value))
    BOOST_DESCRIBE_STRUCT(A, (), (i))

    A a{&b};

} // namespace app

int main() {
    std::cout << boost::json::value_from(app::a) << std::endl;
}

Printing

{"i":{"value":42}}

Primitive Types

However your type is not user-defined. Primitive types like int do not have any associated namespace. The above doesn't work...

You would either have to explicitly include the overload, which requires modifying library code, or you can make the pointer-handling explicit in your code:

    boost::mp11::mp_for_each<D1>([&](auto D) {
        auto&& v = t.*D.pointer;
        if constexpr (std::is_pointer_v<std::remove_cvref_t<decltype(v)>>) {
            obj[D.name] = boost::json::value_from(*v);
        } else {
            obj[D.name] = boost::json::value_from(v);
        }
    });

This will work. Live On Coliru

#include <boost/describe.hpp>
#include <boost/json/src.hpp>
#include <boost/mp11.hpp>
#include <iostream>

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 && !std::is_union<T>::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) {
            auto&& v = t.*D.pointer;
            if constexpr (std::is_pointer_v<std::remove_cvref_t<decltype(v)>>) {
                obj[D.name] = boost::json::value_from(*v);
            } else {
                obj[D.name] = boost::json::value_from(v);
            }
        });
    }

    int b{42};

    struct A {
        int* i;
    };

    BOOST_DESCRIBE_STRUCT(A, (), (i))

    A a{&b};

} // namespace app

int main() {
    std::cout << boost::json::value_from(app::a) << std::endl;
}

Printing

{"i":42}

Notes

Note that raw pointers are problematic at best. It will be completely unclear what to do when deserializing instead. I suspect you might want to look at Boost Serialization instead if you need that kind of support.

  • Related