Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
2 tasks done
RAnders00 opened this issue Dec 21, 2023 · 6 comments
Open
2 tasks done

Comments

@RAnders00
Copy link

RAnders00 commented Dec 21, 2023

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

@nlohmann
Copy link
Owner

@RAnders00
Copy link
Author

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?

@nlohmann
Copy link
Owner

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...

@gregmarr
Copy link
Contributor

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.

@RAnders00
Copy link
Author

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?

@gregmarr
Copy link
Contributor

It should probably just be const&.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants