mirror of
https://github.com/nlohmann/json.git
synced 2026-10-24 23:08:56 +00:00
Compare commits
55
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b632e489a | ||
|
|
2852427b3f | ||
|
|
8085a8f5ec | ||
|
|
cf4e0164c4 | ||
|
|
3562da8556 | ||
|
|
e30d2aed18 | ||
|
|
abd3c275d1 | ||
|
|
3267a6239d | ||
|
|
89b762aa35 | ||
|
|
469e29334b | ||
|
|
cf0c72cd95 | ||
|
|
6ee6351361 | ||
|
|
4f332cbb9e | ||
|
|
80c5fe8a5a | ||
|
|
f8df3df021 | ||
|
|
21bc758b89 | ||
|
|
0e5a906cd5 | ||
|
|
fc347b5779 | ||
|
|
fce5a54722 | ||
|
|
96b5c82496 | ||
|
|
bf27f0ba99 | ||
|
|
7506a1b0bf | ||
|
|
ae59fbb5a4 | ||
|
|
bd7878f899 | ||
|
|
0092111f4d | ||
|
|
c533a107a1 | ||
|
|
d556800512 | ||
|
|
0721d3c389 | ||
|
|
daf104b549 | ||
|
|
643acf23e2 | ||
|
|
df98274f6c | ||
|
|
f503b67ac7 | ||
|
|
e8ab313dde | ||
|
|
727911bc80 | ||
|
|
210a398ce8 | ||
|
|
f266ce92e5 | ||
|
|
92e2e84722 | ||
|
|
0c804e0ad7 | ||
|
|
99aee52002 | ||
|
|
5275930814 | ||
|
|
38147ab676 | ||
|
|
390ebc91b9 | ||
|
|
3a4285ad88 | ||
|
|
8b19b94fd6 | ||
|
|
2e92fe8d26 | ||
|
|
d52cfb7221 | ||
|
|
8262acca0e | ||
|
|
4b3fcd0740 | ||
|
|
35930392d8 | ||
|
|
f8d6aa95eb | ||
|
|
48bd677617 | ||
|
|
379bf8eddb | ||
|
|
c9c9b90f1f | ||
|
|
058d6fd23d | ||
|
|
25e7edad54 |
No files matched your search
@@ -1,42 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
json j = json::parse(R"({"name": "Mary", "age": 42})");
|
||||
|
||||
// look up the key, then test the type: neither step throws
|
||||
for (const char* key : {"name", "age", "city"})
|
||||
{
|
||||
const auto it = j.find(key);
|
||||
if (it == j.end())
|
||||
{
|
||||
std::cout << key << ": missing\n";
|
||||
}
|
||||
else if (it->is_string())
|
||||
{
|
||||
std::cout << key << ": string \"" << it->get_ref<const std::string&>() << "\"\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << key << ": not a string, but " << it->type_name() << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
// get_ptr returns a null pointer if the value has another type
|
||||
const std::string* name = j["name"].get_ptr<const std::string*>();
|
||||
const std::string* age = j["age"].get_ptr<const std::string*>();
|
||||
std::cout << "get_ptr<string*>: name = " << (name != nullptr) << ", age = " << (age != nullptr) << '\n';
|
||||
|
||||
// value() is NOT exception-free: it throws if the key exists with a different type
|
||||
try
|
||||
{
|
||||
std::cout << j.value("name", 0) << '\n';
|
||||
}
|
||||
catch (const json::type_error& e)
|
||||
{
|
||||
std::cout << e.what() << '\n';
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
name: string "Mary"
|
||||
age: not a string, but number
|
||||
city: missing
|
||||
get_ptr<string*>: name = 1, age = 0
|
||||
[json.exception.type_error.302] type must be number, but is string
|
||||
@@ -1,46 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
struct person
|
||||
{
|
||||
std::string name;
|
||||
int age;
|
||||
};
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(person, name, age)
|
||||
|
||||
// Compile this function with exceptions enabled and call it from code that does not use them.
|
||||
std::optional<person> to_person(const json& j)
|
||||
{
|
||||
try
|
||||
{
|
||||
return j.get<person>();
|
||||
}
|
||||
catch (const json::exception&)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
const json good = {{"name", "Mary"}, {"age", 42}};
|
||||
const json bad = {{"name", "Mary"}, {"age", "forty-two"}};
|
||||
const json incomplete = {{"name", "Mary"}};
|
||||
|
||||
for (const json& j : {good, bad, incomplete})
|
||||
{
|
||||
if (const auto p = to_person(j))
|
||||
{
|
||||
std::cout << "person: " << p->name << ", " << p->age << '\n';
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "not a person: " << j << '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
person: Mary, 42
|
||||
not a person: {"age":"forty-two","name":"Mary"}
|
||||
not a person: {"name":"Mary"}
|
||||
@@ -1,28 +0,0 @@
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// None of the calls below throws, so this example also works with -fno-exceptions.
|
||||
int main()
|
||||
{
|
||||
const std::string invalid = R"({"name": "Mary", "age": )";
|
||||
|
||||
// parse() with allow_exceptions = false returns a discarded value
|
||||
json j = json::parse(invalid, nullptr, false);
|
||||
std::cout << std::boolalpha << "parse: discarded = " << j.is_discarded() << '\n';
|
||||
|
||||
// accept() only tells whether the input is valid JSON
|
||||
std::cout << "accept: " << json::accept(invalid) << '\n';
|
||||
|
||||
// the binary formats have the same parameter
|
||||
const std::vector<std::uint8_t> truncated_cbor = {0x83, 0x01};
|
||||
json c = json::from_cbor(truncated_cbor, true, false);
|
||||
std::cout << "from_cbor: discarded = " << c.is_discarded() << '\n';
|
||||
|
||||
// valid input is parsed as usual
|
||||
json valid = json::parse(R"({"name": "Mary"})", nullptr, false);
|
||||
std::cout << "valid input: " << valid << '\n';
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
parse: discarded = true
|
||||
accept: false
|
||||
from_cbor: discarded = true
|
||||
valid input: {"name":"Mary"}
|
||||
@@ -1,42 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <utility>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// line and column (both starting at 1) of a byte index in a text
|
||||
std::pair<std::size_t, std::size_t> line_column(const std::string& input, std::size_t byte)
|
||||
{
|
||||
std::size_t line = 1;
|
||||
std::size_t column = 1;
|
||||
for (std::size_t i = 0; i + 1 < byte && i < input.size(); ++i)
|
||||
{
|
||||
if (input[i] == '\n')
|
||||
{
|
||||
++line;
|
||||
column = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
++column;
|
||||
}
|
||||
}
|
||||
return {line, column};
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
const std::string input = "{\n \"a\": 1,\n \"b\": ]\n}";
|
||||
|
||||
try
|
||||
{
|
||||
const json j = json::parse(input);
|
||||
std::cout << j << '\n';
|
||||
}
|
||||
catch (const json::parse_error& e)
|
||||
{
|
||||
const auto position = line_column(input, e.byte);
|
||||
std::cout << e.what() << '\n';
|
||||
std::cout << "byte " << e.byte << " = line " << position.first << ", column " << position.second << '\n';
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
[json.exception.parse_error.101] parse error at line 3, column 8: syntax error while parsing value - unexpected ']'; expected '[', '{', or a literal
|
||||
byte 20 = line 3, column 8
|
||||
@@ -1,46 +0,0 @@
|
||||
#define JSON_DIAGNOSTIC_POSITIONS 1
|
||||
#include <iostream>
|
||||
#include <utility>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// line and column (both starting at 1) of a byte index in a text
|
||||
std::pair<std::size_t, std::size_t> line_column(const std::string& input, std::size_t byte)
|
||||
{
|
||||
std::size_t line = 1;
|
||||
std::size_t column = 1;
|
||||
for (std::size_t i = 0; i + 1 < byte && i < input.size(); ++i)
|
||||
{
|
||||
if (input[i] == '\n')
|
||||
{
|
||||
++line;
|
||||
column = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
++column;
|
||||
}
|
||||
}
|
||||
return {line, column};
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
const std::string input = "{\n \"name\": \"Mary\",\n \"age\": \"forty-two\"\n}";
|
||||
const json j = json::parse(input); // the input is valid JSON ...
|
||||
|
||||
try
|
||||
{
|
||||
const int age = j["age"]; // ... but the value has the wrong type
|
||||
std::cout << age << '\n';
|
||||
}
|
||||
catch (const json::exception& e)
|
||||
{
|
||||
std::cout << e.what() << '\n';
|
||||
|
||||
// start_pos() is a 0-based index into the input; the helper expects a 1-based byte index
|
||||
const auto position = line_column(input, j["age"].start_pos() + 1);
|
||||
std::cout << "the value starts at line " << position.first << ", column " << position.second << '\n';
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
[json.exception.type_error.302] (bytes 29-40) type must be number, but is string
|
||||
the value starts at line 3, column 10
|
||||
@@ -1,46 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
std::string buffer;
|
||||
|
||||
// call for every chunk; returns true and sets `value` once the buffer holds one complete JSON value
|
||||
bool push(const std::string& chunk, json& value)
|
||||
{
|
||||
buffer += chunk;
|
||||
try
|
||||
{
|
||||
value = json::parse(buffer);
|
||||
buffer.clear();
|
||||
return true;
|
||||
}
|
||||
catch (const json::parse_error& e)
|
||||
{
|
||||
if (e.byte == buffer.size() + 1)
|
||||
{
|
||||
return false; // the input ends prematurely: wait for more data
|
||||
}
|
||||
throw; // a real syntax error
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
json value;
|
||||
for (const std::string chunk : {"{\"na", "me\": \"Ma", "ry\", \"l", "ist\": [1, 2", ", 3]}"})
|
||||
{
|
||||
std::cout << "chunk " << chunk << " -> " << (push(chunk, value) ? "complete" : "incomplete") << '\n';
|
||||
}
|
||||
std::cout << value << '\n';
|
||||
|
||||
// a syntax error in the middle is not mistaken for missing data
|
||||
try
|
||||
{
|
||||
push("[1, ]", value);
|
||||
}
|
||||
catch (const json::parse_error& e)
|
||||
{
|
||||
std::cout << e.what() << '\n';
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
chunk {"na -> incomplete
|
||||
chunk me": "Ma -> incomplete
|
||||
chunk ry", "l -> incomplete
|
||||
chunk ist": [1, 2 -> incomplete
|
||||
chunk , 3]} -> complete
|
||||
{"list":[1,2,3],"name":"Mary"}
|
||||
[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - unexpected ']'; expected '[', '{', or a literal
|
||||
@@ -1,23 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
// a CBOR value followed by other data
|
||||
const std::vector<std::uint8_t> cbor = json::to_cbor(json{1, 2, 3});
|
||||
std::string data(cbor.begin(), cbor.end());
|
||||
data += "TRAILING";
|
||||
|
||||
std::istringstream input(data);
|
||||
json j = json::from_cbor(input, false); // strict = false: stop after the first value
|
||||
|
||||
std::cout << "value: " << j << '\n';
|
||||
std::cout << "bytes used: " << input.tellg() << '\n';
|
||||
|
||||
std::string rest;
|
||||
input >> rest;
|
||||
std::cout << "remaining input: " << rest << '\n';
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
value: [1,2,3]
|
||||
bytes used: 4
|
||||
remaining input: TRAILING
|
||||
@@ -1,18 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
std::istringstream input(R"({"a":1} [2,3] 4 "s")");
|
||||
|
||||
// check for the end of the stream before each read, because a read at the end throws
|
||||
json j;
|
||||
while (input >> std::ws && input.peek() != std::char_traits<char>::eof())
|
||||
{
|
||||
input >> j;
|
||||
std::cout << j << '\n';
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{"a":1}
|
||||
[2,3]
|
||||
4
|
||||
"s"
|
||||
@@ -1,27 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
void handle(const json& record)
|
||||
{
|
||||
std::cout << "handling " << record << '\n';
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
std::istringstream file(R"([{"id": 1}, {"id": 2}, {"id": 3}])");
|
||||
|
||||
json result = json::parse(file, [](int depth, json::parse_event_t event, json & record)
|
||||
{
|
||||
if (depth == 1 && event == json::parse_event_t::object_end)
|
||||
{
|
||||
handle(record);
|
||||
return false; // discard the record
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
std::cout << "result: " << result << '\n';
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
handling {"id":1}
|
||||
handling {"id":2}
|
||||
handling {"id":3}
|
||||
result: []
|
||||
@@ -1,81 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
struct find_id : nlohmann::json_sax<json>
|
||||
{
|
||||
bool next_is_id = false;
|
||||
json::number_unsigned_t id = 0;
|
||||
|
||||
bool key(std::string& name) override
|
||||
{
|
||||
next_is_id = (name == "id");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool number_unsigned(json::number_unsigned_t value) override
|
||||
{
|
||||
if (next_is_id)
|
||||
{
|
||||
id = value;
|
||||
return false; // found: stop parsing
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool null() override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
bool boolean(bool /*val*/) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
bool number_integer(json::number_integer_t /*val*/) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
bool number_float(json::number_float_t /*val*/, const std::string& /*s*/) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
bool string(std::string& /*val*/) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
bool binary(json::binary_t& /*val*/) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
bool start_object(std::size_t /*elements*/) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
bool end_object() override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
bool start_array(std::size_t /*elements*/) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
bool end_array() override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
bool parse_error(std::size_t /*position*/, const std::string& /*last_token*/, const json::exception& /*ex*/) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
find_id handler;
|
||||
const bool completed = json::sax_parse(R"({"x": [1, 2, 3], "id": 42, "rest": [4, 5, 6]})", &handler);
|
||||
|
||||
std::cout << std::boolalpha;
|
||||
std::cout << "parse completed: " << completed << '\n';
|
||||
std::cout << "id: " << handler.id << '\n';
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
parse completed: false
|
||||
id: 42
|
||||
@@ -1,56 +0,0 @@
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace nlohmann
|
||||
{
|
||||
template <typename Rep, typename Period>
|
||||
struct adl_serializer<std::chrono::duration<Rep, Period>>
|
||||
{
|
||||
static void to_json(json& j, const std::chrono::duration<Rep, Period>& d)
|
||||
{
|
||||
j = d.count();
|
||||
}
|
||||
|
||||
static void from_json(const json& j, std::chrono::duration<Rep, Period>& d)
|
||||
{
|
||||
d = std::chrono::duration<Rep, Period>(j.get<Rep>());
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Duration>
|
||||
struct adl_serializer<std::chrono::time_point<std::chrono::system_clock, Duration>>
|
||||
{
|
||||
static void to_json(json& j, const std::chrono::time_point<std::chrono::system_clock, Duration>& t)
|
||||
{
|
||||
j = t.time_since_epoch();
|
||||
}
|
||||
|
||||
static void from_json(const json& j, std::chrono::time_point<std::chrono::system_clock, Duration>& t)
|
||||
{
|
||||
t = std::chrono::time_point<std::chrono::system_clock, Duration>(j.get<Duration>());
|
||||
}
|
||||
};
|
||||
} // namespace nlohmann
|
||||
|
||||
int main()
|
||||
{
|
||||
using namespace std::chrono;
|
||||
|
||||
std::cout << std::boolalpha;
|
||||
|
||||
// the unit is not part of the JSON value
|
||||
const json timeout = milliseconds(1500);
|
||||
std::cout << "duration: " << timeout << '\n';
|
||||
std::cout << "as milliseconds: " << timeout.get<milliseconds>().count() << '\n';
|
||||
|
||||
// a system_clock time point is the time since the Unix epoch
|
||||
const time_point<system_clock, milliseconds> t(milliseconds(1700000000123));
|
||||
const json stamp = t;
|
||||
std::cout << "time point: " << stamp << '\n';
|
||||
|
||||
const auto back = stamp.get<time_point<system_clock, milliseconds>>();
|
||||
std::cout << "round trip: " << (back == t) << '\n';
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
duration: 1500
|
||||
as milliseconds: 1500
|
||||
time point: 1700000000123
|
||||
round trip: true
|
||||
@@ -1,99 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
struct shape
|
||||
{
|
||||
virtual ~shape() = default;
|
||||
virtual json serialize() const = 0;
|
||||
};
|
||||
|
||||
struct circle : shape
|
||||
{
|
||||
double radius;
|
||||
|
||||
explicit circle(double r) : radius(r) {}
|
||||
|
||||
json serialize() const override
|
||||
{
|
||||
return {{"type", "circle"}, {"radius", radius}};
|
||||
}
|
||||
};
|
||||
|
||||
struct rectangle : shape
|
||||
{
|
||||
double width;
|
||||
double height;
|
||||
|
||||
rectangle(double w, double h) : width(w), height(h) {}
|
||||
|
||||
json serialize() const override
|
||||
{
|
||||
return {{"type", "rectangle"}, {"width", width}, {"height", height}};
|
||||
}
|
||||
};
|
||||
|
||||
namespace nlohmann
|
||||
{
|
||||
template <>
|
||||
struct adl_serializer<std::unique_ptr<shape>>
|
||||
{
|
||||
static void to_json(json& j, const std::unique_ptr<shape>& p)
|
||||
{
|
||||
j = p ? p->serialize() : json(nullptr);
|
||||
}
|
||||
|
||||
static void from_json(const json& j, std::unique_ptr<shape>& p)
|
||||
{
|
||||
if (j.is_null())
|
||||
{
|
||||
p.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& type = j.at("type").get_ref<const std::string&>();
|
||||
if (type == "circle")
|
||||
{
|
||||
p = std::make_unique<circle>(j.at("radius").get<double>());
|
||||
}
|
||||
else if (type == "rectangle")
|
||||
{
|
||||
p = std::make_unique<rectangle>(j.at("width").get<double>(), j.at("height").get<double>());
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::invalid_argument("unknown shape type: " + type);
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace nlohmann
|
||||
|
||||
int main()
|
||||
{
|
||||
std::vector<std::unique_ptr<shape>> shapes;
|
||||
shapes.push_back(std::make_unique<circle>(1.5));
|
||||
shapes.push_back(std::make_unique<rectangle>(2, 3));
|
||||
|
||||
const json j = shapes;
|
||||
std::cout << j << '\n';
|
||||
|
||||
// reading creates objects of the derived types
|
||||
const auto back = j.get<std::vector<std::unique_ptr<shape>>>();
|
||||
std::cout << std::boolalpha;
|
||||
std::cout << "first is a circle: " << (dynamic_cast<circle*>(back[0].get()) != nullptr) << '\n';
|
||||
std::cout << "second is a rectangle: " << (dynamic_cast<rectangle*>(back[1].get()) != nullptr) << '\n';
|
||||
|
||||
// an unknown type tag is reported
|
||||
try
|
||||
{
|
||||
json{{"type", "star"}}.get<std::unique_ptr<shape>>();
|
||||
}
|
||||
catch (const std::invalid_argument& e)
|
||||
{
|
||||
std::cout << e.what() << '\n';
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
[{"radius":1.5,"type":"circle"},{"height":3.0,"type":"rectangle","width":2.0}]
|
||||
first is a circle: true
|
||||
second is a rectangle: true
|
||||
unknown shape type: star
|
||||
@@ -1,90 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace nlohmann
|
||||
{
|
||||
template <typename T>
|
||||
struct adl_serializer<std::shared_ptr<T>>
|
||||
{
|
||||
static void to_json(json& j, const std::shared_ptr<T>& p)
|
||||
{
|
||||
if (p)
|
||||
{
|
||||
j = *p;
|
||||
}
|
||||
else
|
||||
{
|
||||
j = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static void from_json(const json& j, std::shared_ptr<T>& p)
|
||||
{
|
||||
if (j.is_null())
|
||||
{
|
||||
p = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
p = std::make_shared<T>(j.get<typename std::remove_const<T>::type>());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct adl_serializer<std::unique_ptr<T>>
|
||||
{
|
||||
static void to_json(json& j, const std::unique_ptr<T>& p)
|
||||
{
|
||||
if (p)
|
||||
{
|
||||
j = *p;
|
||||
}
|
||||
else
|
||||
{
|
||||
j = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static void from_json(const json& j, std::unique_ptr<T>& p)
|
||||
{
|
||||
if (j.is_null())
|
||||
{
|
||||
p = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
p = std::make_unique<T>(j.get<T>());
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace nlohmann
|
||||
|
||||
struct point
|
||||
{
|
||||
int x;
|
||||
int y;
|
||||
};
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(point, x, y)
|
||||
|
||||
int main()
|
||||
{
|
||||
// an empty pointer is written as null
|
||||
const std::vector<std::shared_ptr<point>> points = {std::make_shared<point>(point{1, 2}), nullptr};
|
||||
const json j = points;
|
||||
std::cout << j << '\n';
|
||||
|
||||
// reading allocates new objects
|
||||
const auto back = j.get<std::vector<std::shared_ptr<point>>>();
|
||||
std::cout << back[0]->x << ", " << back[0]->y << ", " << (back[1] == nullptr) << '\n';
|
||||
|
||||
// pointers to const and unique_ptr work as well
|
||||
const auto const_ptr = j[0].get<std::shared_ptr<const point>>();
|
||||
const auto unique = j[0].get<std::unique_ptr<point>>();
|
||||
std::cout << const_ptr->y << ", " << unique->x << '\n';
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
[{"x":1,"y":2},null]
|
||||
1, 2, 1
|
||||
2, 1
|
||||
@@ -1,63 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace geo
|
||||
{
|
||||
struct circ
|
||||
{
|
||||
double radius;
|
||||
};
|
||||
|
||||
struct rect
|
||||
{
|
||||
double width;
|
||||
double height;
|
||||
};
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(circ, radius)
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(rect, width, height)
|
||||
|
||||
using figure = std::variant<circ, rect>;
|
||||
|
||||
void to_json(json& j, const figure& f)
|
||||
{
|
||||
std::visit([&j](const auto & alternative)
|
||||
{
|
||||
j = alternative;
|
||||
}, f);
|
||||
j["type"] = std::holds_alternative<circ>(f) ? "circ" : "rect";
|
||||
}
|
||||
|
||||
void from_json(const json& j, figure& f)
|
||||
{
|
||||
const auto& type = j.at("type").get_ref<const std::string&>();
|
||||
if (type == "circ")
|
||||
{
|
||||
f = j.get<circ>();
|
||||
}
|
||||
else if (type == "rect")
|
||||
{
|
||||
f = j.get<rect>();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::invalid_argument("unknown figure type: " + type);
|
||||
}
|
||||
}
|
||||
} // namespace geo
|
||||
|
||||
int main()
|
||||
{
|
||||
const std::vector<geo::figure> figures = {geo::circ{2}, geo::rect{3, 4}};
|
||||
|
||||
const json j = figures;
|
||||
std::cout << j << '\n';
|
||||
|
||||
const auto back = j.get<std::vector<geo::figure>>();
|
||||
std::cout << "alternatives read back: " << back[0].index() << ", " << back[1].index() << '\n';
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
[{"radius":2.0,"type":"circ"},{"height":4.0,"type":"rect","width":3.0}]
|
||||
alternatives read back: 0, 1
|
||||
@@ -409,32 +409,6 @@ namespace nlohmann {
|
||||
}
|
||||
```
|
||||
|
||||
## Why isn't my `to_json` or `from_json` found?
|
||||
|
||||
The library calls `to_json` and `from_json` without qualification, so the compiler finds them by
|
||||
[argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl) in the namespace of the type, or in the
|
||||
namespaces of its template arguments. The usual reasons for a function that is "not found" are:
|
||||
|
||||
- **The type is an alias of a standard type.** After `#!cpp using Hash = std::array<std::uint8_t, 6>;` the type
|
||||
*is* `std::array<std::uint8_t, 6>`, which lives in namespace `std`. A `to_json(json&, const Hash&)` in your namespace
|
||||
is never selected. Either give the type its own identity, for example `#!cpp struct Hash : std::array<std::uint8_t, 6> {};`
|
||||
(the function is then found in `Hash`'s namespace), or specialize
|
||||
[`adl_serializer<Hash>`](#how-do-i-convert-third-party-types).
|
||||
- **The function is in the wrong namespace.** It must be in the namespace of the type (a global type needs a global
|
||||
function), not in the namespace of the code that calls the conversion. Specializations of `adl_serializer` belong in
|
||||
namespace `nlohmann`.
|
||||
- **It is declared too late.** A conversion can only use what has been declared when it is instantiated. If
|
||||
`#!cpp json j = std::vector<third_party::string>{...};` appears before the `adl_serializer` specialization, the
|
||||
compiler reports an error such as "explicit specialization of `adl_serializer<...>` after instantiation". Put the
|
||||
specialization in a small header that every file includes **before** it uses the type with `json`.
|
||||
- **It is a template.** For `template <class T> struct box`, write `template <class T> void to_json(json&, const box<T>&)`,
|
||||
not an overload for one `T`.
|
||||
- **It worked by accident.** Some versions found `to_json` overloads in `nlohmann::detail` through argument-dependent
|
||||
lookup of the base class of `basic_json`. Do not rely on this; it is not part of the interface.
|
||||
|
||||
For smart pointers, class hierarchies, `std::variant`, and `std::chrono`, see
|
||||
[Recipes for common types](type_recipes.md).
|
||||
|
||||
## Why can't I convert to/from `std::any`?
|
||||
|
||||
`std::any` is intentionally excluded from `get<T>()`/generic conversion support, so `get<std::any>()` and
|
||||
@@ -531,7 +505,6 @@ struct bad_serializer
|
||||
|
||||
## See also
|
||||
|
||||
- [Recipes for common types](type_recipes.md) - smart pointers, class hierarchies, `std::variant`, and `std::chrono`
|
||||
- [Converting values](conversions.md) - the general overview of `get`/`get_to` and implicit conversions
|
||||
- [Specializing enum conversion](enum_conversion.md) - map enums to JSON strings instead of integers
|
||||
- [Supported macros](macros.md) - reference for `NLOHMANN_DEFINE_TYPE_*` and related macros
|
||||
|
||||
@@ -7,9 +7,7 @@ detect bugs in client code.
|
||||
## Switch off runtime assertions
|
||||
|
||||
Runtime assertions can be switched off by defining the preprocessor macro `NDEBUG` (see the
|
||||
[documentation of assert](https://en.cppreference.com/w/cpp/error/assert)) which is the default for release builds. Without `NDEBUG`, the assertions are compiled in and contain the name of the source
|
||||
file (`__FILE__`); see [notes for builds and distribution](../integration/index.md#notes-for-builds-and-distribution)
|
||||
if that path must not end up in your binaries.
|
||||
[documentation of assert](https://en.cppreference.com/w/cpp/error/assert)) which is the default for release builds.
|
||||
|
||||
## Change assertion behavior
|
||||
|
||||
|
||||
@@ -112,29 +112,6 @@ Implicit conversions are convenient but can be surprising (for example, in overl
|
||||
be disabled by defining [`JSON_USE_IMPLICIT_CONVERSIONS`](../api/macros/json_use_implicit_conversions.md) to `#!cpp 0`,
|
||||
which forces the explicit `get` form and can catch unintended conversions at compile time.
|
||||
|
||||
!!! warning "`auto` copies the JSON value; it does not convert it"
|
||||
|
||||
The type of `#!cpp j["name"]` is `json`, not the C++ type of the value inside. `#!cpp auto` therefore deduces
|
||||
`json`:
|
||||
|
||||
```cpp
|
||||
json j = {{"name", "Mary"}, {"age", 42}};
|
||||
|
||||
auto name = j["name"]; // json: a copy of the value; prints as "Mary" (with quotes)
|
||||
std::string s1 = j["name"]; // std::string: implicit conversion
|
||||
auto s2 = j["name"].get<std::string>(); // std::string: explicit conversion
|
||||
auto& age = j["age"]; // json&: a reference, no copy
|
||||
age = 43; // changes j
|
||||
```
|
||||
|
||||
This is also why `#!cpp std::cout << j["name"]` prints the quotes (see
|
||||
[getting a string without quotes](#getting-values-out)), and why a `json` value cannot be passed to `printf`: the
|
||||
argument is not an `int` or a `char*`, and a compiler either rejects passing it through `...` (Clang does) or
|
||||
compiles it and prints garbage. Convert first: `#!cpp std::printf("%d", j["age"].get<int>());`.
|
||||
|
||||
Prefer `get<T>()` over `auto` when you want the C++ value, and `auto&` or `const auto&` when you want to avoid a
|
||||
copy of a large value.
|
||||
|
||||
!!! warning "Conversions do not range-check numbers"
|
||||
|
||||
Just like C++ itself, the `get` family performs numeric conversions without range checks — retrieving a
|
||||
|
||||
@@ -59,25 +59,6 @@ with a string as the first element is treated as an object, everything else as a
|
||||
[FAQ](../home/faq.md#brace-initialization-yields-arrays) for details and the opt-in
|
||||
[`JSON_BRACE_INIT_COPY_SEMANTICS`](../api/macros/json_brace_init_copy_semantics.md) macro.
|
||||
|
||||
### What does this produce?
|
||||
|
||||
The following table shows how some common spellings are interpreted. When in doubt, use `#!cpp json::array()`,
|
||||
`#!cpp json::object()`, or the `#!cpp json(value)` form with parentheses.
|
||||
|
||||
| code | result | note |
|
||||
|-------------------------------------------------|------------------------------|----------------------------------------------|
|
||||
| `#!cpp json j = {};` / `#!cpp json j{};` | `#!json null` | use `json::object()` or `json::array()` |
|
||||
| `#!cpp json j = json::object();` | `#!json {}` | |
|
||||
| `#!cpp json j = json::array();` | `#!json []` | |
|
||||
| `#!cpp json j(true);` | `#!json true` | parentheses create the value itself |
|
||||
| `#!cpp json j = {true};` | `#!json [true]` | braces create a one-element array (`#!cpp json j{true};` depends on the compiler, see the [FAQ](../home/faq.md#brace-initialization-yields-arrays)) |
|
||||
| `#!cpp json j = {{"a", 1}};` | `#!json {"a":1}` | all elements are `[string, value]` pairs: object |
|
||||
| `#!cpp json j = {{"a", 1}, {"b"}};` | `#!json [["a",1],["b"]]` | one element is not a pair: array |
|
||||
| `#!cpp json j = {{"a", 1}, 2};` | `#!json [["a",1],2]` | a plain value among pairs: array |
|
||||
| `#!cpp json j = {{"a", 1, 2}};` | `#!json [["a",1,2]]` | a three-element list is not a pair: array |
|
||||
| `#!cpp json j = "{\"a\":1}";` | `#!json "{\"a\":1}"` | a string **value**; it is not parsed |
|
||||
| `#!cpp json j = json::parse("{\"a\":1}");` | `#!json {"a":1}` | parse text with `parse` or `_json` |
|
||||
|
||||
## Building incrementally
|
||||
|
||||
A value can also be built up piece by piece. Accessing a non-existing object key or array index with
|
||||
|
||||
@@ -21,42 +21,3 @@ flowchart TD
|
||||
A --> F{"just testing first?"}
|
||||
F -->|"yes"| G["contains() / find()"]
|
||||
```
|
||||
|
||||
## Does this key exist?
|
||||
|
||||
Use [`contains`](../../api/basic_json/contains.md) to test for a key; it does not modify the value and does not throw:
|
||||
|
||||
```cpp
|
||||
json j = {{"name", "Mary"}, {"address", {{"city", "Berlin"}}}};
|
||||
|
||||
j.contains("name"); // true
|
||||
j.contains("age"); // false
|
||||
j.contains("city"); // false: contains() only looks at the top level
|
||||
j["address"].contains("city"); // true
|
||||
j.contains(json::json_pointer("/address/city")); // true: a JSON pointer tests a nested path in one call
|
||||
```
|
||||
|
||||
If the value is needed afterwards, [`find`](../../api/basic_json/find.md) tests and accesses with a single lookup:
|
||||
|
||||
```cpp
|
||||
if (auto it = j.find("name"); it != j.end())
|
||||
{
|
||||
std::cout << *it << '\n';
|
||||
}
|
||||
```
|
||||
|
||||
`contains` also returns `true` for a key whose value is `null`; use `j.contains("k") && !j["k"].is_null()` if a
|
||||
`null` should count as missing. To read a value with a fallback instead, see [`value`](default_value.md).
|
||||
|
||||
!!! warning "`operator[]` inserts missing keys"
|
||||
|
||||
Like `#!cpp std::map::operator[]`, the non-const [`operator[]`](unchecked_access.md) adds a `null` value if the key
|
||||
is missing. Testing a key with it therefore *changes the object*:
|
||||
|
||||
```cpp
|
||||
json j = {{"foo", 1}};
|
||||
bool absent = j["bar"].is_null(); // true, but j is now {"bar": null, "foo": 1}
|
||||
```
|
||||
|
||||
Use `contains`/`find` to test, and [`at`](checked_access.md) to access a value that must exist (it throws an
|
||||
[`out_of_range`](../../home/exceptions.md#out-of-range) exception otherwise).
|
||||
@@ -1,113 +0,0 @@
|
||||
# Working Without Exceptions
|
||||
|
||||
By default, the library reports errors by throwing exceptions (see [Exceptions](../home/exceptions.md)). This page
|
||||
collects what to use when exceptions are unwanted or unavailable, for example in embedded systems, game engines, or
|
||||
code compiled with `-fno-exceptions`.
|
||||
|
||||
## Two different questions
|
||||
|
||||
1. **Can my code avoid *catching* exceptions?** Yes. For parsing and for accessing values there are functions that
|
||||
report failure through a return value. They work in a normal build and need no `#!cpp try`/`#!cpp catch`.
|
||||
2. **Can the library be *compiled* without exceptions?** Yes, with `-fno-exceptions` or by defining
|
||||
[`JSON_NOEXCEPTION`](../api/macros/json_noexception.md). Both have the same effect: every `#!cpp throw` in the
|
||||
library becomes a call to `std::abort()`. The functions listed below never reach a `#!cpp throw` for the failures
|
||||
they report, so they keep working in this configuration. Anything else that fails aborts the program.
|
||||
|
||||
## Parsing
|
||||
|
||||
| function | on invalid input |
|
||||
|---------------------------------------------------------------------------------------------|-----------------------------------------------------|
|
||||
| `#!cpp json::parse(input, nullptr, false)` | returns a *discarded* value; test with `is_discarded()` |
|
||||
| [`accept`](../api/basic_json/accept.md) | returns `#!cpp false` |
|
||||
| `#!cpp json::from_cbor(input, true, false)` (and the other `from_*` functions) | returns a *discarded* value |
|
||||
| [`sax_parse`](../api/basic_json/sax_parse.md) with your own handler | returns `#!cpp false` after calling `parse_error` |
|
||||
|
||||
??? example "Example: parse and validate without exceptions"
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/no_exceptions_parse.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
--8<-- "examples/no_exceptions_parse.output"
|
||||
```
|
||||
|
||||
None of the calls in this example throws, so it also compiles and runs with `-fno-exceptions`.
|
||||
|
||||
The discarded value carries no message. If you need the position or the reason, use the SAX interface; see
|
||||
[Parsing and exceptions](parsing/parse_exceptions.md).
|
||||
|
||||
## Accessing values
|
||||
|
||||
The access functions are not equally safe. Only some of them never throw:
|
||||
|
||||
| expression | if the key is missing or the type is wrong |
|
||||
|-------------------------------------|-------------------------------------------------------------------------------|
|
||||
| `#!cpp j.contains("k")` | returns `#!cpp false` for a missing key; never throws |
|
||||
| `#!cpp j.find("k")` | returns `end()` for a missing key; never throws |
|
||||
| `#!cpp j.is_string()`, ... | the `is_*` functions test the type; never throw |
|
||||
| `#!cpp j.get_ptr<const std::string*>()` | returns `#!cpp nullptr` for a different type; never throws |
|
||||
| `#!cpp j.at("k")` | throws `out_of_range.403` for a missing key |
|
||||
| `#!cpp j.get<int>()` | throws `type_error.302` for a value that is not a number |
|
||||
| `#!cpp j.value("k", 0)` | returns the default for a missing key, but **throws `type_error.302` if `k` exists with another type** |
|
||||
| `#!cpp j["k"]` (non-const) | inserts `null` for a missing key |
|
||||
| `#!cpp j["k"]` (const) | undefined behavior for a missing key |
|
||||
|
||||
So reading a value that may be absent or of the wrong type without exceptions takes two steps: look up, then test the
|
||||
type.
|
||||
|
||||
??? example "Example: look up a key and test its type"
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/no_exceptions_access.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
--8<-- "examples/no_exceptions_access.output"
|
||||
```
|
||||
|
||||
The last lines of the example show why `value` is not on the safe list: it returns the default only for a *missing*
|
||||
key.
|
||||
|
||||
For numbers, test with `is_number()` (or `is_number_integer()`, `is_number_unsigned()`, `is_number_float()`) before
|
||||
calling `get<int>()`, and see [number handling](types/number_handling.md) for range and precision. Whether a value
|
||||
fits into the target type is not checked by `get`.
|
||||
|
||||
## Converting to your own types
|
||||
|
||||
`from_json` functions (including the ones generated by the
|
||||
[`NLOHMANN_DEFINE_TYPE_*` macros](arbitrary_types.md#simplify-your-life-with-macros)) report problems by throwing.
|
||||
There is no variant that returns an error code. Without exceptions you have two options:
|
||||
|
||||
- Validate the JSON value yourself, as shown above, and fill your struct field by field.
|
||||
- Convert in a function that is compiled **with** exceptions enabled and catches them at the boundary, returning
|
||||
`std::optional` or an error code to the rest of your code:
|
||||
|
||||
??? example "Example: convert to a struct and report failure as std::optional"
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/no_exceptions_convert.c++17.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
--8<-- "examples/no_exceptions_convert.c++17.output"
|
||||
```
|
||||
|
||||
## Overriding what happens instead of an exception
|
||||
|
||||
With exceptions off, you can replace the `abort()` by your own code, for example to log the message first. See
|
||||
[Switch off exceptions](../home/exceptions.md#switch-off-exceptions) and
|
||||
[`JSON_THROW_USER`](../api/macros/json_throw_user.md). The handler must not return normally.
|
||||
|
||||
## See also
|
||||
|
||||
- [Exceptions](../home/exceptions.md) - all exceptions and their ids
|
||||
- [Parsing and exceptions](parsing/parse_exceptions.md) - the parsing alternatives in detail
|
||||
- [Parsing untrusted input](parsing/untrusted_input.md)
|
||||
- [`JSON_NOEXCEPTION`](../api/macros/json_noexception.md)
|
||||
@@ -33,9 +33,7 @@ json data = json::parse(f);
|
||||
```
|
||||
|
||||
The input must be encoded in UTF-8; other encodings are not supported. A single input may contain only one JSON value.
|
||||
Inputs consisting of multiple values separated by newlines are handled by the [JSON Lines](json_lines.md) format; input
|
||||
that arrives in chunks or contains several values is covered in
|
||||
[Large, streamed, and partial input](large_and_partial_input.md).
|
||||
Inputs consisting of multiple values separated by newlines are handled by the [JSON Lines](json_lines.md) format.
|
||||
|
||||
By default, the library rejects comments and trailing commas. Both can be enabled with parameters of the `parse`
|
||||
function — see [comments](../comments.md) and [trailing commas](../trailing_commas.md).
|
||||
@@ -87,7 +85,7 @@ again without changing any result.
|
||||
|
||||
## SAX vs. DOM parsing
|
||||
|
||||
The library offers two parsing models, and a third, read-only alternative:
|
||||
The library offers two parsing models:
|
||||
|
||||
- **DOM parsing** (the default): the complete input is read and stored as an in-memory `basic_json` value that can be
|
||||
traversed and modified freely. This is what [`parse`](../../api/basic_json/parse.md) does, and it is the right choice
|
||||
@@ -97,13 +95,6 @@ The library offers two parsing models, and a third, read-only alternative:
|
||||
inputs or when you only need to extract parts of the input. See the [SAX interface](sax_interface.md) for details and
|
||||
[`sax_parse`](../../api/basic_json/sax_parse.md) for the API.
|
||||
|
||||
- **Document and views** (`#!cpp <nlohmann/json_view.hpp>`, added in version 3.13.0): [`json_document::parse`](../../api/basic_json_document/parse.md)
|
||||
reads the same JSON grammar but builds a flat, read-only index of the values instead of a `basic_json` tree. Strings and
|
||||
floating-point numbers are read from the text on demand, and only the parts you
|
||||
[`materialize()`](../../api/basic_json_view/materialize.md) become `basic_json` values. This avoids most of the work when you
|
||||
need only a few values from a large or frequently parsed text; see [Zero-copy JSON views](../json_view.md). It is read-only, so use
|
||||
`parse` when you need to modify the value.
|
||||
|
||||
You can influence a DOM parse without switching to the SAX interface by passing a
|
||||
[parser callback](parser_callbacks.md), which is called during parsing and can, for example, discard parts of the input.
|
||||
|
||||
@@ -120,9 +111,7 @@ options.
|
||||
- [`accept`](../../api/basic_json/accept.md) - check if the input is valid JSON
|
||||
- [`sax_parse`](../../api/basic_json/sax_parse.md) - generate SAX events
|
||||
- [JSON Lines](json_lines.md) - parse newline-delimited JSON
|
||||
- [Large, streamed, and partial input](large_and_partial_input.md) - chunked, concatenated, and very large input
|
||||
- [parser callbacks](parser_callbacks.md) - influence the parsing by a callback function
|
||||
- [SAX interface](sax_interface.md) - implement a custom SAX handler
|
||||
- [Zero-copy JSON views](../json_view.md) - parse into a read-only document and read it through views
|
||||
- [parsing and exceptions](parse_exceptions.md) - control error handling
|
||||
- [parsing untrusted input](untrusted_input.md) - what to consider when parsing input from untrusted sources
|
||||
@@ -1,154 +0,0 @@
|
||||
# Large, Streamed, and Partial Input
|
||||
|
||||
[`parse`](../../api/basic_json/parse.md) reads exactly one complete JSON value and builds it in memory. It cannot be
|
||||
paused and resumed, and it does not hand back a half-parsed value when the input ends early. This page shows what to do
|
||||
when the input does not fit that model: it arrives in pieces, it contains several values, or it is too large to hold
|
||||
as a `json` value.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["my input is..."] --> B{"several JSON values in one stream?"}
|
||||
B -->|"yes"| C["read them one at a time"]
|
||||
A --> D{"arriving in chunks (socket, pipe)?"}
|
||||
D -->|"yes"| E["frame the messages, or buffer until complete"]
|
||||
A --> F{"too large to keep in memory?"}
|
||||
F -->|"yes"| G["discard parts with a callback, or use SAX"]
|
||||
A --> H{"only a small part is needed?"}
|
||||
H -->|"yes"| I["stop early with SAX, or read through a view"]
|
||||
```
|
||||
|
||||
## Several values in one stream
|
||||
|
||||
For newline-delimited input, read line by line and parse each line, as described in [JSON Lines](json_lines.md). For
|
||||
values that are simply concatenated, [`operator>>`](../../api/operator_gtgt.md) reads one value and leaves the stream
|
||||
positioned after it. Check for the end of the stream before each read, because a read at the end throws:
|
||||
|
||||
??? example "Example: read concatenated values"
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/partial_input_concatenated.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
--8<-- "examples/partial_input_concatenated.output"
|
||||
```
|
||||
|
||||
The same works for binary formats. [`from_cbor`](../../api/basic_json/from_cbor.md) and its relatives accept
|
||||
`#!cpp strict = false` to stop after the first value and leave the rest of the stream untouched; `tellg()` then reports
|
||||
how many bytes the value took:
|
||||
|
||||
??? example "Example: read one CBOR value and find out how many bytes it used"
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/partial_input_cbor.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
--8<-- "examples/partial_input_cbor.output"
|
||||
```
|
||||
|
||||
## Input arriving in chunks
|
||||
|
||||
A socket, pipe, or HTTP body hands over data in pieces of arbitrary size, and a piece can end in the middle of a
|
||||
number, a string, or an object. The parser does not keep state between calls, so there are two ways to deal with this.
|
||||
|
||||
**Frame the messages.** The most robust approach is a protocol that says where a value ends: one value per line
|
||||
([JSON Lines](json_lines.md)), a length prefix, or a delimiter. Collect the bytes of one message and parse them when
|
||||
the frame is complete. Prefer this whenever you control the protocol.
|
||||
|
||||
**Buffer until the value is complete.** If the data has no framing, append every chunk to a buffer and try to parse
|
||||
it. A parse error at the very end of the buffer means that the input ended prematurely, while an error anywhere else is
|
||||
a real syntax error. The member `byte` of the [`parse_error`](../../home/exceptions.md#jsonexceptionparse_error101)
|
||||
exception is one past the buffer size in the first case:
|
||||
|
||||
??? example "Example: buffer chunks until the value is complete"
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/partial_input_buffering.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
--8<-- "examples/partial_input_buffering.output"
|
||||
```
|
||||
|
||||
The example feeds a document in five chunks. The first four are reported as incomplete, and a syntax error is reported as
|
||||
such, not as missing data.
|
||||
|
||||
!!! warning "Limits of buffering"
|
||||
|
||||
- The buffer must contain exactly one value. Data that belongs to the next value is a syntax error. Use framing if
|
||||
values follow each other without a separator.
|
||||
- A number at the end of the buffer is a complete value, even if more digits are still to come (`12` may be the
|
||||
start of `123`). Buffering cannot detect this; use framing for top-level numbers.
|
||||
- Every attempt parses the buffer from the beginning, so the total work grows quadratically with the size of the
|
||||
value. Limit the size of the buffer for input you do not trust (see [untrusted input](untrusted_input.md)).
|
||||
|
||||
## Input too large for memory
|
||||
|
||||
The input does not have to become one huge `json` value. The two tools below process it piece by piece.
|
||||
|
||||
### Handle and discard elements with a callback
|
||||
|
||||
A [parser callback](parser_callbacks.md) is called for every finished value. If it returns `#!cpp false`, the value is
|
||||
discarded and its memory is released at once. For a file that consists of one huge array (or one huge object) of
|
||||
records, handle each record at depth 1 and discard it:
|
||||
|
||||
??? example "Example: handle and discard each record of a large array"
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/partial_input_discard_records.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
--8<-- "examples/partial_input_discard_records.output"
|
||||
```
|
||||
|
||||
Only one record is in memory at any time. The same recipe, with more detail, is in the
|
||||
[parser callbacks](parser_callbacks.md#recipe-streaming-a-large-homogeneous-array) page. To skip entire members you do not
|
||||
need, discard them when their key is read; see the [example](parser_callbacks.md) for `parse_event_t::key`.
|
||||
|
||||
### Extract a few values and stop with SAX
|
||||
|
||||
With the [SAX interface](sax_interface.md), no value is built at all, and **returning `#!cpp false` from any handler
|
||||
function stops the parser**. [`sax_parse`](../../api/basic_json/sax_parse.md) then returns `#!cpp false`, and the rest
|
||||
of the input is not read. The example finds the first `id` in a document without parsing the remainder:
|
||||
|
||||
??? example "Example: stop parsing as soon as a value is found"
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/partial_input_sax_stop.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
--8<-- "examples/partial_input_sax_stop.output"
|
||||
```
|
||||
|
||||
Note that `sax_parse` returns `#!cpp false` both when a handler stopped the parse and when the input is invalid, so
|
||||
record the reason in the handler (here: `id`) if you need to tell the two apart.
|
||||
|
||||
### Read parts of a large document with a view
|
||||
|
||||
If the whole text is available and you only need to read some of it, a [`json_document`](../json_view.md) from
|
||||
`<nlohmann/json_view.hpp>` (added in version 3.13.0) avoids the `basic_json` tree altogether: it keeps the text, builds a flat index of the values,
|
||||
and gives read-only [views](../../api/basic_json_view/index.md) of the parts you look at. Only what you
|
||||
[`materialize()`](../../api/basic_json_view/materialize.md) becomes a `basic_json` value. See
|
||||
[Zero-copy JSON views](../json_view.md) for details and for the lifetime rules.
|
||||
|
||||
## See also
|
||||
|
||||
- [Parsing](index.md) - overview of the parsing functions
|
||||
- [JSON Lines](json_lines.md) - newline-delimited JSON
|
||||
- [Parser callbacks](parser_callbacks.md) - discard values while parsing
|
||||
- [SAX interface](sax_interface.md) - event-based parsing
|
||||
- [Zero-copy JSON views](../json_view.md) - read parts of a large document without building a tree
|
||||
- [Untrusted input](untrusted_input.md) - limits for input you do not control
|
||||
@@ -21,10 +21,6 @@ catch (json::parse_error& ex)
|
||||
In case exceptions are undesired or not supported by the environment, there are different ways to proceed:
|
||||
|
||||
|
||||
!!! tip
|
||||
|
||||
[Working without exceptions](../no_exceptions.md) also covers accessing values and converting to your own types.
|
||||
|
||||
## Switch off exceptions
|
||||
|
||||
The [`parse()`](../../api/basic_json/parse.md) function accepts a `#!cpp bool` parameter `allow_exceptions` which
|
||||
@@ -85,61 +81,3 @@ The return value indicates whether the parsing should continue, so the function
|
||||
```
|
||||
--8<-- "examples/sax_no_exception.output"
|
||||
```
|
||||
|
||||
## Where did the error occur?
|
||||
|
||||
The message of a [`parse_error`](../../home/exceptions.md#jsonexceptionparse_error101) for JSON text contains the line
|
||||
and column of the error:
|
||||
|
||||
```
|
||||
[json.exception.parse_error.101] parse error at line 3, column 8: syntax error while parsing value - unexpected ']'; expected '[', '{', or a literal
|
||||
```
|
||||
|
||||
The exception object itself only stores the **byte index** of the last read character in the member `byte` (the first
|
||||
byte of the input has index 1). The same index is passed to `parse_error` in the [SAX interface](sax_interface.md). If
|
||||
you need the line and column as numbers, for example to show them in an editor, compute them from the input:
|
||||
|
||||
??? example "Example: compute line and column of a parse error"
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/parse_error_position.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
--8<-- "examples/parse_error_position.output"
|
||||
```
|
||||
|
||||
Columns count bytes, so a line with multi-byte UTF-8 characters before the error may differ from the column your editor
|
||||
shows.
|
||||
|
||||
### Where is the value that caused a later error?
|
||||
|
||||
A `parse_error` is raised while the text is read. Many mistakes show up later: the text is valid JSON, but a value has
|
||||
the wrong type when you convert it (`type_error.302`), or a key is missing (`out_of_range.403`). The exception then says
|
||||
what is wrong, but not where in the input the value was. Define
|
||||
[`JSON_DIAGNOSTIC_POSITIONS`](../../api/macros/json_diagnostic_positions.md) before including the library to add the
|
||||
byte range of the offending value to such messages (since version 3.12.0), and to query the range of any value with
|
||||
[`start_pos()`](../../api/basic_json/start_pos.md) and [`end_pos()`](../../api/basic_json/end_pos.md):
|
||||
|
||||
??? example "Example: locate a value with diagnostic positions"
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/parse_error_position_diagnostic.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
--8<-- "examples/parse_error_position_diagnostic.output"
|
||||
```
|
||||
|
||||
Positions are only available for values created by [`parse`](../../api/basic_json/parse.md), they are not updated when a
|
||||
value is changed, and the macro makes every value 16 bytes larger on 64-bit platforms, so use it where the cost is
|
||||
acceptable, for example for configuration files. Note that `start_pos()` is a 0-based index into the input, whereas
|
||||
`byte` of a `parse_error` starts at 1.
|
||||
|
||||
For binary formats such as CBOR, there are no lines; `byte` is the position in the byte sequence. To find out how many
|
||||
bytes a successfully parsed value used, see
|
||||
[Several values in one stream](large_and_partial_input.md#several-values-in-one-stream).
|
||||
@@ -26,11 +26,9 @@ several alternatives; see [Parsing and exceptions](parse_exceptions.md) for the
|
||||
[example](parse_exceptions.md#user-defined-sax-interface) that overrides it to print instead of throw.
|
||||
|
||||
If exceptions are unavailable entirely (`-fno-exceptions`, or [`JSON_NOEXCEPTION`](../../api/macros/json_noexception.md)
|
||||
defined), every `#!cpp throw` in the library becomes a call to `std::abort()`. The alternatives above do not throw, so
|
||||
`parse()` with `allow_exceptions` set to `#!cpp false` (and likewise `accept()` and the `from_*` functions with
|
||||
`allow_exceptions` set to `#!cpp false`) can still reject invalid input in that configuration; a plain `parse()` call
|
||||
aborts on invalid input. See [Working without exceptions](../no_exceptions.md) and
|
||||
[Switch off exceptions](../../home/exceptions.md#switch-off-exceptions) for the details and for overriding the abort with
|
||||
defined), every `#!cpp throw` in the library becomes a call to `std::abort()` -- there is no way to recover from a
|
||||
parse error of untrusted input in that configuration; see
|
||||
[Switch off exceptions](../../home/exceptions.md#switch-off-exceptions) for the details and for overriding this with
|
||||
`JSON_THROW_USER`.
|
||||
|
||||
## Nesting depth
|
||||
|
||||
@@ -122,9 +122,7 @@ an adapter, since their template argument order does not match what `basic_json`
|
||||
[linear in its size](../api/basic_json/basic_json.md#complexity) for the copy constructor but
|
||||
[constant](../api/basic_json/basic_json.md#complexity) for the move constructor. The same applies to assigning a
|
||||
large `#!cpp std::string`, `#!cpp std::vector`, or other container into a value: pass it as `#!cpp std::move(x)`
|
||||
rather than `x` whenever `x` is no longer needed afterwards. Since version 3.13.0, converting an rvalue container
|
||||
(`#!cpp json j = std::move(vec);`) moves the elements instead of copying them; the source is then left in a valid but
|
||||
unspecified state.
|
||||
rather than `x` whenever `x` is no longer needed afterwards.
|
||||
- **Access without copying.** [`get<T>()`](../api/basic_json/get.md) returns a copy of the stored value converted to
|
||||
`T`. When a reference or pointer to the value already stored inside the `basic_json` is enough,
|
||||
[`get_ref()`](../api/basic_json/get_ref.md) and [`get_ptr()`](../api/basic_json/get_ptr.md) access it directly:
|
||||
@@ -153,29 +151,6 @@ an adapter, since their template argument order does not match what `basic_json`
|
||||
["reserving array capacity"](element_access/unchecked_access.md#performance-reserving-array-capacity) for the
|
||||
one-line recipe.
|
||||
|
||||
## Memory
|
||||
|
||||
- **A value is small, its content is on the heap.** A `basic_json` is 16 bytes on a 64-bit platform (a type tag and a
|
||||
union). Strings, arrays, objects, and binary values are allocated on the heap and owned by the value. Destroying the
|
||||
value frees everything it owns.
|
||||
- **Copies are deep.** After `#!cpp json b = a;`, changing `b` does not change `a`, and both own their memory. Copying a
|
||||
large value therefore doubles its memory; see [Avoiding copies](#avoiding-copies). `#!cpp auto x = j["key"];` is a
|
||||
copy, `#!cpp auto& x = j["key"];` is not.
|
||||
- **A moved-from value is `null`.** After `#!cpp json c = std::move(a);` the value `a` is `#!json null` and owns nothing.
|
||||
- **Reading does not require a tree.** If you only read the data, parse it into a [`json_document`](json_view.md)
|
||||
(`#!cpp <nlohmann/json_view.hpp>`, added in version 3.13.0) instead of a `basic_json`. It stores 16 bytes per value, does not copy floating-point
|
||||
numbers or strings without escape sequences out of the text, and creates no `basic_json` value except for the parts
|
||||
you [`materialize()`](../api/basic_json_view/materialize.md). A document that borrows the text needs the text to
|
||||
outlive it; see [Zero-copy JSON views](json_view.md).
|
||||
- **Containers keep their capacity.** `clear()` and `erase()` on an array remove the elements, but the underlying
|
||||
`#!cpp std::vector` keeps its allocated capacity, like `#!cpp std::vector::clear` does. To give the memory back,
|
||||
assign a new value (`#!cpp j = json::array();`) or destroy the value.
|
||||
- **Freed memory does not always go back to the operating system.** The library returns memory to the allocator
|
||||
(`#!cpp std::allocator` by default, or the [one you configured](types/template_parameters.md)). Whether the process
|
||||
size shrinks then depends on the allocator and the platform, as for any C++ program. A process that parses a large
|
||||
document and drops it is not leaking if its resident size stays high; run it under a tool such as
|
||||
[Valgrind](https://valgrind.org) or the sanitizers to tell a leak from retained memory.
|
||||
|
||||
## Serialization
|
||||
|
||||
[`dump()`](../api/basic_json/dump.md) with the default `#!cpp indent = -1` selects "the most compact representation"
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
# Recipes for Common Types
|
||||
|
||||
The library converts the standard containers, strings, numbers, tuples, `std::optional`, and `std::filesystem::path`
|
||||
(the last two since version 3.12.0, C++17) out of the box, and your own types once you write conversion functions or use
|
||||
the macros (see [Arbitrary type conversions](arbitrary_types.md)). It deliberately does **not** convert smart pointers,
|
||||
`std::variant`, `std::chrono` types, or class hierarchies, because there is more than one reasonable JSON
|
||||
representation for each of them. This page shows a representation for each that you can copy and adapt.
|
||||
|
||||
All recipes use a specialization of [`adl_serializer`](../api/adl_serializer/index.md), which you may provide for types
|
||||
you do not own, or free `to_json`/`from_json` functions in the namespace of the type. Place them where they are
|
||||
visible before any code that converts the type; see
|
||||
[Why isn't my `to_json` found?](arbitrary_types.md#why-isnt-my-to_json-or-from_json-found).
|
||||
|
||||
## Smart pointers
|
||||
|
||||
A `std::shared_ptr<T>` or `std::unique_ptr<T>` is written as the value it points to, or as `#!json null` for an empty
|
||||
pointer, and read back by allocating a new `T`:
|
||||
|
||||
??? example "Example: serialize shared_ptr and unique_ptr"
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/type_recipes_smart_pointers.c++14.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
--8<-- "examples/type_recipes_smart_pointers.c++14.output"
|
||||
```
|
||||
|
||||
With this, containers of smart pointers, pointers to `const`, and members of these types in your own structs convert as
|
||||
usual. A pointer to a base class cannot be filled this way, because `T` would have
|
||||
to be created as a derived type; see the next section.
|
||||
|
||||
Reading creates a new object. Two `shared_ptr`s that pointed to the same object are written twice and read back as two
|
||||
separate objects.
|
||||
|
||||
## Class hierarchies
|
||||
|
||||
!!! tip "Derived classes whose type is known at compile time"
|
||||
|
||||
If you only need to serialize the data members of a derived class, and the code always knows the concrete type, you do
|
||||
not need a type tag. The [`NLOHMANN_DEFINE_DERIVED_TYPE_*` macros](arbitrary_types.md#simplify-your-life-with-macros)
|
||||
(since version 3.12.0) add the members of the derived class to those of its base class. This section is for the case
|
||||
where the concrete type is only known at run time, such as a `std::unique_ptr<shape>` that holds a circle or a
|
||||
rectangle.
|
||||
|
||||
JSON has no notion of types, so a value in a hierarchy needs a **type tag** that says which derived class to create.
|
||||
Write the tag in the object, and select the class from it when reading:
|
||||
|
||||
??? example "Example: serialize a class hierarchy with a type tag"
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/type_recipes_class_hierarchy.c++14.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
--8<-- "examples/type_recipes_class_hierarchy.c++14.output"
|
||||
```
|
||||
|
||||
A `#!cpp std::vector<std::unique_ptr<shape>>` converts to an array of tagged objects and back, creating a `circle`
|
||||
and a `rectangle`. The same works for `std::shared_ptr<shape>`. Remember the virtual destructor: without it, deleting a
|
||||
derived object through the base pointer is undefined behavior.
|
||||
|
||||
## `std::variant`
|
||||
|
||||
A variant has the same problem: the alternative must be recorded. If the alternatives are your own types, write a type
|
||||
tag next to their members, exactly as for a hierarchy:
|
||||
|
||||
??? example "Example: serialize a std::variant with a type tag"
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/type_recipes_variant.c++17.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
--8<-- "examples/type_recipes_variant.c++17.output"
|
||||
```
|
||||
|
||||
In the example, the functions are found
|
||||
because `geo` is the namespace of the template arguments of `#!cpp std::variant<circ, rect>`; for a variant of only
|
||||
standard types (for example `#!cpp std::variant<int, std::string>`) there is no such namespace, and you need an
|
||||
`adl_serializer` specialization or a wrapper type of your own.
|
||||
|
||||
If the alternatives are JSON-native (numbers, strings, ...), a variant such as `#!cpp std::variant<int, std::string>`
|
||||
can instead be written as the plain value, and read by testing the JSON type (`is_number_integer()`, `is_string()`)
|
||||
to choose the alternative.
|
||||
|
||||
## `std::chrono`
|
||||
|
||||
A duration is written as its count and read back with the same representation and period. The **unit is not part of
|
||||
the JSON value**, so document it, for example in the member name (`timeout_ms`):
|
||||
|
||||
??? example "Example: serialize durations and system_clock time points"
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/type_recipes_chrono.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
--8<-- "examples/type_recipes_chrono.output"
|
||||
```
|
||||
|
||||
A `system_clock::time_point` with millisecond precision is written as the number of milliseconds since the Unix epoch.
|
||||
The time point serializer is limited to `system_clock`,
|
||||
because the epoch of other clocks (for example `steady_clock`) has no meaning outside the running program.
|
||||
|
||||
If you prefer a text representation such as ISO 8601 (`2023-11-14T22:13:20Z`), write the string with
|
||||
`#!cpp std::format("{:%FT%TZ}", t)` and read it with `std::chrono::parse` (both C++20), or with a date library, in the
|
||||
same two functions.
|
||||
|
||||
## See also
|
||||
|
||||
- [Arbitrary type conversions](arbitrary_types.md) - the conversion mechanism and macros
|
||||
- [Why isn't my `to_json` found?](arbitrary_types.md#why-isnt-my-to_json-or-from_json-found)
|
||||
- [Converting values](conversions.md) - `get`, implicit conversions, and `std::optional`
|
||||
@@ -39,8 +39,6 @@ classDiagram
|
||||
|
||||
Exceptions are used widely within the library. They can, however, be switched off with either using the compiler flag `-fno-exceptions` or by defining the symbol [`JSON_NOEXCEPTION`](../api/macros/json_noexception.md). In this case, exceptions are replaced by `abort()` calls. You can further control this behavior by defining `JSON_THROW_USER` (overriding `#!cpp throw`), `JSON_TRY_USER` (overriding `#!cpp try`), and `JSON_CATCH_USER` (overriding `#!cpp catch`).
|
||||
|
||||
See [Working without exceptions](../features/no_exceptions.md) for the functions that report errors without throwing.
|
||||
|
||||
Note that [`JSON_THROW_USER`](../api/macros/json_throw_user.md) should leave the current scope (e.g., by throwing or aborting), as continuing after it may yield undefined behavior.
|
||||
|
||||
??? example "Example: switch off exceptions and log errors before aborting"
|
||||
@@ -194,34 +192,6 @@ This error indicates a syntax error while deserializing a JSON text. The error m
|
||||
- Make sure the input is correctly read. Try to write the input to standard output to check if, for instance, the input file was successfully opened.
|
||||
- Paste the input to a JSON validator like <https://jsonlint.com> or a tool like [jq](https://jqlang.github.io/jq/).
|
||||
|
||||
??? info "Parsing a file fails at line 1, column 1 (or \"unexpected end of input\")"
|
||||
|
||||
The messages `attempting to parse an empty input` (since version 3.11.3) and `unexpected end of input` (also the message of
|
||||
earlier versions for an empty input) at the very start of the input mean
|
||||
that the parser did not see a single character. The library only reads from the stream it is given: it does not
|
||||
open files and cannot tell a missing file from an empty one. The usual causes are:
|
||||
|
||||
- **The file was not opened.** A relative path is resolved against the *working directory of the running program*,
|
||||
not against the source file or the project. IDEs and build tools often start the program in a build directory
|
||||
(for example `build/` or `cmake-build-debug/`), so `"example.json"` may need to be `"../example.json"`. Use an
|
||||
absolute path to rule this out.
|
||||
- **The file is empty**, or a previous run truncated it (for example when it was opened for writing).
|
||||
- **The stream was already read.** A stream whose content has been consumed (by printing it with
|
||||
`std::cout << file.rdbuf()`, or by an earlier `accept`/`parse`/`>>`) is at its end. Parse it once, or rewind it
|
||||
with `seekg(0)` after clearing the state with `clear()`.
|
||||
|
||||
Check the stream before parsing it:
|
||||
|
||||
```cpp
|
||||
std::ifstream f("example.json");
|
||||
if (!f.is_open())
|
||||
{
|
||||
std::cerr << "cannot open example.json\n";
|
||||
return 1;
|
||||
}
|
||||
json data = json::parse(f);
|
||||
```
|
||||
|
||||
### json.exception.parse_error.102
|
||||
|
||||
JSON uses the `\uxxxx` format to describe Unicode characters. Code points above 0xFFFF are split into two `\uxxxx` entries ("surrogate pairs"). This error indicates that the surrogate pair is incomplete or contains an invalid code point.
|
||||
|
||||
@@ -58,208 +58,6 @@ json j{obj}; // -> {"key":"value"} (copy, not array)
|
||||
|
||||
Without the macro (default behavior), `json j{obj}` creates `[{"key":"value"}]`. This opt-in macro fixes issue #5074 while preserving backwards compatibility for existing code.
|
||||
|
||||
## Getting started
|
||||
|
||||
### Which file do I need?
|
||||
|
||||
!!! question
|
||||
|
||||
Do I have to clone the repository or build the library?
|
||||
|
||||
No. The library is header-only: the single file [`json.hpp`](https://github.com/nlohmann/json/releases) from the
|
||||
releases page is all you need, including the `_json` literals. Put it in a directory `nlohmann` on your include path.
|
||||
Companion headers (`json_fwd.hpp`, `json_view.hpp`, `json_literals.hpp`) are only needed if you use them. See
|
||||
[Integration](../integration/index.md#header-only).
|
||||
|
||||
### Using CMake or a package manager
|
||||
|
||||
!!! question
|
||||
|
||||
How do I add the library with CMake, Conan, vcpkg, Homebrew, or another tool?
|
||||
|
||||
See [CMake](../integration/cmake.md) for `find_package`, `add_subdirectory`, and `FetchContent`, and
|
||||
[Package managers](../integration/package_managers.md) for the list of supported package managers.
|
||||
|
||||
### The IDE cannot open the header
|
||||
|
||||
!!! question
|
||||
|
||||
The compiler finds `nlohmann/json.hpp`, but my IDE reports "cannot open source file". Why?
|
||||
|
||||
The compiler's include path is set correctly, but the IDE's is not. Add the same directory to the include paths of the
|
||||
IDE (or of its language server); the build itself is not affected.
|
||||
|
||||
### Compile times
|
||||
|
||||
!!! question
|
||||
|
||||
Including the library slows down my build. What can I do?
|
||||
|
||||
Include [`json_fwd.hpp`](../integration/compile_times.md#include-json_fwdhpp-in-headers) in headers and `json.hpp` only
|
||||
in the source files that need it, instantiate `basic_json` once, and consider precompiled headers or
|
||||
[C++20 modules](../features/modules.md). See [Compile times](../integration/compile_times.md).
|
||||
|
||||
## Parsing
|
||||
|
||||
### Parsing a file fails with "unexpected end of input"
|
||||
|
||||
!!! question
|
||||
|
||||
`json::parse(file)` throws `parse_error.101` at line 1, column 1 for a file that is valid JSON. Why?
|
||||
|
||||
The parser did not see any character: the file was most likely not opened. A relative path is resolved against the
|
||||
working directory of the running program, which is often the build directory of your IDE, not the source directory.
|
||||
Check `is_open()` and try an absolute path. Other causes are an empty file and a stream that was already read. See
|
||||
[`parse_error.101`](exceptions.md#jsonexceptionparse_error101).
|
||||
|
||||
### Input that arrives in chunks
|
||||
|
||||
!!! question
|
||||
|
||||
My data comes from a socket in pieces. How do I parse it?
|
||||
|
||||
The parser cannot be resumed. Frame the messages (one value per line, a length prefix) and parse each complete frame, or
|
||||
buffer the chunks until the value is complete; a `parse_error` at the end of the buffer means the input ended
|
||||
prematurely. See [Large, streamed, and partial input](../features/parsing/large_and_partial_input.md#input-arriving-in-chunks).
|
||||
|
||||
### Several values in one stream
|
||||
|
||||
!!! question
|
||||
|
||||
How do I read concatenated JSON values or one value per line?
|
||||
|
||||
Read newline-delimited input line by line ([JSON Lines](../features/parsing/json_lines.md)). For concatenated values, use
|
||||
`operator>>` and check for the end of the stream before each read, as shown in
|
||||
[Several values in one stream](../features/parsing/large_and_partial_input.md#several-values-in-one-stream).
|
||||
|
||||
### Files that are too large for memory
|
||||
|
||||
!!! question
|
||||
|
||||
How do I process a huge JSON file?
|
||||
|
||||
Handle and discard the records of a large array with a [parser callback](../features/parsing/parser_callbacks.md), stop
|
||||
early with the [SAX interface](../features/parsing/sax_interface.md), or read parts of the text through a
|
||||
[`json_document`](../features/json_view.md) (added in version 3.13.0). See
|
||||
[Input too large for memory](../features/parsing/large_and_partial_input.md#input-too-large-for-memory).
|
||||
|
||||
### Position of a parse error
|
||||
|
||||
!!! question
|
||||
|
||||
How do I get the line and column of a parse error?
|
||||
|
||||
The message contains them. The exception stores the byte index in `byte`, from which you can compute both; see
|
||||
[Where did the error occur?](../features/parsing/parse_exceptions.md#where-did-the-error-occur). To locate a value that
|
||||
causes a *later* error, such as a `type_error`, use
|
||||
[`JSON_DIAGNOSTIC_POSITIONS`](../features/parsing/parse_exceptions.md#where-is-the-value-that-caused-a-later-error).
|
||||
|
||||
### Comments and trailing commas
|
||||
|
||||
!!! question
|
||||
|
||||
Can the parser read JSON with comments or trailing commas?
|
||||
|
||||
Yes, when asked to: see [Comments](../features/comments.md) and [Trailing commas](../features/trailing_commas.md). Both
|
||||
are rejected by default.
|
||||
|
||||
## Accessing and creating values
|
||||
|
||||
### Reading a value into a variable
|
||||
|
||||
!!! question
|
||||
|
||||
How do I get a C++ value out of a `json` value?
|
||||
|
||||
Use [`get<T>()`](../api/basic_json/get.md), for example `#!cpp int age = j["age"].get<int>();`, or an implicit conversion.
|
||||
`#!cpp auto x = j["age"];` is **not** a conversion: it copies the `json` value. See
|
||||
[Converting values](../features/conversions.md).
|
||||
|
||||
### Strings are printed with quotes
|
||||
|
||||
!!! question
|
||||
|
||||
`std::cout << j["name"]` prints `"Mary"` with quotes. How do I get the string?
|
||||
|
||||
The value is a `json`, and printing it produces JSON text. Convert it first: `#!cpp j["name"].get<std::string>()`. See
|
||||
[Getting a string without quotes](../features/conversions.md#getting-values-out).
|
||||
|
||||
### Testing whether a key exists
|
||||
|
||||
!!! question
|
||||
|
||||
How do I check if a key exists, without changing the object?
|
||||
|
||||
Use `contains` or `find`. Do not test with the non-const `operator[]`: like `std::map::operator[]`, it inserts a `null`
|
||||
value for a missing key. See [Does this key exist?](../features/element_access/index.md#does-this-key-exist).
|
||||
|
||||
### Reading a value with a fallback
|
||||
|
||||
!!! question
|
||||
|
||||
How do I read a value that may be missing?
|
||||
|
||||
Use [`value`](../features/element_access/default_value.md), which returns a default for a missing key. It still throws if
|
||||
the key exists with a different type; see [Working without exceptions](../features/no_exceptions.md).
|
||||
|
||||
### Iterating and nested values
|
||||
|
||||
!!! question
|
||||
|
||||
How do I loop over an array or object, or reach a nested value?
|
||||
|
||||
See [Iterators](../features/iterators.md) and [`items()`](../api/basic_json/items.md) for loops, and
|
||||
[JSON Pointer](../features/json_pointer.md) to address or test a nested value by path.
|
||||
|
||||
### Unexpected arrays when creating values
|
||||
|
||||
!!! question
|
||||
|
||||
Why does `json j{x}` or `{{"a", 1}, {"b"}}` produce an array instead of what I expected?
|
||||
|
||||
Braces create arrays and objects based on their content, and single-element braces create a one-element array. See
|
||||
[Brace initialization yields arrays](#brace-initialization-yields-arrays) and the table in
|
||||
[Creating values](../features/creating_values.md#what-does-this-produce).
|
||||
|
||||
### Keeping the order of object keys
|
||||
|
||||
!!! question
|
||||
|
||||
The keys of my object come out sorted. How do I keep the insertion order?
|
||||
|
||||
Use `ordered_json`, and call `ordered_json::parse` when reading. See [Object order](../features/object_order.md).
|
||||
|
||||
## Converting your own types
|
||||
|
||||
### Structs, enums, and optional fields
|
||||
|
||||
!!! question
|
||||
|
||||
How do I convert my own types to and from JSON?
|
||||
|
||||
Write `to_json`/`from_json` functions or use the `NLOHMANN_DEFINE_TYPE_*` macros; see
|
||||
[Arbitrary type conversions](../features/arbitrary_types.md). Enums can be mapped to strings
|
||||
([Enum conversion](../features/enum_conversion.md)), and an empty `std::optional` can be omitted
|
||||
([Omitting a field](../features/conversions.md#omitting-a-field-when-serializing-stdoptional)).
|
||||
|
||||
### Smart pointers, variants, chrono, and class hierarchies
|
||||
|
||||
!!! question
|
||||
|
||||
Why can't I convert `std::shared_ptr`, `std::variant`, `std::chrono` types, or a pointer to a base class?
|
||||
|
||||
There is more than one sensible JSON representation for them, so the library leaves the choice to you.
|
||||
[Recipes for common types](../features/type_recipes.md) shows one for each.
|
||||
|
||||
### My `to_json` is not found
|
||||
|
||||
!!! question
|
||||
|
||||
I wrote `to_json`/`from_json`, but the compiler does not find them. Why?
|
||||
|
||||
Typical causes are a type alias of a standard type, a function in the wrong namespace, or a declaration that comes too
|
||||
late. See [Why isn't my `to_json` found?](../features/arbitrary_types.md#why-isnt-my-to_json-or-from_json-found).
|
||||
|
||||
## Limitations
|
||||
|
||||
### Relaxed parsing
|
||||
@@ -404,36 +202,6 @@ Not directly, but the companion project [json-schema-validator](https://github.c
|
||||
builds JSON Schema (draft 7; draft 4 in its older, now-superseded 1.x releases) validation on top of this library
|
||||
and is a common recommendation for this use case.
|
||||
|
||||
### Memory use
|
||||
|
||||
!!! question
|
||||
|
||||
Does the library leak memory? My process stays large after I free a `json` value.
|
||||
|
||||
Destroying a value frees everything it owns. Freed memory is returned to the allocator, which does not necessarily give
|
||||
it back to the operating system, so a high resident size alone is not a leak. Copies are deep, and `clear()` keeps the
|
||||
capacity of an array. See [Memory](../features/performance.md#memory), and use Valgrind or the sanitizers to tell a leak
|
||||
from retained memory.
|
||||
|
||||
### Reproducible builds
|
||||
|
||||
!!! question
|
||||
|
||||
The header path ends up in my binary. How do I get path-independent builds?
|
||||
|
||||
The runtime assertions embed `__FILE__`. Define `NDEBUG` (the default for release builds) or map the path prefix; see
|
||||
[Notes for builds and distribution](../integration/index.md#notes-for-builds-and-distribution).
|
||||
|
||||
### Slow debug builds
|
||||
|
||||
!!! question
|
||||
|
||||
Parsing a large file is very slow in my debug build. Can I compile the library with optimizations?
|
||||
|
||||
The library is header-only, so there is no separate source file. Keep the code that uses `json` in its own source file and
|
||||
compile that file with optimizations; see
|
||||
[Notes for builds and distribution](../integration/index.md#notes-for-builds-and-distribution).
|
||||
|
||||
## Exceptions
|
||||
|
||||
### Parsing without exceptions
|
||||
@@ -442,8 +210,7 @@ compile that file with optimizations; see
|
||||
|
||||
Is it possible to indicate a parse error without throwing an exception?
|
||||
|
||||
Yes, see [Parsing and exceptions](../features/parsing/parse_exceptions.md) and
|
||||
[Working without exceptions](../features/no_exceptions.md).
|
||||
Yes, see [Parsing and exceptions](../features/parsing/parse_exceptions.md).
|
||||
|
||||
|
||||
### Key name in exceptions
|
||||
|
||||
@@ -29,10 +29,6 @@ int main()
|
||||
j["name"] = "Niels";
|
||||
j["list"] = {1, 0, 2};
|
||||
|
||||
// read a value into a C++ variable and use it
|
||||
double pi = j["pi"].get<double>();
|
||||
std::cout << "2 * pi = " << 2 * pi << '\n';
|
||||
|
||||
// serialize with an indent of 4 spaces
|
||||
std::cout << j.dump(4) << '\n';
|
||||
}
|
||||
|
||||
@@ -43,21 +43,6 @@ using json = nlohmann::json;
|
||||
to the files you want to process JSON and set the necessary switches to enable C++11 (e.g., `-std=c++11` for GCC and
|
||||
Clang).
|
||||
|
||||
!!! tip "Which file do I download?"
|
||||
|
||||
For a project without a package manager, the library itself, including the `_json` literals, is **one file**:
|
||||
[`json.hpp`](https://github.com/nlohmann/json/releases/latest/download/json.hpp) from the
|
||||
[releases page](https://github.com/nlohmann/json/releases). You do not need to clone the repository, build
|
||||
anything, or link a library. Put the file in a directory named `nlohmann` that is on your include path (for
|
||||
example `third_party/nlohmann/json.hpp` with `-I third_party`), so that `#include <nlohmann/json.hpp>` works.
|
||||
Download the companion headers listed below only if you use them: `json_view.hpp` for the
|
||||
[zero-copy views](../features/json_view.md), `json_fwd.hpp` for forward declarations, and `json_literals.hpp` if
|
||||
you define `JSON_NO_AUTOMATIC_UDLS`. Keep them in the same `nlohmann` directory.
|
||||
|
||||
Do not copy only parts of the repository's `include/` directory: the headers in it include each other and must be
|
||||
kept together. If your IDE reports "cannot open source file" while the compiler succeeds, the compiler's include
|
||||
path is set correctly but the IDE's is not; add the same directory to the IDE's include paths.
|
||||
|
||||
You can further use file
|
||||
[`single_include/nlohmann/json_fwd.hpp`](https://github.com/nlohmann/json/blob/develop/single_include/nlohmann/json_fwd.hpp)
|
||||
for forward declarations (see [Compile times](compile_times.md)), and file
|
||||
@@ -69,21 +54,3 @@ For the read-only, non-owning [`basic_json_document`](../api/basic_json_document
|
||||
types, additionally include
|
||||
[`single_include/nlohmann/json_view.hpp`](https://github.com/nlohmann/json/blob/develop/single_include/nlohmann/json_view.hpp);
|
||||
see [Zero-copy JSON views](../features/json_view.md).
|
||||
|
||||
## Notes for builds and distribution
|
||||
|
||||
**Reproducible and path-independent builds.** The runtime assertions use `assert`, which embeds the name of the source
|
||||
file (`__FILE__`) in the binary. Release builds do not contain them, because they define `NDEBUG`. If you compile with
|
||||
assertions enabled and need binaries that do not depend on the build directory, either define `NDEBUG` or map the
|
||||
prefix of the path with the compiler option `-ffile-prefix-map=<old>=<new>` (GCC 8 and later, Clang 10 and later). See
|
||||
[Runtime assertions](../features/assertions.md).
|
||||
|
||||
**There is no separate source file to compile.** The library is header-only, so there is no `.cpp` that could be
|
||||
built with different options than the code that includes it. It is also not available as a precompiled library. To
|
||||
compile the library with optimizations in an otherwise unoptimized build (for example to parse large files in a debug
|
||||
build, which is slow because of the unoptimized code and the checks of the debug standard library), keep the code that
|
||||
uses `json` in a separate source file and set the options for that file only. [Compile times](compile_times.md)
|
||||
shows how to [instantiate `basic_json` once](compile_times.md#instantiate-basic_json-once).
|
||||
|
||||
**Debugger visualization.** The Natvis file works with the Visual Studio debugger, including the `cppvsdbg` engine in
|
||||
Visual Studio Code, but not with debuggers that wrap LLDB, such as `codelldb`; see [Debugging](../home/debugging.md).
|
||||
@@ -60,7 +60,6 @@ nav:
|
||||
- Features:
|
||||
- features/index.md
|
||||
- features/arbitrary_types.md
|
||||
- features/type_recipes.md
|
||||
- Binary Formats:
|
||||
- features/binary_formats/index.md
|
||||
- features/binary_formats/bjdata.md
|
||||
@@ -85,13 +84,11 @@ nav:
|
||||
- features/modifying_values.md
|
||||
- features/modules.md
|
||||
- 'nlohmann Namespace': features/namespace.md
|
||||
- features/no_exceptions.md
|
||||
- features/object_order.md
|
||||
- features/performance.md
|
||||
- Parsing:
|
||||
- features/parsing/index.md
|
||||
- features/parsing/json_lines.md
|
||||
- features/parsing/large_and_partial_input.md
|
||||
- features/parsing/parse_exceptions.md
|
||||
- features/parsing/parser_callbacks.md
|
||||
- features/parsing/sax_interface.md
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // transform
|
||||
#include <algorithm> // min, transform
|
||||
#include <array> // array
|
||||
#include <forward_list> // forward_list
|
||||
#include <iterator> // inserter, front_inserter, end
|
||||
@@ -69,12 +69,10 @@ void from_json(const BasicJsonType& j, std::optional<T>& opt)
|
||||
}
|
||||
#endif // JSON_HAS_CPP_17
|
||||
|
||||
// overloads for basic_json template parameters
|
||||
template < typename BasicJsonType, typename ArithmeticType,
|
||||
enable_if_t < std::is_arithmetic<ArithmeticType>::value&&
|
||||
!std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value,
|
||||
int > = 0 >
|
||||
void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)
|
||||
// read a number value of any of the three number types into val;
|
||||
// returns false (leaving val untouched) if j is not a number
|
||||
template<typename BasicJsonType, typename ArithmeticType>
|
||||
bool get_number_value(const BasicJsonType& j, ArithmeticType& val)
|
||||
{
|
||||
switch (static_cast<value_t>(j))
|
||||
{
|
||||
@@ -85,17 +83,17 @@ void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)
|
||||
const auto* ptr = j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>();
|
||||
JSON_ASSERT(ptr != nullptr);
|
||||
val = static_cast<ArithmeticType>(*ptr);
|
||||
break;
|
||||
return true;
|
||||
}
|
||||
case value_t::number_integer:
|
||||
{
|
||||
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>());
|
||||
break;
|
||||
return true;
|
||||
}
|
||||
case value_t::number_float:
|
||||
{
|
||||
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>());
|
||||
break;
|
||||
return true;
|
||||
}
|
||||
|
||||
case value_t::null:
|
||||
@@ -106,7 +104,20 @@ void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
throw_type_must_be("number", j);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// overloads for basic_json template parameters
|
||||
template < typename BasicJsonType, typename ArithmeticType,
|
||||
enable_if_t < std::is_arithmetic<ArithmeticType>::value&&
|
||||
!std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value,
|
||||
int > = 0 >
|
||||
void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_number_value(j, val)))
|
||||
{
|
||||
throw_type_must_be("number", j);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,29 +256,44 @@ inline void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType:
|
||||
arr = *j.template get_ptr<const typename BasicJsonType::array_t*>();
|
||||
}
|
||||
|
||||
// fill a std::array from the JSON array j: the size is checked once and the
|
||||
// elements are read in order, so a short array converts the elements it has and
|
||||
// then throws the out_of_range error at() would throw for the first missing one
|
||||
template<typename BasicJsonType, typename T, std::size_t N>
|
||||
void from_json_std_array(const BasicJsonType& j, std::array<T, N>& arr)
|
||||
{
|
||||
const auto& inner_array = *j.template get_ptr<const typename BasicJsonType::array_t*>();
|
||||
const std::size_t size = (std::min)(N, inner_array.size());
|
||||
for (std::size_t i = 0; i < size; ++i)
|
||||
{
|
||||
arr[i] = inner_array[i].template get<T>();
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(size < N))
|
||||
{
|
||||
static_cast<void>(j.at(size));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename BasicJsonType, typename T, std::size_t N>
|
||||
auto from_json_array_impl(const BasicJsonType& j, std::array<T, N>& arr,
|
||||
priority_tag<2> /*unused*/)
|
||||
-> decltype(j.template get<T>(), void())
|
||||
{
|
||||
for (std::size_t i = 0; i < N; ++i)
|
||||
{
|
||||
arr[i] = j.at(i).template get<T>();
|
||||
}
|
||||
from_json_std_array(j, arr);
|
||||
}
|
||||
|
||||
// reserve() is called through this pair (modeled on from_json_object_reserve)
|
||||
// so from_json_array_impl below has a single body for both ConstructibleArrayType
|
||||
// that support reserve() and those that don't.
|
||||
template<typename ConstructibleArrayType>
|
||||
auto from_json_array_reserve(ConstructibleArrayType& arr, typename ConstructibleArrayType::size_type size, priority_tag<1> /*unused*/)
|
||||
-> decltype(arr.reserve(size), void())
|
||||
// reserve() is called through this pair (used for arrays and objects alike)
|
||||
// so from_json_array_impl and the object from_json have a single body for both
|
||||
// containers that support reserve() and those that don't.
|
||||
template<typename ConstructibleContainerType>
|
||||
auto reserve_if_possible(ConstructibleContainerType& c, typename ConstructibleContainerType::size_type size, priority_tag<1> /*unused*/)
|
||||
-> decltype(c.reserve(size), void())
|
||||
{
|
||||
arr.reserve(size);
|
||||
c.reserve(size);
|
||||
}
|
||||
|
||||
template<typename ConstructibleArrayType>
|
||||
void from_json_array_reserve(ConstructibleArrayType& /*arr*/, std::size_t /*size*/, priority_tag<0> /*unused*/)
|
||||
template<typename ConstructibleContainerType>
|
||||
void reserve_if_possible(ConstructibleContainerType& /*c*/, std::size_t /*size*/, priority_tag<0> /*unused*/)
|
||||
{}
|
||||
|
||||
// append through push_back() where available; containers without it (such as
|
||||
@@ -297,7 +323,7 @@ auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, p
|
||||
{
|
||||
ConstructibleArrayType ret;
|
||||
const auto* inner_array = j.template get_ptr<const typename BasicJsonType::array_t*>();
|
||||
from_json_array_reserve(ret, inner_array->size(), priority_tag<1> {});
|
||||
reserve_if_possible(ret, inner_array->size(), priority_tag<1> {});
|
||||
for (const auto& elem : *inner_array)
|
||||
{
|
||||
// get<BasicJsonType>() returns *this, this won't call a from_json
|
||||
@@ -336,6 +362,37 @@ std::array<T, sizeof...(Idx)> from_json_inplace_array_impl(const BasicJsonType&
|
||||
return { { j.at(Idx).template get<T>()... } };
|
||||
}
|
||||
|
||||
// std::is_trivial is deprecated in C++26 (see binary_writer::to_char_type);
|
||||
// a struct rather than an alias, as astyle indents an #ifdef block of aliases
|
||||
template<typename T>
|
||||
struct is_trivial_element
|
||||
#ifdef JSON_HAS_CPP_26
|
||||
: conjunction<std::is_trivially_copyable<T>, std::is_trivially_default_constructible<T>>
|
||||
#else
|
||||
: std::is_trivial<T>
|
||||
#endif
|
||||
{};
|
||||
|
||||
// the element type may not be default constructible: construct every element
|
||||
// in place (a pack expansion that is unrolled N times)
|
||||
template<typename BasicJsonType, typename T, std::size_t N>
|
||||
std::array<T, N> from_json_make_array(const BasicJsonType& j, identity_tag<std::array<T, N>> tag,
|
||||
std::false_type /*is_trivial_element*/)
|
||||
{
|
||||
return from_json_inplace_array_impl(j, tag, make_index_sequence<N> {});
|
||||
}
|
||||
|
||||
// a trivial element type cannot tell default construction plus assignment from
|
||||
// construction in place, so use a loop instead of unrolling N conversions
|
||||
template<typename BasicJsonType, typename T, std::size_t N>
|
||||
std::array<T, N> from_json_make_array(const BasicJsonType& j, identity_tag<std::array<T, N>> /*unused*/,
|
||||
std::true_type /*is_trivial_element*/)
|
||||
{
|
||||
std::array<T, N> arr; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): every element is assigned or an exception is thrown
|
||||
from_json_std_array(j, arr);
|
||||
return arr;
|
||||
}
|
||||
|
||||
template < typename BasicJsonType, typename T, std::size_t N >
|
||||
auto from_json(const BasicJsonType& j, identity_tag<std::array<T, N>> tag)
|
||||
-> decltype(from_json_inplace_array_impl(j, tag, make_index_sequence<N> {}))
|
||||
@@ -345,7 +402,7 @@ auto from_json(const BasicJsonType& j, identity_tag<std::array<T, N>> tag)
|
||||
throw_type_must_be("array", j);
|
||||
}
|
||||
|
||||
return from_json_inplace_array_impl(j, tag, make_index_sequence<N> {});
|
||||
return from_json_make_array(j, tag, is_trivial_element<T> {});
|
||||
}
|
||||
|
||||
template<typename BasicJsonType>
|
||||
@@ -378,17 +435,6 @@ inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin)
|
||||
}
|
||||
}
|
||||
|
||||
template<typename ConstructibleObjectType>
|
||||
auto from_json_object_reserve(ConstructibleObjectType& obj, typename ConstructibleObjectType::size_type size, priority_tag<1> /*unused*/)
|
||||
-> decltype(obj.reserve(size), void())
|
||||
{
|
||||
obj.reserve(size);
|
||||
}
|
||||
|
||||
template<typename ConstructibleObjectType>
|
||||
inline void from_json_object_reserve(ConstructibleObjectType& /*obj*/, std::size_t /*size*/, priority_tag<0> /*unused*/)
|
||||
{}
|
||||
|
||||
// the keys of the source are sorted for the usual object types, so hinting the
|
||||
// end makes every insertion constant-time (for containers that order their keys
|
||||
// the same way); containers without emplace_hint() (such as ordered_map) fall
|
||||
@@ -417,7 +463,7 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
|
||||
|
||||
ConstructibleObjectType ret;
|
||||
const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();
|
||||
from_json_object_reserve(ret, inner_object->size(), priority_tag<1> {});
|
||||
reserve_if_possible(ret, inner_object->size(), priority_tag<1> {});
|
||||
for (const auto& p : *inner_object)
|
||||
{
|
||||
from_json_object_emplace(ret, p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>(), priority_tag<1> {});
|
||||
@@ -437,42 +483,17 @@ template < typename BasicJsonType, typename ArithmeticType,
|
||||
int > = 0 >
|
||||
inline void from_json(const BasicJsonType& j, ArithmeticType& val)
|
||||
{
|
||||
switch (static_cast<value_t>(j))
|
||||
if (get_number_value(j, val))
|
||||
{
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
// never null: a basic_json whose number_unsigned_t is number_integer_t
|
||||
// stores no number_unsigned values (#2573)
|
||||
const auto* ptr = j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>();
|
||||
JSON_ASSERT(ptr != nullptr);
|
||||
val = static_cast<ArithmeticType>(*ptr);
|
||||
break;
|
||||
}
|
||||
case value_t::number_integer:
|
||||
{
|
||||
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>());
|
||||
break;
|
||||
}
|
||||
case value_t::number_float:
|
||||
{
|
||||
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>());
|
||||
break;
|
||||
}
|
||||
case value_t::boolean:
|
||||
{
|
||||
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::boolean_t*>());
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::null:
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
case value_t::string:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
throw_type_must_be("number", j);
|
||||
return;
|
||||
}
|
||||
|
||||
// unlike get_arithmetic_value(), a boolean is accepted here
|
||||
if (JSON_HEDLEY_UNLIKELY(!j.is_boolean()))
|
||||
{
|
||||
throw_type_must_be("number", j);
|
||||
}
|
||||
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::boolean_t*>());
|
||||
}
|
||||
|
||||
template<typename BasicJsonType, typename Type>
|
||||
|
||||
@@ -66,6 +66,7 @@ enum class exception_id : int
|
||||
invalid_string_or_size = 113, ///< a binary input contains an invalid string or size specification
|
||||
bson_unsupported_type = 114, ///< a BSON record type is not supported
|
||||
invalid_high_precision_number = 115, ///< a UBJSON/BJData high-precision number cannot be parsed
|
||||
invalid_document_image = 116, ///< json_document::load() rejects an image
|
||||
// invalid_iterator
|
||||
iterators_incompatible = 201, ///< the iterators of a range belong to different values
|
||||
iterator_from_other_value = 202, ///< an iterator does not belong to the value it is used with
|
||||
@@ -100,6 +101,8 @@ enum class exception_id : int
|
||||
invalid_utf8 = 316, ///< dump() finds a string that is not valid UTF-8
|
||||
type_not_serializable = 317, ///< a value cannot be serialized to the requested binary format
|
||||
enum_key_duplicate = 318, ///< an enum-keyed map has two keys that serialize to the same string
|
||||
document_binary_value = 319, ///< a binary value is stored in a json_document
|
||||
document_image_unsupported = 320, ///< a json_document image cannot be saved or loaded (discarded document, big-endian target)
|
||||
discarded_value_used = 321, ///< a discarded value is used
|
||||
// out_of_range
|
||||
array_index_out_of_range = 401, ///< an array index is out of range
|
||||
@@ -117,6 +120,7 @@ enum class exception_id : int
|
||||
patch_remove_parent_not_container = 413, ///< the parent of a JSON Patch 'remove' target is not a container
|
||||
patch_move_into_child = 414, ///< a JSON Patch 'move' moves a value into one of its children
|
||||
subtype_out_of_range = 415, ///< a binary subtype does not fit into one byte
|
||||
document_too_large = 416, ///< a json_document input, its edit storage, or an image reaches the 32-bit (4 GiB) limit
|
||||
// other_error
|
||||
internal_error = 500, ///< unreachable code was reached
|
||||
patch_test_failed = 501, ///< a JSON Patch 'test' operation failed
|
||||
@@ -146,6 +150,13 @@ class exception : public std::exception
|
||||
return concat("[json.exception.", ename, '.', std::to_string(id_), "] ");
|
||||
}
|
||||
|
||||
/// build the message "[json.exception.<ename>.<id>] <diagnostics><what_arg>"
|
||||
template<typename BasicJsonContext>
|
||||
static std::string what_impl(const std::string& ename, int id_, const std::string& what_arg, BasicJsonContext context)
|
||||
{
|
||||
return concat(name(ename, id_), diagnostics(context), what_arg);
|
||||
}
|
||||
|
||||
static std::string diagnostics(std::nullptr_t /*leaf_element*/)
|
||||
{
|
||||
return "";
|
||||
@@ -158,48 +169,33 @@ class exception : public std::exception
|
||||
std::vector<std::string> tokens;
|
||||
for (const auto* current = leaf_element; current != nullptr && current->m_parent != nullptr; current = current->m_parent)
|
||||
{
|
||||
switch (current->m_parent->type())
|
||||
const auto* parent = current->m_parent;
|
||||
if (parent->is_array())
|
||||
{
|
||||
case value_t::array:
|
||||
for (std::size_t i = 0; i < parent->m_data.m_value.array->size(); ++i)
|
||||
{
|
||||
for (std::size_t i = 0; i < current->m_parent->m_data.m_value.array->size(); ++i)
|
||||
if (&parent->m_data.m_value.array->operator[](i) == current)
|
||||
{
|
||||
if (¤t->m_parent->m_data.m_value.array->operator[](i) == current)
|
||||
{
|
||||
tokens.emplace_back(std::to_string(i));
|
||||
break;
|
||||
}
|
||||
tokens.emplace_back(std::to_string(i));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::object:
|
||||
{
|
||||
for (const auto& element : *current->m_parent->m_data.m_value.object)
|
||||
{
|
||||
if (&element.second == current)
|
||||
{
|
||||
// data() is null-terminated, so a key containing
|
||||
// a null byte is cut short here rather than
|
||||
// truncating the whole message at what()
|
||||
tokens.emplace_back(element.first.data());
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::null: // LCOV_EXCL_LINE
|
||||
case value_t::string: // LCOV_EXCL_LINE
|
||||
case value_t::boolean: // LCOV_EXCL_LINE
|
||||
case value_t::number_integer: // LCOV_EXCL_LINE
|
||||
case value_t::number_unsigned: // LCOV_EXCL_LINE
|
||||
case value_t::number_float: // LCOV_EXCL_LINE
|
||||
case value_t::binary: // LCOV_EXCL_LINE
|
||||
case value_t::discarded: // LCOV_EXCL_LINE
|
||||
default: // LCOV_EXCL_LINE
|
||||
break; // LCOV_EXCL_LINE
|
||||
}
|
||||
else if (parent->is_object())
|
||||
{
|
||||
for (const auto& element : *parent->m_data.m_value.object)
|
||||
{
|
||||
if (&element.second == current)
|
||||
{
|
||||
// data() is null-terminated, so a key containing
|
||||
// a null byte is cut short here rather than
|
||||
// truncating the whole message at what()
|
||||
tokens.emplace_back(element.first.data());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// all other parent types (primitives) contribute no token
|
||||
}
|
||||
|
||||
if (tokens.empty())
|
||||
@@ -315,7 +311,7 @@ class invalid_iterator : public exception
|
||||
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
|
||||
static invalid_iterator create(int id_, const std::string& what_arg, BasicJsonContext context)
|
||||
{
|
||||
const std::string w = concat(exception::name("invalid_iterator", id_), exception::diagnostics(context), what_arg);
|
||||
const std::string w = exception::what_impl("invalid_iterator", id_, what_arg, context);
|
||||
return {id_, w.c_str()};
|
||||
}
|
||||
|
||||
@@ -339,7 +335,7 @@ class type_error : public exception
|
||||
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
|
||||
static type_error create(int id_, const std::string& what_arg, BasicJsonContext context)
|
||||
{
|
||||
const std::string w = concat(exception::name("type_error", id_), exception::diagnostics(context), what_arg);
|
||||
const std::string w = exception::what_impl("type_error", id_, what_arg, context);
|
||||
return {id_, w.c_str()};
|
||||
}
|
||||
|
||||
@@ -362,7 +358,7 @@ class out_of_range : public exception
|
||||
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
|
||||
static out_of_range create(int id_, const std::string& what_arg, BasicJsonContext context)
|
||||
{
|
||||
const std::string w = concat(exception::name("out_of_range", id_), exception::diagnostics(context), what_arg);
|
||||
const std::string w = exception::what_impl("out_of_range", id_, what_arg, context);
|
||||
return {id_, w.c_str()};
|
||||
}
|
||||
|
||||
@@ -385,7 +381,7 @@ class other_error : public exception
|
||||
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
|
||||
static other_error create(int id_, const std::string& what_arg, BasicJsonContext context)
|
||||
{
|
||||
const std::string w = concat(exception::name("other_error", id_), exception::diagnostics(context), what_arg);
|
||||
const std::string w = exception::what_impl("other_error", id_, what_arg, context);
|
||||
return {id_, w.c_str()};
|
||||
}
|
||||
|
||||
|
||||
@@ -293,6 +293,40 @@ class binary_reader
|
||||
return is_object ? sax->end_object() : sax->end_array();
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief close the innermost open container and report whether that
|
||||
completed the value being read
|
||||
|
||||
The value begun by the driver is complete once its container is not
|
||||
inside another one.
|
||||
|
||||
@param[out] value_complete whether no container is left open
|
||||
@return whether the SAX parser accepted the end event
|
||||
*/
|
||||
bool leave_container_and_check(bool& value_complete)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!leave_container()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
value_complete = container_stack.empty();
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief read an object key and pass it to the SAX parser
|
||||
|
||||
@tparam GetKey member function that reads the key into a string
|
||||
@param[in,out] key string the key is read into; cleared first
|
||||
@return whether reading the key and the SAX event succeeded
|
||||
*/
|
||||
template<bool (binary_reader::*GetKey)(string_t&)>
|
||||
bool read_object_key(string_t& key)
|
||||
{
|
||||
key.clear();
|
||||
return (this->*GetKey)(key) && sax->key(key);
|
||||
}
|
||||
|
||||
//////////
|
||||
// BSON //
|
||||
//////////
|
||||
@@ -1064,6 +1098,34 @@ class binary_reader
|
||||
return last_byte_error(exception_id::invalid_string_or_size, concat("indefinite-length ", type_name, " is not allowed inside indefinite-length ", type_name, "; last byte: 0x", get_token_string()), context);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief report an object key that is not a string
|
||||
|
||||
@param[in] found description of what was found instead of a string key
|
||||
@return false
|
||||
*/
|
||||
bool unsupported_key_type(const char* found) const
|
||||
{
|
||||
return last_byte_error(exception_id::invalid_string_or_size, "only string keys are supported, but found ", "object key", found);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief read the length of a CBOR string or byte array and then its bytes
|
||||
|
||||
@tparam NumberType the type of the length field
|
||||
@tparam ContainerType the destination container (string_t or binary_t)
|
||||
@param[in] context further context information (for diagnostics)
|
||||
@param[out] result container the bytes are appended to
|
||||
|
||||
@return whether reading completed
|
||||
*/
|
||||
template<typename NumberType, typename ContainerType>
|
||||
bool get_cbor_sized_bytes(const char* context, ContainerType& result)
|
||||
{
|
||||
NumberType len{};
|
||||
return get_number(len) && get_bytes(len, context, result);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief reads a definite-length CBOR string
|
||||
|
||||
@@ -1114,26 +1176,22 @@ class binary_reader
|
||||
|
||||
case 0x78: // UTF-8 string (one-byte uint8_t for n follows)
|
||||
{
|
||||
std::uint8_t len{};
|
||||
return get_number(len) && get_string(len, result);
|
||||
return get_cbor_sized_bytes<std::uint8_t>("string", result);
|
||||
}
|
||||
|
||||
case 0x79: // UTF-8 string (two-byte uint16_t for n follow)
|
||||
{
|
||||
std::uint16_t len{};
|
||||
return get_number(len) && get_string(len, result);
|
||||
return get_cbor_sized_bytes<std::uint16_t>("string", result);
|
||||
}
|
||||
|
||||
case 0x7A: // UTF-8 string (four-byte uint32_t for n follow)
|
||||
{
|
||||
std::uint32_t len{};
|
||||
return get_number(len) && get_string(len, result);
|
||||
return get_cbor_sized_bytes<std::uint32_t>("string", result);
|
||||
}
|
||||
|
||||
case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow)
|
||||
{
|
||||
std::uint64_t len{};
|
||||
return get_number(len) && get_string(len, result);
|
||||
return get_cbor_sized_bytes<std::uint64_t>("string", result);
|
||||
}
|
||||
|
||||
default:
|
||||
@@ -1268,7 +1326,7 @@ class binary_reader
|
||||
break;
|
||||
}
|
||||
|
||||
return last_byte_error(exception_id::invalid_string_or_size, "only string keys are supported, but found ", "object key", found);
|
||||
return unsupported_key_type(found);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1321,30 +1379,22 @@ class binary_reader
|
||||
|
||||
case 0x58: // Binary data (one-byte uint8_t for n follows)
|
||||
{
|
||||
std::uint8_t len{};
|
||||
return get_number(len) &&
|
||||
get_binary(len, result);
|
||||
return get_cbor_sized_bytes<std::uint8_t>("binary", result);
|
||||
}
|
||||
|
||||
case 0x59: // Binary data (two-byte uint16_t for n follow)
|
||||
{
|
||||
std::uint16_t len{};
|
||||
return get_number(len) &&
|
||||
get_binary(len, result);
|
||||
return get_cbor_sized_bytes<std::uint16_t>("binary", result);
|
||||
}
|
||||
|
||||
case 0x5A: // Binary data (four-byte uint32_t for n follow)
|
||||
{
|
||||
std::uint32_t len{};
|
||||
return get_number(len) &&
|
||||
get_binary(len, result);
|
||||
return get_cbor_sized_bytes<std::uint32_t>("binary", result);
|
||||
}
|
||||
|
||||
case 0x5B: // Binary data (eight-byte uint64_t for n follow)
|
||||
{
|
||||
std::uint64_t len{};
|
||||
return get_number(len) &&
|
||||
get_binary(len, result);
|
||||
return get_cbor_sized_bytes<std::uint64_t>("binary", result);
|
||||
}
|
||||
|
||||
default:
|
||||
@@ -1559,12 +1609,12 @@ class binary_reader
|
||||
|
||||
if (at_end)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!leave_container()))
|
||||
bool value_complete = false;
|
||||
if (JSON_HEDLEY_UNLIKELY(!leave_container_and_check(value_complete)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// the value begun here is complete once its container is
|
||||
if (container_stack.empty())
|
||||
if (value_complete)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -1573,8 +1623,7 @@ class binary_reader
|
||||
|
||||
if (top.is_object)
|
||||
{
|
||||
key.clear();
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_cbor_object_key(key) || !sax->key(key)))
|
||||
if (JSON_HEDLEY_UNLIKELY(!read_object_key<&binary_reader::get_cbor_object_key>(key)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -2045,20 +2094,17 @@ class binary_reader
|
||||
|
||||
case 0xD9: // str 8
|
||||
{
|
||||
std::uint8_t len{};
|
||||
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
|
||||
return get_string_with_length<std::uint8_t>(result, context);
|
||||
}
|
||||
|
||||
case 0xDA: // str 16
|
||||
{
|
||||
std::uint16_t len{};
|
||||
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
|
||||
return get_string_with_length<std::uint16_t>(result, context);
|
||||
}
|
||||
|
||||
case 0xDB: // str 32
|
||||
{
|
||||
std::uint32_t len{};
|
||||
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
|
||||
return get_string_with_length<std::uint32_t>(result, context);
|
||||
}
|
||||
|
||||
default:
|
||||
@@ -2155,7 +2201,7 @@ class binary_reader
|
||||
break;
|
||||
}
|
||||
|
||||
return last_byte_error(exception_id::invalid_string_or_size, "only string keys are supported, but found ", "object key", found);
|
||||
return unsupported_key_type(found);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -2300,12 +2346,12 @@ class binary_reader
|
||||
|
||||
if (container_stack.back().remaining == 0)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!leave_container()))
|
||||
bool value_complete = false;
|
||||
if (JSON_HEDLEY_UNLIKELY(!leave_container_and_check(value_complete)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// the value begun here is complete once its container is
|
||||
if (container_stack.empty())
|
||||
if (value_complete)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -2318,8 +2364,7 @@ class binary_reader
|
||||
if (is_object)
|
||||
{
|
||||
get();
|
||||
key.clear();
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_msgpack_object_key(key) || !sax->key(key)))
|
||||
if (JSON_HEDLEY_UNLIKELY(!read_object_key<&binary_reader::get_msgpack_object_key>(key)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -2503,8 +2548,7 @@ class binary_reader
|
||||
{
|
||||
case 'U':
|
||||
{
|
||||
std::uint8_t len{};
|
||||
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
|
||||
return get_string_with_length<std::uint8_t>(result, context);
|
||||
}
|
||||
|
||||
case 'i':
|
||||
@@ -2537,8 +2581,7 @@ class binary_reader
|
||||
{
|
||||
break;
|
||||
}
|
||||
std::uint16_t len{};
|
||||
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
|
||||
return get_string_with_length<std::uint16_t>(result, context);
|
||||
}
|
||||
|
||||
case 'm':
|
||||
@@ -2547,8 +2590,7 @@ class binary_reader
|
||||
{
|
||||
break;
|
||||
}
|
||||
std::uint32_t len{};
|
||||
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
|
||||
return get_string_with_length<std::uint32_t>(result, context);
|
||||
}
|
||||
|
||||
case 'M':
|
||||
@@ -2557,8 +2599,7 @@ class binary_reader
|
||||
{
|
||||
break;
|
||||
}
|
||||
std::uint64_t len{};
|
||||
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
|
||||
return get_string_with_length<std::uint64_t>(result, context);
|
||||
}
|
||||
|
||||
default:
|
||||
@@ -3374,12 +3415,12 @@ class binary_reader
|
||||
|
||||
if (at_end)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!leave_container()))
|
||||
bool value_complete = false;
|
||||
if (JSON_HEDLEY_UNLIKELY(!leave_container_and_check(value_complete)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// the value begun here is complete once its container is
|
||||
if (container_stack.empty())
|
||||
if (value_complete)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -3388,8 +3429,7 @@ class binary_reader
|
||||
|
||||
if (top.is_object)
|
||||
{
|
||||
key.clear();
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_bon8_key(key) || !sax->key(key)))
|
||||
if (JSON_HEDLEY_UNLIKELY(!read_object_key<&binary_reader::get_bon8_key>(key)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -4129,6 +4169,22 @@ class binary_reader
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief read a length of type @a NumberType, then a string of that many bytes
|
||||
|
||||
@tparam NumberType the type of the length field
|
||||
@param[out] result string the bytes are appended to
|
||||
@param[in] context further context information (for diagnostics)
|
||||
|
||||
@return whether reading and validating the string succeeded
|
||||
*/
|
||||
template<typename NumberType>
|
||||
bool get_string_with_length(string_t& result, const char* context)
|
||||
{
|
||||
NumberType len{};
|
||||
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief create a byte array by reading bytes from the input
|
||||
|
||||
@@ -4338,37 +4394,7 @@ class binary_reader
|
||||
const std::string& context) const
|
||||
{
|
||||
std::string error_msg = "syntax error while parsing ";
|
||||
|
||||
switch (input_format)
|
||||
{
|
||||
case input_format_t::cbor:
|
||||
error_msg += "CBOR";
|
||||
break;
|
||||
|
||||
case input_format_t::msgpack:
|
||||
error_msg += "MessagePack";
|
||||
break;
|
||||
|
||||
case input_format_t::ubjson:
|
||||
error_msg += "UBJSON";
|
||||
break;
|
||||
|
||||
case input_format_t::bson:
|
||||
error_msg += "BSON";
|
||||
break;
|
||||
|
||||
case input_format_t::bjdata:
|
||||
error_msg += "BJData";
|
||||
break;
|
||||
|
||||
case input_format_t::bon8:
|
||||
error_msg += "BON8";
|
||||
break;
|
||||
|
||||
case input_format_t::json: // LCOV_EXCL_LINE
|
||||
default: // LCOV_EXCL_LINE
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
||||
}
|
||||
error_msg += input_format_name(input_format);
|
||||
|
||||
return concat(error_msg, ' ', context, ": ", detail);
|
||||
}
|
||||
|
||||
@@ -67,6 +67,36 @@ namespace detail
|
||||
/// the supported input formats
|
||||
enum class input_format_t { json, cbor, msgpack, ubjson, bson, bjdata, bon8 };
|
||||
|
||||
/// @return the name of a binary input format, as used in exception messages
|
||||
inline const char* input_format_name(const input_format_t format) noexcept
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case input_format_t::cbor:
|
||||
return "CBOR";
|
||||
case input_format_t::msgpack:
|
||||
return "MessagePack";
|
||||
case input_format_t::ubjson:
|
||||
return "UBJSON";
|
||||
case input_format_t::bson:
|
||||
return "BSON";
|
||||
case input_format_t::bjdata:
|
||||
return "BJData";
|
||||
case input_format_t::bon8:
|
||||
return "BON8";
|
||||
case input_format_t::json: // LCOV_EXCL_LINE
|
||||
default: // LCOV_EXCL_LINE
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
||||
return ""; // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
|
||||
/// @return the message of the parse_error thrown or reported for an empty input
|
||||
inline const char* empty_input_message() noexcept
|
||||
{
|
||||
return "attempting to parse an empty input; check that your input string or stream contains the expected JSON";
|
||||
}
|
||||
|
||||
////////////////////
|
||||
// input adapters //
|
||||
////////////////////
|
||||
@@ -1268,7 +1298,7 @@ inline file_input_adapter input_adapter(std::FILE* file)
|
||||
{
|
||||
if (file == nullptr)
|
||||
{
|
||||
JSON_THROW(parse_error::create(exception_id::syntax_error, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr));
|
||||
JSON_THROW(parse_error::create(exception_id::syntax_error, 0, empty_input_message(), nullptr));
|
||||
}
|
||||
return file_input_adapter(file);
|
||||
}
|
||||
@@ -1277,7 +1307,7 @@ inline input_stream_adapter input_adapter(std::istream& stream)
|
||||
{
|
||||
if (stream.rdbuf() == nullptr)
|
||||
{
|
||||
JSON_THROW(parse_error::create(exception_id::syntax_error, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr));
|
||||
JSON_THROW(parse_error::create(exception_id::syntax_error, 0, empty_input_message(), nullptr));
|
||||
}
|
||||
return input_stream_adapter(stream);
|
||||
}
|
||||
@@ -1302,7 +1332,7 @@ contiguous_bytes_input_adapter input_adapter(CharT b)
|
||||
{
|
||||
if (b == nullptr)
|
||||
{
|
||||
JSON_THROW(parse_error::create(exception_id::syntax_error, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr));
|
||||
JSON_THROW(parse_error::create(exception_id::syntax_error, 0, empty_input_message(), nullptr));
|
||||
}
|
||||
auto length = std::strlen(reinterpret_cast<const char*>(b));
|
||||
const auto* ptr = reinterpret_cast<const char*>(b);
|
||||
|
||||
@@ -321,6 +321,52 @@ struct diagnostic_positions
|
||||
};
|
||||
#endif
|
||||
|
||||
/*!
|
||||
@brief error state shared by the DOM-building SAX parsers
|
||||
|
||||
Holds whether a parse error occurred and whether errors are thrown, and
|
||||
implements the parse_error() and is_errored() members of the SAX interface for
|
||||
json_sax_dom_parser and json_sax_dom_callback_parser.
|
||||
*/
|
||||
class json_sax_dom_error_state
|
||||
{
|
||||
public:
|
||||
explicit json_sax_dom_error_state(const bool allow_exceptions_) noexcept
|
||||
: allow_exceptions(allow_exceptions_)
|
||||
{}
|
||||
|
||||
// make class move-only
|
||||
json_sax_dom_error_state(const json_sax_dom_error_state&) = delete;
|
||||
json_sax_dom_error_state(json_sax_dom_error_state&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)
|
||||
json_sax_dom_error_state& operator=(const json_sax_dom_error_state&) = delete;
|
||||
json_sax_dom_error_state& operator=(json_sax_dom_error_state&&) = delete; // the const member prevents assignment
|
||||
~json_sax_dom_error_state() = default;
|
||||
|
||||
template<class Exception>
|
||||
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/,
|
||||
const Exception& ex)
|
||||
{
|
||||
errored = true;
|
||||
static_cast<void>(ex);
|
||||
if (allow_exceptions)
|
||||
{
|
||||
JSON_THROW(ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool is_errored() const
|
||||
{
|
||||
return errored;
|
||||
}
|
||||
|
||||
private:
|
||||
/// whether a syntax error occurred
|
||||
bool errored = false;
|
||||
/// whether to throw exceptions in case of errors
|
||||
const bool allow_exceptions = true;
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief a hash table that finds the position of a key in an object whose
|
||||
elements are kept in a vector (ordered_map)
|
||||
@@ -429,7 +475,7 @@ read up to it.
|
||||
parse error)
|
||||
*/
|
||||
template<typename BasicJsonType, typename InputAdapterType, bool Collect = false>
|
||||
class json_sax_dom_parser
|
||||
class json_sax_dom_parser : public json_sax_dom_error_state
|
||||
{
|
||||
public:
|
||||
using number_integer_t = typename BasicJsonType::number_integer_t;
|
||||
@@ -445,7 +491,7 @@ class json_sax_dom_parser
|
||||
@param[in] allow_exceptions_ whether parse errors yield exceptions
|
||||
*/
|
||||
explicit json_sax_dom_parser(BasicJsonType& r, const bool allow_exceptions_ = true, lexer_t* lexer_ = nullptr)
|
||||
: root(r), allow_exceptions(allow_exceptions_), m_lexer_ref(lexer_)
|
||||
: json_sax_dom_error_state(allow_exceptions_), root(r), m_lexer_ref(lexer_)
|
||||
{}
|
||||
|
||||
// make class move-only
|
||||
@@ -596,24 +642,6 @@ class json_sax_dom_parser
|
||||
return true;
|
||||
}
|
||||
|
||||
template<class Exception>
|
||||
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/,
|
||||
const Exception& ex)
|
||||
{
|
||||
errored = true;
|
||||
static_cast<void>(ex);
|
||||
if (allow_exceptions)
|
||||
{
|
||||
JSON_THROW(ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr bool is_errored() const
|
||||
{
|
||||
return errored;
|
||||
}
|
||||
|
||||
private:
|
||||
using object_t = typename BasicJsonType::object_t;
|
||||
using array_t = typename BasicJsonType::array_t;
|
||||
@@ -909,6 +937,8 @@ class json_sax_dom_parser
|
||||
/// directly if the value was the one too many.
|
||||
void place_container(const frame& f, frame* enclosing, BasicJsonType&& value, const bool enclosing_is_innermost)
|
||||
{
|
||||
// only a container with a slot may have no enclosing container
|
||||
JSON_ASSERT(f.slot != nullptr || enclosing != nullptr);
|
||||
if (f.slot != nullptr)
|
||||
{
|
||||
*f.slot = std::move(value);
|
||||
@@ -1325,10 +1355,6 @@ class json_sax_dom_parser
|
||||
/// the array that stores the elements of the innermost array as they
|
||||
/// arrive (frames.back().array), or nullptr
|
||||
array_t* stored_array = nullptr;
|
||||
/// whether a syntax error occurred
|
||||
bool errored = false;
|
||||
/// whether to throw exceptions in case of errors
|
||||
const bool allow_exceptions = true;
|
||||
/// the lexer reference to obtain the current position
|
||||
lexer_t* m_lexer_ref = nullptr;
|
||||
};
|
||||
@@ -1353,7 +1379,7 @@ constructor contains the parsed value.
|
||||
@tparam BasicJsonType the JSON type
|
||||
*/
|
||||
template<typename BasicJsonType, typename InputAdapterType>
|
||||
class json_sax_dom_binary_parser
|
||||
class json_sax_dom_binary_parser : public json_sax_dom_error_state
|
||||
{
|
||||
public:
|
||||
using number_integer_t = typename BasicJsonType::number_integer_t;
|
||||
@@ -1369,7 +1395,7 @@ class json_sax_dom_binary_parser
|
||||
@param[in] allow_exceptions_ whether parse errors yield exceptions
|
||||
*/
|
||||
explicit json_sax_dom_binary_parser(BasicJsonType& r, const bool allow_exceptions_ = true, lexer_t* lexer_ = nullptr)
|
||||
: root(r), allow_exceptions(allow_exceptions_), m_lexer_ref(lexer_)
|
||||
: json_sax_dom_error_state(allow_exceptions_), root(r), m_lexer_ref(lexer_)
|
||||
{}
|
||||
|
||||
// make class move-only
|
||||
@@ -1498,24 +1524,6 @@ class json_sax_dom_binary_parser
|
||||
return true;
|
||||
}
|
||||
|
||||
template<class Exception>
|
||||
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/,
|
||||
const Exception& ex)
|
||||
{
|
||||
errored = true;
|
||||
static_cast<void>(ex);
|
||||
if (allow_exceptions)
|
||||
{
|
||||
JSON_THROW(ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr bool is_errored() const
|
||||
{
|
||||
return errored;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
/*!
|
||||
@@ -1569,16 +1577,12 @@ class json_sax_dom_binary_parser
|
||||
std::vector<BasicJsonType*> ref_stack {};
|
||||
/// helper to hold the reference for the next object element
|
||||
BasicJsonType* object_element = nullptr;
|
||||
/// whether a syntax error occurred
|
||||
bool errored = false;
|
||||
/// whether to throw exceptions in case of errors
|
||||
const bool allow_exceptions = true;
|
||||
/// the lexer reference to obtain the current position
|
||||
lexer_t* m_lexer_ref = nullptr;
|
||||
};
|
||||
|
||||
template<typename BasicJsonType, typename InputAdapterType>
|
||||
class json_sax_dom_callback_parser
|
||||
class json_sax_dom_callback_parser : public json_sax_dom_error_state
|
||||
{
|
||||
public:
|
||||
using number_integer_t = typename BasicJsonType::number_integer_t;
|
||||
@@ -1594,7 +1598,7 @@ class json_sax_dom_callback_parser
|
||||
parser_callback_t cb,
|
||||
const bool allow_exceptions_ = true,
|
||||
lexer_t* lexer_ = nullptr)
|
||||
: root(r), callback(std::move(cb)), allow_exceptions(allow_exceptions_), m_lexer_ref(lexer_)
|
||||
: json_sax_dom_error_state(allow_exceptions_), root(r), callback(std::move(cb)), m_lexer_ref(lexer_)
|
||||
{
|
||||
keep_stack.push_back(true);
|
||||
}
|
||||
@@ -1858,24 +1862,6 @@ class json_sax_dom_callback_parser
|
||||
return true;
|
||||
}
|
||||
|
||||
template<class Exception>
|
||||
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/,
|
||||
const Exception& ex)
|
||||
{
|
||||
errored = true;
|
||||
static_cast<void>(ex);
|
||||
if (allow_exceptions)
|
||||
{
|
||||
JSON_THROW(ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr bool is_errored() const
|
||||
{
|
||||
return errored;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
/*!
|
||||
@@ -2142,12 +2128,8 @@ class json_sax_dom_callback_parser
|
||||
/// value is later rejected by the callback, instead of erasing the
|
||||
/// member entirely
|
||||
std::vector<std::pair<BasicJsonType*, BasicJsonType>> duplicate_key_stash {};
|
||||
/// whether a syntax error occurred
|
||||
bool errored = false;
|
||||
/// callback function
|
||||
const parser_callback_t callback = nullptr;
|
||||
/// whether to throw exceptions in case of errors
|
||||
const bool allow_exceptions = true;
|
||||
/// a discarded value for the callback
|
||||
BasicJsonType discarded = BasicJsonType::value_t::discarded;
|
||||
/// the value of the key event for the callback (see key_value())
|
||||
|
||||
@@ -657,194 +657,74 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
|
||||
// invalid control characters
|
||||
case 0x00:
|
||||
{
|
||||
error_message = "invalid string: control character U+0000 (NUL) must be escaped to \\u0000";
|
||||
return token_type::parse_error;
|
||||
}
|
||||
|
||||
case 0x01:
|
||||
{
|
||||
error_message = "invalid string: control character U+0001 (SOH) must be escaped to \\u0001";
|
||||
return token_type::parse_error;
|
||||
}
|
||||
|
||||
case 0x02:
|
||||
{
|
||||
error_message = "invalid string: control character U+0002 (STX) must be escaped to \\u0002";
|
||||
return token_type::parse_error;
|
||||
}
|
||||
|
||||
case 0x03:
|
||||
{
|
||||
error_message = "invalid string: control character U+0003 (ETX) must be escaped to \\u0003";
|
||||
return token_type::parse_error;
|
||||
}
|
||||
|
||||
case 0x04:
|
||||
{
|
||||
error_message = "invalid string: control character U+0004 (EOT) must be escaped to \\u0004";
|
||||
return token_type::parse_error;
|
||||
}
|
||||
|
||||
case 0x05:
|
||||
{
|
||||
error_message = "invalid string: control character U+0005 (ENQ) must be escaped to \\u0005";
|
||||
return token_type::parse_error;
|
||||
}
|
||||
|
||||
case 0x06:
|
||||
{
|
||||
error_message = "invalid string: control character U+0006 (ACK) must be escaped to \\u0006";
|
||||
return token_type::parse_error;
|
||||
}
|
||||
|
||||
case 0x07:
|
||||
{
|
||||
error_message = "invalid string: control character U+0007 (BEL) must be escaped to \\u0007";
|
||||
return token_type::parse_error;
|
||||
}
|
||||
|
||||
case 0x08:
|
||||
{
|
||||
error_message = "invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b";
|
||||
return token_type::parse_error;
|
||||
}
|
||||
|
||||
case 0x09:
|
||||
{
|
||||
error_message = "invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t";
|
||||
return token_type::parse_error;
|
||||
}
|
||||
|
||||
case 0x0A:
|
||||
{
|
||||
error_message = "invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n";
|
||||
return token_type::parse_error;
|
||||
}
|
||||
|
||||
case 0x0B:
|
||||
{
|
||||
error_message = "invalid string: control character U+000B (VT) must be escaped to \\u000B";
|
||||
return token_type::parse_error;
|
||||
}
|
||||
|
||||
case 0x0C:
|
||||
{
|
||||
error_message = "invalid string: control character U+000C (FF) must be escaped to \\u000C or \\f";
|
||||
return token_type::parse_error;
|
||||
}
|
||||
|
||||
case 0x0D:
|
||||
{
|
||||
error_message = "invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r";
|
||||
return token_type::parse_error;
|
||||
}
|
||||
|
||||
case 0x0E:
|
||||
{
|
||||
error_message = "invalid string: control character U+000E (SO) must be escaped to \\u000E";
|
||||
return token_type::parse_error;
|
||||
}
|
||||
|
||||
case 0x0F:
|
||||
{
|
||||
error_message = "invalid string: control character U+000F (SI) must be escaped to \\u000F";
|
||||
return token_type::parse_error;
|
||||
}
|
||||
|
||||
case 0x10:
|
||||
{
|
||||
error_message = "invalid string: control character U+0010 (DLE) must be escaped to \\u0010";
|
||||
return token_type::parse_error;
|
||||
}
|
||||
|
||||
case 0x11:
|
||||
{
|
||||
error_message = "invalid string: control character U+0011 (DC1) must be escaped to \\u0011";
|
||||
return token_type::parse_error;
|
||||
}
|
||||
|
||||
case 0x12:
|
||||
{
|
||||
error_message = "invalid string: control character U+0012 (DC2) must be escaped to \\u0012";
|
||||
return token_type::parse_error;
|
||||
}
|
||||
|
||||
case 0x13:
|
||||
{
|
||||
error_message = "invalid string: control character U+0013 (DC3) must be escaped to \\u0013";
|
||||
return token_type::parse_error;
|
||||
}
|
||||
|
||||
case 0x14:
|
||||
{
|
||||
error_message = "invalid string: control character U+0014 (DC4) must be escaped to \\u0014";
|
||||
return token_type::parse_error;
|
||||
}
|
||||
|
||||
case 0x15:
|
||||
{
|
||||
error_message = "invalid string: control character U+0015 (NAK) must be escaped to \\u0015";
|
||||
return token_type::parse_error;
|
||||
}
|
||||
|
||||
case 0x16:
|
||||
{
|
||||
error_message = "invalid string: control character U+0016 (SYN) must be escaped to \\u0016";
|
||||
return token_type::parse_error;
|
||||
}
|
||||
|
||||
case 0x17:
|
||||
{
|
||||
error_message = "invalid string: control character U+0017 (ETB) must be escaped to \\u0017";
|
||||
return token_type::parse_error;
|
||||
}
|
||||
|
||||
case 0x18:
|
||||
{
|
||||
error_message = "invalid string: control character U+0018 (CAN) must be escaped to \\u0018";
|
||||
return token_type::parse_error;
|
||||
}
|
||||
|
||||
case 0x19:
|
||||
{
|
||||
error_message = "invalid string: control character U+0019 (EM) must be escaped to \\u0019";
|
||||
return token_type::parse_error;
|
||||
}
|
||||
|
||||
case 0x1A:
|
||||
{
|
||||
error_message = "invalid string: control character U+001A (SUB) must be escaped to \\u001A";
|
||||
return token_type::parse_error;
|
||||
}
|
||||
|
||||
case 0x1B:
|
||||
{
|
||||
error_message = "invalid string: control character U+001B (ESC) must be escaped to \\u001B";
|
||||
return token_type::parse_error;
|
||||
}
|
||||
|
||||
case 0x1C:
|
||||
{
|
||||
error_message = "invalid string: control character U+001C (FS) must be escaped to \\u001C";
|
||||
return token_type::parse_error;
|
||||
}
|
||||
|
||||
case 0x1D:
|
||||
{
|
||||
error_message = "invalid string: control character U+001D (GS) must be escaped to \\u001D";
|
||||
return token_type::parse_error;
|
||||
}
|
||||
|
||||
case 0x1E:
|
||||
{
|
||||
error_message = "invalid string: control character U+001E (RS) must be escaped to \\u001E";
|
||||
return token_type::parse_error;
|
||||
}
|
||||
|
||||
case 0x1F:
|
||||
{
|
||||
error_message = "invalid string: control character U+001F (US) must be escaped to \\u001F";
|
||||
static const std::array<const char*, 32> control_character_messages = {{
|
||||
"invalid string: control character U+0000 (NUL) must be escaped to \\u0000",
|
||||
"invalid string: control character U+0001 (SOH) must be escaped to \\u0001",
|
||||
"invalid string: control character U+0002 (STX) must be escaped to \\u0002",
|
||||
"invalid string: control character U+0003 (ETX) must be escaped to \\u0003",
|
||||
"invalid string: control character U+0004 (EOT) must be escaped to \\u0004",
|
||||
"invalid string: control character U+0005 (ENQ) must be escaped to \\u0005",
|
||||
"invalid string: control character U+0006 (ACK) must be escaped to \\u0006",
|
||||
"invalid string: control character U+0007 (BEL) must be escaped to \\u0007",
|
||||
"invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b",
|
||||
"invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t",
|
||||
"invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n",
|
||||
"invalid string: control character U+000B (VT) must be escaped to \\u000B",
|
||||
"invalid string: control character U+000C (FF) must be escaped to \\u000C or \\f",
|
||||
"invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r",
|
||||
"invalid string: control character U+000E (SO) must be escaped to \\u000E",
|
||||
"invalid string: control character U+000F (SI) must be escaped to \\u000F",
|
||||
"invalid string: control character U+0010 (DLE) must be escaped to \\u0010",
|
||||
"invalid string: control character U+0011 (DC1) must be escaped to \\u0011",
|
||||
"invalid string: control character U+0012 (DC2) must be escaped to \\u0012",
|
||||
"invalid string: control character U+0013 (DC3) must be escaped to \\u0013",
|
||||
"invalid string: control character U+0014 (DC4) must be escaped to \\u0014",
|
||||
"invalid string: control character U+0015 (NAK) must be escaped to \\u0015",
|
||||
"invalid string: control character U+0016 (SYN) must be escaped to \\u0016",
|
||||
"invalid string: control character U+0017 (ETB) must be escaped to \\u0017",
|
||||
"invalid string: control character U+0018 (CAN) must be escaped to \\u0018",
|
||||
"invalid string: control character U+0019 (EM) must be escaped to \\u0019",
|
||||
"invalid string: control character U+001A (SUB) must be escaped to \\u001A",
|
||||
"invalid string: control character U+001B (ESC) must be escaped to \\u001B",
|
||||
"invalid string: control character U+001C (FS) must be escaped to \\u001C",
|
||||
"invalid string: control character U+001D (GS) must be escaped to \\u001D",
|
||||
"invalid string: control character U+001E (RS) must be escaped to \\u001E",
|
||||
"invalid string: control character U+001F (US) must be escaped to \\u001F"
|
||||
}
|
||||
};
|
||||
error_message = control_character_messages[static_cast<std::size_t>(current)];
|
||||
return token_type::parse_error;
|
||||
}
|
||||
|
||||
|
||||
@@ -165,23 +165,10 @@ class parser
|
||||
|
||||
if (result)
|
||||
{
|
||||
if (strict)
|
||||
{
|
||||
// strict mode: next byte must be EOF
|
||||
if (get_token() != token_type::end_of_input)
|
||||
{
|
||||
return syntax_error(*sax, exception_message(token_type::end_of_input, "value"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// the caller keeps using the input: position it right after
|
||||
// the value by leaving the character that terminated it
|
||||
m_lexer.release_lookahead();
|
||||
}
|
||||
return finish_input(*sax, strict);
|
||||
}
|
||||
|
||||
return result;
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -202,6 +189,39 @@ class parser
|
||||
return m_lexer.get_string();
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief apply the end-of-input handling after a value was parsed
|
||||
|
||||
In strict mode, the next token must be the end of the input; otherwise
|
||||
parse_error.101 is reported through @a sax. In non-strict mode, the
|
||||
lookahead is released so the caller can keep reading the input right after
|
||||
the parsed value.
|
||||
|
||||
@param[in,out] sax the SAX parser to report an error to
|
||||
@param[in] strict whether to expect the last token to be EOF
|
||||
@return true, or what @a sax returned for the error
|
||||
*/
|
||||
template<typename SAX>
|
||||
bool finish_input(SAX& sax, const bool strict)
|
||||
{
|
||||
if (strict)
|
||||
{
|
||||
// strict mode: next byte must be EOF
|
||||
if (get_token() != token_type::end_of_input)
|
||||
{
|
||||
return syntax_error(sax, exception_message(token_type::end_of_input, "value"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// the caller keeps using the input: position it right after
|
||||
// the value by leaving the character that terminated it
|
||||
m_lexer.release_lookahead();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief run a DOM SAX parser to completion and position the lexer
|
||||
|
||||
@@ -221,20 +241,7 @@ class parser
|
||||
{
|
||||
sax_parse_internal(&sdp);
|
||||
|
||||
if (strict)
|
||||
{
|
||||
// in strict mode, input must be completely read
|
||||
if (get_token() != token_type::end_of_input)
|
||||
{
|
||||
syntax_error(sdp, exception_message(token_type::end_of_input, "value"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// the caller keeps using the input: position it right after
|
||||
// the value by leaving the character that terminated it
|
||||
m_lexer.release_lookahead();
|
||||
}
|
||||
static_cast<void>(finish_input(sdp, strict));
|
||||
|
||||
return !sdp.is_errored();
|
||||
}
|
||||
@@ -411,7 +418,7 @@ class parser
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(m_lexer.get_chars_read_total() == 1))
|
||||
{
|
||||
return syntax_error(*sax, "attempting to parse an empty input; check that your input string or stream contains the expected JSON");
|
||||
return syntax_error(*sax, empty_input_message());
|
||||
}
|
||||
|
||||
return syntax_error(*sax, exception_message(token_type::literal_or_value, "value"));
|
||||
|
||||
@@ -96,7 +96,7 @@ JSON_HEDLEY_ALWAYS_INLINE std::size_t find_string_special(const unsigned char* d
|
||||
// printable ASCII except '"' and '\\' - is emitted unchanged. Note this differs
|
||||
// from is_string_special() only in that 0x7F is also a stop (it is escaped as
|
||||
// \u007f under ensure_ascii).
|
||||
inline bool is_ascii_copyable(unsigned char c) noexcept
|
||||
constexpr bool is_ascii_copyable(unsigned char c) noexcept
|
||||
{
|
||||
return c >= 0x20u && c < 0x7Fu && c != '"' && c != '\\';
|
||||
}
|
||||
|
||||
@@ -108,33 +108,18 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
||||
{
|
||||
JSON_ASSERT(m_object != nullptr);
|
||||
|
||||
switch (m_object->m_data.m_type)
|
||||
if (m_object->is_object())
|
||||
{
|
||||
case value_t::object:
|
||||
{
|
||||
m_it.object_iterator = typename object_t::iterator();
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
m_it.array_iterator = typename array_t::iterator();
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::null:
|
||||
case value_t::string:
|
||||
case value_t::boolean:
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
case value_t::number_float:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
{
|
||||
m_it.primitive_iterator = primitive_iterator_t();
|
||||
break;
|
||||
}
|
||||
m_it.object_iterator = typename object_t::iterator();
|
||||
}
|
||||
else if (m_object->is_array())
|
||||
{
|
||||
m_it.array_iterator = typename array_t::iterator();
|
||||
}
|
||||
else
|
||||
{
|
||||
// primitive (is_primitive(), including discarded)
|
||||
m_it.primitive_iterator = primitive_iterator_t();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,33 +234,28 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
||||
{
|
||||
JSON_ASSERT(m_object != nullptr);
|
||||
|
||||
switch (m_object->m_data.m_type)
|
||||
if (m_object->is_object())
|
||||
{
|
||||
case value_t::object:
|
||||
{
|
||||
m_it.object_iterator = m_object->m_data.m_value.object->end();
|
||||
break;
|
||||
}
|
||||
m_it.object_iterator = m_object->m_data.m_value.object->end();
|
||||
}
|
||||
else if (m_object->is_array())
|
||||
{
|
||||
m_it.array_iterator = m_object->m_data.m_value.array->end();
|
||||
}
|
||||
else
|
||||
{
|
||||
// primitive (is_primitive(), including discarded)
|
||||
m_it.primitive_iterator.set_end();
|
||||
}
|
||||
}
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
m_it.array_iterator = m_object->m_data.m_value.array->end();
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::null:
|
||||
case value_t::string:
|
||||
case value_t::boolean:
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
case value_t::number_float:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
{
|
||||
m_it.primitive_iterator.set_end();
|
||||
break;
|
||||
}
|
||||
/// @throw invalid_iterator.212 if @a other points to another container
|
||||
template<typename IterImpl>
|
||||
JSON_HEDLEY_ALWAYS_INLINE void check_same_container(const IterImpl& other) const
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))
|
||||
{
|
||||
throw_literal<invalid_iterator>(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -424,10 +404,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
||||
bool operator==(const IterImpl& other) const
|
||||
{
|
||||
// if objects are not the same, the comparison is undefined
|
||||
if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))
|
||||
{
|
||||
throw_literal<invalid_iterator>(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object);
|
||||
}
|
||||
check_same_container(other);
|
||||
|
||||
// value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493
|
||||
if (m_object == nullptr)
|
||||
@@ -466,10 +443,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
||||
bool operator<(const iter_impl& other) const
|
||||
{
|
||||
// if objects are not the same, the comparison is undefined
|
||||
if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))
|
||||
{
|
||||
throw_literal<invalid_iterator>(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object);
|
||||
}
|
||||
check_same_container(other);
|
||||
|
||||
// value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493
|
||||
if (m_object == nullptr)
|
||||
@@ -478,25 +452,18 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (m_object->m_data.m_type)
|
||||
if (m_object->is_object())
|
||||
{
|
||||
case value_t::object:
|
||||
throw_literal<invalid_iterator>(exception_id::iterator_order_on_object, "cannot compare order of object iterators", m_object);
|
||||
|
||||
case value_t::array:
|
||||
return (m_it.array_iterator < other.m_it.array_iterator);
|
||||
|
||||
case value_t::null:
|
||||
case value_t::string:
|
||||
case value_t::boolean:
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
case value_t::number_float:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
return (m_it.primitive_iterator < other.m_it.primitive_iterator);
|
||||
throw_literal<invalid_iterator>(exception_id::iterator_order_on_object, "cannot compare order of object iterators", m_object);
|
||||
}
|
||||
|
||||
if (m_object->is_array())
|
||||
{
|
||||
return (m_it.array_iterator < other.m_it.array_iterator);
|
||||
}
|
||||
|
||||
// primitive (is_primitive(), including discarded)
|
||||
return (m_it.primitive_iterator < other.m_it.primitive_iterator);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -534,30 +501,19 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
||||
{
|
||||
JSON_ASSERT(m_object != nullptr);
|
||||
|
||||
switch (m_object->m_data.m_type)
|
||||
if (m_object->is_object())
|
||||
{
|
||||
case value_t::object:
|
||||
throw_literal<invalid_iterator>(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object);
|
||||
throw_literal<invalid_iterator>(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object);
|
||||
}
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
std::advance(m_it.array_iterator, i);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::null:
|
||||
case value_t::string:
|
||||
case value_t::boolean:
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
case value_t::number_float:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
{
|
||||
m_it.primitive_iterator += i;
|
||||
break;
|
||||
}
|
||||
if (m_object->is_array())
|
||||
{
|
||||
std::advance(m_it.array_iterator, i);
|
||||
}
|
||||
else
|
||||
{
|
||||
// primitive (is_primitive(), including discarded)
|
||||
m_it.primitive_iterator += i;
|
||||
}
|
||||
|
||||
return *this;
|
||||
@@ -613,25 +569,18 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
||||
{
|
||||
JSON_ASSERT(m_object != nullptr);
|
||||
|
||||
switch (m_object->m_data.m_type)
|
||||
if (m_object->is_object())
|
||||
{
|
||||
case value_t::object:
|
||||
throw_literal<invalid_iterator>(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object);
|
||||
|
||||
case value_t::array:
|
||||
return m_it.array_iterator - other.m_it.array_iterator;
|
||||
|
||||
case value_t::null:
|
||||
case value_t::string:
|
||||
case value_t::boolean:
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
case value_t::number_float:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
return m_it.primitive_iterator - other.m_it.primitive_iterator;
|
||||
throw_literal<invalid_iterator>(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object);
|
||||
}
|
||||
|
||||
if (m_object->is_array())
|
||||
{
|
||||
return m_it.array_iterator - other.m_it.array_iterator;
|
||||
}
|
||||
|
||||
// primitive (is_primitive(), including discarded)
|
||||
return m_it.primitive_iterator - other.m_it.primitive_iterator;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
||||
@@ -109,35 +109,25 @@ template<typename IteratorType> class iteration_proxy_value
|
||||
{
|
||||
JSON_ASSERT(anchor.m_object != nullptr);
|
||||
|
||||
switch (anchor.m_object->type())
|
||||
if (anchor.m_object->is_array())
|
||||
{
|
||||
// use integer array index as key
|
||||
case value_t::array:
|
||||
if (array_index != array_index_last)
|
||||
{
|
||||
if (array_index != array_index_last)
|
||||
{
|
||||
int_to_string( array_index_str, array_index );
|
||||
array_index_last = array_index;
|
||||
}
|
||||
return array_index_str;
|
||||
int_to_string( array_index_str, array_index );
|
||||
array_index_last = array_index;
|
||||
}
|
||||
|
||||
// use key from the object
|
||||
case value_t::object:
|
||||
return anchor.key();
|
||||
|
||||
// use an empty key for all primitive types
|
||||
case value_t::null:
|
||||
case value_t::string:
|
||||
case value_t::boolean:
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
case value_t::number_float:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
return empty_str;
|
||||
return array_index_str;
|
||||
}
|
||||
|
||||
if (anchor.m_object->is_object())
|
||||
{
|
||||
// use key from the object
|
||||
return anchor.key();
|
||||
}
|
||||
|
||||
// use an empty key for all primitive types
|
||||
return empty_str;
|
||||
}
|
||||
|
||||
/// return value of the iterator
|
||||
|
||||
@@ -38,6 +38,102 @@ NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
{
|
||||
struct json_pointer_access;
|
||||
|
||||
/*!
|
||||
@brief result of parse_array_index()
|
||||
|
||||
json_pointer::array_index() maps each value to the corresponding
|
||||
parse_error/out_of_range exception; json_pointer::contains() and
|
||||
get_checked_or_null(), which must not throw for an out-of-range or
|
||||
unrepresentable index, and the json_view pointer resolution switch on it
|
||||
directly instead.
|
||||
*/
|
||||
enum class array_index_status
|
||||
{
|
||||
ok, ///< @a s is a valid, representable array index
|
||||
leading_zero, ///< @a s begins with '0' but has more than one character
|
||||
not_a_number, ///< @a s is neither empty nor "-" and does not begin with a digit
|
||||
unresolved, ///< @a s could not be converted to an integer
|
||||
exceeds_size_type ///< @a s converts to an integer that exceeds SizeType
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief convert a JSON Pointer reference token into an array index (RFC 6901, Sect. 4)
|
||||
|
||||
@param[in] s reference token to be converted into an array index; its data()
|
||||
must be NUL-terminated
|
||||
@param[out] idx the integer representation of @a s if array_index_status::ok
|
||||
is returned; left unchanged otherwise
|
||||
|
||||
@return whether @a s is a valid array index, and if not, why
|
||||
|
||||
@note this function never throws; json_pointer::array_index(), the callers that
|
||||
must not throw, and the json_view pointer resolution build on it instead
|
||||
of each re-implementing the RFC 6901 digit rules and the range check
|
||||
@note force-inlined: as a free function template it is no longer inlined into
|
||||
the pointer resolution, which makes JsonPointerAt up to 17% slower (#5891)
|
||||
*/
|
||||
template<typename SizeType, typename StringType>
|
||||
JSON_HEDLEY_ALWAYS_INLINE array_index_status parse_array_index(const StringType& s, SizeType& idx) noexcept
|
||||
{
|
||||
// error condition (cf. RFC 6901, Sect. 4)
|
||||
if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0'))
|
||||
{
|
||||
return array_index_status::leading_zero;
|
||||
}
|
||||
|
||||
// error condition (cf. RFC 6901, Sect. 4); this also covers single-
|
||||
// character tokens, so "/x" and "/xy" fail alike; "-" and the empty
|
||||
// token are left to the conversion below and are reported as unresolved
|
||||
if (JSON_HEDLEY_UNLIKELY(!s.empty() && !(s.size() == 1 && s[0] == '-') && !(s[0] >= '0' && s[0] <= '9')))
|
||||
{
|
||||
return array_index_status::not_a_number;
|
||||
}
|
||||
|
||||
// fast path: up to 18 decimal digits cannot overflow unsigned long long or
|
||||
// SizeType (9 digits for a 32-bit SizeType), so strtoull and errno are not needed
|
||||
constexpr std::size_t max_fast_digits = sizeof(SizeType) >= 8 ? 18 : 9;
|
||||
if (!s.empty() && s.size() <= max_fast_digits)
|
||||
{
|
||||
SizeType acc = 0;
|
||||
for (std::size_t i = 0; i < s.size(); ++i)
|
||||
{
|
||||
const char c = s[i];
|
||||
if (JSON_HEDLEY_UNLIKELY(c < '0' || c > '9'))
|
||||
{
|
||||
break; // not a plain number: let strtoull decide
|
||||
}
|
||||
acc = static_cast<SizeType>((acc * 10) + static_cast<SizeType>(c - '0'));
|
||||
if (JSON_HEDLEY_UNLIKELY(i == s.size() - 1))
|
||||
{
|
||||
idx = acc;
|
||||
return array_index_status::ok;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char* p = s.data();
|
||||
char* p_end = nullptr; // NOLINT(misc-const-correctness)
|
||||
errno = 0; // strtoull doesn't reset errno
|
||||
const unsigned long long res = std::strtoull(p, &p_end, 10); // NOLINT(runtime/int)
|
||||
if (p == p_end // invalid input or empty string
|
||||
|| errno == ERANGE // out of range
|
||||
|| JSON_HEDLEY_UNLIKELY(static_cast<std::size_t>(p_end - p) != s.size())) // incomplete read
|
||||
{
|
||||
return array_index_status::unresolved;
|
||||
}
|
||||
|
||||
// the index does not fit into SizeType; on 64-bit platforms this is
|
||||
// only SIZE_MAX itself (see #2203 and #5395)
|
||||
if (res >= static_cast<unsigned long long>((std::numeric_limits<SizeType>::max)())) // NOLINT(runtime/int)
|
||||
{
|
||||
return array_index_status::exceeds_size_type;
|
||||
}
|
||||
|
||||
idx = static_cast<SizeType>(res);
|
||||
return array_index_status::ok;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
|
||||
@@ -302,6 +398,28 @@ class json_pointer
|
||||
JSON_THROW(detail::out_of_range::create(detail::exception_id::patch_on_root, "JSON pointer has no parent", nullptr));
|
||||
}
|
||||
|
||||
/// whether the '~' at @a pos of @a s is not followed by '0' or '1'
|
||||
JSON_HEDLEY_ALWAYS_INLINE static bool is_invalid_escape(const string_t& s, std::size_t pos)
|
||||
{
|
||||
return pos == s.size() - 1 || (s[pos + 1] != '0' && s[pos + 1] != '1');
|
||||
}
|
||||
|
||||
/// @throw parse_error.107 always
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE static void throw_missing_slash(const string_t& pointer)
|
||||
{
|
||||
static_cast<void>(pointer); // unused when JSON_NOEXCEPTION is defined
|
||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_missing_slash, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", pointer, "'"), nullptr));
|
||||
}
|
||||
|
||||
/// @throw parse_error.107 if a nonempty @a pointer does not begin with '/'
|
||||
JSON_HEDLEY_ALWAYS_INLINE static void check_begins_with_slash(const string_t& pointer)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(pointer[0] != '/'))
|
||||
{
|
||||
throw_missing_slash(pointer);
|
||||
}
|
||||
}
|
||||
|
||||
/// @throw out_of_range.404 always
|
||||
template<typename BasicJsonContext>
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE static void throw_unresolved(const string_t& reference_token, BasicJsonContext context)
|
||||
@@ -311,21 +429,7 @@ class json_pointer
|
||||
JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_unresolved, detail::concat("unresolved reference token '", reference_token, "'"), context));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief result of @ref parse_array_index
|
||||
|
||||
@ref array_index maps each value to the corresponding parse_error/out_of_range
|
||||
exception; @ref contains and @ref get_checked_or_null, which must not throw for
|
||||
an out-of-range or unrepresentable index, switch on it directly instead.
|
||||
*/
|
||||
enum class array_index_status
|
||||
{
|
||||
ok, ///< @a s is a valid, representable array index
|
||||
leading_zero, ///< @a s begins with '0' but has more than one character
|
||||
not_a_number, ///< @a s is neither empty nor "-" and does not begin with a digit
|
||||
unresolved, ///< @a s could not be converted to an integer
|
||||
exceeds_size_type ///< @a s converts to an integer that exceeds size_type
|
||||
};
|
||||
using array_index_status = detail::array_index_status;
|
||||
|
||||
/// whether a reference token is "-" (a plain comparison with a string literal is not always inlined)
|
||||
static bool is_dash(const string_t& s) noexcept
|
||||
@@ -333,78 +437,11 @@ class json_pointer
|
||||
return s.size() == 1 && s[0] == '-';
|
||||
}
|
||||
|
||||
/*!
|
||||
@param[in] s reference token to be converted into an array index
|
||||
@param[out] idx the integer representation of @a s if @ref array_index_status::ok
|
||||
is returned; left unchanged otherwise
|
||||
|
||||
@return whether @a s is a valid array index, and if not, why
|
||||
|
||||
@note this function never throws; @ref array_index and the callers that must not
|
||||
throw (@ref contains, @ref get_checked_or_null) build on it instead of each
|
||||
re-implementing the RFC 6901 digit rules and the @a size_type range check
|
||||
*/
|
||||
/// detail::parse_array_index() for the size_type of @a BasicJsonType
|
||||
template<typename BasicJsonType>
|
||||
static array_index_status parse_array_index(const string_t& s, typename BasicJsonType::size_type& idx) noexcept
|
||||
{
|
||||
using size_type = typename BasicJsonType::size_type;
|
||||
|
||||
// error condition (cf. RFC 6901, Sect. 4)
|
||||
if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0'))
|
||||
{
|
||||
return array_index_status::leading_zero;
|
||||
}
|
||||
|
||||
// error condition (cf. RFC 6901, Sect. 4); this also covers single-
|
||||
// character tokens, so "/x" and "/xy" fail alike; "-" and the empty
|
||||
// token are left to the conversion below and are reported as unresolved
|
||||
if (JSON_HEDLEY_UNLIKELY(!s.empty() && !is_dash(s) && !(s[0] >= '0' && s[0] <= '9')))
|
||||
{
|
||||
return array_index_status::not_a_number;
|
||||
}
|
||||
|
||||
// fast path: up to 18 decimal digits cannot overflow unsigned long long or
|
||||
// size_type (9 digits for a 32-bit size_type), so strtoull and errno are not needed
|
||||
constexpr std::size_t max_fast_digits = sizeof(size_type) >= 8 ? 18 : 9;
|
||||
if (!s.empty() && s.size() <= max_fast_digits)
|
||||
{
|
||||
size_type acc = 0;
|
||||
for (std::size_t i = 0; i < s.size(); ++i)
|
||||
{
|
||||
const char c = s[i];
|
||||
if (JSON_HEDLEY_UNLIKELY(c < '0' || c > '9'))
|
||||
{
|
||||
break; // not a plain number: let strtoull decide
|
||||
}
|
||||
acc = static_cast<size_type>((acc * 10) + static_cast<size_type>(c - '0'));
|
||||
if (JSON_HEDLEY_UNLIKELY(i == s.size() - 1))
|
||||
{
|
||||
idx = acc;
|
||||
return array_index_status::ok;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char* p = s.data();
|
||||
char* p_end = nullptr; // NOLINT(misc-const-correctness)
|
||||
errno = 0; // strtoull doesn't reset errno
|
||||
const unsigned long long res = std::strtoull(p, &p_end, 10); // NOLINT(runtime/int)
|
||||
if (p == p_end // invalid input or empty string
|
||||
|| errno == ERANGE // out of range
|
||||
|| JSON_HEDLEY_UNLIKELY(static_cast<std::size_t>(p_end - p) != s.size())) // incomplete read
|
||||
{
|
||||
return array_index_status::unresolved;
|
||||
}
|
||||
|
||||
// the index does not fit into size_type; on 64-bit platforms this is
|
||||
// only SIZE_MAX itself (see #2203 and #5395)
|
||||
if (res >= static_cast<unsigned long long>((std::numeric_limits<size_type>::max)())) // NOLINT(runtime/int)
|
||||
{
|
||||
return array_index_status::exceeds_size_type;
|
||||
}
|
||||
|
||||
idx = static_cast<size_type>(res);
|
||||
return array_index_status::ok;
|
||||
return detail::parse_array_index(s, idx);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -454,10 +491,7 @@ class json_pointer
|
||||
return reference_tokens.empty();
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/'))
|
||||
{
|
||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_missing_slash, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", reference_string, "'"), nullptr));
|
||||
}
|
||||
check_begins_with_slash(reference_string);
|
||||
|
||||
// the whole string is validated, whether or not it matches
|
||||
const std::size_t size = reference_string.size();
|
||||
@@ -465,7 +499,7 @@ class json_pointer
|
||||
pos != string_t::npos;
|
||||
pos = reference_string.find_first_of('~', pos + 1))
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(pos == size - 1 || (reference_string[pos + 1] != '0' && reference_string[pos + 1] != '1')))
|
||||
if (JSON_HEDLEY_UNLIKELY(is_invalid_escape(reference_string, pos)))
|
||||
{
|
||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr));
|
||||
}
|
||||
@@ -743,10 +777,7 @@ class json_pointer
|
||||
}
|
||||
|
||||
const char* const data = key.data();
|
||||
if (JSON_HEDLEY_UNLIKELY(data[0] != '/'))
|
||||
{
|
||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_missing_slash, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", key, "'"), nullptr));
|
||||
}
|
||||
check_begins_with_slash(key);
|
||||
|
||||
// The beginning of the key that it has in common with the previous
|
||||
// one, which was checked already, is skipped: the scan starts with
|
||||
@@ -779,7 +810,7 @@ class json_pointer
|
||||
else if (data[i] == '~')
|
||||
{
|
||||
// ~ must be followed by 0 or 1
|
||||
if (JSON_HEDLEY_UNLIKELY(i + 1 == size || (data[i + 1] != '0' && data[i + 1] != '1')))
|
||||
if (JSON_HEDLEY_UNLIKELY(is_invalid_escape(key, i)))
|
||||
{
|
||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr));
|
||||
}
|
||||
@@ -878,58 +909,46 @@ class json_pointer
|
||||
++end;
|
||||
}
|
||||
|
||||
switch (ptr->type())
|
||||
if (ptr->is_object())
|
||||
{
|
||||
case detail::value_t::object:
|
||||
token.clear();
|
||||
token.append(begin, static_cast<std::size_t>(end - begin));
|
||||
const auto it = ptr->m_data.m_value.object->find(token);
|
||||
if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
|
||||
{
|
||||
token.clear();
|
||||
token.append(begin, static_cast<std::size_t>(end - begin));
|
||||
const auto it = ptr->m_data.m_value.object->find(token);
|
||||
if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
ptr = &it->second;
|
||||
break;
|
||||
}
|
||||
|
||||
case detail::value_t::array:
|
||||
{
|
||||
// an index of up to 18 digits without a leading zero fits into size_type
|
||||
// (9 digits for a 32-bit size_type); everything else is left to json_pointer
|
||||
constexpr std::size_t max_digits = sizeof(typename BasicJsonType::size_type) >= 8 ? 18 : 9;
|
||||
const auto digits = static_cast<std::size_t>(end - begin);
|
||||
if (JSON_HEDLEY_UNLIKELY(digits == 0 || digits > max_digits || (digits > 1 && *begin == '0')))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
typename BasicJsonType::size_type idx = 0;
|
||||
for (const char* c = begin; c != end; ++c)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(*c < '0' || *c > '9'))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
idx = static_cast<typename BasicJsonType::size_type>((idx * 10) + static_cast<typename BasicJsonType::size_type>(*c - '0'));
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||
break;
|
||||
}
|
||||
|
||||
case detail::value_t::null:
|
||||
case detail::value_t::string:
|
||||
case detail::value_t::boolean:
|
||||
case detail::value_t::number_integer:
|
||||
case detail::value_t::number_unsigned:
|
||||
case detail::value_t::number_float:
|
||||
case detail::value_t::binary:
|
||||
case detail::value_t::discarded:
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
ptr = &it->second;
|
||||
}
|
||||
else if (ptr->is_array())
|
||||
{
|
||||
// an index of up to 18 digits without a leading zero fits into size_type
|
||||
// (9 digits for a 32-bit size_type); everything else is left to json_pointer
|
||||
constexpr std::size_t max_digits = sizeof(typename BasicJsonType::size_type) >= 8 ? 18 : 9;
|
||||
const auto digits = static_cast<std::size_t>(end - begin);
|
||||
if (JSON_HEDLEY_UNLIKELY(digits == 0 || digits > max_digits || (digits > 1 && *begin == '0')))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
typename BasicJsonType::size_type idx = 0;
|
||||
for (const char* c = begin; c != end; ++c)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(*c < '0' || *c > '9'))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
idx = static_cast<typename BasicJsonType::size_type>((idx * 10) + static_cast<typename BasicJsonType::size_type>(*c - '0'));
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||
}
|
||||
else
|
||||
{
|
||||
// primitive value with a reference token left
|
||||
return nullptr;
|
||||
}
|
||||
pos = static_cast<std::size_t>(end - path.data());
|
||||
steps.emplace_back(pos, ptr);
|
||||
@@ -979,40 +998,28 @@ class json_pointer
|
||||
: detail::value_t::object;
|
||||
}
|
||||
|
||||
switch (ptr->type())
|
||||
if (ptr->is_object())
|
||||
{
|
||||
case detail::value_t::object:
|
||||
// use unchecked object access
|
||||
ptr = &object_member(*ptr, reference_token);
|
||||
}
|
||||
else if (ptr->is_array())
|
||||
{
|
||||
if (reference_token == "-")
|
||||
{
|
||||
// use unchecked object access
|
||||
ptr = &object_member(*ptr, reference_token);
|
||||
break;
|
||||
// explicitly treat "-" as index beyond the end
|
||||
ptr = &ptr->operator[](ptr->m_data.m_value.array->size());
|
||||
}
|
||||
|
||||
case detail::value_t::array:
|
||||
else
|
||||
{
|
||||
if (reference_token == "-")
|
||||
{
|
||||
// explicitly treat "-" as index beyond the end
|
||||
ptr = &ptr->operator[](ptr->m_data.m_value.array->size());
|
||||
}
|
||||
else
|
||||
{
|
||||
// convert array index to number; unchecked access
|
||||
ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token));
|
||||
}
|
||||
break;
|
||||
// convert array index to number; unchecked access
|
||||
ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token));
|
||||
}
|
||||
|
||||
case detail::value_t::null:
|
||||
case detail::value_t::string:
|
||||
case detail::value_t::boolean:
|
||||
case detail::value_t::number_integer:
|
||||
case detail::value_t::number_unsigned:
|
||||
case detail::value_t::number_float:
|
||||
case detail::value_t::binary:
|
||||
case detail::value_t::discarded:
|
||||
default:
|
||||
throw_unresolved(reference_token, ptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
// primitive value with a reference token left
|
||||
throw_unresolved(reference_token, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1030,51 +1037,39 @@ class json_pointer
|
||||
{
|
||||
for (const auto& reference_token : reference_tokens)
|
||||
{
|
||||
switch (ptr->type())
|
||||
if (ptr->is_object())
|
||||
{
|
||||
case detail::value_t::object:
|
||||
const auto it = ptr->m_data.m_value.object->find(reference_token);
|
||||
if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
|
||||
{
|
||||
const auto it = ptr->m_data.m_value.object->find(reference_token);
|
||||
if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
|
||||
{
|
||||
// at() throws out_of_range.403 with the usual message
|
||||
ptr->at(reference_token);
|
||||
}
|
||||
ptr = &it->second;
|
||||
break;
|
||||
// at() throws out_of_range.403 with the usual message
|
||||
ptr->at(reference_token);
|
||||
}
|
||||
ptr = &it->second;
|
||||
}
|
||||
else if (ptr->is_array())
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
|
||||
{
|
||||
// "-" always fails the range check
|
||||
JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat(
|
||||
"array index '-' (", std::to_string(ptr->m_data.m_value.array->size()),
|
||||
") is out of range"), ptr));
|
||||
}
|
||||
|
||||
case detail::value_t::array:
|
||||
const auto idx = array_index<BasicJsonType>(reference_token);
|
||||
// Bounds check before access to avoid exception with JSON_NOEXCEPTION
|
||||
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
|
||||
{
|
||||
// "-" always fails the range check
|
||||
JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat(
|
||||
"array index '-' (", std::to_string(ptr->m_data.m_value.array->size()),
|
||||
") is out of range"), ptr));
|
||||
}
|
||||
|
||||
const auto idx = array_index<BasicJsonType>(reference_token);
|
||||
// Bounds check before access to avoid exception with JSON_NOEXCEPTION
|
||||
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
|
||||
{
|
||||
JSON_THROW(detail::out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat(
|
||||
"array index ", std::to_string(idx), " is out of range"), ptr));
|
||||
}
|
||||
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||
break;
|
||||
JSON_THROW(detail::out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat(
|
||||
"array index ", std::to_string(idx), " is out of range"), ptr));
|
||||
}
|
||||
|
||||
case detail::value_t::null:
|
||||
case detail::value_t::string:
|
||||
case detail::value_t::boolean:
|
||||
case detail::value_t::number_integer:
|
||||
case detail::value_t::number_unsigned:
|
||||
case detail::value_t::number_float:
|
||||
case detail::value_t::binary:
|
||||
case detail::value_t::discarded:
|
||||
default:
|
||||
throw_unresolved(reference_token, ptr);
|
||||
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||
}
|
||||
else
|
||||
{
|
||||
// primitive value with a reference token left
|
||||
throw_unresolved(reference_token, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1103,44 +1098,32 @@ class json_pointer
|
||||
{
|
||||
for (const auto& reference_token : reference_tokens)
|
||||
{
|
||||
switch (ptr->type())
|
||||
if (ptr->is_object())
|
||||
{
|
||||
case detail::value_t::object:
|
||||
// use unchecked object access; the const operator[]
|
||||
// asserts that the key exists
|
||||
const auto it = ptr->m_data.m_value.object->find(reference_token);
|
||||
JSON_ASSERT(it != ptr->m_data.m_value.object->end());
|
||||
ptr = &it->second;
|
||||
}
|
||||
else if (ptr->is_array())
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
|
||||
{
|
||||
// use unchecked object access; the const operator[]
|
||||
// asserts that the key exists
|
||||
const auto it = ptr->m_data.m_value.object->find(reference_token);
|
||||
JSON_ASSERT(it != ptr->m_data.m_value.object->end());
|
||||
ptr = &it->second;
|
||||
break;
|
||||
// "-" cannot be used for const access
|
||||
JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr));
|
||||
}
|
||||
|
||||
case detail::value_t::array:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
|
||||
{
|
||||
// "-" cannot be used for const access
|
||||
JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr));
|
||||
}
|
||||
|
||||
// use unchecked array access; the const operator[]
|
||||
// asserts that the index exists
|
||||
const auto idx = array_index<BasicJsonType>(reference_token);
|
||||
JSON_ASSERT(idx < ptr->m_data.m_value.array->size());
|
||||
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||
break;
|
||||
}
|
||||
|
||||
case detail::value_t::null:
|
||||
case detail::value_t::string:
|
||||
case detail::value_t::boolean:
|
||||
case detail::value_t::number_integer:
|
||||
case detail::value_t::number_unsigned:
|
||||
case detail::value_t::number_float:
|
||||
case detail::value_t::binary:
|
||||
case detail::value_t::discarded:
|
||||
default:
|
||||
throw_unresolved(reference_token, ptr);
|
||||
// use unchecked array access; the const operator[]
|
||||
// asserts that the index exists
|
||||
const auto idx = array_index<BasicJsonType>(reference_token);
|
||||
JSON_ASSERT(idx < ptr->m_data.m_value.array->size());
|
||||
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||
}
|
||||
else
|
||||
{
|
||||
// primitive value with a reference token left
|
||||
throw_unresolved(reference_token, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1164,66 +1147,54 @@ class json_pointer
|
||||
{
|
||||
for (const auto& reference_token : reference_tokens)
|
||||
{
|
||||
switch (ptr->type())
|
||||
if (ptr->is_object())
|
||||
{
|
||||
case detail::value_t::object:
|
||||
const auto it = ptr->m_data.m_value.object->find(reference_token);
|
||||
if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
|
||||
{
|
||||
const auto it = ptr->m_data.m_value.object->find(reference_token);
|
||||
if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
ptr = &it->second;
|
||||
break;
|
||||
}
|
||||
|
||||
case detail::value_t::array:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
|
||||
{
|
||||
// "-" always fails the range check
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// a malformed index throws parse_error.106/109; an
|
||||
// index that is syntactically valid but cannot be
|
||||
// represented (out_of_range.404/410) is treated like an
|
||||
// out-of-range index below
|
||||
typename BasicJsonType::size_type idx{};
|
||||
switch (parse_array_index<BasicJsonType>(reference_token, idx))
|
||||
{
|
||||
// the branches differ in their messages, not after JSON_THROW's expansion
|
||||
// NOLINTNEXTLINE(bugprone-branch-clone)
|
||||
case array_index_status::leading_zero:
|
||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_leading_zero, 0, detail::concat("array index '", reference_token, "' must not begin with '0'"), nullptr));
|
||||
case array_index_status::not_a_number:
|
||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_not_a_number, 0, detail::concat("array index '", reference_token, "' is not a number"), nullptr));
|
||||
case array_index_status::unresolved:
|
||||
case array_index_status::exceeds_size_type:
|
||||
return nullptr;
|
||||
case array_index_status::ok:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||
break;
|
||||
}
|
||||
|
||||
case detail::value_t::null:
|
||||
case detail::value_t::string:
|
||||
case detail::value_t::boolean:
|
||||
case detail::value_t::number_integer:
|
||||
case detail::value_t::number_unsigned:
|
||||
case detail::value_t::number_float:
|
||||
case detail::value_t::binary:
|
||||
case detail::value_t::discarded:
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
ptr = &it->second;
|
||||
}
|
||||
else if (ptr->is_array())
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
|
||||
{
|
||||
// "-" always fails the range check
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// a malformed index throws parse_error.106/109; an
|
||||
// index that is syntactically valid but cannot be
|
||||
// represented (out_of_range.404/410) is treated like an
|
||||
// out-of-range index below
|
||||
typename BasicJsonType::size_type idx{};
|
||||
switch (parse_array_index<BasicJsonType>(reference_token, idx))
|
||||
{
|
||||
// the branches differ in their messages, not after JSON_THROW's expansion
|
||||
// NOLINTNEXTLINE(bugprone-branch-clone)
|
||||
case array_index_status::leading_zero:
|
||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_leading_zero, 0, detail::concat("array index '", reference_token, "' must not begin with '0'"), nullptr));
|
||||
case array_index_status::not_a_number:
|
||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_not_a_number, 0, detail::concat("array index '", reference_token, "' is not a number"), nullptr));
|
||||
case array_index_status::unresolved:
|
||||
case array_index_status::exceeds_size_type:
|
||||
return nullptr;
|
||||
case array_index_status::ok:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||
}
|
||||
else
|
||||
{
|
||||
// primitive value with a reference token left
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1239,63 +1210,48 @@ class json_pointer
|
||||
{
|
||||
for (const auto& reference_token : reference_tokens)
|
||||
{
|
||||
switch (ptr->type())
|
||||
if (ptr->is_object())
|
||||
{
|
||||
case detail::value_t::object:
|
||||
const auto it = ptr->m_data.m_value.object->find(reference_token);
|
||||
if (it == ptr->m_data.m_value.object->end())
|
||||
{
|
||||
const auto it = ptr->m_data.m_value.object->find(reference_token);
|
||||
if (it == ptr->m_data.m_value.object->end())
|
||||
{
|
||||
// we did not find the key in the object
|
||||
return false;
|
||||
}
|
||||
|
||||
ptr = &it->second;
|
||||
break;
|
||||
}
|
||||
|
||||
case detail::value_t::array:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
|
||||
{
|
||||
// "-" always fails the range check
|
||||
return false;
|
||||
}
|
||||
|
||||
// any parse failure (malformed index, or one that is syntactically
|
||||
// valid but not representable as size_type) means the reference
|
||||
// token cannot denote an existing array element -- contains() must
|
||||
// not throw (see #5395), so it is treated as "not found"
|
||||
typename BasicJsonType::size_type idx{};
|
||||
if (JSON_HEDLEY_UNLIKELY(parse_array_index<BasicJsonType>(reference_token, idx) != array_index_status::ok))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (idx >= ptr->m_data.m_value.array->size())
|
||||
{
|
||||
// index out of range
|
||||
return false;
|
||||
}
|
||||
|
||||
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||
break;
|
||||
}
|
||||
|
||||
case detail::value_t::null:
|
||||
case detail::value_t::string:
|
||||
case detail::value_t::boolean:
|
||||
case detail::value_t::number_integer:
|
||||
case detail::value_t::number_unsigned:
|
||||
case detail::value_t::number_float:
|
||||
case detail::value_t::binary:
|
||||
case detail::value_t::discarded:
|
||||
default:
|
||||
{
|
||||
// we do not expect primitive values if there is still a
|
||||
// reference token to process
|
||||
// we did not find the key in the object
|
||||
return false;
|
||||
}
|
||||
|
||||
ptr = &it->second;
|
||||
}
|
||||
else if (ptr->is_array())
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
|
||||
{
|
||||
// "-" always fails the range check
|
||||
return false;
|
||||
}
|
||||
|
||||
// any parse failure (malformed index, or one that is syntactically
|
||||
// valid but not representable as size_type) means the reference
|
||||
// token cannot denote an existing array element -- contains() must
|
||||
// not throw (see #5395), so it is treated as "not found"
|
||||
typename BasicJsonType::size_type idx{};
|
||||
if (JSON_HEDLEY_UNLIKELY(parse_array_index<BasicJsonType>(reference_token, idx) != array_index_status::ok))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (idx >= ptr->m_data.m_value.array->size())
|
||||
{
|
||||
// index out of range
|
||||
return false;
|
||||
}
|
||||
|
||||
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||
}
|
||||
else
|
||||
{
|
||||
// we do not expect primitive values if there is still a
|
||||
// reference token to process
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1323,10 +1279,7 @@ class json_pointer
|
||||
}
|
||||
|
||||
// check if a nonempty reference string begins with slash
|
||||
if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/'))
|
||||
{
|
||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_missing_slash, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", reference_string, "'"), nullptr));
|
||||
}
|
||||
check_begins_with_slash(reference_string);
|
||||
|
||||
// every slash starts a reference token
|
||||
const std::size_t size = reference_string.size();
|
||||
@@ -1367,9 +1320,7 @@ class json_pointer
|
||||
JSON_ASSERT(reference_token[pos] == '~');
|
||||
|
||||
// ~ must be followed by 0 or 1
|
||||
if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 ||
|
||||
(reference_token[pos + 1] != '0' &&
|
||||
reference_token[pos + 1] != '1')))
|
||||
if (JSON_HEDLEY_UNLIKELY(is_invalid_escape(reference_token, pos)))
|
||||
{
|
||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr));
|
||||
}
|
||||
@@ -1526,50 +1477,34 @@ class json_pointer
|
||||
auto hint = object.end();
|
||||
const auto enter = [&stack, &path, &object, &hint](const BasicJsonType & v)
|
||||
{
|
||||
switch (v.type())
|
||||
if (v.is_array())
|
||||
{
|
||||
case detail::value_t::array:
|
||||
if (v.m_data.m_value.array->empty())
|
||||
{
|
||||
if (v.m_data.m_value.array->empty())
|
||||
{
|
||||
// flatten empty array as null
|
||||
add_flattened(object, hint, path, nullptr, detail::priority_tag<2> {});
|
||||
}
|
||||
else
|
||||
{
|
||||
stack.emplace_back(&v, object_const_iterator(), path.size());
|
||||
}
|
||||
return;
|
||||
// flatten empty array as null
|
||||
add_flattened(object, hint, path, nullptr, detail::priority_tag<2> {});
|
||||
}
|
||||
|
||||
case detail::value_t::object:
|
||||
else
|
||||
{
|
||||
if (v.m_data.m_value.object->empty())
|
||||
{
|
||||
// flatten empty object as null
|
||||
add_flattened(object, hint, path, nullptr, detail::priority_tag<2> {});
|
||||
}
|
||||
else
|
||||
{
|
||||
stack.emplace_back(&v, v.m_data.m_value.object->begin(), path.size());
|
||||
}
|
||||
return;
|
||||
stack.emplace_back(&v, object_const_iterator(), path.size());
|
||||
}
|
||||
|
||||
case detail::value_t::null:
|
||||
case detail::value_t::string:
|
||||
case detail::value_t::boolean:
|
||||
case detail::value_t::number_integer:
|
||||
case detail::value_t::number_unsigned:
|
||||
case detail::value_t::number_float:
|
||||
case detail::value_t::binary:
|
||||
case detail::value_t::discarded:
|
||||
default:
|
||||
}
|
||||
else if (v.is_object())
|
||||
{
|
||||
if (v.m_data.m_value.object->empty())
|
||||
{
|
||||
// add a primitive value with its reference string
|
||||
add_flattened(object, hint, path, v, detail::priority_tag<2> {});
|
||||
return;
|
||||
// flatten empty object as null
|
||||
add_flattened(object, hint, path, nullptr, detail::priority_tag<2> {});
|
||||
}
|
||||
else
|
||||
{
|
||||
stack.emplace_back(&v, v.m_data.m_value.object->begin(), path.size());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// add a primitive value with its reference string
|
||||
add_flattened(object, hint, path, v, detail::priority_tag<2> {});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include <nlohmann/detail/bit_ops.hpp>
|
||||
#include <nlohmann/detail/input/binary_reader.hpp>
|
||||
#include <nlohmann/detail/input/input_adapters.hpp>
|
||||
#include <nlohmann/detail/input/string_scan.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
#include <nlohmann/detail/output/error_handler.hpp>
|
||||
@@ -311,9 +312,7 @@ class binary_writer
|
||||
write_cbor_head(0x40, N);
|
||||
|
||||
// step 2: write each element
|
||||
oa.write_characters(
|
||||
reinterpret_cast<const CharType*>(j.m_data.m_value.binary->data()),
|
||||
N);
|
||||
write_bytes(j.m_data.m_value.binary->data(), N);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -326,7 +325,7 @@ class binary_writer
|
||||
|
||||
case value_t::discarded:
|
||||
default:
|
||||
throw_on_discarded(j, "CBOR");
|
||||
throw_on_discarded(j, input_format_t::cbor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -526,9 +525,7 @@ class binary_writer
|
||||
}
|
||||
|
||||
// step 2: write the byte string
|
||||
oa.write_characters(
|
||||
reinterpret_cast<const CharType*>(j.m_data.m_value.binary->data()),
|
||||
N);
|
||||
write_bytes(j.m_data.m_value.binary->data(), N);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -542,7 +539,7 @@ class binary_writer
|
||||
|
||||
case value_t::discarded:
|
||||
default:
|
||||
throw_on_discarded(j, "MessagePack");
|
||||
throw_on_discarded(j, input_format_t::msgpack);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -626,7 +623,7 @@ class binary_writer
|
||||
case value_t::array:
|
||||
{
|
||||
bool prefix_required = true;
|
||||
const bool write_closer = write_ubjson_start_array(j, use_count, use_type, add_prefix, use_bjdata, prefix_required);
|
||||
const bool write_closer = write_ubjson_start_container<true>(j, use_count, use_type, add_prefix, use_bjdata, prefix_required);
|
||||
|
||||
{
|
||||
const auto& array = *j.m_data.m_value.array;
|
||||
@@ -676,9 +673,7 @@ class binary_writer
|
||||
|
||||
if (use_type)
|
||||
{
|
||||
oa.write_characters(
|
||||
reinterpret_cast<const CharType*>(j.m_data.m_value.binary->data()),
|
||||
j.m_data.m_value.binary->size());
|
||||
write_bytes(j.m_data.m_value.binary->data(), j.m_data.m_value.binary->size());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -704,7 +699,7 @@ class binary_writer
|
||||
}
|
||||
|
||||
bool prefix_required = true;
|
||||
const bool write_closer = write_ubjson_start_object(j, use_count, use_type, add_prefix, use_bjdata, prefix_required);
|
||||
const bool write_closer = write_ubjson_start_container<false>(j, use_count, use_type, add_prefix, use_bjdata, prefix_required);
|
||||
|
||||
write_ubjson_members(j, use_count, use_type, prefix_required, use_bjdata, bjdata_version, depth);
|
||||
|
||||
@@ -718,7 +713,7 @@ class binary_writer
|
||||
|
||||
case value_t::discarded:
|
||||
default:
|
||||
throw_on_discarded(j, use_bjdata ? "BJData" : "UBJSON");
|
||||
throw_on_discarded(j, use_bjdata ? input_format_t::bjdata : input_format_t::ubjson);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -744,11 +739,11 @@ class binary_writer
|
||||
@brief throws because @a j is discarded and cannot be serialized
|
||||
@throw type_error.321 always
|
||||
*/
|
||||
JSON_HEDLEY_NO_RETURN static void throw_on_discarded(const BasicJsonType& j, const char* format_name)
|
||||
JSON_HEDLEY_NO_RETURN static void throw_on_discarded(const BasicJsonType& j, const input_format_t format)
|
||||
{
|
||||
static_cast<void>(j); // unused when JSON_NOEXCEPTION is defined
|
||||
static_cast<void>(format_name);
|
||||
JSON_THROW(type_error::create(exception_id::discarded_value_used, concat("cannot serialize discarded value to ", format_name), &j));
|
||||
static_cast<void>(format);
|
||||
JSON_THROW(type_error::create(exception_id::discarded_value_used, concat("cannot serialize discarded value to ", input_format_name(format)), &j));
|
||||
}
|
||||
|
||||
/// how the range of integer type @a NumberType relates to that of @a TargetType
|
||||
@@ -868,11 +863,11 @@ class binary_writer
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief throws because the integer @a j is too large for @a format_name
|
||||
@brief throws because the integer @a j is too large for @a format
|
||||
@throw out_of_range.407 always
|
||||
*/
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
|
||||
static void throw_integer_out_of_range(const BasicJsonType& j, const char* format_name, const char* range)
|
||||
static void throw_integer_out_of_range(const BasicJsonType& j, const input_format_t format, const char* range)
|
||||
{
|
||||
// neither std::to_string(), which has no overload for integer types
|
||||
// wider than 64 bits, nor dump(), which would also instantiate the
|
||||
@@ -883,9 +878,9 @@ class binary_writer
|
||||
? integer_to_string(j.m_data.m_value.number_unsigned, std::integral_constant<bool, unsigned_limits::is_signed>())
|
||||
: integer_to_string(j.m_data.m_value.number_integer, std::integral_constant<bool, integer_limits::is_signed>());
|
||||
static_cast<void>(number); // unused when JSON_NOEXCEPTION is defined
|
||||
static_cast<void>(format_name);
|
||||
static_cast<void>(format);
|
||||
static_cast<void>(range);
|
||||
JSON_THROW(out_of_range::create(exception_id::integer_too_large, concat("integer number ", number, " cannot be represented by ", format_name, " as it does not fit ", range), &j));
|
||||
JSON_THROW(out_of_range::create(exception_id::integer_too_large, concat("integer number ", number, " cannot be represented by ", input_format_name(format), " as it does not fit ", range), &j));
|
||||
}
|
||||
|
||||
// The exceptions below are thrown from functions of their own, out of line:
|
||||
@@ -928,53 +923,56 @@ class binary_writer
|
||||
JSON_THROW(other_error::create(exception_id::size_marker_required, "use_type requires use_size = true", &j));
|
||||
}
|
||||
|
||||
/// @brief write @a size bytes starting at @a data to the output adapter
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void write_bytes(const void* data, const std::size_t size)
|
||||
{
|
||||
oa.write_characters(static_cast<const CharType*>(data), size);
|
||||
}
|
||||
|
||||
/// @brief write the MessagePack prefix of an array or object with @a N
|
||||
/// elements: the fix marker @a fix_marker | n, or @a marker_16 or
|
||||
/// @a marker_32 followed by the length
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void write_msgpack_container_prefix(const std::size_t N, const BasicJsonType& j,
|
||||
const std::uint8_t fix_marker, const std::uint8_t marker_16, const std::uint8_t marker_32)
|
||||
{
|
||||
const auto n = to_msgpack_length(N, j);
|
||||
if (n <= 15)
|
||||
{
|
||||
// fixarray, fixmap
|
||||
oa.write_character(to_char_type(static_cast<std::uint8_t>(fix_marker | n)));
|
||||
}
|
||||
else if (n <= (std::numeric_limits<std::uint16_t>::max)())
|
||||
{
|
||||
// array 16, map 16
|
||||
oa.write_character(to_char_type(marker_16));
|
||||
write_number(static_cast<std::uint16_t>(n));
|
||||
}
|
||||
else
|
||||
{
|
||||
// array 32, map 32
|
||||
oa.write_character(to_char_type(marker_32));
|
||||
write_number(static_cast<std::uint32_t>(n));
|
||||
}
|
||||
}
|
||||
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void write_msgpack_array_prefix(const std::size_t N, const BasicJsonType& j)
|
||||
{
|
||||
const auto n = to_msgpack_length(N, j);
|
||||
if (n <= 15)
|
||||
{
|
||||
// fixarray
|
||||
oa.write_character(to_char_type(static_cast<std::uint8_t>(0x90 | n)));
|
||||
}
|
||||
else if (n <= (std::numeric_limits<std::uint16_t>::max)())
|
||||
{
|
||||
// array 16
|
||||
oa.write_character(to_char_type(0xDC));
|
||||
write_number(static_cast<std::uint16_t>(n));
|
||||
}
|
||||
else
|
||||
{
|
||||
// array 32
|
||||
oa.write_character(to_char_type(0xDD));
|
||||
write_number(static_cast<std::uint32_t>(n));
|
||||
}
|
||||
write_msgpack_container_prefix(N, j, 0x90, 0xDC, 0xDD);
|
||||
}
|
||||
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void write_msgpack_object_prefix(const std::size_t N, const BasicJsonType& j)
|
||||
{
|
||||
const auto n = to_msgpack_length(N, j);
|
||||
if (n <= 15)
|
||||
{
|
||||
// fixmap
|
||||
oa.write_character(to_char_type(static_cast<std::uint8_t>(0x80 | (n & 0xF))));
|
||||
}
|
||||
else if (n <= (std::numeric_limits<std::uint16_t>::max)())
|
||||
{
|
||||
// map 16
|
||||
oa.write_character(to_char_type(0xDE));
|
||||
write_number(static_cast<std::uint16_t>(n));
|
||||
}
|
||||
else
|
||||
{
|
||||
// map 32
|
||||
oa.write_character(to_char_type(0xDF));
|
||||
write_number(static_cast<std::uint32_t>(n));
|
||||
}
|
||||
write_msgpack_container_prefix(N, j, 0x80, 0xDE, 0xDF);
|
||||
}
|
||||
|
||||
/// @brief a CBOR, MessagePack, or BON8 array or object whose elements
|
||||
/// @ref write_cbor_iterative, @ref write_msgpack_iterative, or
|
||||
/// @ref write_bon8_iterative is still writing
|
||||
/// @brief a CBOR, MessagePack, BON8, or (as the base of @ref ubjson_frame)
|
||||
/// UBJSON array or object whose elements @ref write_container_iterative,
|
||||
/// @ref write_bon8_iterative, or @ref write_ubjson_iterative is still
|
||||
/// writing
|
||||
struct binary_container_frame
|
||||
{
|
||||
explicit binary_container_frame(const BasicJsonType* value_) noexcept
|
||||
@@ -1010,19 +1008,82 @@ class binary_writer
|
||||
typename BasicJsonType::array_t::const_iterator array_it{};
|
||||
};
|
||||
|
||||
/// @brief the CBOR specifics of @ref write_container_iterative
|
||||
struct cbor_walk
|
||||
{
|
||||
static void array_head(binary_writer& w, const BasicJsonType& j)
|
||||
{
|
||||
w.write_cbor_head(0x80, j.m_data.m_value.array->size());
|
||||
}
|
||||
|
||||
static void object_head(binary_writer& w, const BasicJsonType& j)
|
||||
{
|
||||
w.write_cbor_head(0xA0, j.m_data.m_value.object->size());
|
||||
}
|
||||
|
||||
static void scalar(binary_writer& w, const BasicJsonType& j)
|
||||
{
|
||||
w.write_cbor(j);
|
||||
}
|
||||
|
||||
static void string(binary_writer& w, const string_t& key, const BasicJsonType& context)
|
||||
{
|
||||
w.write_cbor_string(key, context);
|
||||
}
|
||||
|
||||
template<typename KeyType>
|
||||
static void key(binary_writer& w, const KeyType& k, const BasicJsonType& object)
|
||||
{
|
||||
w.write_cbor_key(k, object);
|
||||
}
|
||||
};
|
||||
|
||||
/// @brief the MessagePack specifics of @ref write_container_iterative
|
||||
struct msgpack_walk
|
||||
{
|
||||
static void array_head(binary_writer& w, const BasicJsonType& j)
|
||||
{
|
||||
w.write_msgpack_array_prefix(j.m_data.m_value.array->size(), j);
|
||||
}
|
||||
|
||||
static void object_head(binary_writer& w, const BasicJsonType& j)
|
||||
{
|
||||
w.write_msgpack_object_prefix(j.m_data.m_value.object->size(), j);
|
||||
}
|
||||
|
||||
static void scalar(binary_writer& w, const BasicJsonType& j)
|
||||
{
|
||||
w.write_msgpack(j);
|
||||
}
|
||||
|
||||
static void string(binary_writer& w, const string_t& key, const BasicJsonType& context)
|
||||
{
|
||||
w.write_msgpack_string(key, context);
|
||||
}
|
||||
|
||||
template<typename KeyType>
|
||||
static void key(binary_writer& w, const KeyType& k, const BasicJsonType& object)
|
||||
{
|
||||
w.write_msgpack_key(k, object);
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief write @a j with @ref write_cbor, or write its header and push a
|
||||
frame for @ref write_cbor_iterative to continue with its elements
|
||||
@brief write @a j with @a Walk::scalar, or write its header and push a
|
||||
frame for @ref write_container_iterative to continue with its
|
||||
elements
|
||||
|
||||
A scalar, and an empty array or object, are written out in full: there is
|
||||
nothing below them for @ref write_cbor_iterative to come back to, so
|
||||
nothing below them for @ref write_container_iterative to come back to, so
|
||||
nothing is pushed for them.
|
||||
*/
|
||||
void write_cbor_value_or_push(const BasicJsonType& j, std::vector<binary_container_frame>& stack)
|
||||
template<typename Walk>
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void write_container_value_or_push(const BasicJsonType& j, std::vector<binary_container_frame>& stack)
|
||||
{
|
||||
if (j.is_array())
|
||||
{
|
||||
write_cbor_head(0x80, j.m_data.m_value.array->size());
|
||||
Walk::array_head(*this, j);
|
||||
if (!j.m_data.m_value.array->empty())
|
||||
{
|
||||
stack.emplace_back(&j);
|
||||
@@ -1032,7 +1093,7 @@ class binary_writer
|
||||
|
||||
if (j.is_object())
|
||||
{
|
||||
write_cbor_head(0xA0, j.m_data.m_value.object->size());
|
||||
Walk::object_head(*this, j);
|
||||
if (!j.m_data.m_value.object->empty())
|
||||
{
|
||||
stack.emplace_back(&j);
|
||||
@@ -1040,22 +1101,26 @@ class binary_writer
|
||||
return;
|
||||
}
|
||||
|
||||
write_cbor(j);
|
||||
Walk::scalar(*this, j);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write out @a root and everything below it without the call stack
|
||||
|
||||
Emits the same bytes as @ref write_cbor, keeping the containers it has
|
||||
Emits the same bytes as the recursive writer, keeping the containers it has
|
||||
entered on an explicit stack instead of descending into them. Only reached
|
||||
for values nested deeper than @ref recursion_depth_limit, which is why it
|
||||
is not written for speed.
|
||||
is not written for speed. Shared by CBOR and MessagePack, which differ only
|
||||
in the @a Walk policy (@ref cbor_walk, @ref msgpack_walk); BON8 has its own
|
||||
walker, as it tracks an open string run and closes a container with a
|
||||
marker.
|
||||
*/
|
||||
void write_cbor_iterative(const BasicJsonType& root)
|
||||
template<typename Walk>
|
||||
void write_container_iterative(const BasicJsonType& root)
|
||||
{
|
||||
// only a container with elements is ever pushed; see write_cbor_value_or_push
|
||||
// only a container with elements is ever pushed; see write_container_value_or_push
|
||||
std::vector<binary_container_frame> stack;
|
||||
write_cbor_value_or_push(root, stack);
|
||||
write_container_value_or_push<Walk>(root, stack);
|
||||
|
||||
while (!stack.empty())
|
||||
{
|
||||
@@ -1073,7 +1138,7 @@ class binary_writer
|
||||
// read the child before pushing: entering it can move every frame
|
||||
const BasicJsonType* child = &(*current.array_it);
|
||||
++stack.back().array_it;
|
||||
write_cbor_value_or_push(*child, stack);
|
||||
write_container_value_or_push<Walk>(*child, stack);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1085,110 +1150,53 @@ class binary_writer
|
||||
}
|
||||
|
||||
// the key is written directly (not via a temporary basic_json),
|
||||
// with the object as diagnostics context, as in write_cbor
|
||||
write_cbor_key(current.object_it->first, *current.value);
|
||||
// with the object as diagnostics context, as in the recursive writer
|
||||
Walk::key(*this, current.object_it->first, *current.value);
|
||||
const BasicJsonType* child = &(current.object_it->second);
|
||||
++stack.back().object_it;
|
||||
write_cbor_value_or_push(*child, stack);
|
||||
write_container_value_or_push<Walk>(*child, stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write @a j with @ref write_msgpack, or write its header and push a
|
||||
frame for @ref write_msgpack_iterative to continue with its elements
|
||||
|
||||
@sa @ref write_cbor_value_or_push
|
||||
*/
|
||||
void write_msgpack_value_or_push(const BasicJsonType& j, std::vector<binary_container_frame>& stack)
|
||||
void write_cbor_iterative(const BasicJsonType& root)
|
||||
{
|
||||
if (j.is_array())
|
||||
{
|
||||
write_msgpack_array_prefix(j.m_data.m_value.array->size(), j);
|
||||
if (!j.m_data.m_value.array->empty())
|
||||
{
|
||||
stack.emplace_back(&j);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (j.is_object())
|
||||
{
|
||||
write_msgpack_object_prefix(j.m_data.m_value.object->size(), j);
|
||||
if (!j.m_data.m_value.object->empty())
|
||||
{
|
||||
stack.emplace_back(&j);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
write_msgpack(j);
|
||||
write_container_iterative<cbor_walk>(root);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write out @a root and everything below it without the call stack
|
||||
|
||||
@sa @ref write_cbor_iterative
|
||||
*/
|
||||
void write_msgpack_iterative(const BasicJsonType& root)
|
||||
{
|
||||
std::vector<binary_container_frame> stack;
|
||||
write_msgpack_value_or_push(root, stack);
|
||||
|
||||
while (!stack.empty())
|
||||
{
|
||||
const binary_container_frame current = stack.back();
|
||||
|
||||
if (current.value->is_array())
|
||||
{
|
||||
const auto& array = *current.value->m_data.m_value.array;
|
||||
if (current.array_it == array.cend())
|
||||
{
|
||||
stack.pop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
const BasicJsonType* child = &(*current.array_it);
|
||||
++stack.back().array_it;
|
||||
write_msgpack_value_or_push(*child, stack);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto& object = *current.value->m_data.m_value.object;
|
||||
if (current.object_it == object.cend())
|
||||
{
|
||||
stack.pop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
// as in write_cbor_iterative, the key is written directly with
|
||||
// the object as diagnostics context
|
||||
write_msgpack_key(current.object_it->first, *current.value);
|
||||
const BasicJsonType* child = &(current.object_it->second);
|
||||
++stack.back().object_it;
|
||||
write_msgpack_value_or_push(*child, stack);
|
||||
}
|
||||
}
|
||||
write_container_iterative<msgpack_walk>(root);
|
||||
}
|
||||
|
||||
/// @return true when a closing ']' still has to be written after the elements
|
||||
bool write_ubjson_start_array(const BasicJsonType& j, const bool use_count, const bool use_type,
|
||||
const bool add_prefix, const bool use_bjdata, bool& prefix_required)
|
||||
/*!
|
||||
@brief write the start of an array (@a IsArray) or object: its opening
|
||||
marker, the optimized type, and the count
|
||||
|
||||
@return true when a closing ']' or '}' still has to be written after the
|
||||
elements
|
||||
*/
|
||||
template<bool IsArray>
|
||||
bool write_ubjson_start_container(const BasicJsonType& j, const bool use_count, const bool use_type,
|
||||
const bool add_prefix, const bool use_bjdata, bool& prefix_required)
|
||||
{
|
||||
prefix_required = true;
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('['));
|
||||
oa.write_character(to_char_type(IsArray ? '[' : '{'));
|
||||
}
|
||||
|
||||
if (use_type && !j.m_data.m_value.array->empty())
|
||||
const std::size_t size = IsArray ? j.m_data.m_value.array->size() : j.m_data.m_value.object->size();
|
||||
|
||||
if (use_type && size != 0)
|
||||
{
|
||||
if (!use_count)
|
||||
{
|
||||
throw_size_marker_required(j);
|
||||
}
|
||||
const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata);
|
||||
const bool same_prefix = std::all_of(j.begin() + 1, j.end(),
|
||||
// the first element of an array has been compared with itself
|
||||
const bool same_prefix = std::all_of(IsArray ? j.begin() + 1 : j.begin(), j.end(),
|
||||
[this, first_prefix, use_bjdata](const BasicJsonType & v)
|
||||
{
|
||||
return ubjson_prefix(v, use_bjdata) == first_prefix;
|
||||
@@ -1200,8 +1208,9 @@ class binary_writer
|
||||
// those, at one byte per element, so the result can be read back.
|
||||
// Objects are not affected: every element is preceded by its key.
|
||||
const bool valueless_type = (first_prefix == 'Z' || first_prefix == 'T' || first_prefix == 'F');
|
||||
const bool excessive_valueless = valueless_type
|
||||
&& j.m_data.m_value.array->size() > detail::max_valueless_container_size;
|
||||
const bool excessive_valueless = IsArray
|
||||
&& valueless_type
|
||||
&& size > detail::max_valueless_container_size;
|
||||
|
||||
if (same_prefix && !excessive_valueless
|
||||
&& !(use_bjdata && is_bjdata_excluded_type_marker(first_prefix)))
|
||||
@@ -1214,46 +1223,7 @@ class binary_writer
|
||||
|
||||
if (use_count)
|
||||
{
|
||||
write_ubjson_count(j.m_data.m_value.array->size(), use_bjdata);
|
||||
}
|
||||
|
||||
return !use_count;
|
||||
}
|
||||
|
||||
/// @return true when a closing '}' still has to be written after the elements
|
||||
bool write_ubjson_start_object(const BasicJsonType& j, const bool use_count, const bool use_type,
|
||||
const bool add_prefix, const bool use_bjdata, bool& prefix_required)
|
||||
{
|
||||
prefix_required = true;
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('{'));
|
||||
}
|
||||
|
||||
if (use_type && !j.m_data.m_value.object->empty())
|
||||
{
|
||||
if (!use_count)
|
||||
{
|
||||
throw_size_marker_required(j);
|
||||
}
|
||||
const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata);
|
||||
const bool same_prefix = std::all_of(j.begin(), j.end(),
|
||||
[this, first_prefix, use_bjdata](const BasicJsonType & v)
|
||||
{
|
||||
return ubjson_prefix(v, use_bjdata) == first_prefix;
|
||||
});
|
||||
|
||||
if (same_prefix && !(use_bjdata && is_bjdata_excluded_type_marker(first_prefix)))
|
||||
{
|
||||
prefix_required = false;
|
||||
oa.write_character(to_char_type('$'));
|
||||
oa.write_character(first_prefix);
|
||||
}
|
||||
}
|
||||
|
||||
if (use_count)
|
||||
{
|
||||
write_ubjson_count(j.m_data.m_value.object->size(), use_bjdata);
|
||||
write_ubjson_count(size, use_bjdata);
|
||||
}
|
||||
|
||||
return !use_count;
|
||||
@@ -1281,21 +1251,12 @@ class binary_writer
|
||||
|
||||
/// @brief an object or array @ref write_ubjson_iterative is still writing
|
||||
/// the elements of
|
||||
struct ubjson_frame
|
||||
struct ubjson_frame : binary_container_frame
|
||||
{
|
||||
ubjson_frame(const BasicJsonType* value_, const bool prefix_required_) noexcept
|
||||
: value(value_)
|
||||
: binary_container_frame(value_)
|
||||
, prefix_required(prefix_required_)
|
||||
{
|
||||
if (value->is_object())
|
||||
{
|
||||
object_it = value->m_data.m_value.object->cbegin();
|
||||
}
|
||||
else
|
||||
{
|
||||
array_it = value->m_data.m_value.array->cbegin();
|
||||
}
|
||||
}
|
||||
{}
|
||||
|
||||
// declared for GCC's -Weffc++, which asks for them in a class with
|
||||
// pointer members and a non-trivial destructor; the exception
|
||||
@@ -1307,13 +1268,9 @@ class binary_writer
|
||||
ubjson_frame& operator=(ubjson_frame&&) = default;
|
||||
~ubjson_frame() = default;
|
||||
|
||||
/// the array or object being written
|
||||
const BasicJsonType* value;
|
||||
/// whether value's elements each carry their own type marker; an
|
||||
/// optimized ($type) container writes it once for all of them instead
|
||||
bool prefix_required;
|
||||
typename BasicJsonType::object_t::const_iterator object_it{};
|
||||
typename BasicJsonType::array_t::const_iterator array_it{};
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -1324,7 +1281,7 @@ class binary_writer
|
||||
elements of an optimized container, and everything below the
|
||||
top level, never repeat it)
|
||||
|
||||
@sa @ref write_cbor_value_or_push
|
||||
@sa @ref write_container_value_or_push
|
||||
*/
|
||||
void write_ubjson_value_or_push(const BasicJsonType& j, const bool add_prefix, const bool use_count,
|
||||
const bool use_type, const bool use_bjdata, const bjdata_version_t bjdata_version,
|
||||
@@ -1347,11 +1304,11 @@ class binary_writer
|
||||
bool prefix_required = true;
|
||||
if (is_array)
|
||||
{
|
||||
write_ubjson_start_array(j, use_count, use_type, add_prefix, use_bjdata, prefix_required);
|
||||
write_ubjson_start_container<true>(j, use_count, use_type, add_prefix, use_bjdata, prefix_required);
|
||||
}
|
||||
else
|
||||
{
|
||||
write_ubjson_start_object(j, use_count, use_type, add_prefix, use_bjdata, prefix_required);
|
||||
write_ubjson_start_container<false>(j, use_count, use_type, add_prefix, use_bjdata, prefix_required);
|
||||
}
|
||||
|
||||
const bool empty = is_array ? j.m_data.m_value.array->empty() : j.m_data.m_value.object->empty();
|
||||
@@ -1361,7 +1318,7 @@ class binary_writer
|
||||
return;
|
||||
}
|
||||
|
||||
// write_ubjson_start_array/_object return !use_count, i.e. whether a
|
||||
// write_ubjson_start_container returns !use_count, i.e. whether a
|
||||
// closer still has to be written; use_count is constant for the whole
|
||||
// document, so that is recomputed here instead of being carried along
|
||||
if (!use_count)
|
||||
@@ -1550,14 +1507,15 @@ class binary_writer
|
||||
{
|
||||
oa.write_character(to_char_type(element_type));
|
||||
|
||||
if (error_handler == error_handler_t::keep || error_handler == error_handler_t::strict || is_valid_utf8(name))
|
||||
if (JSON_HEDLEY_LIKELY(error_handler == error_handler_t::keep || error_handler == error_handler_t::strict))
|
||||
{
|
||||
oa.write_characters(reinterpret_cast<const CharType*>(name.data()), name.size());
|
||||
write_bytes(name.data(), name.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
const string_t sanitized = sanitize_utf8(name, error_handler);
|
||||
oa.write_characters(reinterpret_cast<const CharType*>(sanitized.data()), sanitized.size());
|
||||
string_t storage;
|
||||
const string_t& written = sanitize_utf8_for_bson_write(name, storage);
|
||||
write_bytes(written.data(), written.size());
|
||||
}
|
||||
|
||||
// the terminating null byte is written explicitly rather than taken
|
||||
@@ -1633,16 +1591,11 @@ class binary_writer
|
||||
{
|
||||
write_bson_entry_header(name, 0x02);
|
||||
|
||||
const bool sanitize = error_handler != error_handler_t::keep
|
||||
&& error_handler != error_handler_t::strict
|
||||
&& !is_valid_utf8(value);
|
||||
const string_t sanitized = sanitize ? sanitize_utf8(value, error_handler) : string_t{};
|
||||
const string_t& written = sanitize ? sanitized : value;
|
||||
string_t storage;
|
||||
const string_t& written = sanitize_utf8_for_bson_write(value, storage);
|
||||
|
||||
write_number<std::int32_t>(to_bson_length(written.size() + 1ul), true);
|
||||
oa.write_characters(
|
||||
reinterpret_cast<const CharType*>(written.data()),
|
||||
written.size());
|
||||
write_bytes(written.data(), written.size());
|
||||
// the terminating null byte is written explicitly rather than taken
|
||||
// from the buffer, so that string_t::data() need not be null-terminated
|
||||
oa.write_character(to_char_type(0x00));
|
||||
@@ -1748,7 +1701,7 @@ class binary_writer
|
||||
|
||||
write_number(value.has_subtype() ? static_cast<std::uint8_t>(value.subtype()) : static_cast<std::uint8_t>(0x00));
|
||||
|
||||
oa.write_characters(reinterpret_cast<const CharType*>(value.data()), value.size());
|
||||
write_bytes(value.data(), value.size());
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1778,14 +1731,14 @@ class binary_writer
|
||||
case value_t::number_integer:
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits<std::int64_t>(j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "BSON", "int64");
|
||||
throw_integer_out_of_range(j, input_format_t::bson, "int64");
|
||||
}
|
||||
return calc_bson_integer_size(static_cast<std::int64_t>(j.m_data.m_value.number_integer));
|
||||
|
||||
case value_t::number_unsigned:
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_unsigned)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "BSON", "uint64");
|
||||
throw_integer_out_of_range(j, input_format_t::bson, "uint64");
|
||||
}
|
||||
return calc_bson_unsigned_size(conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
|
||||
@@ -1796,7 +1749,7 @@ class binary_writer
|
||||
return 0ul;
|
||||
|
||||
case value_t::discarded:
|
||||
throw_on_discarded(j, "BSON");
|
||||
throw_on_discarded(j, input_format_t::bson);
|
||||
|
||||
// LCOV_EXCL_START
|
||||
case value_t::object:
|
||||
@@ -1960,7 +1913,7 @@ class binary_writer
|
||||
// the terminating null byte is written explicitly rather than
|
||||
// taken from the buffer, so that string_t::data() need not be
|
||||
// null-terminated
|
||||
oa.write_characters(reinterpret_cast<const CharType*>(value.data()), value.size());
|
||||
write_bytes(value.data(), value.size());
|
||||
oa.write_character(to_char_type(0x00));
|
||||
return true;
|
||||
}
|
||||
@@ -1977,7 +1930,7 @@ class binary_writer
|
||||
p += sizeof(std::int32_t);
|
||||
*p++ = value.has_subtype() ? to_char_type(static_cast<std::uint8_t>(value.subtype())) : to_char_type(0x00);
|
||||
oa.commit(static_cast<std::size_t>(p - out));
|
||||
oa.write_characters(reinterpret_cast<const CharType*>(value.data()), value.size());
|
||||
write_bytes(value.data(), value.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2025,7 +1978,7 @@ class binary_writer
|
||||
return write_bson_null(name);
|
||||
|
||||
case value_t::discarded:
|
||||
throw_on_discarded(j, "BSON");
|
||||
throw_on_discarded(j, input_format_t::bson);
|
||||
|
||||
// LCOV_EXCL_START
|
||||
case value_t::object:
|
||||
@@ -2806,7 +2759,7 @@ class binary_writer
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_unsigned)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "CBOR", "uint64");
|
||||
throw_integer_out_of_range(j, input_format_t::cbor, "uint64");
|
||||
}
|
||||
write_cbor_head(0x00, conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
return;
|
||||
@@ -2816,7 +2769,7 @@ class binary_writer
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "CBOR", "[-2^64, 2^64-1]");
|
||||
throw_integer_out_of_range(j, input_format_t::cbor, "[-2^64, 2^64-1]");
|
||||
}
|
||||
// CBOR does not differentiate between positive signed
|
||||
// integers and unsigned integers
|
||||
@@ -2827,7 +2780,7 @@ class binary_writer
|
||||
// a negative integer n is encoded as -1 - n
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(-1 - j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "CBOR", "[-2^64, 2^64-1]");
|
||||
throw_integer_out_of_range(j, input_format_t::cbor, "[-2^64, 2^64-1]");
|
||||
}
|
||||
write_cbor_head(0x20, static_cast<std::uint64_t>(-1 - j.m_data.m_value.number_integer));
|
||||
}
|
||||
@@ -2903,7 +2856,7 @@ class binary_writer
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_unsigned)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "MessagePack", "uint64");
|
||||
throw_integer_out_of_range(j, input_format_t::msgpack, "uint64");
|
||||
}
|
||||
write_msgpack_unsigned(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
return;
|
||||
@@ -2913,7 +2866,7 @@ class binary_writer
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "MessagePack", "[-2^63, 2^64-1]");
|
||||
throw_integer_out_of_range(j, input_format_t::msgpack, "[-2^63, 2^64-1]");
|
||||
}
|
||||
// MessagePack does not differentiate between positive
|
||||
// signed integers and unsigned integers.
|
||||
@@ -2923,7 +2876,7 @@ class binary_writer
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_min<std::int64_t>(j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "MessagePack", "[-2^63, 2^64-1]");
|
||||
throw_integer_out_of_range(j, input_format_t::msgpack, "[-2^63, 2^64-1]");
|
||||
}
|
||||
if (j.m_data.m_value.number_integer >= -32)
|
||||
{
|
||||
@@ -3057,40 +3010,37 @@ class binary_writer
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write an object key as a CBOR text string
|
||||
@brief write an object key as a CBOR or MessagePack string (@a Walk)
|
||||
|
||||
A key convertible to string_t is written directly. Other key types (only
|
||||
an explicit conversion, or only a to_json overload) go through a temporary
|
||||
basic_json, as in version 3.12.0; the temporary is then the diagnostics
|
||||
context for strict UTF-8 checks.
|
||||
*/
|
||||
template<typename Key = typename BasicJsonType::object_t::key_type,
|
||||
template<typename Walk, typename Key = typename BasicJsonType::object_t::key_type,
|
||||
enable_if_t<std::is_convertible<Key, string_t>::value, int> = 0>
|
||||
void write_object_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& context)
|
||||
{
|
||||
Walk::string(*this, key, context);
|
||||
}
|
||||
|
||||
template<typename Walk, typename Key = typename BasicJsonType::object_t::key_type,
|
||||
enable_if_t<negation<std::is_convertible<Key, string_t>>::value, int> = 0>
|
||||
void write_object_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& /*context*/)
|
||||
{
|
||||
Walk::scalar(*this, BasicJsonType(key));
|
||||
}
|
||||
|
||||
/// @brief write an object key as a CBOR text string
|
||||
void write_cbor_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& context)
|
||||
{
|
||||
write_cbor_string(key, context);
|
||||
write_object_key<cbor_walk>(key, context);
|
||||
}
|
||||
|
||||
template < typename Key = typename BasicJsonType::object_t::key_type,
|
||||
enable_if_t < !std::is_convertible<Key, string_t>::value, int > = 0 >
|
||||
void write_cbor_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& /*context*/)
|
||||
{
|
||||
write_cbor(BasicJsonType(key));
|
||||
}
|
||||
|
||||
/// @brief write an object key as a MessagePack str, as in @ref write_cbor_key
|
||||
template<typename Key = typename BasicJsonType::object_t::key_type,
|
||||
enable_if_t<std::is_convertible<Key, string_t>::value, int> = 0>
|
||||
/// @brief write an object key as a MessagePack str
|
||||
void write_msgpack_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& context)
|
||||
{
|
||||
write_msgpack_string(key, context);
|
||||
}
|
||||
|
||||
template < typename Key = typename BasicJsonType::object_t::key_type,
|
||||
enable_if_t < !std::is_convertible<Key, string_t>::value, int > = 0 >
|
||||
void write_msgpack_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& /*context*/)
|
||||
{
|
||||
write_msgpack(BasicJsonType(key));
|
||||
write_object_key<msgpack_walk>(key, context);
|
||||
}
|
||||
|
||||
/// the longest string that is written, with its head of one or two
|
||||
@@ -3165,9 +3115,7 @@ class binary_writer
|
||||
write_cbor_head(0x60, sanitized.size());
|
||||
|
||||
// step 2: write the string
|
||||
oa.write_characters(
|
||||
reinterpret_cast<const CharType*>(sanitized.data()),
|
||||
sanitized.size());
|
||||
write_bytes(sanitized.data(), sanitized.size());
|
||||
}
|
||||
|
||||
/////////////
|
||||
@@ -3248,9 +3196,7 @@ class binary_writer
|
||||
}
|
||||
|
||||
// step 2: write the string
|
||||
oa.write_characters(
|
||||
reinterpret_cast<const CharType*>(sanitized.data()),
|
||||
sanitized.size());
|
||||
write_bytes(sanitized.data(), sanitized.size());
|
||||
}
|
||||
|
||||
////////////
|
||||
@@ -3533,9 +3479,7 @@ class binary_writer
|
||||
oa.write_character(to_char_type('S'));
|
||||
}
|
||||
write_number_with_ubjson_prefix(sanitized.size(), true, use_bjdata);
|
||||
oa.write_characters(
|
||||
reinterpret_cast<const CharType*>(sanitized.data()),
|
||||
sanitized.size());
|
||||
write_bytes(sanitized.data(), sanitized.size());
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -4335,7 +4279,7 @@ class binary_writer
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::int64_t>(j.m_data.m_value.number_unsigned)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "BON8", "int64");
|
||||
throw_integer_out_of_range(j, input_format_t::bon8, "int64");
|
||||
}
|
||||
write_bon8_integer(static_cast<std::int64_t>(j.m_data.m_value.number_unsigned));
|
||||
string_open = false;
|
||||
@@ -4346,7 +4290,7 @@ class binary_writer
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits<std::int64_t>(j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "BON8", "int64");
|
||||
throw_integer_out_of_range(j, input_format_t::bon8, "int64");
|
||||
}
|
||||
write_bon8_integer(static_cast<std::int64_t>(j.m_data.m_value.number_integer));
|
||||
string_open = false;
|
||||
@@ -4414,7 +4358,7 @@ class binary_writer
|
||||
|
||||
case value_t::discarded:
|
||||
default:
|
||||
throw_on_discarded(j, "BON8");
|
||||
throw_on_discarded(j, input_format_t::bon8);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4425,7 +4369,7 @@ class binary_writer
|
||||
A scalar, and an empty array or object, are written out in full and not
|
||||
pushed.
|
||||
|
||||
@sa @ref write_cbor_value_or_push
|
||||
@sa @ref write_container_value_or_push
|
||||
*/
|
||||
void write_bon8_value_or_push(const BasicJsonType& j, bool& string_open, std::vector<binary_container_frame>& stack)
|
||||
{
|
||||
@@ -4811,7 +4755,7 @@ class binary_writer
|
||||
}
|
||||
else
|
||||
{
|
||||
oa.write_characters(reinterpret_cast<const CharType*>(s.data()), s.size());
|
||||
write_bytes(s.data(), s.size());
|
||||
string_open = true;
|
||||
}
|
||||
}
|
||||
@@ -4902,6 +4846,31 @@ class binary_writer
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the write-pass counterpart of @ref sanitize_utf8_for_write for BSON
|
||||
|
||||
The size pass (@ref calc_bson_entry_header_size, @ref
|
||||
calc_bson_string_size) has already validated @a s, and thrown for @ref
|
||||
error_handler_t::strict if it was ill-formed. Here, @ref
|
||||
error_handler_t::strict therefore behaves like @ref error_handler_t::keep,
|
||||
and @ref error_handler_t::replace / @ref error_handler_t::ignore produce
|
||||
exactly the bytes the size pass measured. Hence no context is needed.
|
||||
|
||||
@param[in] s the string (value or object key) to write
|
||||
@param[out] storage backing storage for a sanitized copy
|
||||
|
||||
@return a reference to @a s, or to @a storage once it holds a sanitized copy
|
||||
*/
|
||||
const string_t& sanitize_utf8_for_bson_write(const string_t& s, string_t& storage) const
|
||||
{
|
||||
if (error_handler == error_handler_t::keep || error_handler == error_handler_t::strict || is_valid_utf8(s))
|
||||
{
|
||||
return s; // NOLINT(bugprone-return-const-ref-from-parameter): callers pass lvalues that outlive the call
|
||||
}
|
||||
storage = sanitize_utf8(s, error_handler);
|
||||
return storage;
|
||||
}
|
||||
|
||||
/// deleted: anything but a string_t would bind a temporary that dies before the returned reference is used
|
||||
template < typename T, enable_if_t < !std::is_same<T, string_t>::value, int > = 0 >
|
||||
const string_t& sanitize_utf8_for_write(const T& /*s*/, const BasicJsonType& /*context*/, string_t& /*storage*/) const = delete; // NOLINT(hicpp-use-equals-delete,modernize-use-equals-delete): a private helper's guard, not part of the interface
|
||||
|
||||
@@ -966,6 +966,28 @@ class serializer
|
||||
}
|
||||
}
|
||||
|
||||
/// write out the bytes pending in @ref string_buffer and reset @a bytes
|
||||
JSON_HEDLEY_ALWAYS_INLINE void emit_pending(std::size_t& bytes)
|
||||
{
|
||||
if (bytes != 0)
|
||||
{
|
||||
put_buffer(string_buffer, bytes);
|
||||
bytes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// write buffer and reset index if fewer than 13 bytes are left, as this
|
||||
/// is the maximal number of bytes to be written ("\uxxxx\uxxxx\0") for one
|
||||
/// code point
|
||||
JSON_HEDLEY_ALWAYS_INLINE void reserve_string_buffer(std::size_t& bytes)
|
||||
{
|
||||
if (string_buffer.size() - bytes < 13)
|
||||
{
|
||||
put_buffer(string_buffer, bytes);
|
||||
bytes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief worker for @ref dump_escaped
|
||||
|
||||
@@ -1024,11 +1046,7 @@ class serializer
|
||||
{
|
||||
// emit any bytes still pending in string_buffer first to
|
||||
// preserve output order, then write the run directly
|
||||
if (bytes != 0)
|
||||
{
|
||||
put_buffer(string_buffer, bytes);
|
||||
bytes = 0;
|
||||
}
|
||||
emit_pending(bytes);
|
||||
put_run(s, i, run);
|
||||
bytes_after_last_accept = 0;
|
||||
undumped_chars = 0;
|
||||
@@ -1049,11 +1067,10 @@ class serializer
|
||||
{
|
||||
// emit any bytes still pending in string_buffer first to
|
||||
// preserve output order
|
||||
if (bytes != 0)
|
||||
{
|
||||
put_buffer(string_buffer, bytes);
|
||||
bytes = 0;
|
||||
}
|
||||
emit_pending(bytes);
|
||||
// deliberately not put_buffer(): a fixed 8-byte memcpy
|
||||
// has no asserts and no write-through branch, which
|
||||
// matters on the hot path
|
||||
const auto& escape = ascii_escapes()[data[i]];
|
||||
if (JSON_HEDLEY_UNLIKELY(write_buffer.size() - write_buffer_pos < escape.size()))
|
||||
{
|
||||
@@ -1128,14 +1145,7 @@ class serializer
|
||||
string_buffer[bytes++] = s[i];
|
||||
}
|
||||
|
||||
// write buffer and reset index; there must be 13 bytes
|
||||
// left, as this is the maximal number of bytes to be
|
||||
// written ("\uxxxx\uxxxx\0") for one code point
|
||||
if (string_buffer.size() - bytes < 13)
|
||||
{
|
||||
put_buffer(string_buffer, bytes);
|
||||
bytes = 0;
|
||||
}
|
||||
reserve_string_buffer(bytes);
|
||||
|
||||
// remember the byte position of this accept
|
||||
bytes_after_last_accept = bytes;
|
||||
@@ -1187,14 +1197,7 @@ class serializer
|
||||
string_buffer[bytes++] = '\xBD';
|
||||
}
|
||||
|
||||
// write buffer and reset index; there must be 13 bytes
|
||||
// left, as this is the maximal number of bytes to be
|
||||
// written ("\uxxxx\uxxxx\0") for one code point
|
||||
if (string_buffer.size() - bytes < 13)
|
||||
{
|
||||
put_buffer(string_buffer, bytes);
|
||||
bytes = 0;
|
||||
}
|
||||
reserve_string_buffer(bytes);
|
||||
|
||||
bytes_after_last_accept = bytes;
|
||||
}
|
||||
@@ -1227,14 +1230,7 @@ class serializer
|
||||
string_buffer[bytes++] = s[i];
|
||||
}
|
||||
|
||||
// write buffer and reset index; there must be 13 bytes
|
||||
// left, as this is the maximal number of bytes to be
|
||||
// written ("\uxxxx\uxxxx\0") for one code point
|
||||
if (string_buffer.size() - bytes < 13)
|
||||
{
|
||||
put_buffer(string_buffer, bytes);
|
||||
bytes = 0;
|
||||
}
|
||||
reserve_string_buffer(bytes);
|
||||
|
||||
bytes_after_last_accept = bytes;
|
||||
undumped_chars = 0;
|
||||
@@ -1600,30 +1596,17 @@ class serializer
|
||||
*/
|
||||
static const std::array<std::uint8_t, 256>& ascii_copyable() noexcept
|
||||
{
|
||||
static const std::array<std::uint8_t, 256> copyable =
|
||||
{
|
||||
{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x00
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x10
|
||||
1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x20
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x30
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, // 0x50
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 0x70
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x80
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x90
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xA0
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xB0
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xC0
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xD0
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xE0
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 0xF0
|
||||
}
|
||||
};
|
||||
// constant-initialized from the predicate: no runtime loop, no guard
|
||||
static constexpr std::array<std::uint8_t, 256> copyable = make_ascii_copyable(make_index_sequence<256> {});
|
||||
return copyable;
|
||||
}
|
||||
|
||||
template<std::size_t... Is>
|
||||
static constexpr std::array<std::uint8_t, 256> make_ascii_copyable(index_sequence<Is...> /*unused*/) noexcept
|
||||
{
|
||||
return {{ static_cast<std::uint8_t>(is_ascii_copyable(static_cast<unsigned char>(Is)) ? 1 : 0)... }};
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the JSON form of each character below 0x60
|
||||
|
||||
@@ -1752,7 +1735,6 @@ class serializer
|
||||
*/
|
||||
JSON_HEDLEY_NEVER_INLINE std::size_t write_u_escapes(const unsigned char* data, std::size_t n)
|
||||
{
|
||||
constexpr const char* nibble_to_hex = "0123456789abcdef";
|
||||
std::size_t i = 0;
|
||||
while (i < n && data[i] >= 0x80)
|
||||
{
|
||||
@@ -1789,26 +1771,21 @@ class serializer
|
||||
char* d = write_buffer.data() + write_buffer_pos;
|
||||
if (codepoint > 0xFFFFu)
|
||||
{
|
||||
d = put_u_escape(d, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)), nibble_to_hex);
|
||||
d = put_u_escape(d, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)));
|
||||
codepoint = 0xDC00u + (codepoint & 0x3FFu);
|
||||
}
|
||||
d = put_u_escape(d, static_cast<std::uint16_t>(codepoint), nibble_to_hex);
|
||||
d = put_u_escape(d, static_cast<std::uint16_t>(codepoint));
|
||||
write_buffer_pos = static_cast<std::size_t>(d - write_buffer.data());
|
||||
i += length;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
static char* put_u_escape(char* d, const std::uint16_t codeunit, const char* nibble_to_hex) noexcept
|
||||
static char* put_u_escape(char* d, const std::uint16_t codeunit) noexcept
|
||||
{
|
||||
const auto unit = static_cast<std::uint32_t>(codeunit);
|
||||
d[0] = '\\';
|
||||
d[1] = 'u';
|
||||
d[2] = nibble_to_hex[(unit >> 12u) & 0x0Fu];
|
||||
d[3] = nibble_to_hex[(unit >> 8u) & 0x0Fu];
|
||||
d[4] = nibble_to_hex[(unit >> 4u) & 0x0Fu];
|
||||
d[5] = nibble_to_hex[unit & 0x0Fu];
|
||||
return d + 6;
|
||||
return hex4_lower(codeunit, d + 2);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1817,7 +1794,7 @@ class serializer
|
||||
* Branch-free replacement for `snprintf(buf, 7, "\\u%04x", codeunit)` in the
|
||||
* string escaping hot path. It writes exactly six characters ('\\', 'u' and
|
||||
* four hex digits) at position @a pos of @a string_buffer via a nibble
|
||||
* lookup table, avoiding the format-string parsing and locale machinery of
|
||||
* lookup table (@ref hex4_lower), avoiding the format-string parsing and locale machinery of
|
||||
* `snprintf`. Advances @a pos by the number of bytes written (6).
|
||||
*
|
||||
* @param[in] pos position in @a string_buffer to write at; there must
|
||||
@@ -1827,13 +1804,9 @@ class serializer
|
||||
void write_u_escape(std::size_t& pos, std::uint16_t codeunit) noexcept
|
||||
{
|
||||
JSON_ASSERT(string_buffer.size() - pos >= 6);
|
||||
constexpr const char* nibble_to_hex = "0123456789abcdef";
|
||||
string_buffer[pos + 0] = '\\';
|
||||
string_buffer[pos + 1] = 'u';
|
||||
string_buffer[pos + 2] = nibble_to_hex[(codeunit >> 12u) & 0x0Fu];
|
||||
string_buffer[pos + 3] = nibble_to_hex[(codeunit >> 8u) & 0x0Fu];
|
||||
string_buffer[pos + 4] = nibble_to_hex[(codeunit >> 4u) & 0x0Fu];
|
||||
string_buffer[pos + 5] = nibble_to_hex[codeunit & 0x0Fu];
|
||||
hex4_lower(codeunit, &string_buffer[pos + 2]);
|
||||
pos += 6;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,24 @@ inline std::string hex_byte(const std::uint8_t byte)
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write a UTF-16 code unit as four lowercase hexadecimal digits
|
||||
|
||||
The lowercase counterpart of hex_byte(), used for the `\\uXXXX` escapes of the
|
||||
serializers, whose output is lowercase. Writes exactly four characters to
|
||||
@a out and returns the pointer past them.
|
||||
*/
|
||||
JSON_HEDLEY_ALWAYS_INLINE char* hex4_lower(const std::uint16_t codeunit, char* out) noexcept
|
||||
{
|
||||
constexpr const char* nibble_to_hex = "0123456789abcdef";
|
||||
const unsigned int unit = codeunit;
|
||||
out[0] = nibble_to_hex[(unit >> 12u) & 0x0Fu];
|
||||
out[1] = nibble_to_hex[(unit >> 8u) & 0x0Fu];
|
||||
out[2] = nibble_to_hex[(unit >> 4u) & 0x0Fu];
|
||||
out[3] = nibble_to_hex[unit & 0x0Fu];
|
||||
return out + 4;
|
||||
}
|
||||
|
||||
///////////////////
|
||||
// UTF-8 encoding //
|
||||
///////////////////
|
||||
@@ -103,6 +121,66 @@ void encode_utf8(std::uint32_t cp, const Out& out)
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the shape of a well-formed UTF-8 sequence, by its lead byte
|
||||
|
||||
@a length is 1 for ASCII, 2 to 4 for a valid lead byte, and 0 for a byte that
|
||||
cannot start a well-formed sequence (a continuation byte, 0xC0, 0xC1, or 0xF5
|
||||
to 0xFF). For a multibyte sequence, the second byte must lie in
|
||||
[@a second_lo, @a second_hi], and every later byte in [0x80, 0xBF]
|
||||
(RFC 3629, section 4; Unicode Table 3-7). The narrower second-byte ranges
|
||||
exclude overlong forms (0xE0, 0xF0), surrogates (0xED), and code points
|
||||
beyond U+10FFFF (0xF4).
|
||||
*/
|
||||
struct utf8_sequence_shape
|
||||
{
|
||||
std::uint8_t length;
|
||||
std::uint8_t second_lo;
|
||||
std::uint8_t second_hi;
|
||||
};
|
||||
|
||||
/// @return the shape of a UTF-8 sequence starting with @a lead, see utf8_sequence_shape
|
||||
inline utf8_sequence_shape utf8_shape(const std::uint8_t lead) noexcept
|
||||
{
|
||||
if (lead < 0x80u)
|
||||
{
|
||||
return {1, 0, 0};
|
||||
}
|
||||
if (lead < 0xC2u)
|
||||
{
|
||||
return {0, 0, 0};
|
||||
}
|
||||
if (lead <= 0xDFu)
|
||||
{
|
||||
return {2, 0x80u, 0xBFu};
|
||||
}
|
||||
if (lead == 0xE0u)
|
||||
{
|
||||
return {3, 0xA0u, 0xBFu};
|
||||
}
|
||||
if (lead == 0xEDu)
|
||||
{
|
||||
return {3, 0x80u, 0x9Fu};
|
||||
}
|
||||
if (lead <= 0xEFu)
|
||||
{
|
||||
return {3, 0x80u, 0xBFu};
|
||||
}
|
||||
if (lead == 0xF0u)
|
||||
{
|
||||
return {4, 0x90u, 0xBFu};
|
||||
}
|
||||
if (lead <= 0xF3u)
|
||||
{
|
||||
return {4, 0x80u, 0xBFu};
|
||||
}
|
||||
if (lead == 0xF4u)
|
||||
{
|
||||
return {4, 0x80u, 0x8Fu};
|
||||
}
|
||||
return {0, 0, 0};
|
||||
}
|
||||
|
||||
///////////////////
|
||||
// UTF-8 decoding //
|
||||
///////////////////
|
||||
@@ -118,7 +196,7 @@ This is a single-byte step of a "shift-based" UTF-8 decoder originally
|
||||
written by Björn Hoehrmann. See
|
||||
http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
|
||||
|
||||
The library checks UTF-8 well-formedness (RFC 3629, section 4) in three
|
||||
The library checks UTF-8 well-formedness (RFC 3629, section 4) in five
|
||||
places, which differ in speed, diagnostics, and how they read the input:
|
||||
|
||||
- decode() below: the serializer, to escape and, in strict mode, reject
|
||||
@@ -132,10 +210,15 @@ places, which differ in speed, diagnostics, and how they read the input:
|
||||
bulk string scan, the bulk path of the BON8 reader, and the BON8 writer.
|
||||
They must accept exactly what the lexer's switch accepts.
|
||||
- the byte path of binary_reader::get_bon8_string(): BON8 input without bulk
|
||||
access, and the bytes the bulk path leaves to it.
|
||||
access, and the bytes the bulk path leaves to it. It keeps its own ranges:
|
||||
taking them from utf8_shape() made BON8 decoding 2-3% slower (#5891).
|
||||
- check_utf8() in detail/view/edit.hpp: strings stored in an editable
|
||||
json_document, with the index of the offending byte. It also uses
|
||||
utf8_shape().
|
||||
|
||||
All four must accept the same set of sequences, so a change to one needs a
|
||||
matching change to the others.
|
||||
All five must accept the same set of sequences, which the test "UTF-8
|
||||
validators accept the same sequences" in unit-class_lexer.cpp checks
|
||||
exhaustively against decode() and utf8_shape().
|
||||
|
||||
@param[in,out] state the current decoder state
|
||||
@param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT)
|
||||
|
||||
@@ -177,6 +177,36 @@ inline bool owns_memory(const value_t t) noexcept
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief whether a value of the given type is neither an object nor an array
|
||||
|
||||
Unlike basic_json::is_primitive(), this includes value_t::discarded: it names
|
||||
the types that iterators walk with a primitive_iterator_t (a single value, or no
|
||||
value for null and discarded) and that JSON Pointer resolution and the
|
||||
serializer treat as leaves. Every enumerator is listed so that -Wswitch-enum
|
||||
flags this function when a new type is added.
|
||||
*/
|
||||
inline bool is_primitive(const value_t t) noexcept
|
||||
{
|
||||
switch (t)
|
||||
{
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
return false;
|
||||
|
||||
case value_t::null:
|
||||
case value_t::string:
|
||||
case value_t::boolean:
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
case value_t::number_float:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
@brief compare an integer with a floating point number without precision loss
|
||||
|
||||
@@ -51,11 +51,6 @@ struct first_true<false, Conditions...> : std::integral_constant < int, 1 + firs
|
||||
inline void check_utf8(const char* s, std::size_t n)
|
||||
{
|
||||
const auto* const p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
const auto hex = [](unsigned char c)
|
||||
{
|
||||
constexpr const char* digits = "0123456789ABCDEF";
|
||||
return std::string{digits[c >> 4u], digits[c & 0xFu]};
|
||||
};
|
||||
for (std::size_t i = 0; i < n;)
|
||||
{
|
||||
const unsigned char c = p[i];
|
||||
@@ -64,39 +59,22 @@ inline void check_utf8(const char* s, std::size_t n)
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
std::size_t len = 0;
|
||||
unsigned char lo = 0x80;
|
||||
unsigned char hi = 0xBF;
|
||||
if (c >= 0xC2 && c <= 0xDF)
|
||||
const utf8_sequence_shape shape = utf8_shape(c);
|
||||
const std::size_t len = shape.length;
|
||||
if (len == 0)
|
||||
{
|
||||
len = 2;
|
||||
}
|
||||
else if (c >= 0xE0 && c <= 0xEF)
|
||||
{
|
||||
len = 3;
|
||||
lo = c == 0xE0 ? 0xA0 : 0x80;
|
||||
hi = c == 0xED ? 0x9F : 0xBF;
|
||||
}
|
||||
else if (c >= 0xF0 && c <= 0xF4)
|
||||
{
|
||||
len = 4;
|
||||
lo = c == 0xF0 ? 0x90 : 0x80;
|
||||
hi = c == 0xF4 ? 0x8F : 0xBF;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw_type_error(316, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", hex(c)));
|
||||
throw_type_error(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", hex_byte(c)));
|
||||
}
|
||||
for (std::size_t k = 1; k < len; ++k)
|
||||
{
|
||||
if (i + k == n)
|
||||
{
|
||||
throw_type_error(316, concat("incomplete UTF-8 string; last byte: 0x", hex(p[n - 1])));
|
||||
throw_type_error(exception_id::invalid_utf8, concat("incomplete UTF-8 string; last byte: 0x", hex_byte(p[n - 1])));
|
||||
}
|
||||
const unsigned char b = p[i + k];
|
||||
if (b < (k == 1 ? lo : 0x80) || b > (k == 1 ? hi : 0xBF))
|
||||
if (b < (k == 1 ? shape.second_lo : 0x80) || b > (k == 1 ? shape.second_hi : 0xBF))
|
||||
{
|
||||
throw_type_error(316, concat("invalid UTF-8 byte at index ", std::to_string(i + k), ": 0x", hex(b)));
|
||||
throw_type_error(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(i + k), ": 0x", hex_byte(b)));
|
||||
}
|
||||
}
|
||||
i += len;
|
||||
@@ -144,7 +122,7 @@ class editor
|
||||
node* const o = own(object);
|
||||
if (o->kind != static_cast<std::uint8_t>(value_t::object) && o->kind != static_cast<std::uint8_t>(value_t::null))
|
||||
{
|
||||
throw_type_error(305, "cannot use operator[] with a string argument with ", object.type_name());
|
||||
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", object.type_name());
|
||||
}
|
||||
check_utf8(key.data(), key.size());
|
||||
const encoded e = encode(std::forward<V>(value));
|
||||
@@ -195,7 +173,7 @@ class editor
|
||||
node* const a = own(array);
|
||||
if (a->kind != static_cast<std::uint8_t>(value_t::array))
|
||||
{
|
||||
throw_type_error(305, "cannot use operator[] with a numeric argument with ", array.type_name());
|
||||
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", array.type_name());
|
||||
}
|
||||
check_index(idx, a->len);
|
||||
const encoded e = encode(std::forward<V>(value));
|
||||
@@ -212,7 +190,7 @@ class editor
|
||||
node* const a = own(array);
|
||||
if (a->kind != static_cast<std::uint8_t>(value_t::array) && a->kind != static_cast<std::uint8_t>(value_t::null))
|
||||
{
|
||||
throw_type_error(308, "cannot use push_back() with ", array.type_name());
|
||||
throw_type_error(exception_id::push_back_wrong_type, "cannot use push_back() with ", array.type_name());
|
||||
}
|
||||
const encoded e = encode(std::forward<V>(value));
|
||||
if (a->kind == static_cast<std::uint8_t>(value_t::null))
|
||||
@@ -236,7 +214,7 @@ class editor
|
||||
node* const a = own(array);
|
||||
if (a->kind != static_cast<std::uint8_t>(value_t::array))
|
||||
{
|
||||
throw_type_error(309, "cannot use insert() with ", array.type_name());
|
||||
throw_type_error(exception_id::insert_wrong_type, "cannot use insert() with ", array.type_name());
|
||||
}
|
||||
check_index(idx, a->len + 1);
|
||||
const encoded e = encode(std::forward<V>(value));
|
||||
@@ -256,7 +234,7 @@ class editor
|
||||
node* const o = own(object);
|
||||
if (o->kind != static_cast<std::uint8_t>(value_t::object))
|
||||
{
|
||||
throw_type_error(307, "cannot use erase() with ", object.type_name());
|
||||
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", object.type_name());
|
||||
}
|
||||
for (const node* k = nav::first(m_doc, o), *end = nav::end(m_doc, o); k != end; k = document_data::after(k + 1))
|
||||
{
|
||||
@@ -274,7 +252,7 @@ class editor
|
||||
node* const a = own(array);
|
||||
if (a->kind != static_cast<std::uint8_t>(value_t::array))
|
||||
{
|
||||
throw_type_error(307, "cannot use erase() with ", array.type_name());
|
||||
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", array.type_name());
|
||||
}
|
||||
check_index(idx, a->len);
|
||||
node* const h = block_of(m_doc, a, 0);
|
||||
@@ -297,7 +275,7 @@ class editor
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(v.m_doc != &m_doc || v.m_node == nullptr))
|
||||
{
|
||||
throw_invalid_iterator(202, "view does not belong to this document");
|
||||
throw_invalid_iterator(exception_id::iterator_from_other_value, "view does not belong to this document");
|
||||
}
|
||||
edit_state_of(m_doc);
|
||||
return const_cast<node*>(v.m_node); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document
|
||||
@@ -307,7 +285,7 @@ class editor
|
||||
{
|
||||
if (idx >= limit)
|
||||
{
|
||||
throw_out_of_range(401, concat("array index ", std::to_string(idx), " is out of range"));
|
||||
throw_array_index_out_of_range(idx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,7 +426,7 @@ class editor
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(v.m_node == nullptr))
|
||||
{
|
||||
throw_type_error(302, "type must be a value, but is ", "discarded");
|
||||
throw_type_error(exception_id::type_mismatch, "type must be a value, but is ", "discarded");
|
||||
}
|
||||
encoded r;
|
||||
if (!is_container(*v.m_node))
|
||||
@@ -598,7 +576,7 @@ class editor
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(len >= 0xFFFFFFFFu))
|
||||
{
|
||||
throw_out_of_range(416, "strings of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_LINE
|
||||
throw_out_of_range(exception_id::document_too_large, "strings of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_LINE
|
||||
}
|
||||
node n = plain_node(value_t::string);
|
||||
n.flags = static_cast<std::uint8_t>(n.flags | node_flags::edited);
|
||||
@@ -673,12 +651,12 @@ class editor
|
||||
return string_node(s.data(), s.size());
|
||||
}
|
||||
case value_t::binary:
|
||||
throw_type_error(319, "cannot store a binary value in a json_document", "");
|
||||
throw_type_error(exception_id::document_binary_value, "cannot store a binary value in a json_document", "");
|
||||
case value_t::discarded:
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
default:
|
||||
throw_type_error(302, "type must be a value, but is ", "discarded");
|
||||
throw_type_error(exception_id::type_mismatch, "type must be a value, but is ", "discarded");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ inline std::size_t text_capacity(std::size_t cap, std::size_t used, std::size_t
|
||||
constexpr std::size_t limit = 0xFFFFFFFFu;
|
||||
if (NLOHMANN_VIEW_UNLIKELY(used > limit || n > limit - used))
|
||||
{
|
||||
throw_out_of_range(416, "edits of 4 GiB or more are not supported by json_document");
|
||||
throw_out_of_range(exception_id::document_too_large, "edits of 4 GiB or more are not supported by json_document");
|
||||
}
|
||||
const std::size_t needed = used + n;
|
||||
const std::size_t wanted = needed + (std::min)(limit - needed, std::size_t{256});
|
||||
@@ -135,7 +135,7 @@ inline void reserve_moved(document_data& d)
|
||||
document_data::edit_state& e = edit_state_of(d);
|
||||
if (e.moved.size() >= 0xFFFFFFFFu)
|
||||
{
|
||||
throw_out_of_range(416, "more than 4294967295 edited arrays and objects are not supported by json_document"); // LCOV_EXCL_LINE
|
||||
throw_out_of_range(exception_id::document_too_large, "more than 4294967295 edited arrays and objects are not supported by json_document"); // LCOV_EXCL_LINE
|
||||
}
|
||||
if (e.moved.size() == e.moved.capacity() || e.moved_cap.size() == e.moved_cap.capacity())
|
||||
{
|
||||
|
||||
@@ -25,32 +25,52 @@ namespace view
|
||||
// Exceptions are thrown out of line, so that the accessors that may throw stay
|
||||
// small enough to be inlined.
|
||||
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(int id, const char* prefix, const char* type)
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(exception_id id, const char* prefix, const char* type)
|
||||
{
|
||||
NLOHMANN_VIEW_THROW(type_error::create(id, concat(prefix, type), nullptr));
|
||||
}
|
||||
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(int id, const std::string& msg)
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(exception_id id, const std::string& msg)
|
||||
{
|
||||
NLOHMANN_VIEW_THROW(type_error::create(id, msg, nullptr));
|
||||
}
|
||||
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_out_of_range(int id, const std::string& msg)
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_out_of_range(exception_id id, const std::string& msg)
|
||||
{
|
||||
NLOHMANN_VIEW_THROW(out_of_range::create(id, msg, nullptr));
|
||||
}
|
||||
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_iterator(int id, const char* msg)
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_iterator(exception_id id, const char* msg)
|
||||
{
|
||||
NLOHMANN_VIEW_THROW(invalid_iterator::create(id, msg, nullptr));
|
||||
}
|
||||
|
||||
/// a parse error without a position (as those of json_pointer)
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(int id, const std::string& msg)
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(exception_id id, const std::string& msg)
|
||||
{
|
||||
NLOHMANN_VIEW_THROW(parse_error::create(id, 0, msg, nullptr));
|
||||
}
|
||||
|
||||
/// out_of_range.401: the array index @a idx is not below the size
|
||||
template<typename Index>
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_out_of_range(const Index idx)
|
||||
{
|
||||
throw_out_of_range(exception_id::array_index_out_of_range, concat("array index ", std::to_string(idx), " is out of range"));
|
||||
}
|
||||
|
||||
/// out_of_range.402: the JSON Pointer token '-' of an array of @a size elements
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_array_index_past_end(std::size_t size)
|
||||
{
|
||||
throw_out_of_range(exception_id::pointer_past_the_end_index, concat("array index '-' (", std::to_string(size), ") is out of range"));
|
||||
}
|
||||
|
||||
/// out_of_range.403: the object has no member @a key
|
||||
template<typename Key>
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_key_not_found(const Key& key)
|
||||
{
|
||||
throw_out_of_range(exception_id::key_not_found, concat("key '", key, "' not found"));
|
||||
}
|
||||
|
||||
/// SAX handler of the error replay: accepts every event without storing it,
|
||||
/// and throws the exception the parser reports
|
||||
template<typename BasicJsonType>
|
||||
@@ -149,7 +169,7 @@ template<typename BasicJsonType>
|
||||
if (f.code == error_code::input_too_large)
|
||||
{
|
||||
// (the limit is detail::view::max_input_size(): 4 GiB minus 16 bytes)
|
||||
NLOHMANN_VIEW_THROW(out_of_range::create(416, "input of 4294967280 bytes or more is not supported by json_document", nullptr));
|
||||
NLOHMANN_VIEW_THROW(out_of_range::create(exception_id::document_too_large, "input of 4294967280 bytes or more is not supported by json_document", nullptr));
|
||||
}
|
||||
throwing_sax<BasicJsonType> sax;
|
||||
const bool accepted = BasicJsonType::sax_parse(src, src + size, &sax, input_format_t::json, true, ignore_comments, ignore_trailing_commas);
|
||||
@@ -169,7 +189,7 @@ template<typename BasicJsonType>
|
||||
}
|
||||
}
|
||||
pos.chars_read_current_line = off + 1 - line_start;
|
||||
NLOHMANN_VIEW_THROW(parse_error::create(101, pos, "syntax error while parsing value", nullptr));
|
||||
NLOHMANN_VIEW_THROW(parse_error::create(exception_id::syntax_error, pos, "syntax error while parsing value", nullptr));
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
|
||||
@@ -186,7 +186,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
|
||||
inline std::vector<std::uint8_t> save_image(const document_data& d)
|
||||
{
|
||||
#if !NLOHMANN_VIEW_LITTLE_ENDIAN
|
||||
throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
|
||||
throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
|
||||
#endif
|
||||
const std::size_t arena_size = d.arena_size;
|
||||
std::vector<node> compacted;
|
||||
@@ -203,7 +203,7 @@ inline std::vector<std::uint8_t> save_image(const document_data& d)
|
||||
if (NLOHMANN_VIEW_UNLIKELY(text_size >= image_limit || total_arena >= image_limit || count >= image_limit))
|
||||
{
|
||||
// LCOV_EXCL_START (4 GiB)
|
||||
throw_out_of_range(416, "images of 4 GiB or more are not supported by json_document");
|
||||
throw_out_of_range(exception_id::document_too_large, "images of 4 GiB or more are not supported by json_document");
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
image_header h{};
|
||||
@@ -660,7 +660,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
||||
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_image(const char* what)
|
||||
{
|
||||
throw_parse_error(116, concat("invalid json_document image: ", what));
|
||||
throw_parse_error(exception_id::invalid_document_image, concat("invalid json_document image: ", what));
|
||||
}
|
||||
|
||||
/// Read an image into d. The text and the decoded strings stay in the image;
|
||||
@@ -668,7 +668,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
||||
inline void load_image(document_data& d, const std::uint8_t* image, std::size_t size, image_check check)
|
||||
{
|
||||
#if !NLOHMANN_VIEW_LITTLE_ENDIAN
|
||||
throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
|
||||
throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
|
||||
#endif
|
||||
if (image == nullptr || size < sizeof(image_header))
|
||||
{
|
||||
|
||||
@@ -110,7 +110,7 @@ class view_iterator
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(m_value_offset == 0))
|
||||
{
|
||||
throw_invalid_iterator(207, "cannot use key() for non-object iterators");
|
||||
throw_invalid_iterator(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators");
|
||||
}
|
||||
return string_view_t(m_doc->str(*m_pos), m_pos->len);
|
||||
}
|
||||
|
||||
@@ -143,8 +143,6 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
// false positive: the parser's frame stack points into its own storage (never null)
|
||||
// @infer-ignore NULLPTR_DEREFERENCE
|
||||
sax.null();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -32,51 +32,15 @@ enum class pointer_mode
|
||||
contains, ///< as basic_json::contains(): no exceptions at all
|
||||
};
|
||||
|
||||
/// the outcome of reading an array index from a reference token
|
||||
enum class index_status
|
||||
{
|
||||
ok,
|
||||
leading_zero, ///< parse_error.106
|
||||
not_number, ///< parse_error.109
|
||||
unresolved, ///< out_of_range.404
|
||||
too_large, ///< out_of_range.410
|
||||
};
|
||||
/// the outcome of reading an array index: detail::parse_array_index(), shared with json_pointer
|
||||
using index_status = detail::array_index_status;
|
||||
|
||||
/// reads an array index like json_pointer::array_index (RFC 6901, Sect. 4),
|
||||
/// but reports errors instead of throwing them
|
||||
template<typename StringType>
|
||||
index_status array_index(const StringType& s, std::size_t& idx) noexcept
|
||||
{
|
||||
// as basic_json: the empty token and "-" (past the last element) are
|
||||
// unresolved rather than not a number
|
||||
if (s.empty() || (s.size() == 1 && s[0] == '-'))
|
||||
{
|
||||
return index_status::unresolved;
|
||||
}
|
||||
if (s[0] < '0' || s[0] > '9')
|
||||
{
|
||||
return index_status::not_number;
|
||||
}
|
||||
if (s.size() > 1 && s[0] == '0')
|
||||
{
|
||||
return index_status::leading_zero;
|
||||
}
|
||||
std::uint64_t v = 0;
|
||||
for (const auto c : s)
|
||||
{
|
||||
const auto d = static_cast<unsigned>(static_cast<unsigned char>(c)) - '0';
|
||||
if (d > 9 || v > ((std::numeric_limits<std::uint64_t>::max)() - d) / 10)
|
||||
{
|
||||
return index_status::unresolved; // not a number, or beyond unsigned long long
|
||||
}
|
||||
v = (v * 10) + d;
|
||||
}
|
||||
if (v >= (std::numeric_limits<std::size_t>::max)()) // (std::size_t converts to std::uint64_t implicitly)
|
||||
{
|
||||
return index_status::too_large;
|
||||
}
|
||||
idx = static_cast<std::size_t>(v);
|
||||
return index_status::ok;
|
||||
return detail::parse_array_index(s, idx);
|
||||
}
|
||||
|
||||
/// throws the exception json_pointer::array_index throws for this status
|
||||
@@ -86,15 +50,15 @@ template<typename StringType>
|
||||
switch (status)
|
||||
{
|
||||
case index_status::leading_zero:
|
||||
throw_parse_error(106, concat("array index '", s, "' must not begin with '0'"));
|
||||
case index_status::not_number:
|
||||
throw_parse_error(109, concat("array index '", s, "' is not a number"));
|
||||
case index_status::too_large:
|
||||
throw_out_of_range(410, concat("array index ", s, " exceeds size_type")); // LCOV_EXCL_LINE
|
||||
throw_parse_error(exception_id::pointer_index_leading_zero, concat("array index '", s, "' must not begin with '0'"));
|
||||
case index_status::not_a_number:
|
||||
throw_parse_error(exception_id::pointer_index_not_a_number, concat("array index '", s, "' is not a number"));
|
||||
case index_status::exceeds_size_type:
|
||||
throw_out_of_range(exception_id::value_out_of_range, concat("array index ", s, " exceeds size_type")); // LCOV_EXCL_LINE
|
||||
case index_status::unresolved:
|
||||
case index_status::ok:
|
||||
default:
|
||||
throw_out_of_range(404, concat("unresolved reference token '", s, "'"));
|
||||
throw_out_of_range(exception_id::pointer_unresolved, concat("unresolved reference token '", s, "'"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +83,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
||||
{
|
||||
if (mode == pointer_mode::checked)
|
||||
{
|
||||
throw_out_of_range(403, concat("key '", token, "' not found"));
|
||||
throw_key_not_found(token);
|
||||
}
|
||||
return View();
|
||||
}
|
||||
@@ -131,7 +95,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
||||
{
|
||||
if (throwing)
|
||||
{
|
||||
throw_out_of_range(402, concat("array index '-' (", std::to_string(cur.size()), ") is out of range"));
|
||||
throw_array_index_past_end(cur.size());
|
||||
}
|
||||
return View();
|
||||
}
|
||||
@@ -139,7 +103,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
||||
const index_status status = array_index(token, idx);
|
||||
if (status != index_status::ok)
|
||||
{
|
||||
const bool parse_error = status == index_status::leading_zero || status == index_status::not_number;
|
||||
const bool parse_error = status == index_status::leading_zero || status == index_status::not_a_number;
|
||||
if (throwing || (mode == pointer_mode::value && parse_error))
|
||||
{
|
||||
throw_array_index_error(status, token);
|
||||
@@ -150,7 +114,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
||||
{
|
||||
if (mode == pointer_mode::checked)
|
||||
{
|
||||
throw_out_of_range(401, concat("array index ", std::to_string(idx), " is out of range"));
|
||||
throw_array_index_out_of_range(idx);
|
||||
}
|
||||
return View();
|
||||
}
|
||||
@@ -160,7 +124,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
||||
{
|
||||
if (throwing)
|
||||
{
|
||||
throw_out_of_range(404, concat("unresolved reference token '", token, "'"));
|
||||
throw_out_of_range(exception_id::pointer_unresolved, concat("unresolved reference token '", token, "'"));
|
||||
}
|
||||
return View();
|
||||
}
|
||||
|
||||
@@ -1073,21 +1073,17 @@ class view_serializer
|
||||
m_out.put("null");
|
||||
return;
|
||||
}
|
||||
static constexpr bool is_ieee_single_or_double
|
||||
= std::numeric_limits<number_float_t>::is_iec559
|
||||
&& ((std::numeric_limits<number_float_t>::digits == 24 && std::numeric_limits<number_float_t>::max_exponent == 128)
|
||||
|| (std::numeric_limits<number_float_t>::digits == 53 && std::numeric_limits<number_float_t>::max_exponent == 1024));
|
||||
write_float(x, std::integral_constant<bool, is_ieee_single_or_double>());
|
||||
write_float(x, std::integral_constant<bool, ::nlohmann::detail::has_native_float_format<number_float_t>::value>());
|
||||
}
|
||||
|
||||
void write_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/)
|
||||
void write_float(number_float_t x, std::true_type /*has_native_float_format*/)
|
||||
{
|
||||
std::array<char, 64> buf{};
|
||||
const char* const end = ::nlohmann::detail::to_chars(buf.data(), buf.data() + buf.size(), x);
|
||||
m_out.put(buf.data(), static_cast<std::size_t>(end - buf.data()));
|
||||
}
|
||||
|
||||
void write_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/)
|
||||
void write_float(number_float_t x, std::false_type /*has_native_float_format*/)
|
||||
{
|
||||
// other types (e.g. long double) are rare: the library writes them
|
||||
const string_t s = BasicJsonType(x).dump();
|
||||
@@ -1148,8 +1144,8 @@ class view_serializer
|
||||
|
||||
void write_u_escape(std::uint32_t u)
|
||||
{
|
||||
static constexpr const char* hex = "0123456789abcdef";
|
||||
const std::array<char, 6> e = {{'\\', 'u', hex[(u >> 12u) & 0xFu], hex[(u >> 8u) & 0xFu], hex[(u >> 4u) & 0xFu], hex[u & 0xFu]}};
|
||||
std::array<char, 6> e = {{'\\', 'u', 0, 0, 0, 0}};
|
||||
hex4_lower(static_cast<std::uint16_t>(u), &e[2]);
|
||||
m_out.put(e.data(), e.size());
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const nod
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
throw_type_error(302, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind)));
|
||||
throw_type_error(exception_id::type_mismatch, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ std::vector<U, A> vector_value(const View& v)
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!v.is_array()))
|
||||
{
|
||||
throw_type_error(302, "type must be array, but is ", v.type_name());
|
||||
throw_type_error(exception_id::type_mismatch, "type must be array, but is ", v.type_name());
|
||||
}
|
||||
std::vector<U, A> r;
|
||||
r.reserve(v.size());
|
||||
@@ -88,7 +88,7 @@ Map map_value(const View& v)
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!v.is_object()))
|
||||
{
|
||||
throw_type_error(302, "type must be object, but is ", v.type_name());
|
||||
throw_type_error(exception_id::type_mismatch, "type must be object, but is ", v.type_name());
|
||||
}
|
||||
Map r;
|
||||
for (auto it = v.begin(); it != v.end(); ++it)
|
||||
|
||||
+305
-359
@@ -55,7 +55,7 @@
|
||||
#include <nlohmann/detail/abi_macros.hpp> // IWYU pragma: export
|
||||
#include <nlohmann/detail/conversions/from_json.hpp> // IWYU pragma: keep
|
||||
#include <nlohmann/detail/conversions/to_json.hpp> // IWYU pragma: keep
|
||||
#include <nlohmann/detail/exceptions.hpp>
|
||||
#include <nlohmann/detail/exceptions.hpp> // IWYU pragma: export
|
||||
#include <nlohmann/detail/hash.hpp>
|
||||
#include <nlohmann/detail/input/binary_reader.hpp>
|
||||
#include <nlohmann/detail/input/input_adapters.hpp>
|
||||
@@ -376,9 +376,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
result["name"] = "JSON for Modern C++";
|
||||
result["url"] = "https://github.com/nlohmann/json";
|
||||
result["version"]["string"] =
|
||||
detail::concat(std::to_string(NLOHMANN_JSON_VERSION_MAJOR), '.',
|
||||
std::to_string(NLOHMANN_JSON_VERSION_MINOR), '.',
|
||||
std::to_string(NLOHMANN_JSON_VERSION_PATCH));
|
||||
detail::concat<string_t>(detail::to_string<string_t>(static_cast<std::size_t>(NLOHMANN_JSON_VERSION_MAJOR)), '.',
|
||||
detail::to_string<string_t>(static_cast<std::size_t>(NLOHMANN_JSON_VERSION_MINOR)), '.',
|
||||
detail::to_string<string_t>(static_cast<std::size_t>(NLOHMANN_JSON_VERSION_PATCH)));
|
||||
result["version"]["major"] = NLOHMANN_JSON_VERSION_MAJOR;
|
||||
result["version"]["minor"] = NLOHMANN_JSON_VERSION_MINOR;
|
||||
result["version"]["patch"] = NLOHMANN_JSON_VERSION_PATCH;
|
||||
@@ -400,10 +400,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
#elif defined(__clang__)
|
||||
result["compiler"] = {{"family", "clang"}, {"version", __clang_version__}};
|
||||
#elif defined(__GNUC__) || defined(__GNUG__)
|
||||
result["compiler"] = {{"family", "gcc"}, {"version", detail::concat(
|
||||
std::to_string(__GNUC__), '.',
|
||||
std::to_string(__GNUC_MINOR__), '.',
|
||||
std::to_string(__GNUC_PATCHLEVEL__))
|
||||
result["compiler"] = {{"family", "gcc"}, {"version", detail::concat<string_t>(
|
||||
detail::to_string<string_t>(static_cast<std::size_t>(__GNUC__)), '.',
|
||||
detail::to_string<string_t>(static_cast<std::size_t>(__GNUC_MINOR__)), '.',
|
||||
detail::to_string<string_t>(static_cast<std::size_t>(__GNUC_PATCHLEVEL__)))
|
||||
}
|
||||
};
|
||||
#elif defined(__HP_aCC)
|
||||
@@ -421,9 +421,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
#endif
|
||||
|
||||
#if defined(_MSVC_LANG)
|
||||
result["compiler"]["c++"] = std::to_string(_MSVC_LANG);
|
||||
result["compiler"]["c++"] = detail::to_string<string_t>(static_cast<std::size_t>(_MSVC_LANG));
|
||||
#elif defined(__cplusplus)
|
||||
result["compiler"]["c++"] = std::to_string(__cplusplus);
|
||||
result["compiler"]["c++"] = detail::to_string<string_t>(static_cast<std::size_t>(__cplusplus));
|
||||
#else
|
||||
result["compiler"]["c++"] = "unknown";
|
||||
#endif
|
||||
@@ -734,23 +734,15 @@ private:
|
||||
// true if v is not an array/object, or is an already-empty one
|
||||
static bool has_no_children(const basic_json& v) noexcept
|
||||
{
|
||||
switch (v.m_data.m_type)
|
||||
if (v.m_data.m_type == value_t::array)
|
||||
{
|
||||
case value_t::array:
|
||||
return v.m_data.m_value.array->empty();
|
||||
case value_t::object:
|
||||
return v.m_data.m_value.object->empty();
|
||||
case value_t::null:
|
||||
case value_t::string:
|
||||
case value_t::boolean:
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
case value_t::number_float:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
return true;
|
||||
return v.m_data.m_value.array->empty();
|
||||
}
|
||||
if (v.m_data.m_type == value_t::object)
|
||||
{
|
||||
return v.m_data.m_value.object->empty();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// true if no child of the non-empty array/object v has children of
|
||||
@@ -855,28 +847,17 @@ private:
|
||||
}
|
||||
|
||||
public:
|
||||
void destroy_string() noexcept
|
||||
template<typename T>
|
||||
static void destroy_pointer(T*& ptr) noexcept
|
||||
{
|
||||
if (string == nullptr)
|
||||
if (ptr == nullptr)
|
||||
{
|
||||
// not initialized (e.g., due to exception in the ctor)
|
||||
return;
|
||||
}
|
||||
AllocatorType<string_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
|
||||
}
|
||||
|
||||
void destroy_binary() noexcept
|
||||
{
|
||||
if (binary == nullptr)
|
||||
{
|
||||
// not initialized (e.g., due to exception in the ctor)
|
||||
return;
|
||||
}
|
||||
AllocatorType<binary_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);
|
||||
AllocatorType<T> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, ptr);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, ptr, 1);
|
||||
}
|
||||
|
||||
// t must be value_t::array or value_t::object
|
||||
@@ -1042,11 +1023,11 @@ public:
|
||||
switch (t)
|
||||
{
|
||||
case value_t::string:
|
||||
destroy_string();
|
||||
destroy_pointer(string);
|
||||
break;
|
||||
|
||||
case value_t::binary:
|
||||
destroy_binary();
|
||||
destroy_pointer(binary);
|
||||
break;
|
||||
|
||||
case value_t::object:
|
||||
@@ -1108,36 +1089,19 @@ public:
|
||||
void set_parents()
|
||||
{
|
||||
#if JSON_DIAGNOSTICS
|
||||
switch (m_data.m_type)
|
||||
if (m_data.m_type == value_t::array)
|
||||
{
|
||||
case value_t::array:
|
||||
for (auto& element : *m_data.m_value.array)
|
||||
{
|
||||
for (auto& element : *m_data.m_value.array)
|
||||
{
|
||||
element.m_parent = this;
|
||||
}
|
||||
break;
|
||||
element.m_parent = this;
|
||||
}
|
||||
|
||||
case value_t::object:
|
||||
}
|
||||
else if (m_data.m_type == value_t::object)
|
||||
{
|
||||
for (auto& element : *m_data.m_value.object)
|
||||
{
|
||||
for (auto& element : *m_data.m_value.object)
|
||||
{
|
||||
element.second.m_parent = this;
|
||||
}
|
||||
break;
|
||||
element.second.m_parent = this;
|
||||
}
|
||||
|
||||
case value_t::null:
|
||||
case value_t::string:
|
||||
case value_t::boolean:
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
case value_t::number_float:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -2585,8 +2549,7 @@ public:
|
||||
case value_t::string:
|
||||
case value_t::binary:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!first.m_it.primitive_iterator.is_begin()
|
||||
|| !last.m_it.primitive_iterator.is_end()))
|
||||
if (JSON_HEDLEY_UNLIKELY(!primitive_range_is_complete(first, last)))
|
||||
{
|
||||
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_range_out_of_range, "iterators out of range", first.m_object);
|
||||
}
|
||||
@@ -3060,6 +3023,134 @@ public:
|
||||
JSON_THROW(std::length_error(detail::concat("array index ", std::to_string(idx), " exceeds size_type")));
|
||||
}
|
||||
|
||||
/// @throw type_error.309 always, as insert() is not defined for the type of @a j
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
|
||||
static void throw_insert_wrong_type(const basic_json& j)
|
||||
{
|
||||
detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", j);
|
||||
}
|
||||
|
||||
/// @throw invalid_iterator.202 always, as an iterator belongs to another value than @a obj
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
|
||||
static void throw_iterator_from_other_value(const basic_json* obj)
|
||||
{
|
||||
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", obj);
|
||||
}
|
||||
|
||||
/// @throw type_error.310 always, as swap() with the given operand is not defined for the type of @a j
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
|
||||
static void throw_swap_wrong_type(const char* operation, const basic_json& j)
|
||||
{
|
||||
detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, operation, j);
|
||||
}
|
||||
|
||||
/// exchange the stored @a Stored value (if the value has that type) with @a other
|
||||
template<typename Stored, typename Other>
|
||||
void swap_impl(Other& other, const char* operation)
|
||||
{
|
||||
Stored* const stored = get_impl_ptr(static_cast<Stored*>(nullptr));
|
||||
if (JSON_HEDLEY_UNLIKELY(stored == nullptr))
|
||||
{
|
||||
throw_swap_wrong_type(operation, *this);
|
||||
}
|
||||
|
||||
using std::swap;
|
||||
swap(*stored, other);
|
||||
}
|
||||
|
||||
/// whether [first, last) spans the whole of a primitive value
|
||||
template<typename IteratorType>
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
static bool primitive_range_is_complete(const IteratorType& first, const IteratorType& last) noexcept
|
||||
{
|
||||
return first.m_it.primitive_iterator.is_begin() && last.m_it.primitive_iterator.is_end();
|
||||
}
|
||||
|
||||
/// destroy the current (primitive) value and become null
|
||||
void reset_to_null_impl()
|
||||
{
|
||||
m_data.m_value.destroy(m_data.m_type);
|
||||
m_data.m_value = {};
|
||||
m_data.m_type = value_t::null;
|
||||
assert_invariant();
|
||||
}
|
||||
|
||||
/// append @a val (moved or copied) to the array; a null value becomes an array
|
||||
template<typename ValueType>
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void push_back_array_impl(ValueType&& val)
|
||||
{
|
||||
// push_back only works for null objects or arrays
|
||||
if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array())))
|
||||
{
|
||||
detail::throw_cannot_use_with(detail::exception_id::push_back_wrong_type, "push_back()", *this);
|
||||
}
|
||||
|
||||
// transform a null object into an array
|
||||
if (is_null())
|
||||
{
|
||||
convert_null_to<array_t>();
|
||||
}
|
||||
|
||||
// add the element to the array
|
||||
const auto old_capacity = container_capacity();
|
||||
m_data.m_value.array->push_back(std::forward<ValueType>(val));
|
||||
set_parent_after_insert(m_data.m_value.array->back(), old_capacity);
|
||||
}
|
||||
|
||||
/// parse the input behind an already created adapter
|
||||
template<typename InputAdapterType>
|
||||
static basic_json parse_impl(InputAdapterType&& adapter,
|
||||
parser_callback_t cb,
|
||||
const bool allow_exceptions,
|
||||
const bool ignore_comments,
|
||||
const bool ignore_trailing_commas)
|
||||
{
|
||||
basic_json result;
|
||||
parser(std::forward<InputAdapterType>(adapter), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas).parse(true, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// check the input behind an already created adapter
|
||||
template<typename InputAdapterType>
|
||||
static bool accept_impl(InputAdapterType&& adapter,
|
||||
const bool ignore_comments,
|
||||
const bool ignore_trailing_commas)
|
||||
{
|
||||
return parser(std::forward<InputAdapterType>(adapter), nullptr, false, ignore_comments, ignore_trailing_commas, true).accept(true);
|
||||
}
|
||||
|
||||
/// generate SAX events from the input behind an already created adapter
|
||||
template<typename InputAdapterType, typename SAX>
|
||||
static bool sax_parse_impl(InputAdapterType ia, SAX* sax,
|
||||
const input_format_t format,
|
||||
const bool strict,
|
||||
const bool ignore_comments,
|
||||
const bool ignore_trailing_commas,
|
||||
const cbor_tag_handler_t tag_handler)
|
||||
{
|
||||
if (format == input_format_t::json)
|
||||
{
|
||||
return parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict);
|
||||
}
|
||||
return detail::binary_reader<basic_json, InputAdapterType, SAX>(std::move(ia), format).sax_parse(sax, strict, tag_handler);
|
||||
}
|
||||
|
||||
/// serialize @a j into a new vector; @a write receives the vector-backed writer
|
||||
template<typename WriteFunction>
|
||||
static std::vector<std::uint8_t> to_binary_vector(const basic_json& j, const error_handler_t error_handler, WriteFunction write)
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
result.reserve(detail::binary_initial_capacity(j));
|
||||
{
|
||||
// the sink sets the size of result when it is destroyed, which
|
||||
// must happen before result is returned (without NRVO, it is moved)
|
||||
auto writer = vector_writer(result, error_handler);
|
||||
write(writer);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @throw out_of_range.401 always, as @a idx is out of range of @a j
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
|
||||
static void throw_array_index_out_of_range(size_type idx, const basic_json& j)
|
||||
@@ -4042,7 +4133,7 @@ public:
|
||||
// make sure the iterator fits the current value
|
||||
if (JSON_HEDLEY_UNLIKELY(this != pos.m_object))
|
||||
{
|
||||
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
|
||||
throw_iterator_from_other_value(this);
|
||||
}
|
||||
|
||||
IteratorType result = end();
|
||||
@@ -4061,10 +4152,7 @@ public:
|
||||
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_out_of_range, "iterator out of range", this);
|
||||
}
|
||||
|
||||
m_data.m_value.destroy(m_data.m_type);
|
||||
m_data.m_value = {};
|
||||
m_data.m_type = value_t::null;
|
||||
assert_invariant();
|
||||
reset_to_null_impl();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -4114,16 +4202,12 @@ public:
|
||||
case value_t::string:
|
||||
case value_t::binary:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!first.m_it.primitive_iterator.is_begin()
|
||||
|| !last.m_it.primitive_iterator.is_end()))
|
||||
if (JSON_HEDLEY_UNLIKELY(!primitive_range_is_complete(first, last)))
|
||||
{
|
||||
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_range_out_of_range, "iterators out of range", this);
|
||||
}
|
||||
|
||||
m_data.m_value.destroy(m_data.m_type);
|
||||
m_data.m_value = {};
|
||||
m_data.m_type = value_t::null;
|
||||
assert_invariant();
|
||||
reset_to_null_impl();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -4604,34 +4688,20 @@ public:
|
||||
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||
size_type max_size() const noexcept
|
||||
{
|
||||
switch (m_data.m_type)
|
||||
if (m_data.m_type == value_t::array)
|
||||
{
|
||||
case value_t::array:
|
||||
{
|
||||
// delegate call to array_t::max_size()
|
||||
return m_data.m_value.array->max_size();
|
||||
}
|
||||
|
||||
case value_t::object:
|
||||
{
|
||||
// delegate call to object_t::max_size()
|
||||
return m_data.m_value.object->max_size();
|
||||
}
|
||||
|
||||
case value_t::null:
|
||||
case value_t::string:
|
||||
case value_t::boolean:
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
case value_t::number_float:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
{
|
||||
// all other types have max_size() == size()
|
||||
return size();
|
||||
}
|
||||
// delegate call to array_t::max_size()
|
||||
return m_data.m_value.array->max_size();
|
||||
}
|
||||
|
||||
if (m_data.m_type == value_t::object)
|
||||
{
|
||||
// delegate call to object_t::max_size()
|
||||
return m_data.m_value.object->max_size();
|
||||
}
|
||||
|
||||
// all other types have max_size() == size()
|
||||
return size();
|
||||
}
|
||||
|
||||
/// @}
|
||||
@@ -4709,22 +4779,7 @@ public:
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/push_back/
|
||||
void push_back(basic_json&& val)
|
||||
{
|
||||
// push_back only works for null objects or arrays
|
||||
if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array())))
|
||||
{
|
||||
detail::throw_cannot_use_with(detail::exception_id::push_back_wrong_type, "push_back()", *this);
|
||||
}
|
||||
|
||||
// transform a null object into an array
|
||||
if (is_null())
|
||||
{
|
||||
convert_null_to<array_t>();
|
||||
}
|
||||
|
||||
// add the element to the array (move semantics)
|
||||
const auto old_capacity = container_capacity();
|
||||
m_data.m_value.array->push_back(std::move(val));
|
||||
set_parent_after_insert(m_data.m_value.array->back(), old_capacity);
|
||||
push_back_array_impl(std::move(val));
|
||||
// if val is moved from, basic_json move constructor marks it null, so we do not call the destructor
|
||||
}
|
||||
|
||||
@@ -4740,22 +4795,7 @@ public:
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/push_back/
|
||||
void push_back(const basic_json& val)
|
||||
{
|
||||
// push_back only works for null objects or arrays
|
||||
if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array())))
|
||||
{
|
||||
detail::throw_cannot_use_with(detail::exception_id::push_back_wrong_type, "push_back()", *this);
|
||||
}
|
||||
|
||||
// transform a null object into an array
|
||||
if (is_null())
|
||||
{
|
||||
convert_null_to<array_t>();
|
||||
}
|
||||
|
||||
// add the element to the array
|
||||
const auto old_capacity = container_capacity();
|
||||
m_data.m_value.array->push_back(val);
|
||||
set_parent_after_insert(m_data.m_value.array->back(), old_capacity);
|
||||
push_back_array_impl(val);
|
||||
}
|
||||
|
||||
/// @brief add an object to an array
|
||||
@@ -4901,13 +4941,13 @@ public:
|
||||
// insert only works for arrays
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_array()))
|
||||
{
|
||||
detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
|
||||
throw_insert_wrong_type(*this);
|
||||
}
|
||||
|
||||
// check if iterator pos fits to this JSON value
|
||||
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
|
||||
{
|
||||
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
|
||||
throw_iterator_from_other_value(this);
|
||||
}
|
||||
|
||||
// insert to array and return iterator
|
||||
@@ -4921,13 +4961,13 @@ public:
|
||||
// insert only works for arrays
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_array()))
|
||||
{
|
||||
detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
|
||||
throw_insert_wrong_type(*this);
|
||||
}
|
||||
|
||||
// check if iterator pos fits to this JSON value
|
||||
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
|
||||
{
|
||||
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
|
||||
throw_iterator_from_other_value(this);
|
||||
}
|
||||
|
||||
// moving into a local first keeps this safe even if val aliases
|
||||
@@ -4943,13 +4983,13 @@ public:
|
||||
// insert only works for arrays
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_array()))
|
||||
{
|
||||
detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
|
||||
throw_insert_wrong_type(*this);
|
||||
}
|
||||
|
||||
// check if iterator pos fits to this JSON value
|
||||
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
|
||||
{
|
||||
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
|
||||
throw_iterator_from_other_value(this);
|
||||
}
|
||||
|
||||
// insert to array and return iterator
|
||||
@@ -4963,13 +5003,13 @@ public:
|
||||
// insert only works for arrays
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_array()))
|
||||
{
|
||||
detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
|
||||
throw_insert_wrong_type(*this);
|
||||
}
|
||||
|
||||
// check if iterator pos fits to this JSON value
|
||||
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
|
||||
{
|
||||
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
|
||||
throw_iterator_from_other_value(this);
|
||||
}
|
||||
|
||||
// check if range iterators belong to the same JSON object
|
||||
@@ -5000,13 +5040,13 @@ public:
|
||||
// insert only works for arrays
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_array()))
|
||||
{
|
||||
detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
|
||||
throw_insert_wrong_type(*this);
|
||||
}
|
||||
|
||||
// check if iterator pos fits to this JSON value
|
||||
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
|
||||
{
|
||||
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
|
||||
throw_iterator_from_other_value(this);
|
||||
}
|
||||
|
||||
// copy the values first: ilist may refer to elements of this array
|
||||
@@ -5028,7 +5068,7 @@ public:
|
||||
// insert only works for objects
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_object()))
|
||||
{
|
||||
detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
|
||||
throw_insert_wrong_type(*this);
|
||||
}
|
||||
|
||||
// check if range iterators belong to the same JSON object
|
||||
@@ -5270,82 +5310,37 @@ public:
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/swap/
|
||||
void swap(array_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap)
|
||||
{
|
||||
// swap only works for arrays
|
||||
if (JSON_HEDLEY_LIKELY(is_array()))
|
||||
{
|
||||
using std::swap;
|
||||
swap(*(m_data.m_value.array), other);
|
||||
set_parents();
|
||||
}
|
||||
else
|
||||
{
|
||||
detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(array_t&)", *this);
|
||||
}
|
||||
swap_impl<array_t>(other, "swap(array_t&)");
|
||||
set_parents();
|
||||
}
|
||||
|
||||
/// @brief exchanges the values
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/swap/
|
||||
void swap(object_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap)
|
||||
{
|
||||
// swap only works for objects
|
||||
if (JSON_HEDLEY_LIKELY(is_object()))
|
||||
{
|
||||
using std::swap;
|
||||
swap(*(m_data.m_value.object), other);
|
||||
set_parents();
|
||||
}
|
||||
else
|
||||
{
|
||||
detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(object_t&)", *this);
|
||||
}
|
||||
swap_impl<object_t>(other, "swap(object_t&)");
|
||||
set_parents();
|
||||
}
|
||||
|
||||
/// @brief exchanges the values
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/swap/
|
||||
void swap(string_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap)
|
||||
{
|
||||
// swap only works for strings
|
||||
if (JSON_HEDLEY_LIKELY(is_string()))
|
||||
{
|
||||
using std::swap;
|
||||
swap(*(m_data.m_value.string), other);
|
||||
}
|
||||
else
|
||||
{
|
||||
detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(string_t&)", *this);
|
||||
}
|
||||
swap_impl<string_t>(other, "swap(string_t&)");
|
||||
}
|
||||
|
||||
/// @brief exchanges the values
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/swap/
|
||||
void swap(binary_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap)
|
||||
{
|
||||
// swap only works for binary values
|
||||
if (JSON_HEDLEY_LIKELY(is_binary()))
|
||||
{
|
||||
using std::swap;
|
||||
swap(*(m_data.m_value.binary), other);
|
||||
}
|
||||
else
|
||||
{
|
||||
detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(binary_t&)", *this);
|
||||
}
|
||||
swap_impl<binary_t>(other, "swap(binary_t&)");
|
||||
}
|
||||
|
||||
/// @brief exchanges the values
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/swap/
|
||||
void swap(typename binary_t::container_type& other) // NOLINT(bugprone-exception-escape)
|
||||
{
|
||||
// swap only works for binary values
|
||||
if (JSON_HEDLEY_LIKELY(is_binary()))
|
||||
{
|
||||
using std::swap;
|
||||
swap(*(m_data.m_value.binary), other);
|
||||
}
|
||||
else
|
||||
{
|
||||
detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(binary_t::container_type&)", *this);
|
||||
}
|
||||
swap_impl<binary_t>(other, "swap(binary_t::container_type&)");
|
||||
}
|
||||
|
||||
/// @}
|
||||
@@ -6026,10 +6021,7 @@ public:
|
||||
const bool ignore_comments = false,
|
||||
const bool ignore_trailing_commas = false)
|
||||
{
|
||||
basic_json result;
|
||||
auto p = parser(detail::input_adapter(std::forward<InputType>(i)), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas);
|
||||
p.parse(true, result);
|
||||
return result;
|
||||
return parse_impl(detail::input_adapter(std::forward<InputType>(i)), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas);
|
||||
}
|
||||
|
||||
/// @brief deserialize from a pair of character iterators (or an iterator+sentinel pair, C++20 ranges support)
|
||||
@@ -6044,10 +6036,7 @@ public:
|
||||
const bool ignore_comments = false,
|
||||
const bool ignore_trailing_commas = false)
|
||||
{
|
||||
basic_json result;
|
||||
auto p = parser(detail::input_adapter(std::move(first), std::move(last)), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas);
|
||||
p.parse(true, result);
|
||||
return result;
|
||||
return parse_impl(detail::input_adapter(std::move(first), std::move(last)), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas);
|
||||
}
|
||||
|
||||
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||
@@ -6061,10 +6050,7 @@ public:
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
basic_json result;
|
||||
auto p = parser(i.get(), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas);
|
||||
p.parse(true, result);
|
||||
return result;
|
||||
return parse_impl(i.get(), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -6076,7 +6062,7 @@ public:
|
||||
const bool ignore_comments = false,
|
||||
const bool ignore_trailing_commas = false)
|
||||
{
|
||||
return parser(detail::input_adapter(std::forward<InputType>(i)), nullptr, false, ignore_comments, ignore_trailing_commas, true).accept(true);
|
||||
return accept_impl(detail::input_adapter(std::forward<InputType>(i)), ignore_comments, ignore_trailing_commas);
|
||||
}
|
||||
|
||||
/// @brief check if the input is valid JSON (iterator pair, or iterator+sentinel pair for C++20 ranges support)
|
||||
@@ -6088,7 +6074,7 @@ public:
|
||||
const bool ignore_comments = false,
|
||||
const bool ignore_trailing_commas = false)
|
||||
{
|
||||
return parser(detail::input_adapter(std::move(first), std::move(last)), nullptr, false, ignore_comments, ignore_trailing_commas, true).accept(true);
|
||||
return accept_impl(detail::input_adapter(std::move(first), std::move(last)), ignore_comments, ignore_trailing_commas);
|
||||
}
|
||||
|
||||
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||
@@ -6100,7 +6086,7 @@ public:
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return parser(i.get(), nullptr, false, ignore_comments, ignore_trailing_commas, true).accept(true);
|
||||
return accept_impl(i.get(), ignore_comments, ignore_trailing_commas);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -6115,10 +6101,7 @@ public:
|
||||
const bool ignore_trailing_commas = false,
|
||||
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)
|
||||
{
|
||||
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
||||
return format == input_format_t::json
|
||||
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
|
||||
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(sax, strict, tag_handler);
|
||||
return sax_parse_impl(detail::input_adapter(std::forward<InputType>(i)), sax, format, strict, ignore_comments, ignore_trailing_commas, tag_handler);
|
||||
}
|
||||
|
||||
/// @brief generate SAX events (iterator pair, or iterator+sentinel pair for C++20 ranges support)
|
||||
@@ -6133,10 +6116,7 @@ public:
|
||||
const bool ignore_trailing_commas = false,
|
||||
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)
|
||||
{
|
||||
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
||||
return format == input_format_t::json
|
||||
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
|
||||
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(sax, strict, tag_handler);
|
||||
return sax_parse_impl(detail::input_adapter(std::move(first), std::move(last)), sax, format, strict, ignore_comments, ignore_trailing_commas, tag_handler);
|
||||
}
|
||||
|
||||
/// @brief generate SAX events
|
||||
@@ -6170,12 +6150,7 @@ public:
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
auto ia = i.get();
|
||||
return format == input_format_t::json
|
||||
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
||||
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
|
||||
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
||||
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(sax, strict, tag_handler);
|
||||
return sax_parse_impl(i.get(), sax, format, strict, ignore_comments, ignore_trailing_commas, tag_handler);
|
||||
}
|
||||
#endif
|
||||
#if defined(__clang__)
|
||||
@@ -6323,10 +6298,10 @@ public:
|
||||
static std::vector<std::uint8_t> to_cbor(const basic_json& j,
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
result.reserve(detail::binary_initial_capacity(j));
|
||||
vector_writer(result, error_handler).write_cbor(j);
|
||||
return result;
|
||||
return to_binary_vector(j, error_handler, [&](vector_binary_writer<std::uint8_t>& w)
|
||||
{
|
||||
w.write_cbor(j);
|
||||
});
|
||||
}
|
||||
|
||||
/// @brief create a CBOR serialization of a given JSON value
|
||||
@@ -6354,10 +6329,10 @@ public:
|
||||
static std::vector<std::uint8_t> to_msgpack(const basic_json& j,
|
||||
const error_handler_t error_handler = error_handler_t::keep)
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
result.reserve(detail::binary_initial_capacity(j));
|
||||
vector_writer(result, error_handler).write_msgpack(j);
|
||||
return result;
|
||||
return to_binary_vector(j, error_handler, [&](vector_binary_writer<std::uint8_t>& w)
|
||||
{
|
||||
w.write_msgpack(j);
|
||||
});
|
||||
}
|
||||
|
||||
/// @brief create a MessagePack serialization of a given JSON value
|
||||
@@ -6387,10 +6362,10 @@ public:
|
||||
const bool use_type = false,
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
result.reserve(detail::binary_initial_capacity(j));
|
||||
vector_writer(result, error_handler).write_ubjson(j, use_size, use_type);
|
||||
return result;
|
||||
return to_binary_vector(j, error_handler, [&](vector_binary_writer<std::uint8_t>& w)
|
||||
{
|
||||
w.write_ubjson(j, use_size, use_type);
|
||||
});
|
||||
}
|
||||
|
||||
/// @brief create a UBJSON serialization of a given JSON value
|
||||
@@ -6423,10 +6398,10 @@ public:
|
||||
const bjdata_version_t version = bjdata_version_t::draft2,
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
result.reserve(detail::binary_initial_capacity(j));
|
||||
vector_writer(result, error_handler).write_ubjson(j, use_size, use_type, true, true, version);
|
||||
return result;
|
||||
return to_binary_vector(j, error_handler, [&](vector_binary_writer<std::uint8_t>& w)
|
||||
{
|
||||
w.write_ubjson(j, use_size, use_type, true, true, version);
|
||||
});
|
||||
}
|
||||
|
||||
/// @brief create a BJData serialization of a given JSON value
|
||||
@@ -6458,10 +6433,10 @@ public:
|
||||
static std::vector<std::uint8_t> to_bson(const basic_json& j,
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
result.reserve(detail::binary_initial_capacity(j));
|
||||
vector_writer(result, error_handler).write_bson(j);
|
||||
return result;
|
||||
return to_binary_vector(j, error_handler, [&](vector_binary_writer<std::uint8_t>& w)
|
||||
{
|
||||
w.write_bson(j);
|
||||
});
|
||||
}
|
||||
|
||||
/// @brief create a BSON serialization of a given JSON value
|
||||
@@ -6488,10 +6463,10 @@ public:
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/to_bon8/
|
||||
static std::vector<std::uint8_t> to_bon8(const basic_json& j)
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
result.reserve(detail::binary_initial_capacity(j));
|
||||
vector_writer(result).write_bon8(j);
|
||||
return result;
|
||||
return to_binary_vector(j, detail::binary_writer_default_error_handler(), [&](vector_binary_writer<std::uint8_t>& w)
|
||||
{
|
||||
w.write_bon8(j);
|
||||
});
|
||||
}
|
||||
|
||||
/// @brief create a BON8 serialization of a given JSON value
|
||||
@@ -7827,82 +7802,68 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
switch (source.type())
|
||||
if (detail::is_primitive(source.type()))
|
||||
{
|
||||
case value_t::array:
|
||||
// both primitive types: replace value
|
||||
diff_replace(result, path, target);
|
||||
return;
|
||||
}
|
||||
|
||||
if (source.is_array())
|
||||
{
|
||||
const std::size_t path_length = path.size();
|
||||
|
||||
// first pass: traverse common elements
|
||||
std::size_t i = 0;
|
||||
while (i < source.size() && i < target.size())
|
||||
{
|
||||
// recursive call to compare array values at index i
|
||||
if (!(source[i] == target[i]))
|
||||
{
|
||||
diff_append_index(path, i);
|
||||
diff_unequal(result, source[i], target[i], path, depth + 1);
|
||||
path.resize(path_length);
|
||||
}
|
||||
++i;
|
||||
}
|
||||
|
||||
// We now reached the end of at least one array
|
||||
// in a second pass, traverse the remaining elements
|
||||
diff_array_tails(result, source, target, path, i);
|
||||
}
|
||||
else
|
||||
{
|
||||
diff_target_values target_values;
|
||||
std::vector<const_iterator> added;
|
||||
if (diff_object_keys(result, source, target, path, target_values, added))
|
||||
{
|
||||
// fast path: target_values holds, for every member of
|
||||
// source in order, its value in target -- so no further
|
||||
// lookup is needed. Deleted keys are interleaved here
|
||||
// too, in source's original order, to match the
|
||||
// historical (pre-reordering-aware) output order.
|
||||
const std::size_t path_length = path.size();
|
||||
|
||||
// first pass: traverse common elements
|
||||
std::size_t i = 0;
|
||||
while (i < source.size() && i < target.size())
|
||||
for (auto it = source.cbegin(); it != source.cend(); ++it, ++i)
|
||||
{
|
||||
// recursive call to compare array values at index i
|
||||
if (!(source[i] == target[i]))
|
||||
if (target_values[i] != nullptr)
|
||||
{
|
||||
diff_append_index(path, i);
|
||||
diff_unequal(result, source[i], target[i], path, depth + 1);
|
||||
path.resize(path_length);
|
||||
}
|
||||
++i;
|
||||
}
|
||||
|
||||
// We now reached the end of at least one array
|
||||
// in a second pass, traverse the remaining elements
|
||||
diff_array_tails(result, source, target, path, i);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::object:
|
||||
{
|
||||
diff_target_values target_values;
|
||||
std::vector<const_iterator> added;
|
||||
if (diff_object_keys(result, source, target, path, target_values, added))
|
||||
{
|
||||
// fast path: target_values holds, for every member of
|
||||
// source in order, its value in target -- so no further
|
||||
// lookup is needed. Deleted keys are interleaved here
|
||||
// too, in source's original order, to match the
|
||||
// historical (pre-reordering-aware) output order.
|
||||
const std::size_t path_length = path.size();
|
||||
std::size_t i = 0;
|
||||
for (auto it = source.cbegin(); it != source.cend(); ++it, ++i)
|
||||
{
|
||||
if (target_values[i] != nullptr)
|
||||
if (!(it.value() == *target_values[i]))
|
||||
{
|
||||
if (!(it.value() == *target_values[i]))
|
||||
{
|
||||
diff_append_key(path, it.key());
|
||||
diff_unequal(result, it.value(), *target_values[i], path, depth + 1);
|
||||
path.resize(path_length);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// found a key that is not in target -> remove it
|
||||
diff_remove(result, diff_key_path(path, it.key()));
|
||||
diff_append_key(path, it.key());
|
||||
diff_unequal(result, it.value(), *target_values[i], path, depth + 1);
|
||||
path.resize(path_length);
|
||||
}
|
||||
}
|
||||
|
||||
// append the "add" operations for brand-new keys
|
||||
diff_added(result, added, path);
|
||||
else
|
||||
{
|
||||
// found a key that is not in target -> remove it
|
||||
diff_remove(result, diff_key_path(path, it.key()));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::null:
|
||||
case value_t::string:
|
||||
case value_t::boolean:
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
case value_t::number_float:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
{
|
||||
// both primitive types: replace value
|
||||
diff_replace(result, path, target);
|
||||
break;
|
||||
// append the "add" operations for brand-new keys
|
||||
diff_added(result, added, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7948,46 +7909,31 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
switch (s.type())
|
||||
if (detail::is_primitive(s.type()))
|
||||
{
|
||||
case value_t::array:
|
||||
{
|
||||
stack.emplace_back(&s, &t, current_path.size());
|
||||
return;
|
||||
}
|
||||
|
||||
case value_t::object:
|
||||
{
|
||||
std::vector<const basic_json*> target_values;
|
||||
std::vector<const_iterator> added;
|
||||
if (diff_object_keys(result, s, t, current_path, target_values, added))
|
||||
{
|
||||
// fast path: the frame walks source in lockstep with
|
||||
// common_keys, as diff_recursively does, and appends
|
||||
// added_ops once all members are done
|
||||
stack.emplace_back(&s, &t, current_path.size());
|
||||
stack.back().member = s.cbegin();
|
||||
stack.back().target_values = std::move(target_values);
|
||||
stack.back().added = std::move(added);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case value_t::null:
|
||||
case value_t::string:
|
||||
case value_t::boolean:
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
case value_t::number_float:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
{
|
||||
// both primitive types: replace value
|
||||
diff_replace(result, current_path, t);
|
||||
return;
|
||||
}
|
||||
// both primitive types: replace value
|
||||
diff_replace(result, current_path, t);
|
||||
return;
|
||||
}
|
||||
|
||||
if (s.is_array())
|
||||
{
|
||||
stack.emplace_back(&s, &t, current_path.size());
|
||||
return;
|
||||
}
|
||||
std::vector<const basic_json*> target_values;
|
||||
std::vector<const_iterator> added;
|
||||
if (diff_object_keys(result, s, t, current_path, target_values, added))
|
||||
{
|
||||
// fast path: the frame walks source in lockstep with
|
||||
// common_keys, as diff_recursively does, and appends
|
||||
// added_ops once all members are done
|
||||
stack.emplace_back(&s, &t, current_path.size());
|
||||
stack.back().member = s.cbegin();
|
||||
stack.back().target_values = std::move(target_values);
|
||||
stack.back().added = std::move(added);
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
enter(source, target);
|
||||
|
||||
@@ -268,7 +268,7 @@ class basic_json_view
|
||||
{
|
||||
return {};
|
||||
}
|
||||
detail::view::throw_type_error(305, "cannot use operator[] with a string argument with ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", type_name());
|
||||
}
|
||||
return lookup(key);
|
||||
}
|
||||
@@ -294,7 +294,7 @@ class basic_json_view
|
||||
{
|
||||
return {};
|
||||
}
|
||||
detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", type_name());
|
||||
}
|
||||
return idx < m_node->len ? element(idx) : basic_json_view();
|
||||
}
|
||||
@@ -327,12 +327,12 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
||||
{
|
||||
detail::view::throw_type_error(304, "cannot use at() with ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name());
|
||||
}
|
||||
const basic_json_view r = lookup(key);
|
||||
if (NLOHMANN_VIEW_UNLIKELY(r.is_discarded()))
|
||||
{
|
||||
detail::view::throw_out_of_range(403, detail::concat("key '", std::string(key.data(), key.size()), "' not found"));
|
||||
detail::view::throw_key_not_found(std::string(key.data(), key.size()));
|
||||
}
|
||||
return r;
|
||||
}
|
||||
@@ -353,11 +353,11 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_array()))
|
||||
{
|
||||
detail::view::throw_type_error(304, "cannot use at() with ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name());
|
||||
}
|
||||
if (NLOHMANN_VIEW_UNLIKELY(idx >= m_node->len))
|
||||
{
|
||||
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
|
||||
detail::view::throw_array_index_out_of_range(idx);
|
||||
}
|
||||
return element(idx);
|
||||
}
|
||||
@@ -385,7 +385,7 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
||||
{
|
||||
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
|
||||
}
|
||||
const basic_json_view r = lookup(key);
|
||||
return r.is_discarded() ? default_value : r.template get<T>();
|
||||
@@ -395,7 +395,7 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
||||
{
|
||||
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
|
||||
}
|
||||
// (the default is made only if there is no such member)
|
||||
const basic_json_view r = lookup(key);
|
||||
@@ -410,7 +410,7 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
|
||||
{
|
||||
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
|
||||
}
|
||||
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
|
||||
return r.is_discarded() ? default_value : r.template get<T>();
|
||||
@@ -420,7 +420,7 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
|
||||
{
|
||||
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
|
||||
}
|
||||
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
|
||||
return r.is_discarded() ? string_t(default_value) : r.template get<string_t>();
|
||||
@@ -433,7 +433,7 @@ class basic_json_view
|
||||
const iterator it = begin();
|
||||
if (NLOHMANN_VIEW_UNLIKELY(it == end()))
|
||||
{
|
||||
detail::view::throw_invalid_iterator(214, "cannot get value");
|
||||
detail::view::throw_invalid_iterator(detail::exception_id::iterator_value_unavailable, "cannot get value");
|
||||
}
|
||||
return *it;
|
||||
}
|
||||
@@ -586,7 +586,7 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_string()))
|
||||
{
|
||||
detail::view::throw_type_error(302, "type must be string, but is ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be string, but is ", type_name());
|
||||
}
|
||||
return {m_doc->str(*m_node), m_node->len};
|
||||
}
|
||||
@@ -598,7 +598,7 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_number()))
|
||||
{
|
||||
detail::view::throw_type_error(302, "type must be number, but is ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name());
|
||||
}
|
||||
return {m_doc->str(*m_node), detail::view::number_length(*m_node)};
|
||||
}
|
||||
@@ -855,7 +855,7 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_boolean()))
|
||||
{
|
||||
detail::view::throw_type_error(302, "type must be boolean, but is ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be boolean, but is ", type_name());
|
||||
}
|
||||
return (m_node->flags & detail::view::node_flags::is_true) != 0;
|
||||
}
|
||||
@@ -865,7 +865,7 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr))
|
||||
{
|
||||
detail::view::throw_type_error(302, "type must be number, but is ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name());
|
||||
}
|
||||
return detail::view::arithmetic_value<T, BasicJsonType>(*m_doc, *m_node);
|
||||
}
|
||||
@@ -874,7 +874,7 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_null()))
|
||||
{
|
||||
detail::view::throw_type_error(302, "type must be null, but is ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be null, but is ", type_name());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1183,7 +1183,7 @@ class basic_json_document
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
|
||||
{
|
||||
detail::view::throw_type_error(320, "cannot save a discarded json_document");
|
||||
detail::view::throw_type_error(detail::exception_id::document_image_unsupported, "cannot save a discarded json_document");
|
||||
}
|
||||
return detail::view::save_image(*m_data);
|
||||
}
|
||||
@@ -1284,7 +1284,7 @@ class basic_json_document
|
||||
const std::size_t idx = pointer_index(token);
|
||||
if (idx >= 0xFFFFFFFFu)
|
||||
{
|
||||
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
|
||||
detail::view::throw_array_index_out_of_range(idx);
|
||||
}
|
||||
for (std::size_t i = 0; i < idx; ++i)
|
||||
{
|
||||
@@ -1340,7 +1340,7 @@ class basic_json_document
|
||||
{
|
||||
if (ptr.empty())
|
||||
{
|
||||
detail::view::throw_out_of_range(405, "JSON pointer has no parent");
|
||||
detail::view::throw_out_of_range(detail::exception_id::patch_on_root, "JSON pointer has no parent");
|
||||
}
|
||||
const view_type parent = root().at(ptr.parent_pointer());
|
||||
const auto& token = ptr.back();
|
||||
@@ -1360,7 +1360,7 @@ class basic_json_document
|
||||
static_assert(Editable, "only an editable document can be edited: use basic_json_document<BasicJsonType, true> (json_editable_document)");
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
|
||||
{
|
||||
detail::view::throw_invalid_iterator(202, "view does not belong to this document");
|
||||
detail::view::throw_invalid_iterator(detail::exception_id::iterator_from_other_value, "view does not belong to this document");
|
||||
}
|
||||
return detail::view::editor<BasicJsonType, view_type>(*m_data);
|
||||
}
|
||||
@@ -1377,7 +1377,7 @@ class basic_json_document
|
||||
{
|
||||
if (idx < 0)
|
||||
{
|
||||
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
|
||||
detail::view::throw_array_index_out_of_range(idx);
|
||||
}
|
||||
return static_cast<std::size_t>(idx);
|
||||
}
|
||||
|
||||
@@ -125,6 +125,19 @@ private:
|
||||
Container::pop_back();
|
||||
}
|
||||
|
||||
/// @brief remove the entry with the given key; returns the number of removed entries
|
||||
template<class K>
|
||||
size_type erase_impl(const K& key)
|
||||
{
|
||||
const auto it = find_impl(*this, key);
|
||||
if (it != this->end())
|
||||
{
|
||||
erase_at(it);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public:
|
||||
template<class V, detail::enable_if_t<
|
||||
detail::is_constructible<T, V>::value, int> = 0>
|
||||
@@ -214,26 +227,14 @@ public:
|
||||
|
||||
size_type erase(const key_type& key)
|
||||
{
|
||||
const auto it = find_impl(*this, key);
|
||||
if (it != this->end())
|
||||
{
|
||||
erase_at(it);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
return erase_impl(key);
|
||||
}
|
||||
|
||||
template<class KeyType, detail::enable_if_t<
|
||||
detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
|
||||
size_type erase(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward)
|
||||
{
|
||||
const auto it = find_impl(*this, key);
|
||||
if (it != this->end())
|
||||
{
|
||||
erase_at(it);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
return erase_impl(key);
|
||||
}
|
||||
|
||||
iterator erase(iterator pos)
|
||||
|
||||
Generated
+1535
-1716
File diff suppressed because it is too large.
Load diff
Generated
+96
-140
@@ -2819,32 +2819,52 @@ namespace view
|
||||
// Exceptions are thrown out of line, so that the accessors that may throw stay
|
||||
// small enough to be inlined.
|
||||
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(int id, const char* prefix, const char* type)
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(exception_id id, const char* prefix, const char* type)
|
||||
{
|
||||
NLOHMANN_VIEW_THROW(type_error::create(id, concat(prefix, type), nullptr));
|
||||
}
|
||||
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(int id, const std::string& msg)
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(exception_id id, const std::string& msg)
|
||||
{
|
||||
NLOHMANN_VIEW_THROW(type_error::create(id, msg, nullptr));
|
||||
}
|
||||
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_out_of_range(int id, const std::string& msg)
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_out_of_range(exception_id id, const std::string& msg)
|
||||
{
|
||||
NLOHMANN_VIEW_THROW(out_of_range::create(id, msg, nullptr));
|
||||
}
|
||||
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_iterator(int id, const char* msg)
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_iterator(exception_id id, const char* msg)
|
||||
{
|
||||
NLOHMANN_VIEW_THROW(invalid_iterator::create(id, msg, nullptr));
|
||||
}
|
||||
|
||||
/// a parse error without a position (as those of json_pointer)
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(int id, const std::string& msg)
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(exception_id id, const std::string& msg)
|
||||
{
|
||||
NLOHMANN_VIEW_THROW(parse_error::create(id, 0, msg, nullptr));
|
||||
}
|
||||
|
||||
/// out_of_range.401: the array index @a idx is not below the size
|
||||
template<typename Index>
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_out_of_range(const Index idx)
|
||||
{
|
||||
throw_out_of_range(exception_id::array_index_out_of_range, concat("array index ", std::to_string(idx), " is out of range"));
|
||||
}
|
||||
|
||||
/// out_of_range.402: the JSON Pointer token '-' of an array of @a size elements
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_array_index_past_end(std::size_t size)
|
||||
{
|
||||
throw_out_of_range(exception_id::pointer_past_the_end_index, concat("array index '-' (", std::to_string(size), ") is out of range"));
|
||||
}
|
||||
|
||||
/// out_of_range.403: the object has no member @a key
|
||||
template<typename Key>
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_key_not_found(const Key& key)
|
||||
{
|
||||
throw_out_of_range(exception_id::key_not_found, concat("key '", key, "' not found"));
|
||||
}
|
||||
|
||||
/// SAX handler of the error replay: accepts every event without storing it,
|
||||
/// and throws the exception the parser reports
|
||||
template<typename BasicJsonType>
|
||||
@@ -2943,7 +2963,7 @@ template<typename BasicJsonType>
|
||||
if (f.code == error_code::input_too_large)
|
||||
{
|
||||
// (the limit is detail::view::max_input_size(): 4 GiB minus 16 bytes)
|
||||
NLOHMANN_VIEW_THROW(out_of_range::create(416, "input of 4294967280 bytes or more is not supported by json_document", nullptr));
|
||||
NLOHMANN_VIEW_THROW(out_of_range::create(exception_id::document_too_large, "input of 4294967280 bytes or more is not supported by json_document", nullptr));
|
||||
}
|
||||
throwing_sax<BasicJsonType> sax;
|
||||
const bool accepted = BasicJsonType::sax_parse(src, src + size, &sax, input_format_t::json, true, ignore_comments, ignore_trailing_commas);
|
||||
@@ -2963,7 +2983,7 @@ template<typename BasicJsonType>
|
||||
}
|
||||
}
|
||||
pos.chars_read_current_line = off + 1 - line_start;
|
||||
NLOHMANN_VIEW_THROW(parse_error::create(101, pos, "syntax error while parsing value", nullptr));
|
||||
NLOHMANN_VIEW_THROW(parse_error::create(exception_id::syntax_error, pos, "syntax error while parsing value", nullptr));
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
@@ -3038,7 +3058,7 @@ inline std::size_t text_capacity(std::size_t cap, std::size_t used, std::size_t
|
||||
constexpr std::size_t limit = 0xFFFFFFFFu;
|
||||
if (NLOHMANN_VIEW_UNLIKELY(used > limit || n > limit - used))
|
||||
{
|
||||
throw_out_of_range(416, "edits of 4 GiB or more are not supported by json_document");
|
||||
throw_out_of_range(exception_id::document_too_large, "edits of 4 GiB or more are not supported by json_document");
|
||||
}
|
||||
const std::size_t needed = used + n;
|
||||
const std::size_t wanted = needed + (std::min)(limit - needed, std::size_t{256});
|
||||
@@ -3089,7 +3109,7 @@ inline void reserve_moved(document_data& d)
|
||||
document_data::edit_state& e = edit_state_of(d);
|
||||
if (e.moved.size() >= 0xFFFFFFFFu)
|
||||
{
|
||||
throw_out_of_range(416, "more than 4294967295 edited arrays and objects are not supported by json_document"); // LCOV_EXCL_LINE
|
||||
throw_out_of_range(exception_id::document_too_large, "more than 4294967295 edited arrays and objects are not supported by json_document"); // LCOV_EXCL_LINE
|
||||
}
|
||||
if (e.moved.size() == e.moved.capacity() || e.moved_cap.size() == e.moved_cap.capacity())
|
||||
{
|
||||
@@ -3719,11 +3739,6 @@ struct first_true<false, Conditions...> : std::integral_constant < int, 1 + firs
|
||||
inline void check_utf8(const char* s, std::size_t n)
|
||||
{
|
||||
const auto* const p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
const auto hex = [](unsigned char c)
|
||||
{
|
||||
constexpr const char* digits = "0123456789ABCDEF";
|
||||
return std::string{digits[c >> 4u], digits[c & 0xFu]};
|
||||
};
|
||||
for (std::size_t i = 0; i < n;)
|
||||
{
|
||||
const unsigned char c = p[i];
|
||||
@@ -3732,39 +3747,22 @@ inline void check_utf8(const char* s, std::size_t n)
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
std::size_t len = 0;
|
||||
unsigned char lo = 0x80;
|
||||
unsigned char hi = 0xBF;
|
||||
if (c >= 0xC2 && c <= 0xDF)
|
||||
const utf8_sequence_shape shape = utf8_shape(c);
|
||||
const std::size_t len = shape.length;
|
||||
if (len == 0)
|
||||
{
|
||||
len = 2;
|
||||
}
|
||||
else if (c >= 0xE0 && c <= 0xEF)
|
||||
{
|
||||
len = 3;
|
||||
lo = c == 0xE0 ? 0xA0 : 0x80;
|
||||
hi = c == 0xED ? 0x9F : 0xBF;
|
||||
}
|
||||
else if (c >= 0xF0 && c <= 0xF4)
|
||||
{
|
||||
len = 4;
|
||||
lo = c == 0xF0 ? 0x90 : 0x80;
|
||||
hi = c == 0xF4 ? 0x8F : 0xBF;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw_type_error(316, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", hex(c)));
|
||||
throw_type_error(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", hex_byte(c)));
|
||||
}
|
||||
for (std::size_t k = 1; k < len; ++k)
|
||||
{
|
||||
if (i + k == n)
|
||||
{
|
||||
throw_type_error(316, concat("incomplete UTF-8 string; last byte: 0x", hex(p[n - 1])));
|
||||
throw_type_error(exception_id::invalid_utf8, concat("incomplete UTF-8 string; last byte: 0x", hex_byte(p[n - 1])));
|
||||
}
|
||||
const unsigned char b = p[i + k];
|
||||
if (b < (k == 1 ? lo : 0x80) || b > (k == 1 ? hi : 0xBF))
|
||||
if (b < (k == 1 ? shape.second_lo : 0x80) || b > (k == 1 ? shape.second_hi : 0xBF))
|
||||
{
|
||||
throw_type_error(316, concat("invalid UTF-8 byte at index ", std::to_string(i + k), ": 0x", hex(b)));
|
||||
throw_type_error(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(i + k), ": 0x", hex_byte(b)));
|
||||
}
|
||||
}
|
||||
i += len;
|
||||
@@ -3812,7 +3810,7 @@ class editor
|
||||
node* const o = own(object);
|
||||
if (o->kind != static_cast<std::uint8_t>(value_t::object) && o->kind != static_cast<std::uint8_t>(value_t::null))
|
||||
{
|
||||
throw_type_error(305, "cannot use operator[] with a string argument with ", object.type_name());
|
||||
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", object.type_name());
|
||||
}
|
||||
check_utf8(key.data(), key.size());
|
||||
const encoded e = encode(std::forward<V>(value));
|
||||
@@ -3863,7 +3861,7 @@ class editor
|
||||
node* const a = own(array);
|
||||
if (a->kind != static_cast<std::uint8_t>(value_t::array))
|
||||
{
|
||||
throw_type_error(305, "cannot use operator[] with a numeric argument with ", array.type_name());
|
||||
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", array.type_name());
|
||||
}
|
||||
check_index(idx, a->len);
|
||||
const encoded e = encode(std::forward<V>(value));
|
||||
@@ -3880,7 +3878,7 @@ class editor
|
||||
node* const a = own(array);
|
||||
if (a->kind != static_cast<std::uint8_t>(value_t::array) && a->kind != static_cast<std::uint8_t>(value_t::null))
|
||||
{
|
||||
throw_type_error(308, "cannot use push_back() with ", array.type_name());
|
||||
throw_type_error(exception_id::push_back_wrong_type, "cannot use push_back() with ", array.type_name());
|
||||
}
|
||||
const encoded e = encode(std::forward<V>(value));
|
||||
if (a->kind == static_cast<std::uint8_t>(value_t::null))
|
||||
@@ -3904,7 +3902,7 @@ class editor
|
||||
node* const a = own(array);
|
||||
if (a->kind != static_cast<std::uint8_t>(value_t::array))
|
||||
{
|
||||
throw_type_error(309, "cannot use insert() with ", array.type_name());
|
||||
throw_type_error(exception_id::insert_wrong_type, "cannot use insert() with ", array.type_name());
|
||||
}
|
||||
check_index(idx, a->len + 1);
|
||||
const encoded e = encode(std::forward<V>(value));
|
||||
@@ -3924,7 +3922,7 @@ class editor
|
||||
node* const o = own(object);
|
||||
if (o->kind != static_cast<std::uint8_t>(value_t::object))
|
||||
{
|
||||
throw_type_error(307, "cannot use erase() with ", object.type_name());
|
||||
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", object.type_name());
|
||||
}
|
||||
for (const node* k = nav::first(m_doc, o), *end = nav::end(m_doc, o); k != end; k = document_data::after(k + 1))
|
||||
{
|
||||
@@ -3942,7 +3940,7 @@ class editor
|
||||
node* const a = own(array);
|
||||
if (a->kind != static_cast<std::uint8_t>(value_t::array))
|
||||
{
|
||||
throw_type_error(307, "cannot use erase() with ", array.type_name());
|
||||
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", array.type_name());
|
||||
}
|
||||
check_index(idx, a->len);
|
||||
node* const h = block_of(m_doc, a, 0);
|
||||
@@ -3965,7 +3963,7 @@ class editor
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(v.m_doc != &m_doc || v.m_node == nullptr))
|
||||
{
|
||||
throw_invalid_iterator(202, "view does not belong to this document");
|
||||
throw_invalid_iterator(exception_id::iterator_from_other_value, "view does not belong to this document");
|
||||
}
|
||||
edit_state_of(m_doc);
|
||||
return const_cast<node*>(v.m_node); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document
|
||||
@@ -3975,7 +3973,7 @@ class editor
|
||||
{
|
||||
if (idx >= limit)
|
||||
{
|
||||
throw_out_of_range(401, concat("array index ", std::to_string(idx), " is out of range"));
|
||||
throw_array_index_out_of_range(idx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4116,7 +4114,7 @@ class editor
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(v.m_node == nullptr))
|
||||
{
|
||||
throw_type_error(302, "type must be a value, but is ", "discarded");
|
||||
throw_type_error(exception_id::type_mismatch, "type must be a value, but is ", "discarded");
|
||||
}
|
||||
encoded r;
|
||||
if (!is_container(*v.m_node))
|
||||
@@ -4266,7 +4264,7 @@ class editor
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(len >= 0xFFFFFFFFu))
|
||||
{
|
||||
throw_out_of_range(416, "strings of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_LINE
|
||||
throw_out_of_range(exception_id::document_too_large, "strings of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_LINE
|
||||
}
|
||||
node n = plain_node(value_t::string);
|
||||
n.flags = static_cast<std::uint8_t>(n.flags | node_flags::edited);
|
||||
@@ -4341,12 +4339,12 @@ class editor
|
||||
return string_node(s.data(), s.size());
|
||||
}
|
||||
case value_t::binary:
|
||||
throw_type_error(319, "cannot store a binary value in a json_document", "");
|
||||
throw_type_error(exception_id::document_binary_value, "cannot store a binary value in a json_document", "");
|
||||
case value_t::discarded:
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
default:
|
||||
throw_type_error(302, "type must be a value, but is ", "discarded");
|
||||
throw_type_error(exception_id::type_mismatch, "type must be a value, but is ", "discarded");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4974,7 +4972,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
|
||||
inline std::vector<std::uint8_t> save_image(const document_data& d)
|
||||
{
|
||||
#if !NLOHMANN_VIEW_LITTLE_ENDIAN
|
||||
throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
|
||||
throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
|
||||
#endif
|
||||
const std::size_t arena_size = d.arena_size;
|
||||
std::vector<node> compacted;
|
||||
@@ -4991,7 +4989,7 @@ inline std::vector<std::uint8_t> save_image(const document_data& d)
|
||||
if (NLOHMANN_VIEW_UNLIKELY(text_size >= image_limit || total_arena >= image_limit || count >= image_limit))
|
||||
{
|
||||
// LCOV_EXCL_START (4 GiB)
|
||||
throw_out_of_range(416, "images of 4 GiB or more are not supported by json_document");
|
||||
throw_out_of_range(exception_id::document_too_large, "images of 4 GiB or more are not supported by json_document");
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
image_header h{};
|
||||
@@ -5448,7 +5446,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
||||
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_image(const char* what)
|
||||
{
|
||||
throw_parse_error(116, concat("invalid json_document image: ", what));
|
||||
throw_parse_error(exception_id::invalid_document_image, concat("invalid json_document image: ", what));
|
||||
}
|
||||
|
||||
/// Read an image into d. The text and the decoded strings stay in the image;
|
||||
@@ -5456,7 +5454,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
||||
inline void load_image(document_data& d, const std::uint8_t* image, std::size_t size, image_check check)
|
||||
{
|
||||
#if !NLOHMANN_VIEW_LITTLE_ENDIAN
|
||||
throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
|
||||
throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
|
||||
#endif
|
||||
if (image == nullptr || size < sizeof(image_header))
|
||||
{
|
||||
@@ -5844,7 +5842,7 @@ class view_iterator
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(m_value_offset == 0))
|
||||
{
|
||||
throw_invalid_iterator(207, "cannot use key() for non-object iterators");
|
||||
throw_invalid_iterator(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators");
|
||||
}
|
||||
return string_view_t(m_doc->str(*m_pos), m_pos->len);
|
||||
}
|
||||
@@ -6152,8 +6150,6 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
// false positive: the parser's frame stack points into its own storage (never null)
|
||||
// @infer-ignore NULLPTR_DEREFERENCE
|
||||
sax.null();
|
||||
break;
|
||||
}
|
||||
@@ -6238,51 +6234,15 @@ enum class pointer_mode
|
||||
contains, ///< as basic_json::contains(): no exceptions at all
|
||||
};
|
||||
|
||||
/// the outcome of reading an array index from a reference token
|
||||
enum class index_status
|
||||
{
|
||||
ok,
|
||||
leading_zero, ///< parse_error.106
|
||||
not_number, ///< parse_error.109
|
||||
unresolved, ///< out_of_range.404
|
||||
too_large, ///< out_of_range.410
|
||||
};
|
||||
/// the outcome of reading an array index: detail::parse_array_index(), shared with json_pointer
|
||||
using index_status = detail::array_index_status;
|
||||
|
||||
/// reads an array index like json_pointer::array_index (RFC 6901, Sect. 4),
|
||||
/// but reports errors instead of throwing them
|
||||
template<typename StringType>
|
||||
index_status array_index(const StringType& s, std::size_t& idx) noexcept
|
||||
{
|
||||
// as basic_json: the empty token and "-" (past the last element) are
|
||||
// unresolved rather than not a number
|
||||
if (s.empty() || (s.size() == 1 && s[0] == '-'))
|
||||
{
|
||||
return index_status::unresolved;
|
||||
}
|
||||
if (s[0] < '0' || s[0] > '9')
|
||||
{
|
||||
return index_status::not_number;
|
||||
}
|
||||
if (s.size() > 1 && s[0] == '0')
|
||||
{
|
||||
return index_status::leading_zero;
|
||||
}
|
||||
std::uint64_t v = 0;
|
||||
for (const auto c : s)
|
||||
{
|
||||
const auto d = static_cast<unsigned>(static_cast<unsigned char>(c)) - '0';
|
||||
if (d > 9 || v > ((std::numeric_limits<std::uint64_t>::max)() - d) / 10)
|
||||
{
|
||||
return index_status::unresolved; // not a number, or beyond unsigned long long
|
||||
}
|
||||
v = (v * 10) + d;
|
||||
}
|
||||
if (v >= (std::numeric_limits<std::size_t>::max)()) // (std::size_t converts to std::uint64_t implicitly)
|
||||
{
|
||||
return index_status::too_large;
|
||||
}
|
||||
idx = static_cast<std::size_t>(v);
|
||||
return index_status::ok;
|
||||
return detail::parse_array_index(s, idx);
|
||||
}
|
||||
|
||||
/// throws the exception json_pointer::array_index throws for this status
|
||||
@@ -6292,15 +6252,15 @@ template<typename StringType>
|
||||
switch (status)
|
||||
{
|
||||
case index_status::leading_zero:
|
||||
throw_parse_error(106, concat("array index '", s, "' must not begin with '0'"));
|
||||
case index_status::not_number:
|
||||
throw_parse_error(109, concat("array index '", s, "' is not a number"));
|
||||
case index_status::too_large:
|
||||
throw_out_of_range(410, concat("array index ", s, " exceeds size_type")); // LCOV_EXCL_LINE
|
||||
throw_parse_error(exception_id::pointer_index_leading_zero, concat("array index '", s, "' must not begin with '0'"));
|
||||
case index_status::not_a_number:
|
||||
throw_parse_error(exception_id::pointer_index_not_a_number, concat("array index '", s, "' is not a number"));
|
||||
case index_status::exceeds_size_type:
|
||||
throw_out_of_range(exception_id::value_out_of_range, concat("array index ", s, " exceeds size_type")); // LCOV_EXCL_LINE
|
||||
case index_status::unresolved:
|
||||
case index_status::ok:
|
||||
default:
|
||||
throw_out_of_range(404, concat("unresolved reference token '", s, "'"));
|
||||
throw_out_of_range(exception_id::pointer_unresolved, concat("unresolved reference token '", s, "'"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6325,7 +6285,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
||||
{
|
||||
if (mode == pointer_mode::checked)
|
||||
{
|
||||
throw_out_of_range(403, concat("key '", token, "' not found"));
|
||||
throw_key_not_found(token);
|
||||
}
|
||||
return View();
|
||||
}
|
||||
@@ -6337,7 +6297,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
||||
{
|
||||
if (throwing)
|
||||
{
|
||||
throw_out_of_range(402, concat("array index '-' (", std::to_string(cur.size()), ") is out of range"));
|
||||
throw_array_index_past_end(cur.size());
|
||||
}
|
||||
return View();
|
||||
}
|
||||
@@ -6345,7 +6305,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
||||
const index_status status = array_index(token, idx);
|
||||
if (status != index_status::ok)
|
||||
{
|
||||
const bool parse_error = status == index_status::leading_zero || status == index_status::not_number;
|
||||
const bool parse_error = status == index_status::leading_zero || status == index_status::not_a_number;
|
||||
if (throwing || (mode == pointer_mode::value && parse_error))
|
||||
{
|
||||
throw_array_index_error(status, token);
|
||||
@@ -6356,7 +6316,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
||||
{
|
||||
if (mode == pointer_mode::checked)
|
||||
{
|
||||
throw_out_of_range(401, concat("array index ", std::to_string(idx), " is out of range"));
|
||||
throw_array_index_out_of_range(idx);
|
||||
}
|
||||
return View();
|
||||
}
|
||||
@@ -6366,7 +6326,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
||||
{
|
||||
if (throwing)
|
||||
{
|
||||
throw_out_of_range(404, concat("unresolved reference token '", token, "'"));
|
||||
throw_out_of_range(exception_id::pointer_unresolved, concat("unresolved reference token '", token, "'"));
|
||||
}
|
||||
return View();
|
||||
}
|
||||
@@ -7459,21 +7419,17 @@ class view_serializer
|
||||
m_out.put("null");
|
||||
return;
|
||||
}
|
||||
static constexpr bool is_ieee_single_or_double
|
||||
= std::numeric_limits<number_float_t>::is_iec559
|
||||
&& ((std::numeric_limits<number_float_t>::digits == 24 && std::numeric_limits<number_float_t>::max_exponent == 128)
|
||||
|| (std::numeric_limits<number_float_t>::digits == 53 && std::numeric_limits<number_float_t>::max_exponent == 1024));
|
||||
write_float(x, std::integral_constant<bool, is_ieee_single_or_double>());
|
||||
write_float(x, std::integral_constant<bool, ::nlohmann::detail::has_native_float_format<number_float_t>::value>());
|
||||
}
|
||||
|
||||
void write_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/)
|
||||
void write_float(number_float_t x, std::true_type /*has_native_float_format*/)
|
||||
{
|
||||
std::array<char, 64> buf{};
|
||||
const char* const end = ::nlohmann::detail::to_chars(buf.data(), buf.data() + buf.size(), x);
|
||||
m_out.put(buf.data(), static_cast<std::size_t>(end - buf.data()));
|
||||
}
|
||||
|
||||
void write_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/)
|
||||
void write_float(number_float_t x, std::false_type /*has_native_float_format*/)
|
||||
{
|
||||
// other types (e.g. long double) are rare: the library writes them
|
||||
const string_t s = BasicJsonType(x).dump();
|
||||
@@ -7534,8 +7490,8 @@ class view_serializer
|
||||
|
||||
void write_u_escape(std::uint32_t u)
|
||||
{
|
||||
static constexpr const char* hex = "0123456789abcdef";
|
||||
const std::array<char, 6> e = {{'\\', 'u', hex[(u >> 12u) & 0xFu], hex[(u >> 8u) & 0xFu], hex[(u >> 4u) & 0xFu], hex[u & 0xFu]}};
|
||||
std::array<char, 6> e = {{'\\', 'u', 0, 0, 0, 0}};
|
||||
hex4_lower(static_cast<std::uint16_t>(u), &e[2]);
|
||||
m_out.put(e.data(), e.size());
|
||||
}
|
||||
|
||||
@@ -7732,7 +7688,7 @@ NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const nod
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
throw_type_error(302, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind)));
|
||||
throw_type_error(exception_id::type_mismatch, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7742,7 +7698,7 @@ std::vector<U, A> vector_value(const View& v)
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!v.is_array()))
|
||||
{
|
||||
throw_type_error(302, "type must be array, but is ", v.type_name());
|
||||
throw_type_error(exception_id::type_mismatch, "type must be array, but is ", v.type_name());
|
||||
}
|
||||
std::vector<U, A> r;
|
||||
r.reserve(v.size());
|
||||
@@ -7760,7 +7716,7 @@ Map map_value(const View& v)
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!v.is_object()))
|
||||
{
|
||||
throw_type_error(302, "type must be object, but is ", v.type_name());
|
||||
throw_type_error(exception_id::type_mismatch, "type must be object, but is ", v.type_name());
|
||||
}
|
||||
Map r;
|
||||
for (auto it = v.begin(); it != v.end(); ++it)
|
||||
@@ -7999,7 +7955,7 @@ class basic_json_view
|
||||
{
|
||||
return {};
|
||||
}
|
||||
detail::view::throw_type_error(305, "cannot use operator[] with a string argument with ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", type_name());
|
||||
}
|
||||
return lookup(key);
|
||||
}
|
||||
@@ -8025,7 +7981,7 @@ class basic_json_view
|
||||
{
|
||||
return {};
|
||||
}
|
||||
detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", type_name());
|
||||
}
|
||||
return idx < m_node->len ? element(idx) : basic_json_view();
|
||||
}
|
||||
@@ -8058,12 +8014,12 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
||||
{
|
||||
detail::view::throw_type_error(304, "cannot use at() with ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name());
|
||||
}
|
||||
const basic_json_view r = lookup(key);
|
||||
if (NLOHMANN_VIEW_UNLIKELY(r.is_discarded()))
|
||||
{
|
||||
detail::view::throw_out_of_range(403, detail::concat("key '", std::string(key.data(), key.size()), "' not found"));
|
||||
detail::view::throw_key_not_found(std::string(key.data(), key.size()));
|
||||
}
|
||||
return r;
|
||||
}
|
||||
@@ -8084,11 +8040,11 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_array()))
|
||||
{
|
||||
detail::view::throw_type_error(304, "cannot use at() with ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name());
|
||||
}
|
||||
if (NLOHMANN_VIEW_UNLIKELY(idx >= m_node->len))
|
||||
{
|
||||
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
|
||||
detail::view::throw_array_index_out_of_range(idx);
|
||||
}
|
||||
return element(idx);
|
||||
}
|
||||
@@ -8116,7 +8072,7 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
||||
{
|
||||
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
|
||||
}
|
||||
const basic_json_view r = lookup(key);
|
||||
return r.is_discarded() ? default_value : r.template get<T>();
|
||||
@@ -8126,7 +8082,7 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
||||
{
|
||||
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
|
||||
}
|
||||
// (the default is made only if there is no such member)
|
||||
const basic_json_view r = lookup(key);
|
||||
@@ -8141,7 +8097,7 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
|
||||
{
|
||||
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
|
||||
}
|
||||
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
|
||||
return r.is_discarded() ? default_value : r.template get<T>();
|
||||
@@ -8151,7 +8107,7 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
|
||||
{
|
||||
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
|
||||
}
|
||||
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
|
||||
return r.is_discarded() ? string_t(default_value) : r.template get<string_t>();
|
||||
@@ -8164,7 +8120,7 @@ class basic_json_view
|
||||
const iterator it = begin();
|
||||
if (NLOHMANN_VIEW_UNLIKELY(it == end()))
|
||||
{
|
||||
detail::view::throw_invalid_iterator(214, "cannot get value");
|
||||
detail::view::throw_invalid_iterator(detail::exception_id::iterator_value_unavailable, "cannot get value");
|
||||
}
|
||||
return *it;
|
||||
}
|
||||
@@ -8317,7 +8273,7 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_string()))
|
||||
{
|
||||
detail::view::throw_type_error(302, "type must be string, but is ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be string, but is ", type_name());
|
||||
}
|
||||
return {m_doc->str(*m_node), m_node->len};
|
||||
}
|
||||
@@ -8329,7 +8285,7 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_number()))
|
||||
{
|
||||
detail::view::throw_type_error(302, "type must be number, but is ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name());
|
||||
}
|
||||
return {m_doc->str(*m_node), detail::view::number_length(*m_node)};
|
||||
}
|
||||
@@ -8586,7 +8542,7 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_boolean()))
|
||||
{
|
||||
detail::view::throw_type_error(302, "type must be boolean, but is ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be boolean, but is ", type_name());
|
||||
}
|
||||
return (m_node->flags & detail::view::node_flags::is_true) != 0;
|
||||
}
|
||||
@@ -8596,7 +8552,7 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr))
|
||||
{
|
||||
detail::view::throw_type_error(302, "type must be number, but is ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name());
|
||||
}
|
||||
return detail::view::arithmetic_value<T, BasicJsonType>(*m_doc, *m_node);
|
||||
}
|
||||
@@ -8605,7 +8561,7 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_null()))
|
||||
{
|
||||
detail::view::throw_type_error(302, "type must be null, but is ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be null, but is ", type_name());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -8914,7 +8870,7 @@ class basic_json_document
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
|
||||
{
|
||||
detail::view::throw_type_error(320, "cannot save a discarded json_document");
|
||||
detail::view::throw_type_error(detail::exception_id::document_image_unsupported, "cannot save a discarded json_document");
|
||||
}
|
||||
return detail::view::save_image(*m_data);
|
||||
}
|
||||
@@ -9015,7 +8971,7 @@ class basic_json_document
|
||||
const std::size_t idx = pointer_index(token);
|
||||
if (idx >= 0xFFFFFFFFu)
|
||||
{
|
||||
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
|
||||
detail::view::throw_array_index_out_of_range(idx);
|
||||
}
|
||||
for (std::size_t i = 0; i < idx; ++i)
|
||||
{
|
||||
@@ -9071,7 +9027,7 @@ class basic_json_document
|
||||
{
|
||||
if (ptr.empty())
|
||||
{
|
||||
detail::view::throw_out_of_range(405, "JSON pointer has no parent");
|
||||
detail::view::throw_out_of_range(detail::exception_id::patch_on_root, "JSON pointer has no parent");
|
||||
}
|
||||
const view_type parent = root().at(ptr.parent_pointer());
|
||||
const auto& token = ptr.back();
|
||||
@@ -9091,7 +9047,7 @@ class basic_json_document
|
||||
static_assert(Editable, "only an editable document can be edited: use basic_json_document<BasicJsonType, true> (json_editable_document)");
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
|
||||
{
|
||||
detail::view::throw_invalid_iterator(202, "view does not belong to this document");
|
||||
detail::view::throw_invalid_iterator(detail::exception_id::iterator_from_other_value, "view does not belong to this document");
|
||||
}
|
||||
return detail::view::editor<BasicJsonType, view_type>(*m_data);
|
||||
}
|
||||
@@ -9108,7 +9064,7 @@ class basic_json_document
|
||||
{
|
||||
if (idx < 0)
|
||||
{
|
||||
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
|
||||
detail::view::throw_array_index_out_of_range(idx);
|
||||
}
|
||||
return static_cast<std::size_t>(idx);
|
||||
}
|
||||
|
||||
@@ -2042,3 +2042,118 @@ TEST_CASE("string scanning kernels")
|
||||
CHECK(nlohmann::detail::read_eight_bytes(bytes.data() + 8) == 0xFF0F0E0D0C0B0A09u);
|
||||
CHECK(nlohmann::detail::read_eight_bytes(reinterpret_cast<const char*>(bytes.data()) + 3) == 0x0B0A090807060504u);
|
||||
}
|
||||
|
||||
TEST_CASE("find_ascii_copyable_run agrees with is_ascii_copyable for every byte")
|
||||
{
|
||||
// the SWAR mask in find_ascii_copyable_run() encodes the predicate a
|
||||
// second time; check every byte value at every position of a 24-byte buffer
|
||||
// (three words) and for several lengths, with the byte at the end or inside
|
||||
for (unsigned int value = 0; value < 256; ++value)
|
||||
{
|
||||
for (std::size_t position = 0; position < 24; ++position)
|
||||
{
|
||||
std::array<unsigned char, 24> buffer{};
|
||||
buffer.fill(static_cast<unsigned char>('a'));
|
||||
buffer[position] = static_cast<unsigned char>(value);
|
||||
const bool copyable = nlohmann::detail::is_ascii_copyable(static_cast<unsigned char>(value));
|
||||
for (const std::size_t n :
|
||||
{
|
||||
position, position + 1, std::size_t{24}
|
||||
})
|
||||
{
|
||||
if (n > 24)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const std::size_t expected = (copyable || n <= position) ? n : position;
|
||||
CAPTURE(value)
|
||||
CAPTURE(position)
|
||||
CAPTURE(n)
|
||||
CHECK(nlohmann::detail::find_ascii_copyable_run(buffer.data(), n) == expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("UTF-8 validators accept the same sequences")
|
||||
{
|
||||
// The library checks UTF-8 in several places: detail::decode (serializer),
|
||||
// the per-lead-byte switch of the lexer (json::accept), validate_one_utf8
|
||||
// (bulk string scan), utf8_shape, and the byte path of the BON8 reader.
|
||||
// They must accept exactly the same sequences (RFC 3629, section 4).
|
||||
using nlohmann::json;
|
||||
|
||||
// bytes for the third and fourth position: the first two pairs are checked
|
||||
// against all validators, the others only against the cheap ones
|
||||
const std::array<std::array<unsigned char, 2>, 6> tails =
|
||||
{
|
||||
{
|
||||
{{0x80, 0x80}}, {{0xBF, 0xBF}}, {{0x7F, 0x80}}, {{0xC0, 0x80}}, {{0x80, 0x7F}}, {{0x80, 0xC0}}
|
||||
}
|
||||
};
|
||||
|
||||
for (unsigned lead = 0x80; lead <= 0xFF; ++lead)
|
||||
{
|
||||
const auto shape = nlohmann::detail::utf8_shape(static_cast<std::uint8_t>(lead));
|
||||
for (unsigned second = 0; second <= 0xFF; ++second)
|
||||
{
|
||||
for (std::size_t t = 0; t < tails.size(); ++t)
|
||||
{
|
||||
const std::array<unsigned char, 4> bytes = {{static_cast<unsigned char>(lead), static_cast<unsigned char>(second), tails[t][0], tails[t][1]}};
|
||||
|
||||
// expectation from the shape: the sequence is bytes[0..length)
|
||||
const std::size_t length = shape.length;
|
||||
bool expected = length >= 2 && second >= shape.second_lo && second <= shape.second_hi;
|
||||
for (std::size_t i = 2; expected && i < length; ++i)
|
||||
{
|
||||
expected = bytes[i] >= 0x80 && bytes[i] <= 0xBF;
|
||||
}
|
||||
|
||||
// the DFA of the serializer
|
||||
bool dfa = length >= 2;
|
||||
if (dfa)
|
||||
{
|
||||
std::uint8_t state = nlohmann::detail::UTF8_ACCEPT;
|
||||
std::uint32_t codep = 0;
|
||||
for (std::size_t i = 0; i < length; ++i)
|
||||
{
|
||||
nlohmann::detail::decode(state, codep, bytes[i]);
|
||||
}
|
||||
dfa = state == nlohmann::detail::UTF8_ACCEPT;
|
||||
}
|
||||
|
||||
// the bulk path: exactly the sequence, and one byte short
|
||||
const std::size_t bulk = nlohmann::detail::validate_one_utf8(bytes.data(), length);
|
||||
const std::size_t bulk_short = length > 0 ? nlohmann::detail::validate_one_utf8(bytes.data(), length - 1) : 0;
|
||||
|
||||
CAPTURE(lead)
|
||||
CAPTURE(second)
|
||||
CAPTURE(t)
|
||||
CHECK((dfa == expected && bulk == (expected ? length : 0) && bulk_short == 0));
|
||||
|
||||
if (t < 2 && length >= 2)
|
||||
{
|
||||
// the lexer's byte path
|
||||
const std::string sequence(reinterpret_cast<const char*>(bytes.data()), length); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
CHECK(json::accept("\"" + sequence + "\"") == expected);
|
||||
|
||||
// the byte path of the BON8 reader (a string ends at 0xFF)
|
||||
if (lead >= 0xC2 && lead <= 0xF7)
|
||||
{
|
||||
std::vector<std::uint8_t> packed(bytes.begin(), bytes.begin() + static_cast<std::ptrdiff_t>(length));
|
||||
packed.push_back(0xFF);
|
||||
const json j = json::from_bon8(packed, true, false);
|
||||
if (expected)
|
||||
{
|
||||
CHECK(j == json(sequence));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_FALSE((j.is_string() && j.get<std::string>() == sequence));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -361,6 +361,40 @@ TEST_CASE("constructors")
|
||||
CHECK_THROWS_WITH_AS((j.get<std::pair<int, int>>()), "[json.exception.out_of_range.401] array index 1 is out of range", json::out_of_range&);
|
||||
CHECK_THROWS_WITH_AS((j.get<std::tuple<int, int>>()), "[json.exception.out_of_range.401] array index 1 is out of range", json::out_of_range&);
|
||||
CHECK_THROWS_WITH_AS((j.get<std::array<int, 3>>()), "[json.exception.out_of_range.401] array index 1 is out of range", json::out_of_range&);
|
||||
#if JSON_DIAGNOSTICS
|
||||
// the exception names the element that has the wrong type
|
||||
CHECK_THROWS_WITH_AS((j.get<std::array<std::string, 3>>()), "[json.exception.type_error.302] (/0) type must be string, but is number", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS((j.get<std::array<std::string, 3>>()), "[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
||||
#endif
|
||||
|
||||
// the elements are converted in order, so a type error comes before a missing element
|
||||
json const j2{1, "two"};
|
||||
std::array<int, 3> a{{0, 0, 0}};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS((j2.get<std::array<int, 3>>()), "[json.exception.type_error.302] (/1) type must be number, but is string", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j2.get_to(a), "[json.exception.type_error.302] (/1) type must be number, but is string", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS((j2.get<std::array<int, 3>>()), "[json.exception.type_error.302] type must be number, but is string", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j2.get_to(a), "[json.exception.type_error.302] type must be number, but is string", json::type_error&);
|
||||
#endif
|
||||
CHECK_THROWS_WITH_AS(j.get_to(a), "[json.exception.out_of_range.401] array index 1 is out of range", json::out_of_range&);
|
||||
#if !defined(JSON_NOEXCEPTION) // without exceptions, the get_to calls in CHECK_THROWS_WITH_AS do not run
|
||||
CHECK(a[0] == 1);
|
||||
#endif
|
||||
|
||||
json const j3{"one", "two"};
|
||||
CHECK_THROWS_WITH_AS((j3.get<std::array<std::string, 3>>()), "[json.exception.out_of_range.401] array index 2 is out of range", json::out_of_range&);
|
||||
}
|
||||
|
||||
SECTION("std::array from a longer array or from booleans")
|
||||
{
|
||||
json const j{1, 2.5, true, 4u};
|
||||
CHECK(j.get<std::array<int, 3>>() == (std::array<int, 3> {{1, 2, 1}}));
|
||||
std::array<int, 4> a{{0, 0, 0, 0}};
|
||||
j.get_to(a);
|
||||
CHECK(a == (std::array<int, 4> {{1, 2, 1, 4}}));
|
||||
CHECK(j.get<std::array<int, 0>>().empty());
|
||||
}
|
||||
|
||||
SECTION("std::forward_list<json>")
|
||||
|
||||
Reference in new issue
Block a user