Home > Software engineering >  Disable to_json and from_json if member variable types don't support nlohmann's json libra
Disable to_json and from_json if member variable types don't support nlohmann's json libra

Time:08-17

I'm using nlohmann's single header json library to serialise a class I wrote. I want to use this class with various types (including but not limited to boost's multiprecision types). The problem is that some types including boost's cpp_bin_float_quad don't support to_json or from_json.

If my class's member variable doesn't have its own to/from_json then the class fails to compile. In this case, users should still be able to use this class's core functionality (member functions, etc) but not let them save/load the class's member variables.

Ideally, if the user needs to save/load the class with their custom types, then they can make the to/from_json functions for their custom types. Preferably the solution will work for C 11.

Here's a mwe to show the issue:

#include "json.hpp"
#include <boost/multiprecision/cpp_bin_float.hpp>
#include <iostream>

template <typename T>
class A {
protected:
  T m_x;
public:
  A() {m_x = static_cast<T>(1);}
  A(T x) : m_x(x) {}

  template <typename T1>
  friend void to_json(nlohmann::json& j, const A<T1>& a);
  template <typename T1>
  friend void from_json(const nlohmann::json& j, A<T1>& a);
};

/* I want useless versions of these two functions
 * if T doesn't have it's own to/from_json functions! */
template <typename T>
void to_json(nlohmann::json& j, const A<T>& a) {
  j = nlohmann::json{{"x", a.m_x}};
}

template <typename T>
void from_json(const nlohmann::json& j, A<T>& a) {
  j.at("x").get_to(a.m_x);
}

// no problems with this MY_TYPE
//#define MY_TYPE double

// fails because cpp_bin_float_quad doesn't have to/from_json
#define MY_TYPE boost::multiprecision::cpp_bin_float_quad

int main(){
  A<MY_TYPE> a(2);
  nlohmann::json js = a;
  std::cout << js << std::endl;
  auto s2 = js.get<A<MY_TYPE>>();
}

When I try compiling I see errors like these:

mwe.cpp:23:5: error: no matching function for call to ‘nlohmann::json_v3_11_0::basic_json<>::basic_json(<brace-enclosed initializer list>)’
   23 |   j = nlohmann::json{{"x", a.m_x}};

mwe.cpp:28:19: error: no matching function for call to ‘nlohmann::json_v3_11_0::basic_json<>::get_to(boost::multiprecision::number<boost::multiprecision::backends::cpp_bin_float<113, boost::multiprecision::backends::digit_base_2, void, short int, -16382, 16383>, boost::multiprecision::et_off>&) const’
   28 |   j.at("x").get_to(a.m_x);

CodePudding user response:

It looks like you want these friend functions not to exist when they don’t make sense. Try changing the signature of your functions to something like this

template <typename T>
auto to_json(nlohmann::json& j, const A<T>& a) -> decltype(nlohmann::json{{char const*{}, a.m_x}}, void()) {
  j = nlohmann::json{{"x", a.m_x}};
}

CodePudding user response:

Looks like we can use SFINAE with nlohmann's has_to_json and has_from_json to solve this problem. The specific solution I used for my mwe was this:

template <typename T>
class A {
protected:
  T m_x;
public:
  A() {m_x = static_cast<T>(1);}
  A(T x) : m_x(x) {}

  template <typename T1,
           typename std::enable_if<nlohmann::detail::has_to_json<nlohmann::json,T1>::value, bool>::type>
  friend void to_json(nlohmann::json& j, const A<T1>& a);

  template <typename T1,
           typename std::enable_if<nlohmann::detail::has_from_json<nlohmann::json,T1>::value, bool>::type>
  friend void from_json(const nlohmann::json& j, A<T1>& a);
};

template <typename T,
         typename std::enable_if<nlohmann::detail::has_to_json<nlohmann::json,T>::value, bool>::type = true>
void to_json(nlohmann::json& j, const A<T>& a) {
  j = nlohmann::json{{"x", a.m_x}};
}

template <typename T,
         typename std::enable_if<!nlohmann::detail::has_to_json<nlohmann::json,T>::value, bool>::type = true>
void to_json(nlohmann::json& j, const A<T>& a) {
  throw std::invalid_argument(std::string(typeid(T).name())   " does not implement nlohmann's to_json");
}

template <typename T,
         typename std::enable_if<nlohmann::detail::has_from_json<nlohmann::json,T>::value, bool>::type = true>
void from_json(const nlohmann::json& j, A<T>& a) {
  j.at("x").get_to(a.m_x);
}

template <typename T,
         typename std::enable_if<!nlohmann::detail::has_from_json<nlohmann::json,T>::value, bool>::type = true>
void from_json(const nlohmann::json& j, A<T>& a) {
  throw std::invalid_argument(std::string(typeid(T).name())   " does not implement nlohmann's from_json");
}

Notes:

  • We only have to mark one version of to/from_json as a friend which is nice
  • I am not sure if using BasicJsonType=nlohmann::json could be an issue...
  • Related