mirror of
https://github.com/nlohmann/json.git
synced 2026-09-20 23:18:30 +00:00
Merge remote-tracking branch 'origin/develop' into HEAD
Resolves a conflict in tests/src/unit-diagnostics.cpp: develop's swap(array_t&)/swap(object_t&) parent-pointer regression test and this branch's copy-descent regression test both added a new SECTION at the same place; kept both as siblings. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
@@ -11,8 +11,10 @@
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
/* forward declarations */
|
||||
class alt_string;
|
||||
@@ -22,6 +24,10 @@ void int_to_string(alt_string& target, std::size_t value); // NOLINT(misc-use-in
|
||||
/*
|
||||
* This is virtually a string class.
|
||||
* It covers std::string under the hood.
|
||||
*
|
||||
* It deliberately does not provide c_str(), back(), find(str, pos), replace(),
|
||||
* or substr(): the library must not rely on them. Do not add members here
|
||||
* without checking that the library actually needs them.
|
||||
*/
|
||||
class alt_string
|
||||
{
|
||||
@@ -106,11 +112,6 @@ class alt_string
|
||||
return str_impl < op.str_impl;
|
||||
}
|
||||
|
||||
const char* c_str() const
|
||||
{
|
||||
return str_impl.c_str();
|
||||
}
|
||||
|
||||
char& operator[](std::size_t index)
|
||||
{
|
||||
return str_impl[index];
|
||||
@@ -121,16 +122,6 @@ class alt_string
|
||||
return str_impl[index];
|
||||
}
|
||||
|
||||
char& back()
|
||||
{
|
||||
return str_impl.back();
|
||||
}
|
||||
|
||||
const char& back() const
|
||||
{
|
||||
return str_impl.back();
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
str_impl.clear();
|
||||
@@ -146,28 +137,11 @@ class alt_string
|
||||
return str_impl.empty();
|
||||
}
|
||||
|
||||
std::size_t find(const alt_string& str, std::size_t pos = 0) const
|
||||
{
|
||||
return str_impl.find(str.str_impl, pos);
|
||||
}
|
||||
|
||||
std::size_t find_first_of(char c, std::size_t pos = 0) const
|
||||
{
|
||||
return str_impl.find_first_of(c, pos);
|
||||
}
|
||||
|
||||
alt_string substr(std::size_t pos = 0, std::size_t count = npos) const
|
||||
{
|
||||
const std::string s = str_impl.substr(pos, count);
|
||||
return {s.data(), s.size()};
|
||||
}
|
||||
|
||||
alt_string& replace(std::size_t pos, std::size_t count, const alt_string& str)
|
||||
{
|
||||
str_impl.replace(pos, count, str.str_impl);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void reserve( std::size_t new_cap = 0 )
|
||||
{
|
||||
str_impl.reserve(new_cap);
|
||||
@@ -202,6 +176,31 @@ bool operator<(const char* op1, const alt_string& op2) noexcept
|
||||
|
||||
TEST_CASE("alternative string type")
|
||||
{
|
||||
SECTION("binary formats")
|
||||
{
|
||||
alt_json doc;
|
||||
doc["pi"] = 3.141;
|
||||
doc["happy"] = true;
|
||||
doc["list"] = {1, 2, 3};
|
||||
|
||||
CHECK(alt_json::from_cbor(alt_json::to_cbor(doc)) == doc);
|
||||
CHECK(alt_json::from_msgpack(alt_json::to_msgpack(doc)) == doc);
|
||||
// BSON is not covered: it additionally needs string_t::find(value_type),
|
||||
// which alt_string does not provide
|
||||
CHECK(alt_json::from_ubjson(alt_json::to_ubjson(doc)) == doc);
|
||||
|
||||
// a UBJSON high-precision number is parsed into a std::string that the
|
||||
// reader has to hand to the SAX interface as an alt_string
|
||||
const std::vector<uint8_t> high_precision =
|
||||
{
|
||||
'H', 'i', 0x16, '3', '.', '1', '4', '1', '5', '9', '2', '6', '5', '3',
|
||||
'5', '8', '9', '7', '9', '3', '2', '3', '8', '4', '6'
|
||||
};
|
||||
const auto number = alt_json::from_ubjson(high_precision);
|
||||
CHECK(number.is_number_float());
|
||||
CHECK(number.get<double>() == doctest::Approx(3.14159265358979323846));
|
||||
}
|
||||
|
||||
SECTION("dump")
|
||||
{
|
||||
{
|
||||
@@ -332,6 +331,15 @@ TEST_CASE("alternative string type")
|
||||
|
||||
CHECK(j.at(alt_json::json_pointer("/foo/0")) == j["foo"][0]);
|
||||
CHECK(j.at(alt_json::json_pointer("/foo/1")) == j["foo"][1]);
|
||||
|
||||
// RFC 6901 escaping works without string_t::find(str, pos), replace(),
|
||||
// and substr()
|
||||
auto j2 = alt_json::parse(R"({"a/b": 1, "m~n": 2, "~/~~//": 3})");
|
||||
CHECK(j2.at(alt_json::json_pointer("/a~1b")) == 1);
|
||||
CHECK(j2.at(alt_json::json_pointer("/m~0n")) == 2);
|
||||
CHECK(j2.at(alt_json::json_pointer("/~0~1~0~0~1~1")) == 3);
|
||||
CHECK(alt_json::json_pointer("/~0~1~0~0~1~1").to_string() == alt_string("/~0~1~0~0~1~1"));
|
||||
CHECK(j2.flatten().unflatten() == j2);
|
||||
}
|
||||
|
||||
SECTION("patch")
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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 <memory>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
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>;
|
||||
|
||||
// a std::vector whose at() is hidden: the library performs its own bounds
|
||||
// check and must not fall back to the container's checked accessor
|
||||
template<class T, class Allocator = std::allocator<T>>
|
||||
class vector_without_at : public std::vector<T, Allocator>
|
||||
{
|
||||
public:
|
||||
vector_without_at() = default;
|
||||
|
||||
// the array of an initializer list is built from a range
|
||||
template<class InputIt>
|
||||
vector_without_at(InputIt first, InputIt last) : std::vector<T, Allocator>(first, last) {}
|
||||
|
||||
void at() = delete;
|
||||
};
|
||||
|
||||
using no_at_json = nlohmann::basic_json<std::map, vector_without_at>;
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("array type without capacity()")
|
||||
{
|
||||
SECTION("the iterators take their exception specification from the container")
|
||||
{
|
||||
// basic_json's iterators move exactly as the container iterators do:
|
||||
// their move operations are defaulted without a declared noexcept,
|
||||
// because an array or object type whose iterator is not nothrow move
|
||||
// constructible would otherwise have them deleted (std::deque's is not
|
||||
// with libstdc++ before 11, and neither are MSVC's debug iterators)
|
||||
CHECK(std::is_nothrow_move_constructible<nlohmann::json::iterator>::value ==
|
||||
(std::is_nothrow_move_constructible<nlohmann::json::object_t::iterator>::value
|
||||
&& std::is_nothrow_move_constructible<nlohmann::json::array_t::iterator>::value));
|
||||
CHECK(std::is_nothrow_move_assignable<nlohmann::json::iterator>::value ==
|
||||
(std::is_nothrow_move_assignable<nlohmann::json::object_t::iterator>::value
|
||||
&& std::is_nothrow_move_assignable<nlohmann::json::array_t::iterator>::value));
|
||||
CHECK(std::is_nothrow_move_constructible<nlohmann::json::const_iterator>::value ==
|
||||
(std::is_nothrow_move_constructible<nlohmann::json::object_t::const_iterator>::value
|
||||
&& std::is_nothrow_move_constructible<nlohmann::json::array_t::const_iterator>::value));
|
||||
|
||||
// and they are movable at all, which is what dropping the declared
|
||||
// noexcept buys for a std::deque array
|
||||
CHECK(std::is_move_constructible<deque_json::iterator>::value);
|
||||
CHECK(std::is_move_assignable<deque_json::iterator>::value);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("array type without at()")
|
||||
{
|
||||
// built in memory rather than parsed, so that the exception message does
|
||||
// not gain a byte range with JSON_DIAGNOSTIC_POSITIONS
|
||||
no_at_json j = {1, 2, 3};
|
||||
const auto& jc = j;
|
||||
|
||||
CHECK(j.at(0) == 1);
|
||||
CHECK(j.at(2) == 3);
|
||||
CHECK(jc.at(2) == 3);
|
||||
|
||||
CHECK_THROWS_WITH_AS(j.at(3), "[json.exception.out_of_range.401] array index 3 is out of range", no_at_json::out_of_range);
|
||||
CHECK_THROWS_WITH_AS(jc.at(3), "[json.exception.out_of_range.401] array index 3 is out of range", no_at_json::out_of_range);
|
||||
|
||||
CHECK(j.at(no_at_json::json_pointer("/1")) == 2);
|
||||
CHECK_THROWS_AS(j.at(no_at_json::json_pointer("/3")), no_at_json::out_of_range);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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 <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#include <cstddef>
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// a BinaryType whose value type is signed: the elements must still be
|
||||
// processed as the numbers 0..255
|
||||
using char_binary_json = nlohmann::basic_json <
|
||||
std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t,
|
||||
double, std::allocator, nlohmann::adl_serializer, std::vector<char>, void >;
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
// a BinaryType whose value type is not an integer type at all
|
||||
using byte_binary_json = nlohmann::basic_json <
|
||||
std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t,
|
||||
double, std::allocator, nlohmann::adl_serializer, std::vector<std::byte>, void >;
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("binary type whose value type is not std::uint8_t")
|
||||
{
|
||||
SECTION("a signed value type does not dump negative numbers")
|
||||
{
|
||||
const std::vector<char> chars{'\0', '\x01', '\xFF'};
|
||||
CHECK(char_binary_json::binary(chars).dump() == R"({"bytes":[0,1,255],"subtype":null})");
|
||||
CHECK(char_binary_json::binary(chars, 42).dump() == R"({"bytes":[0,1,255],"subtype":42})");
|
||||
CHECK(char_binary_json::binary({}).dump() == R"({"bytes":[],"subtype":null})");
|
||||
}
|
||||
|
||||
SECTION("the default binary type is unchanged")
|
||||
{
|
||||
CHECK(nlohmann::json::binary({0, 1, 255}, 42).dump() == R"({"bytes":[0,1,255],"subtype":42})");
|
||||
}
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
SECTION("dumping a value type that is not an integer")
|
||||
{
|
||||
const std::vector<std::byte> bytes{std::byte{0}, std::byte{1}, std::byte{0xFF}};
|
||||
CHECK(byte_binary_json::binary(bytes).dump() == R"({"bytes":[0,1,255],"subtype":null})");
|
||||
CHECK(byte_binary_json::binary(bytes, 42).dump() == R"({"bytes":[0,1,255],"subtype":42})");
|
||||
CHECK(byte_binary_json::binary({}).dump() == R"({"bytes":[],"subtype":null})");
|
||||
}
|
||||
|
||||
SECTION("hashing and the binary formats")
|
||||
{
|
||||
const std::vector<std::byte> bytes{std::byte{0}, std::byte{1}, std::byte{0xFF}};
|
||||
const auto j = byte_binary_json::binary(bytes);
|
||||
|
||||
CHECK(std::hash<byte_binary_json> {}(j) == std::hash<byte_binary_json> {}(j));
|
||||
CHECK(byte_binary_json::from_cbor(byte_binary_json::to_cbor(j)) == j);
|
||||
CHECK(byte_binary_json::from_msgpack(byte_binary_json::to_msgpack(j)) == j);
|
||||
|
||||
// UBJSON has no binary type, so binary values are written as an array
|
||||
CHECK(byte_binary_json::from_ubjson(byte_binary_json::to_ubjson(j)) == byte_binary_json({0, 1, 255}));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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 <cstdint>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// An ObjectType that does *not* define a key_compare member type, which is
|
||||
// what every hash map looks like to the library.
|
||||
//
|
||||
// A hash map is deliberately not used here: object_t is probed for
|
||||
// key_compare inside the definition of basic_json, that is, while basic_json
|
||||
// is still an incomplete type, and whether a hash map can be instantiated
|
||||
// with an incomplete mapped type depends on the standard library (libstdc++ 9
|
||||
// needs the size of the mapped type for its node type and rejects it). So the
|
||||
// object type wraps a std::map instead of inheriting from it: an earlier
|
||||
// version derived from std::map and shadowed the inherited key_compare type
|
||||
// with a same-named member function, relying on ordinary member hiding to
|
||||
// make key_compare unreachable as a type. MSVC 2017 (AppVeyor, /std:c++17)
|
||||
// does not honor that hiding for a typename-qualified lookup performed from
|
||||
// outside the class and still resolves key_compare to the base's comparator
|
||||
// type, so the library's probe incorrectly found one. Composition sidesteps
|
||||
// the question entirely: with no base class, there is no key_compare to find
|
||||
// under any lookup rule.
|
||||
template<class Key, class T, class Compare, class Allocator>
|
||||
class no_key_compare_map
|
||||
{
|
||||
using map_t = std::map<Key, T, Compare, Allocator>;
|
||||
map_t data;
|
||||
|
||||
public:
|
||||
using key_type = typename map_t::key_type;
|
||||
using mapped_type = typename map_t::mapped_type;
|
||||
using value_type = typename map_t::value_type;
|
||||
using size_type = typename map_t::size_type;
|
||||
using allocator_type = typename map_t::allocator_type;
|
||||
using iterator = typename map_t::iterator;
|
||||
using const_iterator = typename map_t::const_iterator;
|
||||
|
||||
// -Weffc++ asks for the member to be initialized in the member
|
||||
// initialization list, which a defaulted constructor does not do; the
|
||||
// exception specification a defaulted one would have carried has to be
|
||||
// written out as well, or -Wnoexcept objects where the standard library
|
||||
// takes noexcept(construct(...))
|
||||
no_key_compare_map() noexcept(std::is_nothrow_default_constructible<map_t>::value) : data() {}
|
||||
|
||||
// converting between two basic_json types builds the object from a range
|
||||
template<class InputIt>
|
||||
no_key_compare_map(InputIt first, InputIt last) : data(first, last) {}
|
||||
|
||||
iterator begin() noexcept
|
||||
{
|
||||
return data.begin();
|
||||
}
|
||||
iterator end() noexcept
|
||||
{
|
||||
return data.end();
|
||||
}
|
||||
const_iterator begin() const noexcept
|
||||
{
|
||||
return data.begin();
|
||||
}
|
||||
const_iterator end() const noexcept
|
||||
{
|
||||
return data.end();
|
||||
}
|
||||
const_iterator cbegin() const noexcept
|
||||
{
|
||||
return data.cbegin();
|
||||
}
|
||||
const_iterator cend() const noexcept
|
||||
{
|
||||
return data.cend();
|
||||
}
|
||||
|
||||
bool empty() const noexcept
|
||||
{
|
||||
return data.empty();
|
||||
}
|
||||
size_type size() const noexcept
|
||||
{
|
||||
return data.size();
|
||||
}
|
||||
size_type max_size() const noexcept
|
||||
{
|
||||
return data.max_size();
|
||||
}
|
||||
void clear() noexcept
|
||||
{
|
||||
data.clear();
|
||||
}
|
||||
|
||||
iterator find(const key_type& key)
|
||||
{
|
||||
return data.find(key);
|
||||
}
|
||||
const_iterator find(const key_type& key) const
|
||||
{
|
||||
return data.find(key);
|
||||
}
|
||||
size_type count(const key_type& key) const
|
||||
{
|
||||
return data.count(key);
|
||||
}
|
||||
|
||||
std::pair<iterator, bool> emplace(const key_type& key, const mapped_type& value)
|
||||
{
|
||||
return data.emplace(key, value);
|
||||
}
|
||||
|
||||
std::pair<iterator, bool> insert(const value_type& value)
|
||||
{
|
||||
return data.insert(value);
|
||||
}
|
||||
|
||||
template<class InputIt>
|
||||
void insert(InputIt first, InputIt last)
|
||||
{
|
||||
data.insert(first, last);
|
||||
}
|
||||
|
||||
mapped_type& operator[](const key_type& key)
|
||||
{
|
||||
return data[key];
|
||||
}
|
||||
|
||||
mapped_type& at(const key_type& key)
|
||||
{
|
||||
return data.at(key);
|
||||
}
|
||||
const mapped_type& at(const key_type& key) const
|
||||
{
|
||||
return data.at(key);
|
||||
}
|
||||
|
||||
iterator erase(iterator pos)
|
||||
{
|
||||
return data.erase(pos);
|
||||
}
|
||||
iterator erase(iterator first, iterator last)
|
||||
{
|
||||
return data.erase(first, last);
|
||||
}
|
||||
size_type erase(const key_type& key)
|
||||
{
|
||||
return data.erase(key);
|
||||
}
|
||||
|
||||
void swap(no_key_compare_map& other) noexcept(noexcept(data.swap(other.data)))
|
||||
{
|
||||
data.swap(other.data);
|
||||
}
|
||||
|
||||
friend bool operator==(const no_key_compare_map& lhs, const no_key_compare_map& rhs)
|
||||
{
|
||||
return lhs.data == rhs.data;
|
||||
}
|
||||
friend bool operator<(const no_key_compare_map& lhs, const no_key_compare_map& rhs)
|
||||
{
|
||||
return lhs.data < rhs.data;
|
||||
}
|
||||
};
|
||||
|
||||
using no_key_compare_json = nlohmann::basic_json<no_key_compare_map>;
|
||||
|
||||
// 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 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")
|
||||
{
|
||||
CHECK(std::is_same < no_key_compare_json::object_comparator_t,
|
||||
no_key_compare_json::default_object_comparator_t >::value);
|
||||
}
|
||||
|
||||
SECTION("object types defining key_compare are unaffected")
|
||||
{
|
||||
CHECK(std::is_same<nlohmann::json::object_comparator_t,
|
||||
nlohmann::json::object_t::key_compare>::value);
|
||||
CHECK(std::is_same<nlohmann::ordered_json::object_comparator_t,
|
||||
nlohmann::ordered_json::object_t::key_compare>::value);
|
||||
}
|
||||
|
||||
SECTION("creating and accessing values")
|
||||
{
|
||||
no_key_compare_json j;
|
||||
j["one"] = 1;
|
||||
j["two"] = "zwei";
|
||||
j["three"]["nested"] = true;
|
||||
|
||||
CHECK(j.size() == 3);
|
||||
CHECK(j.at("one") == 1);
|
||||
CHECK(j["two"] == "zwei");
|
||||
CHECK(j["three"]["nested"] == true);
|
||||
CHECK(j.contains("one"));
|
||||
CHECK(!j.contains("four"));
|
||||
CHECK(j.find("one") != j.end());
|
||||
CHECK(j.count("one") == 1);
|
||||
CHECK(j.erase("one") == 1);
|
||||
CHECK(j.size() == 2);
|
||||
}
|
||||
|
||||
SECTION("serialization and deserialization")
|
||||
{
|
||||
const auto j = no_key_compare_json::parse(R"({"a":[1,2,3],"b":{"c":null}})");
|
||||
CHECK(j["a"].size() == 3);
|
||||
CHECK(j["a"][2] == 3);
|
||||
CHECK(j["b"]["c"].is_null());
|
||||
CHECK(no_key_compare_json::parse(j.dump()) == j);
|
||||
}
|
||||
|
||||
SECTION("binary formats")
|
||||
{
|
||||
const auto j = no_key_compare_json::parse(R"({"a":[1,2,3],"b":"x"})");
|
||||
CHECK(no_key_compare_json::from_cbor(no_key_compare_json::to_cbor(j)) == j);
|
||||
CHECK(no_key_compare_json::from_msgpack(no_key_compare_json::to_msgpack(j)) == j);
|
||||
}
|
||||
|
||||
SECTION("flatten and unflatten")
|
||||
{
|
||||
// "o" has a key that looks like an array index, so unflatten() must
|
||||
// not turn it into an array
|
||||
const auto j = no_key_compare_json::parse(
|
||||
R"({"c":[1,2,3],"d":{"e":"s"},"n":[[0,1],[2]],"o":{"2":"x"}})");
|
||||
CHECK(j.flatten().unflatten() == j);
|
||||
}
|
||||
|
||||
SECTION("conversion to and from nlohmann::json")
|
||||
{
|
||||
const auto j = no_key_compare_json::parse(R"({"a":1,"b":[true,null]})");
|
||||
const nlohmann::json converted(j);
|
||||
|
||||
CHECK(converted.is_object());
|
||||
CHECK(converted["a"] == 1);
|
||||
CHECK(converted["b"][0] == true);
|
||||
CHECK(converted["b"][1].is_null());
|
||||
CHECK(no_key_compare_json(converted) == j);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,5 +330,36 @@ TEST_CASE("Regression tests for extended diagnostics")
|
||||
CHECK(i == 0);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Regression test - swap(array_t&)/swap(object_t&) must update JSON_DIAGNOSTICS parent pointers")
|
||||
{
|
||||
// swap(array_t&)
|
||||
{
|
||||
json j = json::array();
|
||||
json::array_t arr = {json::array({1})};
|
||||
j.swap(arr);
|
||||
|
||||
// parent pointers of the moved-in elements must point into j, not
|
||||
// into the now-defunct free-standing array_t
|
||||
CHECK_THROWS_WITH_AS(j[0][0].get<std::string>(), "[json.exception.type_error.302] (/0/0) type must be string, but is number", json::type_error);
|
||||
|
||||
// must not trigger assert_invariant() in a debug/assert-enabled build
|
||||
json const k = j;
|
||||
CHECK(k == j);
|
||||
}
|
||||
|
||||
// swap(object_t&)
|
||||
{
|
||||
json o = json::object();
|
||||
json::object_t obj = {{"a", json::array({1})}};
|
||||
o.swap(obj);
|
||||
|
||||
CHECK_THROWS_WITH_AS(o["a"][0].get<std::string>(), "[json.exception.type_error.302] (/a/0) type must be string, but is number", json::type_error);
|
||||
|
||||
// must not trigger assert_invariant() in a debug/assert-enabled build
|
||||
json const p = o;
|
||||
CHECK(p == o);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -507,6 +507,16 @@ TEST_CASE("JSON pointers")
|
||||
// explicit roundtrip check
|
||||
CHECK(j.flatten().unflatten() == j);
|
||||
|
||||
// an object is only unflattened to an array if one of its keys is the
|
||||
// reference token 0; this must not depend on which key is seen first
|
||||
CHECK(json({{"/2", "x"}}).unflatten() == json({{"2", "x"}}));
|
||||
CHECK(json({{"/10", "y"}, {"/2", "z"}}).unflatten() == json({{"10", "y"}, {"2", "z"}}));
|
||||
CHECK(json({{"/0", 1}, {"/1", 2}}).unflatten() == json({1, 2}));
|
||||
CHECK(json({{"/1", 2}, {"/0", 1}}).unflatten() == json({1, 2}));
|
||||
CHECK(json({{"/0", 1}, {"/2", 3}}).unflatten() == json({1, nullptr, 3}));
|
||||
CHECK(json({{"/a/1", 2}, {"/a/0", 1}}).unflatten() == json({{"a", {1, 2}}}));
|
||||
CHECK(json({{"/a/1", 2}, {"/a/x", 1}}).unflatten() == json({{"a", {{"1", 2}, {"x", 1}}}}));
|
||||
|
||||
// roundtrip for primitive values
|
||||
json j_null;
|
||||
CHECK(j_null.flatten().unflatten() == j_null);
|
||||
|
||||
Reference in New Issue
Block a user