nlohmann / json

JSON for Modern C++

Home Page:https://json.nlohmann.me

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

NLOHMANN_JSON_FROM* macros not comptaible with non-default-constructible types

RAnders00 opened this issue · comments

Description

The family of NLOHMANN_JSON_FROM* macros all seem to be incompatible with any kind of type that does/cannot have a default constructor.

Reproduction steps

See code example below, including Godbolt link.

Expected vs. actual results

Expected result: There is a macro available that can generate a from_json function that compiles successfully.
Actual result: Template compile error.

Minimal code example

Given the following simple class:

class NonDefaultConstructible {
public:
  explicit NonDefaultConstructible(std::string inner_value)
      : inner_value{std::move(inner_value)} {
            // e.g. validate the value, or it could be transformed in the
            // initializer list above
        };

  [[nodiscard]] std::string get_inner_value() const {
    return this->inner_value;
  }

private:
  std::string inner_value;
};

From the documentation I gather that the only way to json-convert this type is to specialize adl_serializer for it:

NLOHMANN_JSON_NAMESPACE_BEGIN
template <> struct adl_serializer<NonDefaultConstructible> {
  static void to_json(json &json_value, const NonDefaultConstructible &data) {
    json_value = data.get_inner_value();
  }

  static NonDefaultConstructible from_json(const json &json_value) {
    return NonDefaultConstructible{json_value.template get<std::string>()};
  }
};
NLOHMANN_JSON_NAMESPACE_END

This works just fine, but now structs containing this type cannot be json-converted using the macros anymore:

struct SimpleStruct {
  uint64_t first_value;
  NonDefaultConstructible second_value;
  std::string third_value;
};

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(SimpleStruct, first_value, second_value,
                                   third_value)

Godbolt link: https://godbolt.org/z/q8oxq61sx (gcc trunk, nlohmann_json trunk)

Error messages

In file included from <source>:1:
<source>: In function 'void from_json(const nlohmann::json_v3_11_1::json&, SimpleStruct&)':
<source>:37:1: error: no matching function for call to 'nlohmann::json_v3_11_1::basic_json<>::get_to(NonDefaultConstructible&) const'
   37 | NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(SimpleStruct, first_value, second_value,
      | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/libs/nlohmann_json/v3.11.1/single_include/nlohmann/json.hpp:20754:17: note: candidate: 'template<class ValueType, typename std::enable_if<((! nlohmann::json_v3_11_1::detail::is_basic_json<BasicJsonType>::value) && nlohmann::json_v3_11_1::detail::has_from_json<nlohmann::json_v3_11_1::basic_json<>, ValueType, void>::value), int>::type <anonymous> > ValueType& nlohmann::json_v3_11_1::basic_json<ObjectType, ArrayType, StringType, BooleanType, NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer, BinaryType>::get_to(ValueType&) const [with typename std::enable_if<((! nlohmann::json_v3_11_1::detail::is_basic_json<BasicJsonType>::value) && nlohmann::json_v3_11_1::detail::has_from_json<nlohmann::json_v3_11_1::basic_json<ObjectType, ArrayType, StringType, BooleanType, NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer, BinaryType>, ValueType>::value), int>::type <anonymous> = ValueType; ObjectType = std::map; ArrayType = std::vector; StringType = std::__cxx11::basic_string<char>; BooleanType = bool; NumberIntegerType = long int; NumberUnsignedType = long unsigned int; NumberFloatType = double; AllocatorType = std::allocator; JSONSerializer = nlohmann::json_v3_11_1::adl_serializer; BinaryType = std::vector<unsigned char>]'
20754 |     ValueType & get_to(ValueType& v) const noexcept(noexcept(
      |                 ^~~~~~
/opt/compiler-explorer/libs/nlohmann_json/v3.11.1/single_include/nlohmann/json.hpp:20754:17: note:   template argument deduction/substitution failed:
/opt/compiler-explorer/libs/nlohmann_json/v3.11.1/single_include/nlohmann/json.hpp:20753:28: error: no type named 'type' in 'struct std::enable_if<false, int>'
20753 |                    int > = 0 >
      |                            ^
/opt/compiler-explorer/libs/nlohmann_json/v3.11.1/single_include/nlohmann/json.hpp:20767:17: note: candidate: 'template<class ValueType, typename std::enable_if<nlohmann::json_v3_11_1::detail::is_basic_json<BasicJsonType>::value, int>::type <anonymous> > ValueType& nlohmann::json_v3_11_1::basic_json<ObjectType, ArrayType, StringType, BooleanType, NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer, BinaryType>::get_to(ValueType&) const [with typename std::enable_if<nlohmann::json_v3_11_1::detail::is_basic_json<BasicJsonType>::value, int>::type <anonymous> = ValueType; ObjectType = std::map; ArrayType = std::vector; StringType = std::__cxx11::basic_string<char>; BooleanType = bool; NumberIntegerType = long int; NumberUnsignedType = long unsigned int; NumberFloatType = double; AllocatorType = std::allocator; JSONSerializer = nlohmann::json_v3_11_1::adl_serializer; BinaryType = std::vector<unsigned char>]'
20767 |     ValueType & get_to(ValueType& v) const
      |                 ^~~~~~
/opt/compiler-explorer/libs/nlohmann_json/v3.11.1/single_include/nlohmann/json.hpp:20767:17: note:   template argument deduction/substitution failed:
/opt/compiler-explorer/libs/nlohmann_json/v3.11.1/single_include/nlohmann/json.hpp:20766:25: error: no type named 'type' in 'struct std::enable_if<false, int>'
20766 |                  int> = 0>
      |                         ^
/opt/compiler-explorer/libs/nlohmann_json/v3.11.1/single_include/nlohmann/json.hpp:20778:11: note: candidate: 'template<class T, long unsigned int N, class Array, typename std::enable_if<nlohmann::json_v3_11_1::detail::has_from_json<nlohmann::json_v3_11_1::basic_json<>, Array, void>::value, int>::type <anonymous> > Array nlohmann::json_v3_11_1::basic_json<ObjectType, ArrayType, StringType, BooleanType, NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer, BinaryType>::get_to(T (&)[N]) const [with long unsigned int N = T; Array = N; typename std::enable_if<nlohmann::json_v3_11_1::detail::has_from_json<nlohmann::json_v3_11_1::basic_json<ObjectType, ArrayType, StringType, BooleanType, NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer, BinaryType>, Array>::value, int>::type <anonymous> = Array; ObjectType = std::map; ArrayType = std::vector; StringType = std::__cxx11::basic_string<char>; BooleanType = bool; NumberIntegerType = long int; NumberUnsignedType = long unsigned int; NumberFloatType = double; AllocatorType = std::allocator; JSONSerializer = nlohmann::json_v3_11_1::adl_serializer; BinaryType = std::vector<unsigned char>]'
20778 |     Array get_to(T (&v)[N]) const // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
      |           ^~~~~~
/opt/compiler-explorer/libs/nlohmann_json/v3.11.1/single_include/nlohmann/json.hpp:20778:11: note:   template argument deduction/substitution failed:
<source>:37:1: note:   mismatched types 'T [N]' and 'NonDefaultConstructible'
   37 | NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(SimpleStruct, first_value, second_value,
      | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Compiler and operating system

g++ 11.4, g++ trunk, clang++ 11.0.1, clang trunk, OS: ubuntu 22.04/godbolt

Library version

3.11.3

Validation

In this case, I need to use the adl_serializer specialization everywhere? I see. Thank you for the swift response, I had indeed overlooked this limitation.

Would you be open to tracking this deficiency as a feature request/request for enhancement then?

Would you be open to tracking this deficiency as a feature request/request for enhancement then?

Sure - though I hope it does not mean adding another dozen of macros for this case...

This works just fine, but now structs containing this type cannot be json-converted using the macros anymore:
Godbolt link: https://godbolt.org/z/q8oxq61sx (gcc trunk, nlohmann_json trunk)

That's simply because you didn't implement the version of from_json that takes the destination object by reference. You need that in order to support the get_to from the macros. Also, you should use the templated version of the first parameter so that you can also support ordered_json.

NLOHMANN_JSON_NAMESPACE_BEGIN
template <> struct adl_serializer<NonDefaultConstructible> {
    template<typename BasicJsonType>
    static void to_json(BasicJsonType &json_value, const NonDefaultConstructible &data) {
      json_value = data.get_inner_value();
    }

    template<typename BasicJsonType>
    static void from_json(BasicJsonType && j, NonDefaultConstructible &val) 
    {
      // There is no setter for this member, so I had to create a new one and move it into the original.
      // I assume in the original there is a setter, which would make this easier.
      val = NonDefaultConstructible{j.template get<std::string>()};
    }
};
NLOHMANN_JSON_NAMESPACE_END

@nlohmann It would probably be a good idea to update the docs examples to use the template BasicJsonType.

template<typename BasicJsonType>
static void from_json(BasicJsonType && j, NonDefaultConstructible &val) 

Just curious, why the forwarding reference? When I try it, it seems to work with a const BasicJsonType& too, and clangd gives me a warning that the forwarding reference is never forwarded inside the function body?

It should probably just be const&.