mirror of
https://github.com/nlohmann/json.git
synced 2026-10-31 09:55:42 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
07ac676393 | ||
|
|
1db091b784 |
No files matched your search
@@ -253,6 +253,34 @@ Two conversions never copy at all:
|
||||
|
||||
Both results are only valid as long as the view — and, for a string with no escapes, the borrowed source text — is.
|
||||
|
||||
### `string_view` members
|
||||
|
||||
A struct whose member is a `#!cpp std::string_view` (C++17) can be decoded from a view with the
|
||||
`NLOHMANN_DEFINE_TYPE_*` macros, and the member is not copied either: for a string without escape sequences it points
|
||||
into the document's source text, and for a string with escape sequences into the document's buffer of decoded strings.
|
||||
The member is valid as long as the document is alive and, for a document that
|
||||
[borrows its source text](#ownership-and-lifetime) (as [`parse()`](../api/basic_json_document/parse.md) does for
|
||||
an lvalue `#!cpp std::string`, a `#!cpp std::string_view`, or a C string), as long as that text is.
|
||||
|
||||
```cpp
|
||||
struct record
|
||||
{
|
||||
std::string_view name;
|
||||
int count;
|
||||
NLOHMANN_DEFINE_TYPE_INTRUSIVE(record, name, count)
|
||||
};
|
||||
|
||||
const std::string text = R"({"name": "widget", "count": 3})";
|
||||
const json_document doc = json_document::parse(text); // borrows text
|
||||
const record r = doc.root().get<record>(); // r.name points into text, valid while text and doc are
|
||||
```
|
||||
|
||||
!!! warning "Decoding into a `string_view` from a `json` value dangles at once"
|
||||
|
||||
The same struct also compiles with `#!cpp json::parse(text).get<record>()`, but there `name` points into the
|
||||
temporary `json` value, which is destroyed at the end of the statement. Use a named
|
||||
[`json_document`](../api/json_document.md), as above, to decode such a struct.
|
||||
|
||||
## Writing generic code
|
||||
|
||||
A view's type names its `BasicJsonType` and its [`view_mode`](../api/view_mode.md):
|
||||
|
||||
@@ -465,17 +465,15 @@ class binary_reader
|
||||
}
|
||||
|
||||
const std::size_t element_type_parse_position = chars_read;
|
||||
// an array's elements are named "0", "1", ... in the wire format,
|
||||
// and those names are not passed on: they are skipped unless
|
||||
// error_handler_t::strict must reject ill-formed UTF-8 in them
|
||||
const bool is_object = container_stack.back().is_object;
|
||||
key.clear();
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key, is_object || error_handler == error_handler_t::strict)))
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_object && !sax->key(key))
|
||||
// an array's elements are named "0", "1", ... in the wire format,
|
||||
// and those names are not passed on
|
||||
if (container_stack.back().is_object && !sax->key(key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -491,18 +489,14 @@ class binary_reader
|
||||
@brief Parses a C-style string from the BSON input.
|
||||
@param[in,out] result A reference to the string variable where the read
|
||||
string is to be stored.
|
||||
@param[in] store whether the characters are needed; if not (the names of
|
||||
an array's elements), they are skipped and @a result may
|
||||
be left unchanged, and they are not checked against
|
||||
@ref error_handler
|
||||
@return `true` if the \\x00-byte indicating the end of the string was
|
||||
encountered before the EOF; false` indicates an unexpected EOF.
|
||||
*/
|
||||
bool get_bson_cstr(string_t& result, const bool store = true)
|
||||
bool get_bson_cstr(string_t& result)
|
||||
{
|
||||
if (get_bson_cstr_bulk(result, store, std::integral_constant<bool, bulk_scan> {}))
|
||||
if (get_bson_cstr_bulk(result, std::integral_constant<bool, bulk_scan> {}))
|
||||
{
|
||||
return !store || check_string_utf8(result, "key");
|
||||
return check_string_utf8(result, "key");
|
||||
}
|
||||
|
||||
auto out = std::back_inserter(result);
|
||||
@@ -515,7 +509,7 @@ class binary_reader
|
||||
}
|
||||
if (current == 0x00)
|
||||
{
|
||||
return !store || check_string_utf8(result, "key");
|
||||
return check_string_utf8(result, "key");
|
||||
}
|
||||
*out++ = static_cast<typename string_t::value_type>(current);
|
||||
}
|
||||
@@ -525,11 +519,10 @@ class binary_reader
|
||||
@brief read a C-style string from contiguous input in one step
|
||||
|
||||
@param[in,out] result the string to append to
|
||||
@param[in] store whether to append the characters to @a result
|
||||
@return whether the string was read; if the input has no \\x00-byte, nothing
|
||||
is read, and @ref get_bson_cstr reports the end of the input
|
||||
*/
|
||||
bool get_bson_cstr_bulk(string_t& result, const bool store, std::true_type /*bulk*/)
|
||||
bool get_bson_cstr_bulk(string_t& result, std::true_type /*bulk*/)
|
||||
{
|
||||
const std::size_t remaining = ia.bulk_remaining();
|
||||
if (remaining == 0)
|
||||
@@ -537,15 +530,18 @@ class binary_reader
|
||||
return false;
|
||||
}
|
||||
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
|
||||
const std::size_t length = find_zero_byte(data, remaining);
|
||||
// a plain loop rather than std::memchr: most keys are short (array
|
||||
// indices are keys, too), and the call would cost more than it saves
|
||||
std::size_t length = 0;
|
||||
while (length < remaining && data[length] != 0x00)
|
||||
{
|
||||
++length;
|
||||
}
|
||||
if (length == remaining)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (store)
|
||||
{
|
||||
result.append(reinterpret_cast<const typename string_t::value_type*>(data), length);
|
||||
}
|
||||
result.append(reinterpret_cast<const typename string_t::value_type*>(data), length);
|
||||
// consume the string and its \x00-byte, as the byte-wise path does
|
||||
ia.bulk_skip(length + 1);
|
||||
chars_read += length + 1;
|
||||
@@ -554,7 +550,7 @@ class binary_reader
|
||||
}
|
||||
|
||||
/// input that is not contiguous: C-style strings are read byte by byte
|
||||
bool get_bson_cstr_bulk(string_t& /*result*/, const bool /*store*/, std::false_type /*bulk*/) const noexcept
|
||||
bool get_bson_cstr_bulk(string_t& /*result*/, std::false_type /*bulk*/) const noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -90,34 +90,6 @@ JSON_HEDLEY_ALWAYS_INLINE std::size_t find_string_special(const unsigned char* d
|
||||
return n;
|
||||
}
|
||||
|
||||
// return the index of the first 0x00 byte in [data, data+n), or n if there is
|
||||
// none; scans 8 bytes at a time (BSON's C-style strings)
|
||||
JSON_HEDLEY_ALWAYS_INLINE std::size_t find_zero_byte(const unsigned char* data, std::size_t n) noexcept
|
||||
{
|
||||
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
||||
constexpr std::uint64_t high = 0x8080808080808080ull;
|
||||
std::size_t i = 0;
|
||||
for (; i + 8 <= n; i += 8)
|
||||
{
|
||||
const std::uint64_t v = read_eight_bytes(data + i);
|
||||
const std::uint64_t zero = (v - ones) & ~v & high;
|
||||
if (zero != 0)
|
||||
{
|
||||
// the lowest flagged byte is the first zero byte (see
|
||||
// find_string_special())
|
||||
return i + (static_cast<std::size_t>(count_trailing_zeros(zero)) / 8);
|
||||
}
|
||||
}
|
||||
for (; i < n; ++i)
|
||||
{
|
||||
if (data[i] == 0x00)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
// classify a byte as one the serializer must NOT copy verbatim when
|
||||
// ensure_ascii is requested: the closing quote, an escape, a control character
|
||||
// (< 0x20), DEL (0x7F), or any non-ASCII byte (>= 0x80). Everything else -
|
||||
|
||||
Generated
+18
-50
@@ -12458,34 +12458,6 @@ JSON_HEDLEY_ALWAYS_INLINE std::size_t find_string_special(const unsigned char* d
|
||||
return n;
|
||||
}
|
||||
|
||||
// return the index of the first 0x00 byte in [data, data+n), or n if there is
|
||||
// none; scans 8 bytes at a time (BSON's C-style strings)
|
||||
JSON_HEDLEY_ALWAYS_INLINE std::size_t find_zero_byte(const unsigned char* data, std::size_t n) noexcept
|
||||
{
|
||||
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
||||
constexpr std::uint64_t high = 0x8080808080808080ull;
|
||||
std::size_t i = 0;
|
||||
for (; i + 8 <= n; i += 8)
|
||||
{
|
||||
const std::uint64_t v = read_eight_bytes(data + i);
|
||||
const std::uint64_t zero = (v - ones) & ~v & high;
|
||||
if (zero != 0)
|
||||
{
|
||||
// the lowest flagged byte is the first zero byte (see
|
||||
// find_string_special())
|
||||
return i + (static_cast<std::size_t>(count_trailing_zeros(zero)) / 8);
|
||||
}
|
||||
}
|
||||
for (; i < n; ++i)
|
||||
{
|
||||
if (data[i] == 0x00)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
// classify a byte as one the serializer must NOT copy verbatim when
|
||||
// ensure_ascii is requested: the closing quote, an escape, a control character
|
||||
// (< 0x20), DEL (0x7F), or any non-ASCII byte (>= 0x80). Everything else -
|
||||
@@ -18269,17 +18241,15 @@ class binary_reader
|
||||
}
|
||||
|
||||
const std::size_t element_type_parse_position = chars_read;
|
||||
// an array's elements are named "0", "1", ... in the wire format,
|
||||
// and those names are not passed on: they are skipped unless
|
||||
// error_handler_t::strict must reject ill-formed UTF-8 in them
|
||||
const bool is_object = container_stack.back().is_object;
|
||||
key.clear();
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key, is_object || error_handler == error_handler_t::strict)))
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_object && !sax->key(key))
|
||||
// an array's elements are named "0", "1", ... in the wire format,
|
||||
// and those names are not passed on
|
||||
if (container_stack.back().is_object && !sax->key(key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -18295,18 +18265,14 @@ class binary_reader
|
||||
@brief Parses a C-style string from the BSON input.
|
||||
@param[in,out] result A reference to the string variable where the read
|
||||
string is to be stored.
|
||||
@param[in] store whether the characters are needed; if not (the names of
|
||||
an array's elements), they are skipped and @a result may
|
||||
be left unchanged, and they are not checked against
|
||||
@ref error_handler
|
||||
@return `true` if the \\x00-byte indicating the end of the string was
|
||||
encountered before the EOF; false` indicates an unexpected EOF.
|
||||
*/
|
||||
bool get_bson_cstr(string_t& result, const bool store = true)
|
||||
bool get_bson_cstr(string_t& result)
|
||||
{
|
||||
if (get_bson_cstr_bulk(result, store, std::integral_constant<bool, bulk_scan> {}))
|
||||
if (get_bson_cstr_bulk(result, std::integral_constant<bool, bulk_scan> {}))
|
||||
{
|
||||
return !store || check_string_utf8(result, "key");
|
||||
return check_string_utf8(result, "key");
|
||||
}
|
||||
|
||||
auto out = std::back_inserter(result);
|
||||
@@ -18319,7 +18285,7 @@ class binary_reader
|
||||
}
|
||||
if (current == 0x00)
|
||||
{
|
||||
return !store || check_string_utf8(result, "key");
|
||||
return check_string_utf8(result, "key");
|
||||
}
|
||||
*out++ = static_cast<typename string_t::value_type>(current);
|
||||
}
|
||||
@@ -18329,11 +18295,10 @@ class binary_reader
|
||||
@brief read a C-style string from contiguous input in one step
|
||||
|
||||
@param[in,out] result the string to append to
|
||||
@param[in] store whether to append the characters to @a result
|
||||
@return whether the string was read; if the input has no \\x00-byte, nothing
|
||||
is read, and @ref get_bson_cstr reports the end of the input
|
||||
*/
|
||||
bool get_bson_cstr_bulk(string_t& result, const bool store, std::true_type /*bulk*/)
|
||||
bool get_bson_cstr_bulk(string_t& result, std::true_type /*bulk*/)
|
||||
{
|
||||
const std::size_t remaining = ia.bulk_remaining();
|
||||
if (remaining == 0)
|
||||
@@ -18341,15 +18306,18 @@ class binary_reader
|
||||
return false;
|
||||
}
|
||||
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
|
||||
const std::size_t length = find_zero_byte(data, remaining);
|
||||
// a plain loop rather than std::memchr: most keys are short (array
|
||||
// indices are keys, too), and the call would cost more than it saves
|
||||
std::size_t length = 0;
|
||||
while (length < remaining && data[length] != 0x00)
|
||||
{
|
||||
++length;
|
||||
}
|
||||
if (length == remaining)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (store)
|
||||
{
|
||||
result.append(reinterpret_cast<const typename string_t::value_type*>(data), length);
|
||||
}
|
||||
result.append(reinterpret_cast<const typename string_t::value_type*>(data), length);
|
||||
// consume the string and its \x00-byte, as the byte-wise path does
|
||||
ia.bulk_skip(length + 1);
|
||||
chars_read += length + 1;
|
||||
@@ -18358,7 +18326,7 @@ class binary_reader
|
||||
}
|
||||
|
||||
/// input that is not contiguous: C-style strings are read byte by byte
|
||||
bool get_bson_cstr_bulk(string_t& /*result*/, const bool /*store*/, std::false_type /*bulk*/) const noexcept
|
||||
bool get_bson_cstr_bulk(string_t& /*result*/, std::false_type /*bulk*/) const noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
+2
-35
@@ -1164,13 +1164,7 @@ TEST_CASE("BSON keys from contiguous and stream input")
|
||||
// contiguous input reads a key up to its \x00-byte in one step, a stream
|
||||
// reads it byte by byte; both must give the same value or error for the
|
||||
// complete document and for every truncation of it
|
||||
json j = {{"", true}, {"k", {1, 2, 3}}, {std::string(40, 'x'), {{"nested key", "value"}}}};
|
||||
// keys and array element names ("0" to "11") that end at every position of
|
||||
// an 8-byte word, which contiguous input scans at a time
|
||||
for (std::size_t length = 1; length <= 17; ++length)
|
||||
{
|
||||
j[std::string(length, static_cast<char>('a' + length))] = json::array({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, {11}});
|
||||
}
|
||||
const json j = {{"", true}, {"k", {1, 2, 3}}, {std::string(40, 'x'), {{"nested key", "value"}}}};
|
||||
const std::vector<std::uint8_t> bson = json::to_bson(j);
|
||||
CHECK(json::from_bson(bson) == j);
|
||||
|
||||
@@ -1190,7 +1184,7 @@ TEST_CASE("BSON keys from contiguous and stream input")
|
||||
}
|
||||
try
|
||||
{
|
||||
std::istringstream stream(std::string(reinterpret_cast<const char*>(input.data()), input.size()));
|
||||
std::istringstream stream(std::string(input.begin(), input.end()));
|
||||
from_stream = json::from_bson(stream).dump();
|
||||
}
|
||||
catch (const json::parse_error& e)
|
||||
@@ -1200,33 +1194,6 @@ TEST_CASE("BSON keys from contiguous and stream input")
|
||||
CHECK(from_vector == from_stream);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("BSON array element names")
|
||||
{
|
||||
// an array's element names are not part of the value, so they are not
|
||||
// passed on; error_handler_t::strict still rejects ill-formed UTF-8 in them
|
||||
const std::vector<std::uint8_t> bson =
|
||||
{
|
||||
0x1D, 0x00, 0x00, 0x00, // size (little endian)
|
||||
0x04, 'a', 0x00, // entry: array "a"
|
||||
0x15, 0x00, 0x00, 0x00, // size (little endian)
|
||||
0x08, 0xFF, 0x00, 0x01, // entry: boolean named "\xFF", true
|
||||
0x08, '1', '2', '3', '4', '5', '6', '7', '8', '9', 0x00, 0x00, // entry: boolean named "123456789", false
|
||||
0x00, // end of the array
|
||||
0x00 // end of the document
|
||||
};
|
||||
const json expected = {{"a", {true, false}}};
|
||||
|
||||
CHECK(json::from_bson(bson) == expected);
|
||||
CHECK(json::from_bson(bson, true, true, json::error_handler_t::replace) == expected);
|
||||
CHECK(json::from_bson(bson, true, true, json::error_handler_t::ignore) == expected);
|
||||
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bson(bson, true, true, json::error_handler_t::strict), "[json.exception.parse_error.113] parse error at byte 14: syntax error while parsing BSON key: invalid string: ill-formed UTF-8 byte", json::parse_error&);
|
||||
|
||||
std::istringstream stream(std::string(reinterpret_cast<const char*>(bson.data()), bson.size()));
|
||||
CHECK(json::from_bson(stream) == expected);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE("Negative size of binary value")
|
||||
|
||||
@@ -39,6 +39,7 @@ using nlohmann::ordered_json_document;
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#endif
|
||||
|
||||
// NLOHMANN_JSON_SERIALIZE_ENUM uses a static std::pair; same() compares floating-point values on purpose
|
||||
@@ -469,5 +470,70 @@ TEST_CASE("json_view from_json")
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
namespace view_from_json
|
||||
{
|
||||
// a string_view member is not copied: it points into the source text or, for a
|
||||
// string with escape sequences, into the buffer of decoded strings of the document
|
||||
struct named_view
|
||||
{
|
||||
// initialized for GCC -Weffc++
|
||||
// NOLINTNEXTLINE(readability-redundant-member-init)
|
||||
std::string_view name{};
|
||||
int n = 0;
|
||||
NLOHMANN_DEFINE_TYPE_INTRUSIVE(named_view, name, n)
|
||||
};
|
||||
} // namespace view_from_json
|
||||
|
||||
TEST_CASE("json_view from_json: string_view members borrow from the document")
|
||||
{
|
||||
using view_from_json::named_view;
|
||||
|
||||
// whether all of s is inside the bytes of text
|
||||
const auto within = [](std::string_view s, std::string_view text)
|
||||
{
|
||||
const auto first = reinterpret_cast<std::uintptr_t>(text.data());
|
||||
const auto begin = reinterpret_cast<std::uintptr_t>(s.data());
|
||||
return begin >= first && begin + s.size() <= first + text.size();
|
||||
};
|
||||
|
||||
SECTION("a string without escapes points into the source text")
|
||||
{
|
||||
const std::string text = R"({"name": "hello", "n": 1})";
|
||||
const json_document d = json_document::parse(text);
|
||||
REQUIRE_FALSE(d.owns_source());
|
||||
const auto r = d.root().get<named_view>();
|
||||
CHECK(r.name == "hello");
|
||||
CHECK(r.n == 1);
|
||||
CHECK(within(r.name, text));
|
||||
CHECK(within(r.name, d.source()));
|
||||
}
|
||||
|
||||
SECTION("a string with escapes points into the document, not into the source text")
|
||||
{
|
||||
const std::string text = R"({"name": "a\nb", "n": 2})";
|
||||
json_document d = json_document::parse(text);
|
||||
REQUIRE_FALSE(d.owns_source());
|
||||
const auto r = d.root().get<named_view>();
|
||||
CHECK(r.name == "a\nb");
|
||||
CHECK(r.n == 2);
|
||||
CHECK_FALSE(within(r.name, text));
|
||||
// valid as long as the document is alive, even if the document is moved
|
||||
const json_document moved = std::move(d);
|
||||
CHECK(r.name == "a\nb");
|
||||
CHECK(moved.root().get<named_view>().name == r.name);
|
||||
}
|
||||
|
||||
SECTION("a document that owns its text keeps the members readable while it is alive")
|
||||
{
|
||||
const json_document d = json_document::parse(std::string(R"({"name": "owned", "n": 3})"));
|
||||
REQUIRE(d.owns_source());
|
||||
const auto r = d.root().get<named_view>();
|
||||
CHECK(r.name == "owned");
|
||||
CHECK(within(r.name, d.source()));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||
DOCTEST_GCC_SUPPRESS_WARNING_POP
|
||||
Reference in new issue
Block a user