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

Non-member operator== breaks enum (de)serialization #1647

Closed
AnthonyVH opened this issue Jun 20, 2019 · 13 comments · Fixed by #1821
Closed

Non-member operator== breaks enum (de)serialization #1647

AnthonyVH opened this issue Jun 20, 2019 · 13 comments · Fixed by #1821
Assignees
Labels
confirmed kind: bug release item: 🐛 bug fix solution: proposed fix a fix for the issue has been proposed and waits for confirmation
Milestone

Comments

@AnthonyVH
Copy link
Contributor

  • What is the issue you have?

    Compile error when trying to (de)serialize an enum.

  • Please describe the steps to reproduce the issue. Can you provide a small but working code example?

    AFAIK the minimum requirement to trigger this is:

    • A type T for which the "non-default-constructible" form of to/from_json has been defined, and for which
      non-member operator== is defined.
    • An enum for which (de)serialization is defined using NLOMANN_JSON_SERIALIZE_ENUM.

    MWE (Wandbox), see code below.

    Again AFAIK, this is what happens: the comparisons in NLOHMANN_JSON_SERIALIZE_ENUM on lines 571 and 583 are against different types (BasicJsonType & ENUM_TYPE). This causes the compiler to try and convert both sides to a common type. Since there's a non-member operator== for dummy::foo, it will try to convert both sides to dummy::foo. And since to/from_json has been defined, it tries to use that to deserialize the JSON. It seems that for some reason the wrong get<dummy::foo>() is called, which then gives a compile error. That's as far as I got, I haven't had more time to try and debug all the template code.

    PS: Seems to me the capture [j] on line 581 should be [&j], the JSON object is being copied right now.

#include <json.hpp>

namespace dummy {
  struct foo { };
  bool operator== (foo const & lhs, foo const & rhs);

  enum class fruit { apple };
} // ns dummy

namespace nlohmann {
  template <>
  struct adl_serializer<dummy::foo> {
    static dummy::foo from_json (json const & j);
    static void to_json (json & result, dummy::foo const & obj);
  };
} // ns nlohmann

namespace dummy {
  NLOHMANN_JSON_SERIALIZE_ENUM(fruit, {
    { fruit::apple, "apple" }
  })
} //  ns dummy

int main () {
  auto val = nlohmann::json("apple").get<dummy::fruit>();
}
  • What is the expected behavior?

    Code compiles and (de)serializes enums properly.

  • And what is the actual behavior instead?

    The code gives a compile error.

  • Which compiler and operating system are you using? Is it a supported compiler?

    Tried with gcc 7.3.0 and gcc 9.1.0 (on Wandbox).

  • Did you use a released version of the library or the version from the develop branch?

    Release 3.6.1, single header version.

  • If you experience a compilation error: can you compile and run the unit tests?

    I haven't gotten around to this. If it helps I can try that this weekend.

@stale
Copy link

stale bot commented Jul 20, 2019

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the state: stale the issue has not been updated in a while and will be closed automatically soon unless it is updated label Jul 20, 2019
@AnthonyVH
Copy link
Contributor Author

Replying to keep this alive and kicking.

@stale stale bot removed the state: stale the issue has not been updated in a while and will be closed automatically soon unless it is updated label Jul 20, 2019
@stale
Copy link

stale bot commented Aug 19, 2019

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the state: stale the issue has not been updated in a while and will be closed automatically soon unless it is updated label Aug 19, 2019
@AnthonyVH
Copy link
Contributor Author

Alive and kicking bug, AFAIK.

@stale stale bot removed the state: stale the issue has not been updated in a while and will be closed automatically soon unless it is updated label Aug 19, 2019
@nlohmann
Copy link
Owner

nlohmann commented Sep 2, 2019

I can confirm the bug with GCC on macOS:

