mirror of
https://github.com/nlohmann/json.git
synced 2026-08-29 04:17:32 +00:00
Relax the ArrayType and ObjectType requirements
Two requirements forced users of otherwise suitable containers to write a wrapper, and neither was load-bearing. array_t::capacity() was read in push_back(), emplace_back(), operator+=(), and operator[](size_type), but set_parent() only looks at the value under JSON_DIAGNOSTICS; without diagnostics it was computed and discarded. Read it through array_capacity(), which reports unknown_size() when diagnostics are off or when the array type has no capacity() at all, and treat an unknown capacity as "the elements may have moved" so the parent pointers are refreshed conservatively. std::deque now works as ArrayType, in both builds, and capacity() is no longer named at all in a default build. Since the capacity is now only meaningful for array insertions, it moves out of set_parent() into set_parent_after_array_insert(). basic_json::erase(iterator) assigned the object's erase() return value, which requires the container to return the following iterator. Abseil's hash maps return void to avoid computing a successor the caller may not need. Detect that and compute the successor before erasing; containers that return an iterator, including the vector-backed ordered_map where a precomputed successor would be wrong, keep the existing path. Together these leave an Abseil hash map needing only an alias that restores the template argument order, and no adapter at all for std::deque. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018hxZxz8svM54c6ATEvXp5E Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
co-authored by
Claude Opus 5
parent
96806af2dc
commit
5d93f35463
@@ -0,0 +1,89 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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>
|
||||
|
||||
#include <deque>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// std::deque has no capacity() member function, which the library only needs
|
||||
// to detect a reallocation for JSON_DIAGNOSTICS
|
||||
using deque_json = nlohmann::basic_json<std::map, std::deque>;
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("array type without capacity()")
|
||||
{
|
||||
SECTION("adding elements")
|
||||
{
|
||||
deque_json j = deque_json::array();
|
||||
j.push_back(1);
|
||||
j.push_back("two");
|
||||
j.emplace_back(3);
|
||||
j += 4;
|
||||
|
||||
CHECK(j.size() == 4);
|
||||
CHECK(j == deque_json({1, "two", 3, 4}));
|
||||
CHECK(j.back() == 4);
|
||||
CHECK(j.front() == 1);
|
||||
}
|
||||
|
||||
SECTION("accessing and modifying elements")
|
||||
{
|
||||
auto j = deque_json::parse(R"([1,2,3])");
|
||||
|
||||
CHECK(j[1] == 2);
|
||||
CHECK(j.at(2) == 3);
|
||||
|
||||
// growing through operator[] fills up with null values
|
||||
j[5] = 6;
|
||||
CHECK(j.size() == 6);
|
||||
CHECK(j[4].is_null());
|
||||
CHECK(j[5] == 6);
|
||||
|
||||
j.erase(0);
|
||||
CHECK(j == deque_json({2, 3, nullptr, nullptr, 6}));
|
||||
|
||||
auto it = j.erase(j.begin());
|
||||
CHECK(*it == 3);
|
||||
|
||||
j.insert(j.begin(), 1);
|
||||
CHECK(j.front() == 1);
|
||||
}
|
||||
|
||||
SECTION("serialization and deserialization")
|
||||
{
|
||||
const auto j = deque_json::parse(R"({"a":[1,[2,3]],"b":[]})");
|
||||
CHECK(j.dump() == R"({"a":[1,[2,3]],"b":[]})");
|
||||
CHECK(deque_json::parse(j.dump()) == j);
|
||||
CHECK(deque_json::from_cbor(deque_json::to_cbor(j)) == j);
|
||||
|
||||
// empty containers are flattened to null and cannot be restored
|
||||
const auto nested = deque_json::parse(R"({"a":[1,[2,3]]})");
|
||||
CHECK(nested.flatten().unflatten() == nested);
|
||||
}
|
||||
|
||||
SECTION("references stay valid while the array grows")
|
||||
{
|
||||
deque_json j = deque_json::array();
|
||||
j.push_back(1);
|
||||
auto& first = j[0];
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
j.push_back(i);
|
||||
}
|
||||
CHECK(&first == &j[0]);
|
||||
CHECK(first == 1);
|
||||
}
|
||||
}
|
||||
@@ -35,8 +35,76 @@ struct unordered_map_object
|
||||
|
||||
using unordered_json = nlohmann::basic_json<unordered_map_object>;
|
||||
|
||||
// An ObjectType whose erase(iterator) returns void rather than the following
|
||||
// iterator, as for instance Abseil's hash maps do
|
||||
template<class Key, class T, class Compare, class Allocator>
|
||||
struct void_erase_map : std::map<Key, T, Compare, Allocator>
|
||||
{
|
||||
using base_t = std::map<Key, T, Compare, Allocator>;
|
||||
using base_t::base_t;
|
||||
using iterator = typename base_t::iterator;
|
||||
using base_t::erase;
|
||||
|
||||
void erase(iterator pos)
|
||||
{
|
||||
base_t::erase(pos);
|
||||
}
|
||||
};
|
||||
|
||||
using void_erase_json = nlohmann::basic_json<void_erase_map>;
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("object type whose erase() returns void")
|
||||
{
|
||||
SECTION("erasing every element through the returned iterator")
|
||||
{
|
||||
void_erase_json j;
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
j["k" + std::to_string(i)] = i;
|
||||
}
|
||||
|
||||
std::size_t erased = 0;
|
||||
for (auto it = j.begin(); it != j.end(); ++erased)
|
||||
{
|
||||
it = j.erase(it);
|
||||
}
|
||||
CHECK(erased == 8);
|
||||
CHECK(j.empty());
|
||||
}
|
||||
|
||||
SECTION("erasing in the middle returns the following element")
|
||||
{
|
||||
void_erase_json j;
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
j["k" + std::to_string(i)] = i;
|
||||
}
|
||||
|
||||
auto it = j.begin();
|
||||
++it;
|
||||
const auto after = j.erase(it);
|
||||
CHECK(j.size() == 3);
|
||||
CHECK(after.key() == "k2");
|
||||
CHECK(after.value() == 2);
|
||||
CHECK(!j.contains("k1"));
|
||||
}
|
||||
|
||||
SECTION("the other erase overloads are unaffected")
|
||||
{
|
||||
void_erase_json j;
|
||||
j["a"] = 1;
|
||||
j["b"] = 2;
|
||||
j["c"] = 3;
|
||||
|
||||
CHECK(j.erase("a") == 1);
|
||||
CHECK(j.erase("nope") == 0);
|
||||
j.erase(j.begin(), j.end());
|
||||
CHECK(j.empty());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("object type without key_compare")
|
||||
{
|
||||
SECTION("object_comparator_t falls back to default_object_comparator_t")
|
||||
|
||||
Reference in New Issue
Block a user