mirror of
https://github.com/nlohmann/json.git
synced 2026-08-29 20:47:14 +00:00
Reduce the string_t and array_t members the library requires
Several members were required only because of how the library happened to be written, not because the functionality needs them. Dropping them widens the set of usable string and array types, and one of them was also a performance problem. string_t: - c_str() is gone. Every call site already knew the length and passed it along, so data() is enough. The one place that did not, the diagnostics path in exceptions.hpp, now builds the token from data() and size(), which also stops it from truncating keys that contain a null byte. - back() is gone; the serializer indexes the last character instead. - find(str, pos), replace(), and substr() are gone. escape() and unescape() rebuilt the string with one replace() per escaped character, which moves the tail every time: escaping a string of n characters that all need escaping cost O(n^2). Both now scan with find_first_of() -- a member the pointer parser already required -- and append whole runs, so the common case is one search and one copy. Escaping 64000 tildes drops from 717 ms to 20 ms; a string with nothing to escape gets faster too (8.4 ms to 5.8 ms), because the scan is still a single memchr per pass. json_pointer::split() takes its reference tokens with the (const char*, size_type) constructor rather than substr(). - json_pointer::to_string() accumulates with concat<string_t> instead of letting concat default to std::string and converting afterwards, so streaming a json_pointer no longer requires string_t to be assignable from a std::string. array_t: - at(size_type) is gone. basic_json::at(size_type) checked the index by calling array_t::at() and translating std::out_of_range, which also required the array type to throw that exact exception. It now compares against size() and uses operator[]. The thrown exception, its message, and the behaviour under JSON_NOEXCEPTION are unchanged. The BSON writer wrote the terminating null byte out of the string's own buffer (size() + 1). It now writes the byte itself, so string_t::data() need not be null-terminated for to_bson(). The tests pin the reduced API: alt_string loses the five dropped members and gains coverage of the escaping paths, and a std::vector whose at() is hidden is used as an ArrayType. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
@@ -24,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
|
||||
{
|
||||
@@ -108,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];
|
||||
@@ -123,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();
|
||||
@@ -148,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);
|
||||
@@ -359,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")
|
||||
|
||||
@@ -12,7 +12,9 @@
|
||||
|
||||
#include <deque>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -21,6 +23,19 @@ namespace
|
||||
// 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:
|
||||
using std::vector<T, Allocator>::vector;
|
||||
|
||||
void at() = delete;
|
||||
};
|
||||
|
||||
using no_at_json = nlohmann::basic_json<std::map, vector_without_at>;
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("array type without capacity()")
|
||||
@@ -87,3 +102,19 @@ TEST_CASE("array type without capacity()")
|
||||
CHECK(first == 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("array type without at()")
|
||||
{
|
||||
auto j = no_at_json::parse(R"([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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user