In file included from issue1647.cpp:1:
single_include/nlohmann/json.hpp: In instantiation of 'ValueType nlohmann::basic_json<ObjectType, ArrayType, StringType, BooleanType, NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer>::get() const [with ValueTypeCV = const dummy::foo; ValueType = dummy::foo; typename std::enable_if<((! std::is_same<nlohmann::basic_json<ObjectType, ArrayType, StringType, BooleanType, NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer>, ValueType>::value) && nlohmann::detail::has_non_default_from_json<nlohmann::basic_json<ObjectType, ArrayType, StringType, BooleanType, NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer>, ValueType>::value), int>::type <anonymous> = 0; ObjectType = std::map; ArrayType = std::vector; StringType = std::__cxx11::basic_string<char>; BooleanType = bool; NumberIntegerType = long long int; NumberUnsignedType = long long unsigned int; NumberFloatType = double; AllocatorType = std::allocator; JSONSerializer = nlohmann::adl_serializer]':
single_include/nlohmann/json.hpp:17290:20:   recursively required by substitution of 'template<class Default, template<class ...> class Op, class ... Args> struct nlohmann::detail::detector<Default, typename nlohmann::detail::make_void<Op<Args ...> >::type, Op, Args ...> [with Default = nlohmann::detail::nonesuch; Op = nlohmann::detail::get_template_function; Args = {const nlohmann::basic_json<std::map, std::vector, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, long long int, long long unsigned int, double, std::allocator, nlohmann::adl_serializer>&, const dummy::foo}]'
single_include/nlohmann/json.hpp:17290:20:   required by substitution of 'template<class ValueType, typename std::enable_if<((((((! std::is_pointer<_Tp>::value) && (! std::is_same<ValueType, nlohmann::detail::json_ref<nlohmann::basic_json<> > >::value)) && (! std::is_same<ValueType, char>::value)) && (! nlohmann::detail::is_basic_json<BasicJsonType>::value)) && (! std::is_same<ValueType, std::initializer_list<char> >::value)) && typename nlohmann::detail::detector<nlohmann::detail::nonesuch, void, nlohmann::detail::get_template_function, const nlohmann::basic_json<std::map, std::vector, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, long long int, long long unsigned int, double, std::allocator, nlohmann::adl_serializer>&, ValueType>::value_t::value), int>::type <anonymous> > nlohmann::basic_json<>::operator ValueType<ValueType, <enumerator> >() const [with ValueType = const dummy::foo; typename std::enable_if<((((((! std::is_pointer<_Tp>::value) && (! std::is_same<ValueType, nlohmann::detail::json_ref<nlohmann::basic_json<> > >::value)) && (! std::is_same<ValueType, char>::value)) && (! nlohmann::detail::is_basic_json<BasicJsonType>::value)) && (! std::is_same<ValueType, std::initializer_list<char> >::value)) && typename nlohmann::detail::detector<nlohmann::detail::nonesuch, void, nlohmann::detail::get_template_function, const nlohmann::basic_json<std::map, std::vector, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, long long int, long long unsigned int, double, std::allocator, nlohmann::adl_serializer>&, ValueType>::value_t::value), int>::type <anonymous> = <missing>]'
issue1647.cpp:22:5:   required from 'void dummy::from_json(const BasicJsonType&, dummy::fruit&) [with BasicJsonType = nlohmann::basic_json<>]'
single_include/nlohmann/json.hpp:3145:25:   required from 'decltype ((nlohmann::detail::from_json(j, val), void())) nlohmann::detail::from_json_fn::operator()(const BasicJsonType&, T&) const [with BasicJsonType = nlohmann::basic_json<>; T = dummy::fruit; decltype ((nlohmann::detail::from_json(j, val), void())) = void]'
single_include/nlohmann/json.hpp:3702:30:   required from 'static decltype ((nlohmann::{anonymous}::from_json(forward<BasicJsonType>(j), val), void())) nlohmann::adl_serializer<T, SFINAE>::from_json(BasicJsonType&&, ValueType&) [with BasicJsonType = const nlohmann::basic_json<>&; ValueType = dummy::fruit; <template-parameter-1-1> = dummy::fruit; <template-parameter-1-2> = void; decltype ((nlohmann::{anonymous}::from_json(forward<BasicJsonType>(j), val), void())) = void]'
single_include/nlohmann/json.hpp:17002:45:   required from 'ValueType nlohmann::basic_json<ObjectType, ArrayType, StringType, BooleanType, NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer>::get() const [with ValueTypeCV = dummy::fruit; ValueType = dummy::fruit; typename std::enable_if<(((! nlohmann::detail::is_basic_json<U>::value) && nlohmann::detail::has_from_json<nlohmann::basic_json<ObjectType, ArrayType, StringType, BooleanType, NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer>, ValueType>::value) && (! nlohmann::detail::has_non_default_from_json<nlohmann::basic_json<ObjectType, ArrayType, StringType, BooleanType, NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer>, ValueType>::value)), int>::type <anonymous> = 0; ObjectType = std::map; ArrayType = std::vector; StringType = std::__cxx11::basic_string<char>; BooleanType = bool; NumberIntegerType = long long int; NumberUnsignedType = long long unsigned int; NumberFloatType = double; AllocatorType = std::allocator; JSONSerializer = nlohmann::adl_serializer]'
issue1647.cpp:29:58:   required from here
single_include/nlohmann/json.hpp:17042:78: error: no matching function for call to 'nlohmann::adl_serializer<const dummy::foo, void>::from_json(const nlohmann::basic_json<>&)'
                                        JSONSerializer<ValueTypeCV>::from_json(std::declval<const basic_json_t&>())))
                                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from issue1647.cpp:1:
