add std::format and fmt support

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann
2026-07-01 14:27:56 +02:00
parent 730b57775d
commit 699a239067
18 changed files with 513 additions and 1 deletions
+1 -1
View File
@@ -55,5 +55,5 @@ int main()
// use every result so the references cannot be optimized away
return (a == 1 && last == 3 && b == 2 && lit.size() == 3
&& m.size() == 1 && !dumped.empty() && !os.str().empty())
? 0 : 1;
? 0 : 1;
}
+86
View File
@@ -0,0 +1,86 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++ (supporting code)
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#include "doctest_compatibility.h"
#include <nlohmann/json.hpp>
using json = nlohmann::json;
using ordered_json = nlohmann::ordered_json;
namespace
{
// call format_as() the same way fmt's ADL-based dispatch would: unqualified,
// found only via argument-dependent lookup on the (namespace-qualified) argument type.
template<typename BasicJsonType>
std::string call_format_as_via_adl(const BasicJsonType& j)
{
return format_as(j);
}
} // namespace
TEST_CASE("format_as<nlohmann::json>")
{
// null
CHECK(format_as(json(nullptr)) == json(nullptr).dump());
// boolean
CHECK(format_as(json(true)) == json(true).dump());
CHECK(format_as(json(false)) == json(false).dump());
// string (including a value that needs escaping/UTF-8 handling)
CHECK(format_as(json("")) == json("").dump());
CHECK(format_as(json("foo")) == json("foo").dump());
CHECK(format_as(json("foo\"bar\\baz\nqux")) == json("foo\"bar\\baz\nqux").dump());
CHECK(format_as(json("\xc3\xa4\xc3\xb6\xc3\xbc")) == json("\xc3\xa4\xc3\xb6\xc3\xbc").dump());
// number
CHECK(format_as(json(0)) == json(0).dump());
CHECK(format_as(json(-1)) == json(-1).dump());
CHECK(format_as(json(static_cast<unsigned>(42))) == json(static_cast<unsigned>(42)).dump());
CHECK(format_as(json(42.23)) == json(42.23).dump());
// array
CHECK(format_as(json::array()) == json::array().dump());
CHECK(format_as(json::array({1, 2, 3})) == json::array({1, 2, 3}).dump());
// object
CHECK(format_as(json::object()) == json::object().dump());
CHECK(format_as(json::object({{"foo", "bar"}})) == json::object({{"foo", "bar"}}).dump());
// nested/mixed structure
const json j_nested = {{"foo", 1}, {"bar", {1, 2, 3}}, {"baz", {{"a", nullptr}, {"b", false}}}};
CHECK(format_as(j_nested) == j_nested.dump());
// binary
CHECK(format_as(json::binary({})) == json::binary({}).dump());
CHECK(format_as(json::binary({1, 2, 3}, 42)) == json::binary({1, 2, 3}, 42).dump());
// discarded
CHECK(format_as(json(json::value_t::discarded)) == json(json::value_t::discarded).dump());
}
TEST_CASE("format_as<nlohmann::ordered_json>")
{
// spot-check a non-default basic_json instantiation, since
// NLOHMANN_BASIC_JSON_TPL_DECLARATION must deduce correctly there too
CHECK(format_as(ordered_json(nullptr)) == ordered_json(nullptr).dump());
CHECK(format_as(ordered_json::object({{"foo", "bar"}, {"baz", 42}})) ==
ordered_json::object({{"foo", "bar"}, {"baz", 42}}).dump());
CHECK(format_as(ordered_json::array({1, 2, 3})) == ordered_json::array({1, 2, 3}).dump());
}
TEST_CASE("format_as<nlohmann::json> is found via ADL")
{
// this is how fmt actually calls it: unqualified, relying on argument-dependent
// lookup finding nlohmann::format_as via the argument's namespace
const json j = {{"foo", 1}, {"bar", {1, 2, 3}}};
CHECK(call_format_as_via_adl(j) == j.dump());
const ordered_json oj = {{"foo", 1}, {"bar", {1, 2, 3}}};
CHECK(call_format_as_via_adl(oj) == oj.dump());
}
+69
View File
@@ -0,0 +1,69 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++ (supporting code)
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
// cmake/test.cmake selects the C++ standard versions with which to build a
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
// When using macros that are only defined for particular versions of the standard
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
// version macro in a comment close by, like this:
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
#include "doctest_compatibility.h"
#include <nlohmann/json.hpp>
using json = nlohmann::json;
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
#if JSON_HAS_STD_FORMAT
#include <iterator>
#include <string>
TEST_CASE("std::formatter<nlohmann::json>")
{
SECTION("compact formatting matches dump()")
{
CHECK(std::format("{}", json(nullptr)) == json(nullptr).dump());
CHECK(std::format("{}", json(true)) == json(true).dump());
CHECK(std::format("{}", json(42)) == json(42).dump());
CHECK(std::format("{}", json(42.23)) == json(42.23).dump());
CHECK(std::format("{}", json("foo")) == json("foo").dump());
CHECK(std::format("{}", json::array({1, 2, 3})) == json::array({1, 2, 3}).dump());
const json j = {{"foo", 1}, {"bar", {1, 2, 3}}};
CHECK(std::format("{}", j) == j.dump());
}
SECTION("'#' triggers pretty-printing with an indent of 4, like dump(4)")
{
const json j = {{"foo", 1}, {"bar", {1, 2, 3}}};
CHECK(std::format("{:#}", j) == j.dump(4));
CHECK(std::format("{:#}", json::array()) == json::array().dump(4));
}
SECTION("format args other than an empty spec or '#' are rejected")
{
// std::vformat parses the format string at runtime (unlike std::format, whose
// format_string type is checked at compile time), so it lets us verify that an
// invalid spec throws std::format_error without needing a compile-time-illegal
// format string.
const json j = 42;
CHECK_THROWS_AS(std::vformat("{:x}", std::make_format_args(j)), std::format_error);
CHECK_THROWS_AS(std::vformat("{:10}", std::make_format_args(j)), std::format_error);
}
SECTION("std::format_to writes through an arbitrary output iterator")
{
const json j = {{"foo", 1}, {"bar", {1, 2, 3}}};
std::string out;
std::format_to(std::back_inserter(out), "{}", j);
CHECK(out == j.dump());
}
}
#endif