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

std::map key conversion with to_json #607

Closed
RobotCaleb opened this issue Jun 5, 2017 · 14 comments
Closed

std::map key conversion with to_json #607

RobotCaleb opened this issue Jun 5, 2017 · 14 comments

Comments

@RobotCaleb
Copy link

RobotCaleb commented Jun 5, 2017

I read that you can use maps as long as a std::string can be constructed from the key.

I don't understand why this doesn't work. As I've written it I think it's fulfilling the constraint I mentioned above.

enum class EC {
    A
};

static void to_json(json& j, EC const& ae) {
    j = std::string{"FD"};
}

static void from_json(const json& j, EC& ae) {
    ae = EC::A;
}

int main()
{
    std::unordered_map<EC, int> imap;
    json j = imap;
}
@nlohmann
Copy link
Owner

nlohmann commented Jun 5, 2017

The problem is already when the unordered map is defined:

#include <unordered_map>

enum class EC {
    A
};

int main()
{
    std::unordered_map<EC, int> imap;
}

Error:

c++ -Isrc -std=c++11    issue.cpp   -o issue
In file included from issue.cpp:1:
In file included from /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/unordered_map:369:
In file included from /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/__hash_table:16:
In file included from /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/memory:599:
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/type_traits:1010:38: error: implicit
      instantiation of undefined template 'std::__1::hash<EC>'
    : public integral_constant<bool, __is_empty(_Tp)> {};
                                     ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/unordered_map:383:18: note: in
      instantiation of template class 'std::__1::is_empty<std::__1::hash<EC> >' requested here
          bool = is_empty<_Hash>::value && !__libcpp_is_final<_Hash>::value
                 ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/unordered_map:765:13: note: in
      instantiation of default argument for '__unordered_map_hasher<EC, std::__1::__hash_value_type<EC, int>, std::__1::hash<EC> >' required here
    typedef __unordered_map_hasher<key_type, __value_type, hasher>   __hasher;
            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
issue.cpp:9:33: note: in instantiation of template class 'std::__1::unordered_map<EC, int, std::__1::hash<EC>, std::__1::equal_to<EC>,
      std::__1::allocator<std::__1::pair<const EC, int> > >' requested here
    std::unordered_map<EC, int> imap;
                                ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/memory:3171:29: note: template is
      declared here
template <class _Tp> struct hash;
                            ^
1 error generated.
make: *** [issue] Error 1

This can be fixed by defining a hash for type EC (note this is a very naive implementation):

namespace std
{
    template<>
    struct hash<EC>
    {
        std::size_t operator()(const EC& e) const
        {
            return 0;
        }
    };
}

Unfortunately, this does not yet fix the problem:

