mirror of
https://github.com/nlohmann/json.git
synced 2026-09-27 22:05:54 +01:00
* Test JSON_BRACE_INIT_COPY_SEMANTICS for real, and fix one-element tuples under it
The opt-in JSON_BRACE_INIT_COPY_SEMANTICS was never exercised by CI:
- Its only test, in unit-regression3.cpp, was guarded by
`#if defined(JSON_BRACE_INIT_COPY_SEMANTICS)` after the #include. The
header #undefs the macro unconditionally in macro_unscope.hpp, so the
guard was always false and the test compiled to nothing, whatever -D
flag was passed.
- The ci_test_brace_init_copy_semantics target that passes the flag was
not named by any workflow.
Move the test into its own translation unit that defines the macro before
including the header, as unit-diagnostics.cpp does for JSON_DIAGNOSTICS.
It now runs in every CI job and for every standard. Remove the unused
target: it ran the whole suite with the macro, and that suite deliberately
relies on default brace-init semantics in about 90 places
(e.g. `json({1})` meaning `[1]`), so it could never pass.
Running the whole suite with the macro did find one library bug:
to_json for std::tuple builds `j = { std::get<Idx>(t)... }`, so with copy
semantics a one-element tuple became its element. `json(std::tuple<int>{5})`
was `5` instead of `[5]`, and `get<std::tuple<int>>()` threw type_error.302
on the result. Under the macro, a one-element tuple now builds exactly what
the default deduction builds. Without the macro nothing changes.
The new tests also pin that the library's other conversions produce the
same values with and without the macro. The macro page now says that the
macro affects every single-element list (`json j = {1}` is `1`), and that
all translation units must agree on it, since it has no ABI tag.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Make JSON_BRACE_INIT_COPY_SEMANTICS part of the ABI tag
The macro changes the body of the initializer-list constructor and adds a
to_json_tuple_impl overload, both with the same mangled names in either
mode, so mixing translation units silently picked one definition. Encode
it in the inline namespace as `_bics`, as JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
does with `_ldvcmp`. The macro is new in the unreleased 3.13.0, so no
existing namespace name changes.
- Move the macro's default into abi_macros.hpp so json_fwd.hpp computes
the same namespace, and keep it defined under JSON_TEST_KEEP_MACROS.
- Check the tag in the ABI config tests and in the unit test.
- List `_bics` (and the missing `_dp`) in the namespace docs and in the
natvis generator; regenerate nlohmann_json.natvis.
- Replace the "define it consistently" warning with an ABI note.
Suggested by @gregmarr in the review of #5544.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Fix the cppcheck, clang-tidy and legacy-comparison CI failures
- to_json_tuple_impl() moved the element in both branches of a ternary;
only one runs, but cppcheck reported accessMoved. Use if/else.
- The ABI tag test looked for "json_abi_bics", which misses when another
tag comes first, as in json_abi_ldvcmp_bics; look for "_bics".
- readability-qualified-auto in the items() test.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
---------
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
168 lines
5.0 KiB
C++
168 lines
5.0 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
|
|
|
|
#include "doctest_compatibility.h"
|
|
|
|
// This file tests the opt-in JSON_BRACE_INIT_COPY_SEMANTICS, so it defines the
|
|
// macro itself rather than relying on a -D flag, and runs in every build.
|
|
#ifdef JSON_BRACE_INIT_COPY_SEMANTICS
|
|
#undef JSON_BRACE_INIT_COPY_SEMANTICS
|
|
#endif
|
|
|
|
#define JSON_BRACE_INIT_COPY_SEMANTICS 1
|
|
|
|
#include <nlohmann/json.hpp>
|
|
using nlohmann::json;
|
|
|
|
#include <array>
|
|
#include <list>
|
|
#include <map>
|
|
#include <string>
|
|
#include <tuple>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#define STRINGIZE_EX(x) #x
|
|
#define STRINGIZE(x) STRINGIZE_EX(x)
|
|
|
|
TEST_CASE("JSON_BRACE_INIT_COPY_SEMANTICS")
|
|
{
|
|
SECTION("the macro is part of the ABI tag")
|
|
{
|
|
const std::string ns = STRINGIZE(NLOHMANN_JSON_NAMESPACE);
|
|
// other tags may come before it, e.g. json_abi_ldvcmp_bics
|
|
CHECK(ns.find("_bics") != std::string::npos);
|
|
}
|
|
|
|
SECTION("single-element brace initialization copies the element (#5074)")
|
|
{
|
|
json const j_obj = {{"key", "value"}, {"num", 42}};
|
|
json const j_arr = {1, 2, 3};
|
|
|
|
// object: brace init copies instead of wrapping
|
|
json const j1{j_obj};
|
|
CHECK(j1.is_object());
|
|
CHECK(j1 == j_obj);
|
|
|
|
// array: brace init copies instead of wrapping
|
|
json const j2{j_arr};
|
|
CHECK(j2.is_array());
|
|
CHECK(j2.size() == 3);
|
|
CHECK(j2 == j_arr);
|
|
|
|
// this applies to any single element, not only to JSON values
|
|
json const j3{true};
|
|
CHECK(j3.is_boolean());
|
|
|
|
json const j4{42};
|
|
CHECK(j4.is_number_integer());
|
|
|
|
json const j5 = {1};
|
|
CHECK(j5 == 1);
|
|
|
|
json const j6 = {"text"};
|
|
CHECK(j6 == "text");
|
|
|
|
json const j7 = {{1, 2}};
|
|
CHECK(j7 == json::array({1, 2}));
|
|
}
|
|
|
|
SECTION("what the macro does not change")
|
|
{
|
|
// lists with more than one element are unaffected
|
|
json const j1 = {1, 2};
|
|
CHECK(j1.is_array());
|
|
CHECK(j1.size() == 2);
|
|
|
|
// a single [string, value] pair still describes an object
|
|
json const j2 = {{"key", "value"}};
|
|
CHECK(j2.is_object());
|
|
CHECK(j2["key"] == "value");
|
|
|
|
// json::array() always creates an array
|
|
json const j3 = json::array({1});
|
|
CHECK(j3.is_array());
|
|
CHECK(j3.size() == 1);
|
|
CHECK(j3[0] == 1);
|
|
|
|
json const j_obj = {{"key", "value"}};
|
|
json const j4 = json::array({j_obj});
|
|
CHECK(j4.is_array());
|
|
CHECK(j4.size() == 1);
|
|
CHECK(j4[0] == j_obj);
|
|
}
|
|
|
|
SECTION("conversions build the same values as without the macro")
|
|
{
|
|
SECTION("one-element std::tuple")
|
|
{
|
|
json const j1 = std::tuple<int> {5};
|
|
CHECK(j1.dump() == "[5]");
|
|
CHECK(std::get<0>(j1.get<std::tuple<int>>()) == 5);
|
|
|
|
json const j2 = std::tuple<std::string> {"text"};
|
|
CHECK(j2.dump() == "[\"text\"]");
|
|
CHECK(std::get<0>(j2.get<std::tuple<std::string>>()) == "text");
|
|
|
|
json const j3 = std::tuple<json> {json::array({1, 2})};
|
|
CHECK(j3.dump() == "[[1,2]]");
|
|
|
|
// as without the macro, a [string, value] pair becomes an object
|
|
// member (see the known limitation documented for std::pair)
|
|
json const j4 = std::tuple<std::pair<std::string, int>> {{"a", 1}};
|
|
CHECK(j4.dump() == "{\"a\":1}");
|
|
}
|
|
|
|
SECTION("tuples with more elements")
|
|
{
|
|
json const j1 = std::tuple<int, std::string> {1, "a"};
|
|
CHECK(j1.dump() == "[1,\"a\"]");
|
|
|
|
json const j2 = std::tuple<> {};
|
|
CHECK(j2.dump() == "[]");
|
|
}
|
|
|
|
SECTION("one-element containers")
|
|
{
|
|
json const j1 = std::vector<int> {1};
|
|
CHECK(j1.dump() == "[1]");
|
|
CHECK(j1.get<std::vector<int>>() == std::vector<int> {1});
|
|
|
|
std::array<int, 1> const arr = {{1}};
|
|
json const j2 = arr;
|
|
CHECK(j2.dump() == "[1]");
|
|
|
|
json const j3 = std::list<std::string> {"a"};
|
|
CHECK(j3.dump() == "[\"a\"]");
|
|
|
|
json const j4 = std::map<std::string, int> {{"a", 1}};
|
|
CHECK(j4.dump() == "{\"a\":1}");
|
|
|
|
json const j5 = std::map<int, int> {{1, 2}};
|
|
CHECK(j5.dump() == "[[1,2]]");
|
|
}
|
|
|
|
SECTION("std::pair")
|
|
{
|
|
json const j = std::pair<int, int> {1, 2};
|
|
CHECK(j.dump() == "[1,2]");
|
|
CHECK((j.get<std::pair<int, int>>() == std::pair<int, int> {1, 2}));
|
|
}
|
|
|
|
SECTION("items()")
|
|
{
|
|
json j_obj = {{"key", 1}};
|
|
for (const auto& el : j_obj.items())
|
|
{
|
|
json const j = el;
|
|
CHECK(j.dump() == "{\"key\":1}");
|
|
}
|
|
}
|
|
}
|
|
}
|