single_include/nlohmann/json.hpp:3698:17: note: candidate: 'template<class BasicJsonType, class ValueType> static decltype ((nlohmann::{anonymous}::from_json(forward<BasicJsonType>(j), val), void())) nlohmann::adl_serializer<T, SFINAE>::from_json(BasicJsonType&&, ValueType&) [with BasicJsonType = BasicJsonType; ValueType = ValueType; <template-parameter-1-1> = const dummy::foo; <template-parameter-1-2> = void]'
     static auto from_json(BasicJsonType&& j, ValueType& val) noexcept(
                 ^~~~~~~~~
single_include/nlohmann/json.hpp:3698:17: note:   template argument deduction/substitution failed:
In file included from issue1647.cpp:1:
single_include/nlohmann/json.hpp:17042:78: note:   candidate expects 2 arguments, 1 provided
                                        JSONSerializer<ValueTypeCV>::from_json(std::declval<const basic_json_t&>())))
                                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
single_include/nlohmann/json.hpp:20030:17: warning: inline function 'bool nlohmann::operator==(nlohmann::basic_json<>::const_reference, nlohmann::basic_json<>::const_reference)' used but never defined
     friend bool operator==(const_reference lhs, const_reference rhs) noexcept
                 ^~~~~~~~
In file included from /usr/local/Cellar/gcc@8/8.3.0/include/c++/8.3.0/algorithm:62,
                 from single_include/nlohmann/json.hpp:37,
                 from issue1647.cpp:1:
/usr/local/Cellar/gcc@8/8.3.0/include/c++/8.3.0/bits/stl_algo.h:3921:5: error: '_IIter std::find_if(_IIter, _IIter, _Predicate) [with _IIter = const std::pair<dummy::fruit, nlohmann::basic_json<> >*; _Predicate = dummy::from_json(const BasicJsonType&, dummy::fruit&) [with BasicJsonType = nlohmann::basic_json<>]::<lambda(const std::pair<dummy::fruit, nlohmann::basic_json<> >&)>]', declared using local type 'dummy::from_json(const BasicJsonType&, dummy::fruit&) [with BasicJsonType = nlohmann::basic_json<>]::<lambda(const std::pair<dummy::fruit, nlohmann::basic_json<> >&)>', is used but never defined [-fpermissive]
     find_if(_InputIterator __first, _InputIterator __last,
     ^~~~~~~
make: *** [issue1647] Error 1

Strangely, the code compiles without issues using Clang.

@stale
Copy link

stale bot commented Oct 2, 2019

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the state: stale the issue has not been updated in a while and will be closed automatically soon unless it is updated label Oct 2, 2019
@AnthonyVH
Copy link
Contributor Author

Poking the bot.

@stale stale bot removed the state: stale the issue has not been updated in a while and will be closed automatically soon unless it is updated label Oct 2, 2019
@pkngpkng
Copy link

In msvc, enum in namespace can't serialze to json. Only change to these can fix this problom, but it enable when use basic json type.

#define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...)                                            \
    inline void to_json(nlohmann::json& j, const ENUM_TYPE& e)                                  \
    {                                                                                           \
        static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE " must be an enum!");          \
        static const std::pair<ENUM_TYPE, nlohmann::json> m[] = __VA_ARGS__;                    \
        auto it = std::find_if(std::begin(m), std::end(m),                                      \
                               [e](const std::pair<ENUM_TYPE, nlohmann::json>& ej_pair) -> bool \
        {                                                                                       \
            return ej_pair.first == e;                                                          \
        });                                                                                     \
        j = ((it != std::end(m)) ? it : std::begin(m))->second;                                 \
    }                                                                                           \
    inline void from_json(const nlohmann::json& j, ENUM_TYPE& e)                                \
    {                                                                                           \
        static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE " must be an enum!");          \
        static const std::pair<ENUM_TYPE, nlohmann::json> m[] = __VA_ARGS__;                    \
        auto it = std::find_if(std::begin(m), std::end(m),                                      \
                               [j](const std::pair<ENUM_TYPE, nlohmann::json>& ej_pair) -> bool \
        {                                                                                       \
            return ej_pair.second == j;                                                         \
        });                                                                                     \
        e = ((it != std::end(m)) ? it : std::begin(m))->first;                                  \
    }

