mirror of
https://github.com/nlohmann/json.git
synced 2026-09-30 22:15:19 +00:00
* Add tests for uncovered code paths Cover code the test suite did not reach, found from the Coveralls report of develop and a local coverage run of HEAD: - dump() of every kind of value below the bound of the recursive descent (pretty-printed objects, binary values, discarded values, scalars), and flushes of the escape and write buffers mid-string and mid-binary - the iterative comparison: objects with different keys, containers that are a prefix of each other, and elements that cannot be ordered, each both at the top level and below the nesting bound - SAX handlers that stop at any event, including the end of a nested container, in the BSON, CBOR, MessagePack, UBJSON and BJData readers - from_bson/cbor/msgpack/ubjson/bjdata returning a discarded value through the iterator and pointer overloads - JSON Patch, diff, merge_patch and update(..., true) on ordered_json - smaller gaps: get_allocator(), to_ubjson/to_bjdata into a string, value() with an unresolvable JSON pointer, integer/float comparison below the integer range and with negative fractions, conversion to a custom binary type, std::formatter::parse on a spec without '}', unescape() of a lone '~', and the callback parser's start_array() Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Cover more paths that were thought unreachable - parse_float_fast() declining malformed or inexact input, called directly since the lexer only passes well-formed numbers to it - a UTF-16 high surrogate followed by a unit above the low surrogates - self-assignment of a const_iterator - a truncated CBOR string read through non-contiguous iterators - serializing a long double under the de_DE locale, which undoes the locale's decimal point and thousands separator - values read from a binary format carrying no diagnostic positions, with and without a parser callback Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix the CI failures of the new coverage tests - declare the self-assignment reference const (misc-const-correctness) - expect the (/path) prefix that JSON_DIAGNOSTICS adds to the messages of the failing ordered_json patch operations Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Expect the byte range JSON_DIAGNOSTIC_POSITIONS adds to the patch errors Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Build the expected dump of the nested-object test with += clang-tidy (performance-inefficient-string-concatenation) reported the chain of operator+ calls that assembled the expected indented output. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Compare the BJData and UBJSON test outputs byte by byte Building a std::string from the byte vector converts each byte implicitly, which -fsanitize=integer reports for bytes of 0x80 and above (ci_test_clang_sanitizer). Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
150 lines
6.4 KiB
C++
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 json = nlohmann::json;
|
|
using ordered_json = 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
|