Cannot use std::format on nholman::json objects
Description
I've opened a json file and parsed it with this library and I wrote this:
std::string connection_config = std::format("host={} port={} dbname={} user={} password='{}'", db_config.at("host"),
db_config.at("port"), db_config.at("dbname"), db_config.at("user"),
db_config.at("password"));
However I get this error:
In template: call to deleted constructor of 'typename format_context::formatter_type<basic_json<std::map, std::vector, string, bool, long, unsigned long, double, std::allocator, nlohmann::adl_serializer, vector<unsigned char, allocator<unsigned char>>>>' (aka 'formatter<nlohmann::basic_json<std::map, std::vector, std::string, bool, long, unsigned long, double, std::allocator, nlohmann::adl_serializer, std::vector<unsigned char, std::allocator<unsigned char>>>, char>')
Note of interest: this only happens with std::format, fmt::format is fine
Reproduction steps
Open a file with std::ifstream Pass the file object to nlohmann::json Parse the json file Try to format it using std::format
Expected vs. actual results
Expected result: The json array being formatted properly Actual result: error
Minimal code example
auto db_config_file = std::ifstream {"~/.reacatio/database.json"};
json db_config = json::parse(db_config_file);
db_config_file.close();
std::string connection_config = std::format("host={} port={} dbname={} user={} password='{}'", db_config.at("host"),
db_config.at("port"), db_config.at("dbname"), db_config.at("user"),
db_config.at("password"));
Error messages
In template: call to deleted constructor of 'typename format_context::formatter_type<basic_json<std::map, std::vector, string, bool, long, unsigned long, double, std::allocator, nlohmann::adl_serializer, vector<unsigned char, allocator<unsigned char>>>>' (aka 'formatter<nlohmann::basic_json<std::map, std::vector, std::string, bool, long, unsigned long, double, std::allocator, nlohmann::adl_serializer, std::vector<unsigned char, std::allocator<unsigned char>>>, char>')
Compiler and operating system
clang 17, linux
Library version
3.11.2
Validation
- [ ] The bug also occurs if the latest version from the
developbranch is used. - [ ] I can successfully compile and run the unit tests.
User-defined types must opt in to being formatted by std::format by specializing std::formatter. https://en.cppreference.com/w/cpp/utility/format/formatter
fmt::format is probably picking up implicit conversions, which means that it can get them very very wrong. What happens if you have a json file with numbers instead of strings? Does fmt::format display them properly? I would be very surprised, unless someone added support for this specific library.
I assume that std::format explicitly excludes implicit conversions to types that std::formatter is enabled for, which is much safer.
For some reason now fmt is also now not working (I'm pretty sure it did before, maybe an earlier version)
In file included from test.cpp:4:
In file included from /usr/include/fmt/format.h:49:
/usr/include/fmt/core.h:2548:45: error: implicit instantiation of undefined template 'fmt::detail::type_is_unformattable_for<nlohmann::basic_json<>, char>'
type_is_unformattable_for<T, char_type> _;
^
/usr/include/fmt/core.h:2611:23: note: in instantiation of function template specialization 'fmt::detail::parse_format_specs<nlohmann::basic_json<>, fmt::detail::compile_parse_context<char>>' requested here
parse_funcs_{&parse_format_specs<Args, parse_context_type>...} {}
^
/usr/include/fmt/core.h:2740:47: note: in instantiation of member function 'fmt::detail::format_string_checker<char, nlohmann::basic_json<>, nlohmann::basic_json<>>::format_string_checker' requested here
detail::parse_format_string<true>(str_, checker(s));
^
test.cpp:9:27: note: in instantiation of function template specialization 'fmt::basic_format_string<char, nlohmann::basic_json<> &, nlohmann::basic_json<> &>::basic_format_string<char[6], 0>' requested here
std::cout << fmt::format("{} {}", data.at("host"), data.at("port"));
^
/usr/include/fmt/core.h:1554:45: note: template is declared here
template <typename T, typename Char> struct type_is_unformattable_for;
^
test.cpp:9:27: error: call to consteval function 'fmt::basic_format_string<char, nlohmann::basic_json<> &, nlohmann::basic_json<> &>::basic_format_string<char[6], 0>' is not a constant expression
std::cout << fmt::format("{} {}", data.at("host"), data.at("port"));
^
In file included from test.cpp:4:
In file included from /usr/include/fmt/format.h:49:
/usr/include/fmt/core.h:1576:63: error: implicit instantiation of undefined template 'fmt::detail::type_is_unformattable_for<nlohmann::basic_json<>, char>'
type_is_unformattable_for<T, typename Context::char_type> _;
^
/usr/include/fmt/core.h:1808:23: note: in instantiation of function template specialization 'fmt::detail::make_arg<true, fmt::basic_format_context<fmt::appender, char>, nlohmann::basic_json<>, 0>' requested here
data_{detail::make_arg<is_packed, Context>(args)...} {
^
/usr/include/fmt/core.h:1826:10: note: in instantiation of function template specialization 'fmt::format_arg_store<fmt::basic_format_context<fmt::appender, char>, nlohmann::basic_json<>, nlohmann::basic_json<>>::format_arg_store<nlohmann::basic_json<>, nlohmann::basic_json<>>' requested here
return {args...};
^
/usr/include/fmt/core.h:2788:28: note: in instantiation of function template specialization 'fmt::make_format_args<fmt::basic_format_context<fmt::appender, char>, nlohmann::basic_json<>, nlohmann::basic_json<>>' requested here
return vformat(fmt, fmt::make_format_args(args...));
^
test.cpp:9:20: note: in instantiation of function template specialization 'fmt::format<nlohmann::basic_json<> &, nlohmann::basic_json<> &>' requested here
std::cout << fmt::format("{} {}", data.at("host"), data.at("port"));
^
/usr/include/fmt/core.h:1554:45: note: template is declared here
template <typename T, typename Char> struct type_is_unformattable_for;
^
/usr/include/fmt/core.h:1579:3: error: static assertion failed due to requirement 'formattable': Cannot format an argument. To make type T formattable provide a formatter<T> specialization: https://fmt.dev/latest/api.html#udt
static_assert(
^
4 errors generated.
Anyhow then ig nlohmann::json would need a std::formatter?
Anyhow then ig nlohmann::json would need a std::formatter?
Yes. I assume, but can't say for sure, that a PR would be welcome as long as it was sufficient protected such that it didn't affect the ability to build as C++11. There are other features that are conditionally available based on the C++ standard in use. @nlohmann would have the final say on that.
As far as I'm aware there are micros to check C++ version and not define a function if it's not matching, so maybe this could be added only if c++20 flag is added.
Yes, this library has JSON_HAS_CPP_20 for that.
I ran into this when updating dependencies.
For fmt you can observe this break in this godbolt example: https://godbolt.org/z/jx7fTen7Y
with fmt 9.1.0 it works, with 10.0.0 it still compiles but throws and with 10.1.1 it finally doesn't compile anymore.
I guess something like this would fix the issue for std::format
#if defined(JSON_HAS_CPP_20) && __has_include(<format>)
#include <format>
template <>
struct std::formatter<json> {
constexpr auto parse(std::format_parse_context& ctx) {
return ctx.begin();
}
auto format(const json& j, std::format_context& ctx) const {
return std::format_to(ctx.out(), "{}", to_string(j));
}
};
#endif
but this doesn't fix it for fmt :(.
https://github.com/fmtlib/fmt/issues/3648
To implement a formatter which would work from fmt 10.0.0 onwards it would suffice to implement format_as as follows:
namespace nlohmann {
auto format_as(const json& j) { return j.dump(); }
}
Godbolt: https://godbolt.org/z/vEEad83h3