mirror of
https://github.com/nlohmann/json.git
synced 2026-10-03 14:34:52 +07:00
With JSON_BRACE_INIT_COPY_SEMANTICS enabled, single-element brace
initialization from a JSON value decided whether to copy the value or
build an object by inspecting the value's runtime shape: a two-element
array whose first element is a string, such as ["key", 42], was turned
into an object instead of being copied. This made the behavior depend
on the element's content, and it did not distinguish an existing value
of this shape from a nested braced pair written in the source, such as
the inner {"key", "value"} of {{"key", "value"}}.
json_ref now records whether it was constructed from a braced list
(true only for the std::initializer_list<json_ref> constructor used
for nested braced lists) or from a value. The initializer-list
constructor uses this to copy or move a single non-braced-list element
before deciding whether the list describes an object, so a JSON value
is always copied regardless of its shape, while a braced pair written
in the source still creates an object.
Fixes #5662.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
190 lines
5.8 KiB
C++
190 lines
5.8 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("single-element brace initialization copies a pair-shaped array value (#5662)")
|
|
{
|
|
// a JSON value that happens to be a 2-element array whose first
|
|
// element is a string must still be copied, not turned into an
|
|
// object; only a braced list written in the source, such as the
|
|
// inner {"key", "value"} of {{"key", "value"}}, describes an object
|
|
json const pair_shaped = json::array({"key", 42});
|
|
|
|
json const j1{pair_shaped};
|
|
CHECK(j1.is_array());
|
|
CHECK(j1 == pair_shaped);
|
|
|
|
json const j2 = {pair_shaped};
|
|
CHECK(j2.is_array());
|
|
CHECK(j2 == pair_shaped);
|
|
|
|
// the same holds for an rvalue of the same shape
|
|
json const j3{json::array({"key", 42})};
|
|
CHECK(j3.is_array());
|
|
CHECK(j3 == pair_shaped);
|
|
}
|
|
|
|
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}");
|
|
}
|
|
}
|
|
}
|
|
}
|