Files
json/tests/src/unit-std-format.cpp
T
Niels Lohmann 330fbead59 Document clang-tidy exclusions, drop unneeded ones, and clean up doc examples (#5909)
* Document clang-tidy exclusions and drop unneeded ones

Re-enable every excluded check in a run of clang-tidy 22 over all tests
and remove the seven that never fire (cert-err58-cpp,
clang-analyzer-webkit.NoUncountedMemberChecker,
cppcoreguidelines-virtual-class-destructor, google-readability-function-size,
hicpp-function-size, hicpp-no-assembler, readability-function-size).

Document the reason for every remaining exclusion at the top of the file.

Exclude in advance the checks that only exist in clang-tidy 23 and are
noise here (bugprone-signed-bitwise, *-explicit-constructor,
readability-trailing-comma, modernize-use-string-view), plus
modernize-use-integer-sign-comparison, which needs C++20 std::cmp_less
and flags deliberate casts.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix clang-tidy findings in documentation examples and replace redundant aliases

Apply clang-tidy fixes to the documentation examples:
- performance-avoid-endl: std::endl -> '\n'
- readability-qualified-auto: auto -> auto* / const auto*
- performance-for-range-copy: copy -> const reference
- google-readability-casting: C-style cast -> static_cast

Replace redundant aliases (readability-redundant-qualified-alias) such as
"using json = nlohmann::json;" with "using nlohmann::json;" in the
documentation, README, examples, tests, and benchmarks. Aliases at class
scope and aliases of other types (with_base_class_t etc.) are kept.

No changes to the library or its public API. All changed examples were
compiled and run; their output matches the stored .output files (except
max_size and meta, which depend on the platform and compiler).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Format doc examples with the pinned astyle

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-25 12:07:51 +01:00

150 lines
6.4 KiB
C++

// __ _____ _____ _____
// __| | __| | | | 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 nlohmann::json;
using nlohmann::ordered_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("a width sets the indent, like dump(width), with or without '#'")
{
const json j = {{"foo", 1}, {"bar", {1, 2, 3}}};
CHECK(std::format("{:2}", j) == j.dump(2));
CHECK(std::format("{:#2}", j) == j.dump(2));
CHECK(std::format("{:8}", j) == j.dump(8));
// multi-digit widths must accumulate every digit, not just the first
CHECK(std::format("{:12}", j) == j.dump(12));
CHECK(std::format("{:#12}", j) == j.dump(12));
CHECK(std::format("{:10}", j) == j.dump(10));
}
SECTION("bare alignment with no fill character defaults to a space indent character")
{
const json j = {{"foo", 1}, {"bar", {1, 2, 3}}};
// without a preceding fill character, the alignment character itself must not
// be mistaken for the indent character -- the default space is kept
CHECK(std::format("{:<}", j) == j.dump());
CHECK(std::format("{:>}", j) == j.dump());
CHECK(std::format("{:^}", j) == j.dump());
CHECK(std::format("{:<3}", j) == j.dump(3, ' '));
CHECK(std::format("{:>3}", j) == j.dump(3, ' '));
CHECK(std::format("{:^3}", j) == j.dump(3, ' '));
}
SECTION("fill-and-align sets the indent character, like dump(indent, indent_char)")
{
const json j = {{"foo", 1}, {"bar", {1, 2, 3}}};
CHECK(std::format("{:.>#}", j) == j.dump(4, '.'));
CHECK(std::format("{:.>#3}", j) == j.dump(3, '.'));
CHECK(std::format("{:.>3}", j) == j.dump(3, '.'));
// the alignment direction itself ('<', '>', '^') has no separate meaning for
// JSON values -- only the fill character before it is used as the indent character
CHECK(std::format("{:.<3}", j) == j.dump(3, '.'));
CHECK(std::format("{:.^3}", j) == j.dump(3, '.'));
}
SECTION("format args with no meaning for JSON values 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("{:+}", std::make_format_args(j)), std::format_error); // sign
CHECK_THROWS_AS(std::vformat("{:-}", std::make_format_args(j)), std::format_error); // sign
CHECK_THROWS_AS(std::vformat("{: }", std::make_format_args(j)), std::format_error); // sign
CHECK_THROWS_AS(std::vformat("{:04}", std::make_format_args(j)), std::format_error); // '0' flag
CHECK_THROWS_AS(std::vformat("{:.2}", std::make_format_args(j)), std::format_error); // precision
CHECK_THROWS_AS(std::vformat("{:L}", std::make_format_args(j)), std::format_error); // locale
const int dynamic_width = 4;
CHECK_THROWS_AS(std::vformat("{:{}}", std::make_format_args(j, dynamic_width)), std::format_error); // dynamic width
}
SECTION("a format spec may run to the end of the parse context")
{
// std::format always hands parse() a range that still holds the closing
// '}', but a parse context may also end right after the spec
const auto parse = [](const char* spec)
{
std::format_parse_context ctx(spec);
std::formatter<json> f;
CHECK(f.parse(ctx) == ctx.end());
return f;
};
CHECK(parse("").indent == -1);
CHECK(parse(">").indent == -1);
CHECK(parse("#").indent == 4);
CHECK(parse("3").indent == 3);
CHECK(parse("#12").indent == 12);
const auto f = parse(".>");
CHECK(f.indent == -1);
CHECK(f.indent_char == '.');
}
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());
}
}
TEST_CASE("std::formatter<nlohmann::ordered_json>")
{
// spot-check a non-default basic_json instantiation, since the formatter
// is written against the generic NLOHMANN_BASIC_JSON_TPL_DECLARATION
// template and must actually instantiate (and behave correctly) for
// template arguments other than nlohmann::json
const ordered_json j = {{"foo", 1}, {"bar", {1, 2, 3}}};
CHECK(std::format("{}", j) == j.dump());
CHECK(std::format("{:#}", j) == j.dump(4));
CHECK(std::format("{:2}", j) == j.dump(2));
}
#endif