@AnthonyVH
Copy link
Contributor Author

@pkngpkng I'm not quite sure what you're trying to say. Are you saying there's a bug in the NLOHMANN_JSON_SERIALIZE_ENUM code related to the bug I'm reporting here?

@pkngpkng
Copy link

@AnthonyVH Yes

@nlohmann
Copy link
Owner

Any idea how to proceed here?

@pkngpkng
Copy link

define a new marco to serialize basic json type? @nlohmann
jusk like:

#define NLOHMANN_BASIC_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...)                                            \
    inline void to_json(nlohmann::json& j, const ENUM_TYPE& e)                                  \
    {                                                                                           \
        static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE " must be an enum!");          \
        static const std::pair<ENUM_TYPE, nlohmann::json> m[] = __VA_ARGS__;                    \
        auto it = std::find_if(std::begin(m), std::end(m),                                      \
                               [e](const std::pair<ENUM_TYPE, nlohmann::json>& ej_pair) -> bool \
        {                                                                                       \
            return ej_pair.first == e;                                                          \
        });                                                                                     \
        j = ((it != std::end(m)) ? it : std::begin(m))->second;                                 \
    }                                                                                           \
    inline void from_json(const nlohmann::json& j, ENUM_TYPE& e)                                \
    {                                                                                           \
        static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE " must be an enum!");          \
        static const std::pair<ENUM_TYPE, nlohmann::json> m[] = __VA_ARGS__;                    \
        auto it = std::find_if(std::begin(m), std::end(m),                                      \
                               [j](const std::pair<ENUM_TYPE, nlohmann::json>& ej_pair) -> bool \
        {                                                                                       \
            return ej_pair.second == j;                                                         \
        });                                                                                     \
        e = ((it != std::end(m)) ? it : std::begin(m))->first;                                  \
    }

@AnthonyVH
Copy link
Contributor Author

@nlohmann I'll try to find some time to dig into the weirdness that is happening.

@pkngpkng I'm not quite sure I follow what you are suggesting. There's already functionality available to serialize the basic JSON types. I don't see how throwing extra macros in the mix would help.

nlohmann added a commit that referenced this issue Nov 5, 2019
@nlohmann nlohmann added release item: 🐛 bug fix solution: proposed fix a fix for the issue has been proposed and waits for confirmation labels Nov 5, 2019
@nlohmann nlohmann self-assigned this Nov 5, 2019
@nlohmann nlohmann added this to the Release 3.7.1 milestone Nov 5, 2019
falbrechtskirchinger added a commit to falbrechtskirchinger/json that referenced this issue May 1, 2022
nlohmann added a commit that referenced this issue May 1, 2022
* ⬆️ Doctest 2.4.7

* 👷 add CI step for ICPC

* 👷 add CI step for ICPC

* 👷 add CI step for ICPC

* ⬇️ downgrade to Doctest 2.4.6

* 👷 add CI step for ICPC

* 👷 add CI step for ICPC

* 👷 add CI step for ICPC

* 👷 add CI step for ICPC

* 👷 add CI step for ICPC

* 🔇 suppress warning #2196: routine is both "inline" and "noinline"

* Re-enable <filesystem> detection on ICPC

* Limit regression test for #3070 to Clang and GCC >=8.4

* Disable deprecation warnings on ICPC

* Disable regression test for #1647 on ICPC (C++20)

* Fix compilation failure of regression test for #3077 on ICPC

* Disable wstring unit test on ICPC

Fixes:
  error 913: invalid multibyte character sequence

* Add ICPC to README

Co-authored-by: Niels Lohmann <mail@nlohmann.me>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
confirmed kind: bug release item: 🐛 bug fix solution: proposed fix a fix for the issue has been proposed and waits for confirmation
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants