json icon indicating copy to clipboard operation
json copied to clipboard

NLOHMANN_DEFINE_TYPE_INTRUSIVE with nlohmann::json::json_pointer

Open risa2000 opened this issue 2 years ago • 9 comments

Description

Using NLOHMANN_DEFINE_TYPE_INTRUSIVE (or non-intrusive) macro to define conversions for struct with nlohmann::json::json_pointer member fails to compile (MSVC v19.3 - VS 2022), with or without explicit conversion.

Reproduction steps

Compile the sample code. Here is a Godbolt link https://godbolt.org/z/fdrPrcT7T

Expected vs. actual results

Expecting it will compile.

Minimal code example

#include <nlohmann/json.hpp>
#include <fmt/format.h>
#include <string>

inline void to_json(nlohmann::json& j, const nlohmann::json::json_pointer& crj)
{
    j = nlohmann::json(crj.to_string());
}

inline void from_json(const nlohmann::json& cj, nlohmann::json::json_pointer& rj)
{
    rj = nlohmann::json::json_pointer(cj.get<std::string>());
}

struct Test {
    nlohmann::json::json_pointer ptr;
//    std::string ptr;
    nlohmann::json value;
    NLOHMANN_DEFINE_TYPE_INTRUSIVE(Test, ptr, value);
};

// Type your code here, or load an example.
int main() {
    Test test{nlohmann::json::json_pointer("/test"), 10};
    nlohmann::json jtest = test;
    fmt::print("jtest={}\n", jtest.dump());
}

Error messages

example.cpp
<source>(19): error C2672: 'nlohmann::json_abi_v3_11_2::basic_json<std::map,std::vector,std::string,bool,int64_t,uint64_t,double,std::allocator,nlohmann::json_abi_v3_11_2::adl_serializer,std::vector<uint8_t,std::allocator<uint8_t>>>::get_to': no matching overloaded function found
C:/data/libraries/installed/x64-windows/include\nlohmann/json.hpp(1809): note: could be 'Array nlohmann::json_abi_v3_11_2::basic_json<std::map,std::vector,std::string,bool,int64_t,uint64_t,double,std::allocator,nlohmann::json_abi_v3_11_2::adl_serializer,std::vector<uint8_t,std::allocator<uint8_t>>>::get_to(T (&)[N]) noexcept(<expr>) const'
<source>(19): note: 'Array nlohmann::json_abi_v3_11_2::basic_json<std::map,std::vector,std::string,bool,int64_t,uint64_t,double,std::allocator,nlohmann::json_abi_v3_11_2::adl_serializer,std::vector<uint8_t,std::allocator<uint8_t>>>::get_to(T (&)[N]) noexcept(<expr>) const': could not deduce template argument for 'T (&)[N]' from 'nlohmann::json_abi_v3_11_2::json_pointer<StringType>'
        with
        [
            StringType=std::string
        ]
C:/data/libraries/installed/x64-windows/include\nlohmann/json.hpp(1809): note: see declaration of 'nlohmann::json_abi_v3_11_2::basic_json<std::map,std::vector,std::string,bool,int64_t,uint64_t,double,std::allocator,nlohmann::json_abi_v3_11_2::adl_serializer,std::vector<uint8_t,std::allocator<uint8_t>>>::get_to'
C:/data/libraries/installed/x64-windows/include\nlohmann/json.hpp(1798): note: or       'ValueType &nlohmann::json_abi_v3_11_2::basic_json<std::map,std::vector,std::string,bool,int64_t,uint64_t,double,std::allocator,nlohmann::json_abi_v3_11_2::adl_serializer,std::vector<uint8_t,std::allocator<uint8_t>>>::get_to(ValueType &) const'
<source>(19): note: 'ValueType &nlohmann::json_abi_v3_11_2::basic_json<std::map,std::vector,std::string,bool,int64_t,uint64_t,double,std::allocator,nlohmann::json_abi_v3_11_2::adl_serializer,std::vector<uint8_t,std::allocator<uint8_t>>>::get_to(ValueType &) const': could not deduce template argument for '__formal'
C:/data/libraries/installed/x64-windows/include\nlohmann/json.hpp(1798): note: see declaration of 'nlohmann::json_abi_v3_11_2::basic_json<std::map,std::vector,std::string,bool,int64_t,uint64_t,double,std::allocator,nlohmann::json_abi_v3_11_2::adl_serializer,std::vector<uint8_t,std::allocator<uint8_t>>>::get_to'
C:/data/libraries/installed/x64-windows/include\nlohmann/json.hpp(1785): note: or       'ValueType &nlohmann::json_abi_v3_11_2::basic_json<std::map,std::vector,std::string,bool,int64_t,uint64_t,double,std::allocator,nlohmann::json_abi_v3_11_2::adl_serializer,std::vector<uint8_t,std::allocator<uint8_t>>>::get_to(ValueType &) noexcept(<expr>) const'
<source>(19): note: 'ValueType &nlohmann::json_abi_v3_11_2::basic_json<std::map,std::vector,std::string,bool,int64_t,uint64_t,double,std::allocator,nlohmann::json_abi_v3_11_2::adl_serializer,std::vector<uint8_t,std::allocator<uint8_t>>>::get_to(ValueType &) noexcept(<expr>) const': could not deduce template argument for '__formal'
C:/data/libraries/installed/x64-windows/include\nlohmann/json.hpp(1785): note: see declaration of 'nlohmann::json_abi_v3_11_2::basic_json<std::map,std::vector,std::string,bool,int64_t,uint64_t,double,std::allocator,nlohmann::json_abi_v3_11_2::adl_serializer,std::vector<uint8_t,std::allocator<uint8_t>>>::get_to'
Compiler returned: 2

Compiler and operating system

MSVC v19.3 - VS 2022

Library version

3.11.2

Validation

risa2000 avatar Aug 11 '23 11:08 risa2000

This issue has been marked as stale because it has been open for 90 days without activity. If this issue is still relevant, please add a comment or remove the "stale" label. Otherwise, it will be closed in 10 days. Thank you for helping us prioritize our work!

github-actions[bot] avatar Feb 02 '25 00:02 github-actions[bot]

The to_json and from_json functions need to be in the namespace of the type, not global functions unless the type is global. You are probably better off using the third party conversion option adl_serializer https://github.com/nlohmann/json?tab=readme-ov-file#arbitrary-types-conversions:~:text=I%20convert%20third%2D-,party,-types%3F

gregmarr avatar Feb 02 '25 01:02 gregmarr

@gregmarr Right! A trivial overlook on my side :(. If I declare the conversions in nlohmann namespace it works as expected (https://godbolt.org/z/K94hcz7ja):

#include <nlohmann/json.hpp>
#include <string>
#include <cstdio>

namespace nlohmann {
inline void to_json(json& j, const json::json_pointer& crj)
{
    j = json(crj.to_string());
}

inline void from_json(const json& cj, json::json_pointer& rj)
{
    rj = nlohmann::json::json_pointer(cj.get<std::string>());
}
}

struct Test {
    nlohmann::json::json_pointer ptr;
    nlohmann::json value;
    NLOHMANN_DEFINE_TYPE_INTRUSIVE(Test, ptr, value);
};

// Type your code here, or load an example.
int main() {
    Test test{.ptr = nlohmann::json::json_pointer("/test"), .value = 10};
    nlohmann::json jtest = test;
    printf("jtest=%s\n", jtest.dump().c_str());
}

The remaining question is if it would make sense to have these conversions in the library instead. json::json_pointer is a "native" library type. Writing a conversion function for this, while trivial, looks like something library could do as well. What do you think?

risa2000 avatar Feb 02 '25 19:02 risa2000

I'm not sure it's obvious that everyone would want this represented in their JSON as a plain string.

gregmarr avatar Feb 02 '25 19:02 gregmarr

It is basically an analogy to std::filesystem::path. I am not sure there is any other (sane) way how to represent it in JSON as much as there is any other (sane) way how to represent std::filesystem::path. I give you the benefit of doubt though ;-).

risa2000 avatar Feb 02 '25 19:02 risa2000

It's really @nlohmann's call. It might make sense for there to be a default here. The only reason I wasn't sure is that it could be reference to something in the JSON itself, and it would change based on where in the tree you did a dump(). You could pull a JSON file into a larger json object, and that could change the reference. That's probably a niche enough case that it would need special handling and the default of it being a string may be sufficient.

gregmarr avatar Feb 03 '25 15:02 gregmarr

@gregmarr I am not sure I am following you. For me json::json_pointer is an opaque structure, which may (in the same way as std::filesystem::path) have a semantical meaning (i.e. will be pointing to some valid sub JSON) or not (the "path" will not exist, will be invalid, etc.). What do you mean by "it could be referencing to something in JSON itself"?

risa2000 avatar Feb 03 '25 18:02 risa2000

I mean you have a JSON file that has the object /a/b/1 which is pointed to by the json_pointer at /c/d/0, but then you load that file into an existing json object at /x/y, so now /a/b/1 is /x/y/a/b/1 so the pointer which is now at /x/y/c/d/0 is invalid.

    json j = { "x": { "q": "" } };
    j["x"]["y"] = json::parse(str);
    auto out = j.dump();

Again, I think this is something that can likely be ignored for the common case.

gregmarr avatar Feb 03 '25 21:02 gregmarr

This issue has been marked as stale because it has been open for 90 days without activity. If this issue is still relevant, please add a comment or remove the "stale" label. Otherwise, it will be closed in 10 days. Thank you for helping us prioritize our work!

github-actions[bot] avatar May 09 '25 00:05 github-actions[bot]

This issue has been closed after being marked as stale for 10 days without any further activity. If this was done in error or the issue is still relevant, please feel free to reopen it or create a new issue. We appreciate your understanding and contributions.

github-actions[bot] avatar Oct 26 '25 00:10 github-actions[bot]