c++ -Isrc -std=c++11    issue.cpp   -o issue
In file included from issue.cpp:2:
src/json.hpp:1102:9: error: static_assert failed "could not find to_json() method in T's namespace"
        static_assert(sizeof(BasicJsonType) == 0,
        ^             ~~~~~~~~~~~~~~~~~~~~~~~~~~
src/json.hpp:1111:16: note: in instantiation of function template specialization
      'nlohmann::detail::to_json_fn::call<nlohmann::basic_json<std::map, std::vector, std::__1::basic_string<char>, bool, long long, unsigned long
      long, double, std::allocator, adl_serializer>, const std::__1::pair<const EC, int> &>' requested here
        return call(j, std::forward<T>(val), priority_tag<1> {});
               ^
src/json.hpp:1201:9: note: in instantiation of function template specialization
      'nlohmann::detail::to_json_fn::operator()<nlohmann::basic_json<std::map, std::vector, std::__1::basic_string<char>, bool, long long,
      unsigned long long, double, std::allocator, adl_serializer>, const std::__1::pair<const EC, int> &>' requested here
        ::nlohmann::to_json(j, std::forward<ValueType>(val));
        ^
src/json.hpp:2287:28: note: in instantiation of function template specialization 'nlohmann::adl_serializer<std::__1::pair<const EC, int>,
      void>::to_json<nlohmann::basic_json<std::map, std::vector, std::__1::basic_string<char>, bool, long long, unsigned long long, double,
      std::allocator, adl_serializer>, const std::__1::pair<const EC, int> &>' requested here
        JSONSerializer<U>::to_json(*this, std::forward<CompatibleType>(val));
                           ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/memory:1752:31: note: in instantiation of
      function template specialization 'nlohmann::basic_json<std::map, std::vector, std::__1::basic_string<char>, bool, long long, unsigned long
      long, double, std::allocator, adl_serializer>::basic_json<const std::__1::pair<const EC, int> &, std::__1::pair<const EC, int>, 0>'
      requested here
            ::new((void*)__p) _Up(_VSTD::forward<_Args>(__args)...);
                              ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/memory:1668:18: note: in instantiation of
      function template specialization 'std::__1::allocator<nlohmann::basic_json<std::map, std::vector, std::__1::basic_string<char>, bool, long
      long, unsigned long long, double, std::allocator, adl_serializer> >::construct<nlohmann::basic_json<std::map, std::vector,
      std::__1::basic_string<char>, bool, long long, unsigned long long, double, std::allocator, adl_serializer>, const std::__1::pair<const EC,
      int> &>' requested here
            {__a.construct(__p, _VSTD::forward<_Args>(__args)...);}
                 ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/memory:1514:14: note: (skipping 8
      contexts in backtrace; use -ftemplate-backtrace-limit=0 to see all)
            {__construct(__has_construct<allocator_type, _Tp*, _Args...>(),
             ^
src/json.hpp:1096:16: note: in instantiation of function template specialization 'nlohmann::detail::to_json<nlohmann::basic_json<std::map,
      std::vector, std::__1::basic_string<char>, bool, long long, unsigned long long, double, std::allocator, adl_serializer>,
      std::__1::unordered_map<EC, int, std::__1::hash<EC>, std::__1::equal_to<EC>, std::__1::allocator<std::__1::pair<const EC, int> > >, 0>'
      requested here
        return to_json(j, std::forward<T>(val));
               ^
src/json.hpp:1111:16: note: in instantiation of function template specialization
      'nlohmann::detail::to_json_fn::call<nlohmann::basic_json<std::map, std::vector, std::__1::basic_string<char>, bool, long long, unsigned long
      long, double, std::allocator, adl_serializer>, std::__1::unordered_map<EC, int, std::__1::hash<EC>, std::__1::equal_to<EC>,
      std::__1::allocator<std::__1::pair<const EC, int> > > &>' requested here
        return call(j, std::forward<T>(val), priority_tag<1> {});
               ^
src/json.hpp:1201:9: note: in instantiation of function template specialization
      'nlohmann::detail::to_json_fn::operator()<nlohmann::basic_json<std::map, std::vector, std::__1::basic_string<char>, bool, long long,
      unsigned long long, double, std::allocator, adl_serializer>, std::__1::unordered_map<EC, int, std::__1::hash<EC>, std::__1::equal_to<EC>,
      std::__1::allocator<std::__1::pair<const EC, int> > > &>' requested here
        ::nlohmann::to_json(j, std::forward<ValueType>(val));
        ^
src/json.hpp:2287:28: note: in instantiation of function template specialization 'nlohmann::adl_serializer<std::__1::unordered_map<EC, int,
      std::__1::hash<EC>, std::__1::equal_to<EC>, std::__1::allocator<std::__1::pair<const EC, int> > >,
      void>::to_json<nlohmann::basic_json<std::map, std::vector, std::__1::basic_string<char>, bool, long long, unsigned long long, double,
      std::allocator, adl_serializer>, std::__1::unordered_map<EC, int, std::__1::hash<EC>, std::__1::equal_to<EC>,
      std::__1::allocator<std::__1::pair<const EC, int> > > &>' requested here
        JSONSerializer<U>::to_json(*this, std::forward<CompatibleType>(val));
                           ^
issue.cpp:33:14: note: in instantiation of function template specialization 'nlohmann::basic_json<std::map, std::vector,
      std::__1::basic_string<char>, bool, long long, unsigned long long, double, std::allocator,
      adl_serializer>::basic_json<std::__1::unordered_map<EC, int, std::__1::hash<EC>, std::__1::equal_to<EC>,
      std::__1::allocator<std::__1::pair<const EC, int> > > &, std::__1::unordered_map<EC, int, std::__1::hash<EC>, std::__1::equal_to<EC>,
      std::__1::allocator<std::__1::pair<const EC, int> > >, 0>' requested here
    json j = imap;
             ^
1 error generated.
make: *** [issue] Error 1

I'm not sure about this yet. :-(

@nlohmann
Copy link
Owner

nlohmann commented Jun 5, 2017

@theodelrieu Any idea on this? This may be related to #600.

@nlohmann
Copy link
Owner

nlohmann commented Jun 5, 2017

(Not helpful, but worth noting:)

The README says:

Likewise, any associative key-value containers (std::map, std::multimap, std::unordered_map, std::unordered_multimap) whose keys can construct an std::string and whose values can be used to construct JSON types (see examples above) can be used to to create a JSON object.

In this example, there is no conversion from EC to std::string possible. The conversion to json is not used here, and I am not sure whether it is a good idea to use it.

@theodelrieu
Copy link
Contributor

Indeed, only map-like types with a key convertible to std::string are supported by default.

However, you can partially specialize adl_serializer like this:

namespace nlohmann {
template <typename T>
adl_serializer<std::unordered_map<EC, T>> {
  // define to_json/from_json here
};
}

@RobotCaleb
Copy link
Author

RobotCaleb commented Jun 6, 2017 via email

@theodelrieu
Copy link
Contributor

We support JSON serialization for enums and enum classes by default, using the underlying numeric value.

However, in this case, we are not converting an enum into a JSON value, but as a part of a JSON value (an object here). The library cannot call a to_json method to convert the key for that reason, this is why providing your own methods are not helping here.

The approach I mentioned ahead will allow you to handle that key "stringification" yourself.
This is a more advanced use of the arbitrary type conversion feature, you can find more info in this Readme section

@nlohmann
Copy link
Owner

nlohmann commented Jun 6, 2017

I agree with @theodelrieu. You need to provide your own enum-to-string conversion.

@RobotCaleb
Copy link
Author

@nlohmann and by that you mean via the adl_serializer specialization? Because, I am providing enum-to-string conversion (at least, I thought I was via to/from_json)

I'll try out the adl_serializer. Do I have to build the json from the map myself? That is, what's the bare minimum I can provide in the serializer to get the serializing/deserializing?

@nlohmann
Copy link
Owner

nlohmann commented Jun 7, 2017

The to/from_json functions only describe how a type can be converted to json. In your case, you described how EC is converted to json. However, there is no conversion from EC to std::string provided.

@RobotCaleb
Copy link
Author

RobotCaleb commented Jun 7, 2017

Okay, thanks.

What is the proper usage of adl_serializer for populating from a map? I don't need a whole example, necessarily. I'm just not clear on where to start. Thanks.

namespace nlohmann {
template <typename T>
adl_serializer<std::unordered_map<EC, T>> {
  // define to_json/from_json here
  void to_json(json& j, std::unordered_map<EC, T> const& map) {
    // loop on map entries here?
    // j = ...
  }

  // likewise for from_json?
};
}

@nlohmann
Copy link
Owner

nlohmann commented Jun 8, 2017

I think @theodelrieu can help with that.

@theodelrieu
Copy link
Contributor

First, do not forget to make your methods static.

Something like this will work:

for (auto const& elem : map) {
  auto const strKey = convertToString(elem.first);
  j[strKey] = elem.second;
}

Of course there needs to be a to_json method for each type T that will be used in your map.

Same thing for the from_json, but you need to modify the loop to expose the key:

for (auto const& elem : json::iterator_wrapper(j)) {
  auto const enumKey = enumFromString(elem.key());
  map[enumKey] = elem.value();
}

@nlohmann
Copy link
Owner

@RobotCaleb - Did @theodelrieu s hints work for you?

@RobotCaleb
Copy link
Author

I switched away from this to just being explicit with tinyxml2. I appreciate your prompt responses, though.

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

No branches or pull requests

3 participants