mirror of
https://github.com/nlohmann/json.git
synced 2026-10-25 07:18:55 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7d245db7e | ||
|
|
d38a04bd3e |
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
|
||||
|
||||
|
||||
@@ -124,29 +124,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
|
||||
|
||||
@@ -61,25 +61,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
|
||||
|
||||
@@ -22,45 +22,6 @@ flowchart TD
|
||||
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).
|
||||
|
||||
## See also
|
||||
|
||||
- [**operator[]**](../../api/basic_json/operator%5B%5D.md) - unchecked access
|
||||
|
||||
@@ -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)
|
||||
@@ -36,9 +36,7 @@ json data = json::parse(f);
|
||||
The input must be encoded in UTF-8; other encodings are not supported. The exception is wide-character input, which
|
||||
is read as UTF-16 or UTF-32 -- depending on the character type -- and converted to UTF-8; see the
|
||||
[`parse`](../../api/basic_json/parse.md) page for an example. 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).
|
||||
@@ -94,7 +92,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
|
||||
@@ -104,13 +102,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.
|
||||
|
||||
@@ -148,10 +139,8 @@ 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
|
||||
- [Zero-copy JSON views](../json_view.md) - parse into a document and read it through a view
|
||||
@@ -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"
|
||||
@@ -195,34 +193,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
|
||||
@@ -405,36 +203,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
|
||||
@@ -443,8 +211,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
|
||||
|
||||
@@ -31,10 +31,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
|
||||
@@ -70,24 +55,6 @@ 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).
|
||||
|
||||
## See also
|
||||
|
||||
- [CMake](cmake.md) - integrate the library into a CMake project
|
||||
|
||||
@@ -106,7 +106,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
|
||||
@@ -131,13 +130,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
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||
#include <nlohmann/detail/view/node.hpp>
|
||||
#include <nlohmann/detail/view/scan.hpp>
|
||||
#include <nlohmann/detail/view/simd.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
@@ -103,12 +104,56 @@ NLOHMANN_VIEW_NOINLINE FloatType float_value(const char* first, const node& n)
|
||||
return convert_float<FloatType>(first, last, dot, mantissa_end);
|
||||
}
|
||||
|
||||
#if NLOHMANN_VIEW_VECTOR
|
||||
/*!
|
||||
@brief the value of the first 16 digits of a run of digits with a point after
|
||||
int_digits of them (or none, if int_digits >= 16)
|
||||
|
||||
The 17 bytes at p are read at once: the bytes after the point move down by one,
|
||||
then pairs, quadruples, and octets of digits are combined by multiplications
|
||||
(after simdjson). The bytes [p, p + 32) must be readable. Bytes that are not
|
||||
digits (only in a damaged image loaded with image_check::bounds) give a wrong
|
||||
value, but no undefined behavior.
|
||||
*/
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t sixteen_digits(const unsigned char* p, unsigned int_digits) noexcept
|
||||
{
|
||||
#if NLOHMANN_VIEW_NEON
|
||||
const uint8x16_t low = vld1q_u8(p);
|
||||
const uint8x16_t index = vcombine_u8(vcreate_u8(0x0706050403020100u), vcreate_u8(0x0F0E0D0C0B0A0908u));
|
||||
const uint8x16_t before_point = vcltq_u8(index, vdupq_n_u8(static_cast<std::uint8_t>(int_digits)));
|
||||
const uint8x16_t digits = vsubq_u8(vbslq_u8(before_point, low, vextq_u8(low, vld1q_u8(p + 16), 1)), vdupq_n_u8('0'));
|
||||
const uint16x8_t twos = vpaddlq_u8(vmulq_u8(digits, vreinterpretq_u8_u16(vdupq_n_u16(0x010A))));
|
||||
const uint32x4_t fours = vpaddlq_u16(vmulq_u16(twos, vreinterpretq_u16_u32(vdupq_n_u32(0x00010064))));
|
||||
const uint64x2_t eights = vpaddlq_u32(vmulq_u32(fours, vreinterpretq_u32_u64(vdupq_n_u64(0x0000000100002710u))));
|
||||
return (vgetq_lane_u64(eights, 0) * 100000000u) + vgetq_lane_u64(eights, 1);
|
||||
#else
|
||||
// NOLINTBEGIN(portability-simd-intrinsics)
|
||||
const __m128i low = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i high = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p + 16))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i before_point = _mm_cmpgt_epi8(_mm_set1_epi8(static_cast<char>(int_digits)), _mm_setr_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15));
|
||||
const __m128i moved = _mm_or_si128(_mm_srli_si128(low, 1), _mm_slli_si128(high, 15));
|
||||
const __m128i digits = _mm_sub_epi8(_mm_or_si128(_mm_and_si128(before_point, low), _mm_andnot_si128(before_point, moved)), _mm_set1_epi8('0'));
|
||||
// (16-bit lanes: SSE2 has no multiply-add of bytes)
|
||||
const __m128i tens = _mm_setr_epi16(10, 1, 10, 1, 10, 1, 10, 1);
|
||||
const __m128i twos = _mm_packs_epi32(_mm_madd_epi16(_mm_unpacklo_epi8(digits, _mm_setzero_si128()), tens),
|
||||
_mm_madd_epi16(_mm_unpackhi_epi8(digits, _mm_setzero_si128()), tens));
|
||||
const __m128i fours = _mm_madd_epi16(twos, _mm_setr_epi16(100, 1, 100, 1, 100, 1, 100, 1));
|
||||
const __m128i eights = _mm_madd_epi16(_mm_packs_epi32(fours, fours), _mm_setr_epi16(10000, 1, 10000, 1, 10000, 1, 10000, 1));
|
||||
const auto upper = static_cast<std::uint32_t>(_mm_cvtsi128_si32(eights));
|
||||
const auto lower = static_cast<std::uint32_t>(_mm_cvtsi128_si32(_mm_srli_si128(eights, 4)));
|
||||
// NOLINTEND(portability-simd-intrinsics)
|
||||
return (std::uint64_t{upper} * 100000000u) + lower;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
/*!
|
||||
@brief the digits of a float token with at most 19 digits, from its layout
|
||||
|
||||
The digit layout recorded while parsing says where the integer digits, the
|
||||
fraction digits, and the exponent are, so the digits are read eight at a
|
||||
time without scanning.
|
||||
time without scanning (with SIMD, a token of 16 to 19 digits sixteen at a
|
||||
time).
|
||||
|
||||
@param[in] p first character of the token
|
||||
@param[in] e end of the token
|
||||
@@ -118,15 +163,34 @@ NLOHMANN_VIEW_ALWAYS_INLINE float_significand layout_decimal(const unsigned char
|
||||
{
|
||||
const bool negative = *p == '-';
|
||||
p += negative ? 1 : 0;
|
||||
std::uint64_t w = parse_upto19(p, int_digits, limit);
|
||||
p += int_digits;
|
||||
std::uint64_t w = 0;
|
||||
std::int64_t q = 0;
|
||||
if (frac_digits != 0)
|
||||
#if NLOHMANN_VIEW_VECTOR
|
||||
const unsigned count = int_digits + frac_digits;
|
||||
if (count >= 16 && limit - p >= 32)
|
||||
{
|
||||
w = (w * int_pow10(frac_digits)) + parse_upto19(p + 1, frac_digits, limit);
|
||||
p += 1 + frac_digits;
|
||||
// (the point, if any, follows the integer digits; the digits after
|
||||
// the first 16 are at most three)
|
||||
w = sixteen_digits(p, int_digits);
|
||||
for (unsigned i = 16; i < count; ++i)
|
||||
{
|
||||
w = (w * 10) + static_cast<std::uint64_t>(p[i + (i >= int_digits ? 1 : 0)] - '0');
|
||||
}
|
||||
p += count + (frac_digits != 0 ? 1 : 0);
|
||||
q = -static_cast<std::int64_t>(frac_digits);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
w = parse_upto19(p, int_digits, limit);
|
||||
p += int_digits;
|
||||
if (frac_digits != 0)
|
||||
{
|
||||
w = (w * int_pow10(frac_digits)) + parse_upto19(p + 1, frac_digits, limit);
|
||||
p += 1 + frac_digits;
|
||||
q = -static_cast<std::int64_t>(frac_digits);
|
||||
}
|
||||
}
|
||||
if (p != e)
|
||||
{
|
||||
// [eE][+-]digits; huge exponents saturate (the parser rejected
|
||||
@@ -161,12 +225,22 @@ NLOHMANN_VIEW_ALWAYS_INLINE float_significand layout_decimal(const unsigned char
|
||||
The result is correctly rounded by the lexer's conversion
|
||||
(detail::decimal_to_float(): Clinger's fast path where both operands are
|
||||
exact, else the Eisel-Lemire algorithm, which needs no fallback for up to 19
|
||||
digits), so it is the value parse() produces.
|
||||
digits), so it is the value parse() produces. Tokens of 16 or more digits go
|
||||
to Eisel-Lemire directly: the fast path takes few of them (none of 17 digits),
|
||||
and its check is a branch that data like GeoJSON coordinates mispredicts.
|
||||
*/
|
||||
template<typename FloatType>
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE FloatType layout_float(const unsigned char* p, const unsigned char* e, unsigned int_digits, unsigned frac_digits, const unsigned char* limit) noexcept
|
||||
{
|
||||
return decimal_to_float<FloatType>(layout_decimal(p, e, int_digits, frac_digits, limit));
|
||||
const float_significand d = layout_decimal(p, e, int_digits, frac_digits, limit);
|
||||
if (int_digits + frac_digits >= 16)
|
||||
{
|
||||
using format = ieee_binary_format<std::numeric_limits<FloatType>::digits>;
|
||||
native_float_t<FloatType> value{};
|
||||
float_from_bits(eisel_lemire<format>(d.exponent, d.w) | (d.negative ? (std::uint64_t{1} << format::sign_bit()) : 0u), value);
|
||||
return value;
|
||||
}
|
||||
return decimal_to_float<FloatType>(d);
|
||||
}
|
||||
|
||||
/// the value of a float set by an edit: its token (the shortest round-trip
|
||||
@@ -185,6 +259,80 @@ NLOHMANN_VIEW_NOINLINE FloatType edited_float(const char* token, const node& n)
|
||||
return float_value<FloatType>(token, n);
|
||||
}
|
||||
|
||||
#if NLOHMANN_VIEW_VECTOR
|
||||
/// eisel_lemire() for the token w * 10^q of coordinate_float()
|
||||
template<typename Format>
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t coordinate_bits(std::int64_t q, std::uint64_t w, Format /*format*/) noexcept
|
||||
{
|
||||
return eisel_lemire<Format>(q, w);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief (binary64) eisel_lemire() without the cases that tokens of 16 or 17
|
||||
digits with 5 or more after the point cannot reach: a value of 0, a
|
||||
subnormal, an infinity, or a tie (q < -4)
|
||||
*/
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t coordinate_bits(std::int64_t q, std::uint64_t w, ieee_binary_format<53> format) noexcept
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(w == 0 || q > -5))
|
||||
{
|
||||
return eisel_lemire<ieee_binary_format<53>>(q, w);
|
||||
}
|
||||
static_cast<void>(format);
|
||||
const int lz = count_leading_zeros(w);
|
||||
const std::uint64_t normalized = w << static_cast<unsigned>(lz);
|
||||
const auto index = static_cast<std::size_t>(2 * (q - pow5_128_smallest_power));
|
||||
const uint128_parts product = full_multiplication(normalized, pow5_128()[index]);
|
||||
std::uint64_t high = product.high;
|
||||
if (NLOHMANN_VIEW_UNLIKELY((high & 0x1FFu) == 0x1FFu))
|
||||
{
|
||||
// (the lower bits may carry into the result)
|
||||
const std::uint64_t second = full_multiplication(normalized, pow5_128()[index + 1]).high;
|
||||
high += product.low + second < product.low ? 1u : 0u;
|
||||
}
|
||||
const auto upperbit = static_cast<unsigned>(high >> 63u);
|
||||
std::uint64_t mantissa = high >> (upperbit + 9);
|
||||
std::uint64_t power2 = static_cast<std::uint64_t>(((217706 * q) >> 16) + 63 + static_cast<std::int64_t>(upperbit) - lz + 1023);
|
||||
mantissa = (mantissa + (mantissa & 1u)) >> 1u;
|
||||
const bool carry = mantissa >= (std::uint64_t{2} << 52u);
|
||||
mantissa = carry ? (std::uint64_t{1} << 52u) : mantissa;
|
||||
power2 += carry ? 1u : 0u;
|
||||
return (mantissa & ~(std::uint64_t{1} << 52u)) | (power2 << 52u);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief (binary32 and binary64) the value of a float token of the source with
|
||||
16 or 17 digits, a point, and no exponent, the shape of most coordinates
|
||||
written with full precision; whether the token of n has this shape
|
||||
|
||||
These tokens skip the general layout of layout_decimal(): no digits beyond
|
||||
the 17th, no exponent (and Eisel-Lemire directly, as in layout_float()).
|
||||
*/
|
||||
template<typename FloatType>
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE bool coordinate_float(const document_data& d, const node& n, FloatType& value) noexcept
|
||||
{
|
||||
const unsigned int_digits = n.extra & 0xFFu;
|
||||
const unsigned frac_digits = n.extra >> 8u;
|
||||
const unsigned count = int_digits + frac_digits;
|
||||
const auto* const first = reinterpret_cast<const unsigned char*>(d.src + n.off); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
const unsigned negative = first[0] == '-' ? 1u : 0u;
|
||||
if (count - 16u > 1u || frac_digits == 0 || n.len != negative + count + 1 || d.size - n.off < 33)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const unsigned char* const digits = first + negative;
|
||||
const std::uint64_t w = sixteen_digits(digits, int_digits);
|
||||
// (the 17th digit follows the point)
|
||||
const std::uint64_t w17 = (w * 10) + static_cast<std::uint64_t>(digits[17] - '0');
|
||||
using format = ieee_binary_format<std::numeric_limits<FloatType>::digits>;
|
||||
native_float_t<FloatType> result{};
|
||||
float_from_bits(coordinate_bits(-static_cast<std::int64_t>(frac_digits), count == 17 ? w17 : w, format{}) | (static_cast<std::uint64_t>(negative) << format::sign_bit()), result);
|
||||
value = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// the value of the float token of a node, as parse() converts it; floats and
|
||||
/// doubles with at most 19 digits are converted from the digit layout
|
||||
template<typename FloatType>
|
||||
@@ -200,6 +348,13 @@ FloatType float_value(const document_data& d, const node& n)
|
||||
template<typename FloatType>
|
||||
FloatType float_value(const document_data& d, const node& n, std::true_type /*binary32 or binary64*/)
|
||||
{
|
||||
#if NLOHMANN_VIEW_VECTOR
|
||||
FloatType value {};
|
||||
if (coordinate_float(d, n, value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
#endif
|
||||
const unsigned int_digits = n.extra & 0xFFu;
|
||||
const unsigned frac_digits = n.extra >> 8u;
|
||||
if (NLOHMANN_VIEW_LIKELY(int_digits + frac_digits <= 19)) // (255 marks "many")
|
||||
|
||||
@@ -14,7 +14,9 @@
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint8_t, uint32_t
|
||||
#include <cstring> // memcpy, memset
|
||||
#include <functional> // less
|
||||
#include <limits> // numeric_limits
|
||||
#include <memory> // unique_ptr
|
||||
#include <type_traits> // integral_constant
|
||||
#include <vector> // vector
|
||||
|
||||
@@ -384,6 +386,127 @@ struct depth_counter<false>
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief where the double x nearest to a token w * 10^q lies, for 10^15 <= w <
|
||||
10^17 and -17 <= q <= -1 (Eisel-Lemire, then a fixed-point position)
|
||||
|
||||
dump() writes the shortest decimal in the rounding interval of x (the closest
|
||||
one if there are several, see zmij::to_shortest()). Its digits are those of a
|
||||
multiple of 10^g of the token's last digit, with g = dec_exp + 1 - q for x's
|
||||
decimal exponent dec_exp: the multiple of 10^g nearest to x if it lies in the
|
||||
interval (at most one does), else the multiple of 10^(g - 1) nearest to x
|
||||
(see token_digits_1() and token_digits_2()). For g <= 0, the token itself is
|
||||
in the interval.
|
||||
|
||||
The position is exact in a 64-bit fixed-point number with 56 bits after the
|
||||
point (of the token's last digit): x / 10^q = 2c * 5^-q / 2^t for x = c * 2^e2
|
||||
and t = q + 1 - e2, with at most 56 bits after the point here, and |x / 10^q -
|
||||
w| is at most half the spacing of the doubles, below 12 units. So only the low
|
||||
64 bits of 2c * 5^-q are needed. c and e2 come from Eisel-Lemire's product
|
||||
directly (as eisel_lemire() rounds it: no tie is possible for q < -4, and its
|
||||
second product is added where it can carry into the result).
|
||||
|
||||
@param[out] offset x / 10^q - w + 32, with 56 bits after the point
|
||||
@param[out] radius half the spacing of the doubles at x, likewise (+ 1 if
|
||||
the interval is closed)
|
||||
@return g; 3 if the digits are not decided here (a power of two, whose
|
||||
interval is not symmetric, or out of range)
|
||||
*/
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE int locate_token(std::uint64_t w, int q, std::uint64_t& offset, std::uint64_t& radius) noexcept
|
||||
{
|
||||
const int lz = count_leading_zeros(w);
|
||||
const std::uint64_t normalized = w << static_cast<unsigned>(lz);
|
||||
const auto index = static_cast<std::size_t>(2 * (q - pow5_128_smallest_power));
|
||||
const uint128_parts product = full_multiplication(normalized, pow5_128()[index]);
|
||||
std::uint64_t high = product.high;
|
||||
if (NLOHMANN_VIEW_UNLIKELY((high & 0x1FFu) == 0x1FFu))
|
||||
{
|
||||
// (as eisel_lemire(): the lower bits may carry into the result)
|
||||
const std::uint64_t second = full_multiplication(normalized, pow5_128()[index + 1]).high;
|
||||
high += product.low + second < product.low ? 1u : 0u;
|
||||
}
|
||||
const auto upperbit = static_cast<unsigned>(high >> 63u);
|
||||
const std::uint64_t mantissa = high >> (upperbit + 9);
|
||||
const std::uint64_t c = (mantissa + (mantissa & 1u)) >> 1u;
|
||||
// e2 as eisel_lemire() computes the exponent (log2(10) ~ 217706 / 2^16)
|
||||
const int e2 = static_cast<int>((217706 * static_cast<std::int64_t>(q)) >> 16) + 63 + static_cast<int>(upperbit) - lz - 52;
|
||||
const int t = q + 1 - e2;
|
||||
const int g = ((e2 * 315653) >> 20) + 1 - q; // (dec_exp as in zmij::to_shortest(), for a regular x)
|
||||
static const std::array<std::uint64_t, 17> fives = {{1u, 5u, 25u, 125u, 625u, 3125u, 15625u, 78125u, 390625u, 1953125u, 9765625u, 48828125u, 244140625u, 1220703125u, 6103515625u, 30517578125u, 152587890625u}};
|
||||
const std::uint64_t five = fives[static_cast<std::size_t>(-q)]; // 5^-q
|
||||
const unsigned s = static_cast<unsigned>(56 - t) & 63u;
|
||||
offset = ((2 * c * five) << s) - (w << 56u) + (std::uint64_t{32} << 56u);
|
||||
radius = (five << s) + ((c & 1u) == 0 ? 1u : 0u);
|
||||
// (c = 2^52: a power of two; c = 2^53: rounded up to one. With 8 to 16
|
||||
// fraction digits, as the batch's tokens have, t is in [15, 40] and g at
|
||||
// most 2. x lies halfway between two candidates only if its distance from
|
||||
// the token is a whole unit (g == 2) or a half (g == 1): such tokens are
|
||||
// left to the full conversion)
|
||||
const bool decided = c - ((std::uint64_t{1} << 52u) + 1) < (std::uint64_t{1} << 52u) - 1
|
||||
&& (offset & ((std::uint64_t{1} << 55u) - 1)) != 0;
|
||||
return decided ? g : 3;
|
||||
}
|
||||
|
||||
/// the distance of a position (see locate_token()) from the biased point 32
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t biased_distance(std::uint64_t position) noexcept
|
||||
{
|
||||
constexpr std::uint64_t center = std::uint64_t{32} << 56u;
|
||||
return position >= center ? position - center : center - position;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief (g == 1) the digits dump() writes for a token, from the position of its
|
||||
double (see locate_token()): the multiple of 10 nearest to x if it is in the
|
||||
interval, else w rounded to x (an exact tie between two candidates, like
|
||||
754570942.33984373, whose double is 754570942.33984375, is excluded by
|
||||
locate_token())
|
||||
|
||||
With |x - w| at most the radius, which is below half the spacing of the
|
||||
candidates, the multiple of 10 nearest to x is w - r10 or the one above it.
|
||||
*/
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t token_digits_1(std::uint64_t w, std::uint64_t offset, std::uint64_t radius, std::uint64_t r10) noexcept
|
||||
{
|
||||
constexpr std::uint64_t one = std::uint64_t{1} << 56u;
|
||||
const std::uint64_t from = offset + (r10 * one); // x - (w - r10) (+ 32)
|
||||
const bool up = from > 37 * one; // (nearer to the multiple above)
|
||||
const bool shorter = biased_distance(from - (up ? 10 * one : 0)) < radius;
|
||||
const std::uint64_t a = w - r10 + (up ? 10u : 0u);
|
||||
const std::uint64_t b = w + ((offset + (one / 2)) >> 56u) - 32; // w rounded to x
|
||||
return shorter ? a : b;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief (g == 2) the digits dump() writes for a token, as token_digits_1(): the
|
||||
multiple of 100 nearest to x if it is in the interval, else the multiple of 10
|
||||
nearest to x
|
||||
*/
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t token_digits_2(std::uint64_t w, std::uint64_t offset, std::uint64_t radius, std::uint64_t r10, std::uint64_t r100) noexcept
|
||||
{
|
||||
constexpr std::uint64_t one = std::uint64_t{1} << 56u;
|
||||
const std::uint64_t from = offset + (r100 * one);
|
||||
const bool up = from > 82 * one;
|
||||
const bool shorter = biased_distance(from - (up ? 100 * one : 0)) < radius;
|
||||
const std::uint64_t a = w - r100 + (up ? 100u : 0u);
|
||||
// x - (w - r10), in [-12, 21]: the multiple of 10 nearest to x is that one,
|
||||
// or one or two above, or one below
|
||||
const std::uint64_t y = offset + (r10 * one);
|
||||
const std::uint64_t steps = (y > 37 * one ? 1u : 0u) + (y > 47 * one ? 1u : 0u);
|
||||
const bool down = y < 27 * one;
|
||||
const std::uint64_t b = w - r10 + (10 * steps) - (down ? 10u : 0u);
|
||||
return shorter ? a : b;
|
||||
}
|
||||
|
||||
/// the four ASCII digits of x < 10^4, the first in the lowest byte (as
|
||||
/// little-endian memory holds them)
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE std::uint32_t four_digit_text(std::uint32_t x) noexcept
|
||||
{
|
||||
const std::uint32_t ab = (x * 5243u) >> 19u; // x / 100
|
||||
std::uint32_t v = ab | ((x - (ab * 100u)) << 16u);
|
||||
const std::uint32_t a = ((v * 103u) >> 10u) & 0x000F000Fu; // / 10 per 16-bit lane
|
||||
v = a | ((v - (a * 10u)) << 8u);
|
||||
return v + 0x30303030u;
|
||||
}
|
||||
|
||||
/// how the view's dump() writes a value
|
||||
struct dump_style
|
||||
{
|
||||
@@ -711,6 +834,7 @@ class view_serializer
|
||||
std::array<frame, 32> buffer; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::vector<frame> heap;
|
||||
small_stack<frame> stack(buffer.data(), buffer.size(), heap);
|
||||
float_batch batch;
|
||||
const char* const src = m_doc.src;
|
||||
const char* const src_end = src + m_doc.size;
|
||||
char* w = m_out.cursor();
|
||||
@@ -850,7 +974,7 @@ class view_serializer
|
||||
else if (enabled(fast_double::value))
|
||||
{
|
||||
room(64);
|
||||
w = write_double_at(w, *n);
|
||||
w = write_double_ahead(w, src, n, batch);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -875,6 +999,20 @@ class view_serializer
|
||||
case value_t::array:
|
||||
{
|
||||
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object);
|
||||
// (read-only documents, compact output) a pair of floats,
|
||||
// like the coordinates of GeoJSON, without a frame
|
||||
if (!Editable && !Pretty && !SourceNumbers && enabled(fast_double::value) && n->next == 3 && n->len == 2
|
||||
&& n[1].kind == static_cast<std::uint8_t>(value_t::number_float) && n[2].kind == static_cast<std::uint8_t>(value_t::number_float))
|
||||
{
|
||||
room(2 * 64);
|
||||
*w++ = '[';
|
||||
w = write_double_ahead(w, src, n + 1, batch);
|
||||
*w++ = ',';
|
||||
w = write_double_ahead(w, src, n + 2, batch);
|
||||
*w++ = ']';
|
||||
n += 2; // (the element before the next node)
|
||||
break;
|
||||
}
|
||||
room(8);
|
||||
if (n->len == 0)
|
||||
{
|
||||
@@ -984,6 +1122,247 @@ class view_serializer
|
||||
}
|
||||
}
|
||||
|
||||
/// (float_batch) the mark of digits whose rounding carried beyond the
|
||||
/// token's last four (see mark())
|
||||
static constexpr std::uint64_t carried() noexcept
|
||||
{
|
||||
return std::uint64_t{1} << 63u;
|
||||
}
|
||||
|
||||
/// floats of 16 or 17 digits ahead of the walk, converted together (see
|
||||
/// convert_ahead()), with their digits as mark() keeps them
|
||||
struct float_batch
|
||||
{
|
||||
static constexpr std::size_t capacity() noexcept
|
||||
{
|
||||
return 256;
|
||||
}
|
||||
/// (on the heap, allocated by the first conversion: a frame of more
|
||||
/// than a page made every dump of a small document slower)
|
||||
struct entries
|
||||
{
|
||||
std::array<const node*, 256> at; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::array<std::uint64_t, 256> digits; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
};
|
||||
std::unique_ptr<entries> storage{};
|
||||
const node** at = nullptr;
|
||||
std::uint64_t* digits = nullptr;
|
||||
std::size_t count = 0;
|
||||
std::size_t next = 0;
|
||||
const node* scanned = nullptr; ///< where the last scan of the node array ended
|
||||
};
|
||||
|
||||
/// (doubles) the float at n as dump() writes it, at w (64 bytes of room):
|
||||
/// from the batch if it holds n
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE char* write_double_ahead(char* w, const char* src, const node* n, float_batch& batch)
|
||||
{
|
||||
#if NLOHMANN_VIEW_VECTOR && NLOHMANN_VIEW_LITTLE_ENDIAN
|
||||
// (digits 0: not written by the batch; carried(): rounding carried
|
||||
// beyond the last four digits)
|
||||
if (batch.next != batch.count && batch.at[batch.next] == n && batch.digits[batch.next] - 1 < carried() - 1)
|
||||
{
|
||||
return write_token_digits(w, src, *n, batch.digits[batch.next++]);
|
||||
}
|
||||
return write_double_beside(w, n, batch);
|
||||
#else
|
||||
static_cast<void>(src);
|
||||
static_cast<void>(batch);
|
||||
return write_double_at(w, *n);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if NLOHMANN_VIEW_VECTOR && NLOHMANN_VIEW_LITTLE_ENDIAN
|
||||
/// write_double_ahead() for a float that is not the batch's next one (out
|
||||
/// of line: the walk is faster with less code in its loop)
|
||||
NLOHMANN_VIEW_NOINLINE char* write_double_beside(char* w, const node* n, float_batch& batch)
|
||||
{
|
||||
// (an editable document's walk can skip nodes of the array)
|
||||
const std::less<const node*> before;
|
||||
while (batch.next != batch.count && before(batch.at[batch.next], n))
|
||||
{
|
||||
++batch.next;
|
||||
}
|
||||
if ((batch.next != batch.count && batch.at[batch.next] == n) || convert_ahead(n, batch))
|
||||
{
|
||||
const std::uint64_t digits = batch.digits[batch.next++];
|
||||
if ((digits & carried()) != 0)
|
||||
{
|
||||
return write_carried_digits(w, *n, digits & ~carried());
|
||||
}
|
||||
if (digits != 0)
|
||||
{
|
||||
return write_token_digits(w, m_doc.src, *n, digits);
|
||||
}
|
||||
}
|
||||
return write_double_at(w, *n);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief convert the floats of 16 or 17 digits among the next nodes of the
|
||||
array at once; whether n is the first of them
|
||||
|
||||
Such tokens (like the coordinates of GeoJSON, written with 17 digits) are
|
||||
written as the token with its last four digits replaced
|
||||
(write_token_digits()). Each step (the digits, locate_token(),
|
||||
token_digits_1/2()) is a long chain of dependent operations; one loop per
|
||||
step over many floats lets the processor overlap the floats, which it does
|
||||
not across the steps of one float (on Apple M1, one loop for the digits and
|
||||
locate_token() made the dump of canada.json 12 % slower). Each node of the array
|
||||
is scanned once; a node that the walk does not reach in order (in an
|
||||
editable document) is written without the batch.
|
||||
*/
|
||||
NLOHMANN_VIEW_NOINLINE bool convert_ahead(const node* n, float_batch& batch)
|
||||
{
|
||||
const std::less<const node*> before;
|
||||
const node* const last = m_doc.nodes + m_doc.node_count;
|
||||
if (before(n, batch.scanned) || before(n, m_doc.nodes) || !before(n, last))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const char* const src = m_doc.src;
|
||||
const char* const src_end = src + m_doc.size;
|
||||
const node* const stop = last - n > 1024 ? n + 1024 : last;
|
||||
batch.count = 0;
|
||||
batch.next = 0;
|
||||
// the digits of each token (and its last four from the text)
|
||||
std::array<std::uint64_t, 256> digits; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::array<int, 256> exponent; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::array<std::uint32_t, 256> last_four; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
const node* p = n;
|
||||
if (!batch.storage)
|
||||
{
|
||||
batch.storage.reset(new typename float_batch::entries); // NOLINT(cppcoreguidelines-owning-memory,modernize-make-unique): C++11
|
||||
batch.at = batch.storage->at.data();
|
||||
batch.digits = batch.storage->digits.data();
|
||||
}
|
||||
for (; p != stop && batch.count != float_batch::capacity(); ++p)
|
||||
{
|
||||
// a token of the source: 16 or 17 digits, at least 8 after the
|
||||
// point, no exponent, 32 bytes to read
|
||||
const unsigned frac_digits = p->extra >> 8u;
|
||||
const unsigned int_digits = p->extra & 0xFFu;
|
||||
const unsigned count = int_digits + frac_digits;
|
||||
if (p->kind == static_cast<std::uint8_t>(value_t::number_float) && (p->flags & node_flags::storage) == 0
|
||||
&& count - 16u <= 1u && frac_digits >= 8 && src_end - (src + p->off) >= 33)
|
||||
{
|
||||
const auto* const token = reinterpret_cast<const unsigned char*>(src + p->off); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
const unsigned sign = token[0] == '-' ? 1 : 0;
|
||||
const unsigned char* const first = token + sign;
|
||||
const std::uint64_t w = sixteen_digits(first, int_digits);
|
||||
// (the 17th digit follows the point; a token with an exponent
|
||||
// gets digits outside the range of locate_token(), and is not
|
||||
// written by the batch. A token 0.xxx with 16 significant
|
||||
// digits is: its double is written as "0." and its digits too)
|
||||
const std::uint64_t w17 = (w * 10) + static_cast<std::uint64_t>(first[17] - '0');
|
||||
const std::size_t i = batch.count++;
|
||||
batch.at[i] = p;
|
||||
digits[i] = p->len != sign + count + 1 ? 0 : (count == 17 ? w17 : w);
|
||||
exponent[i] = -static_cast<int>(frac_digits);
|
||||
// (byte 0: the last digit, byte 1: the last two, bits 16-29: the last four)
|
||||
std::uint32_t four = 0;
|
||||
std::memcpy(&four, token + p->len - 4, 4);
|
||||
four -= 0x30303030u;
|
||||
const std::uint32_t pairs = ((four & 0x0F0F0F0Fu) * 2561u) >> 8u; // (byte 0, 2: the pairs)
|
||||
last_four[i] = (four >> 24u) | ((pairs >> 16u & 0xFFu) << 8u) | ((((pairs & 0x00FF00FFu) * 6553601u) >> 16u) << 16u);
|
||||
}
|
||||
}
|
||||
batch.scanned = p;
|
||||
// the position of each double, and the floats of either kind
|
||||
std::array<std::uint64_t, 256> offset; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::array<std::uint64_t, 256> radius; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::array<std::uint8_t, 256> kind_1; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::array<std::uint8_t, 256> kind_2; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::size_t count_1 = 0;
|
||||
std::size_t count_2 = 0;
|
||||
for (std::size_t i = 0; i != batch.count; ++i)
|
||||
{
|
||||
const std::uint64_t w = digits[i];
|
||||
// (the range excludes the values of bytes that are not digits, only
|
||||
// in a damaged image loaded with image_check::bounds)
|
||||
const int g = w - 1000000000000000u < 99000000000000000u ? locate_token(w, exponent[i], offset[i], radius[i]) : 3;
|
||||
batch.digits[i] = g <= 0 ? (last_four[i] >> 16u) + 1 : 0;
|
||||
kind_1[count_1] = static_cast<std::uint8_t>(i);
|
||||
count_1 += g == 1 ? 1 : 0;
|
||||
kind_2[count_2] = static_cast<std::uint8_t>(i);
|
||||
count_2 += g == 2 ? 1 : 0;
|
||||
}
|
||||
// the digits to write, in one loop per kind (see mark())
|
||||
for (std::size_t k = 0; k != count_1; ++k)
|
||||
{
|
||||
const std::size_t i = kind_1[k];
|
||||
const std::uint64_t s = token_digits_1(digits[i], offset[i], radius[i], last_four[i] & 0xFFu);
|
||||
batch.digits[i] = mark(digits[i], s, last_four[i] >> 16u);
|
||||
}
|
||||
for (std::size_t k = 0; k != count_2; ++k)
|
||||
{
|
||||
const std::size_t i = kind_2[k];
|
||||
const std::uint64_t s = token_digits_2(digits[i], offset[i], radius[i], last_four[i] & 0xFFu, (last_four[i] >> 8u) & 0xFFu);
|
||||
batch.digits[i] = mark(digits[i], s, last_four[i] >> 16u);
|
||||
}
|
||||
return batch.count != 0 && batch.at[0] == n;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief a float token of 16 or 17 digits as dump() writes it, at w (64 bytes
|
||||
of room), from the digits of its double (see locate_token()) whose
|
||||
rounding carried beyond the token's last four digits
|
||||
|
||||
(like 43.42027299999999 -> 43.420273): the digits go to the writer of
|
||||
Zmij's results as its 15 or 16 leading digits and one more, which omits
|
||||
the trailing zeros.
|
||||
*/
|
||||
NLOHMANN_VIEW_NOINLINE char* write_carried_digits(char* w, const node& n, std::uint64_t digits) const noexcept
|
||||
{
|
||||
const int exponent = -static_cast<int>(n.extra >> 8u);
|
||||
const std::uint64_t leading = digits / 10;
|
||||
const auto last = static_cast<unsigned char>(digits - (leading * 10));
|
||||
const bool seventeen = digits >= 10000000000000000u;
|
||||
*w = '-';
|
||||
w += m_doc.src[n.off] == '-' ? 1 : 0;
|
||||
return ::nlohmann::detail::dtoa_impl::write_shortest(w, seventeen ? ::nlohmann::detail::zmij::shortest_decimal{leading, exponent, last, last != 0}
|
||||
: ::nlohmann::detail::zmij::shortest_decimal{digits, exponent - 1, 0, false});
|
||||
}
|
||||
|
||||
/// the digits s of a token with the digits w and the last four digits
|
||||
/// r4, as the batch keeps them: the new last four digits + 1 (only those
|
||||
/// are written over the token's), or s marked if it differs from w beyond
|
||||
/// them (0 stays for a float the batch does not write)
|
||||
static std::uint64_t mark(std::uint64_t w, std::uint64_t s, std::uint64_t r4) noexcept
|
||||
{
|
||||
const std::uint64_t last = s - (w - r4);
|
||||
return last < 10000 ? last + 1 : s | carried();
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief a float token of 16 or 17 digits as dump() writes it, at w (64 bytes
|
||||
of room): the token with its last four digits replaced by digits - 1 (see
|
||||
mark(); the others are the token's), and without trailing zeros after the
|
||||
point, but one
|
||||
*/
|
||||
char* write_token_digits(char* w, const char* src, const node& n, std::uint64_t digits)
|
||||
{
|
||||
const char* const token = src + n.off;
|
||||
std::memcpy(w, token, 32);
|
||||
char* const end = w + n.len;
|
||||
const std::uint32_t last = four_digit_text(static_cast<std::uint32_t>(digits - 1));
|
||||
std::memcpy(end - 4, &last, 4);
|
||||
// (the token's text before the new digits, from the source: reading
|
||||
// the output back would wait for the stores)
|
||||
std::uint64_t before = 0;
|
||||
std::memcpy(&before, token + n.len - 12, 8);
|
||||
const std::uint32_t last_zeros = last ^ 0x30303030u;
|
||||
const std::uint64_t before_zeros = before ^ 0x3030303030303030u;
|
||||
if (NLOHMANN_VIEW_UNLIKELY(last_zeros == 0 && before_zeros == 0))
|
||||
{
|
||||
return write_double_at(w, n); // (twelve or more trailing zeros)
|
||||
}
|
||||
const unsigned zeros = last_zeros != 0 ? static_cast<unsigned>(count_leading_zeros(last_zeros)) / 8 - 4
|
||||
: 4 + (static_cast<unsigned>(count_leading_zeros(before_zeros)) / 8);
|
||||
const unsigned frac_digits = n.extra >> 8u;
|
||||
return end - (zeros < frac_digits ? zeros : frac_digits - 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// a float node as dump() writes it
|
||||
void write_float_node(const node& n)
|
||||
{
|
||||
|
||||
Generated
+544
-9
@@ -4605,6 +4605,8 @@ NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
// #include <nlohmann/detail/view/scan.hpp>
|
||||
|
||||
// #include <nlohmann/detail/view/simd.hpp>
|
||||
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
@@ -4689,12 +4691,56 @@ NLOHMANN_VIEW_NOINLINE FloatType float_value(const char* first, const node& n)
|
||||
return convert_float<FloatType>(first, last, dot, mantissa_end);
|
||||
}
|
||||
|
||||
#if NLOHMANN_VIEW_VECTOR
|
||||
/*!
|
||||
@brief the value of the first 16 digits of a run of digits with a point after
|
||||
int_digits of them (or none, if int_digits >= 16)
|
||||
|
||||
The 17 bytes at p are read at once: the bytes after the point move down by one,
|
||||
then pairs, quadruples, and octets of digits are combined by multiplications
|
||||
(after simdjson). The bytes [p, p + 32) must be readable. Bytes that are not
|
||||
digits (only in a damaged image loaded with image_check::bounds) give a wrong
|
||||
value, but no undefined behavior.
|
||||
*/
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t sixteen_digits(const unsigned char* p, unsigned int_digits) noexcept
|
||||
{
|
||||
#if NLOHMANN_VIEW_NEON
|
||||
const uint8x16_t low = vld1q_u8(p);
|
||||
const uint8x16_t index = vcombine_u8(vcreate_u8(0x0706050403020100u), vcreate_u8(0x0F0E0D0C0B0A0908u));
|
||||
const uint8x16_t before_point = vcltq_u8(index, vdupq_n_u8(static_cast<std::uint8_t>(int_digits)));
|
||||
const uint8x16_t digits = vsubq_u8(vbslq_u8(before_point, low, vextq_u8(low, vld1q_u8(p + 16), 1)), vdupq_n_u8('0'));
|
||||
const uint16x8_t twos = vpaddlq_u8(vmulq_u8(digits, vreinterpretq_u8_u16(vdupq_n_u16(0x010A))));
|
||||
const uint32x4_t fours = vpaddlq_u16(vmulq_u16(twos, vreinterpretq_u16_u32(vdupq_n_u32(0x00010064))));
|
||||
const uint64x2_t eights = vpaddlq_u32(vmulq_u32(fours, vreinterpretq_u32_u64(vdupq_n_u64(0x0000000100002710u))));
|
||||
return (vgetq_lane_u64(eights, 0) * 100000000u) + vgetq_lane_u64(eights, 1);
|
||||
#else
|
||||
// NOLINTBEGIN(portability-simd-intrinsics)
|
||||
const __m128i low = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i high = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p + 16))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i before_point = _mm_cmpgt_epi8(_mm_set1_epi8(static_cast<char>(int_digits)), _mm_setr_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15));
|
||||
const __m128i moved = _mm_or_si128(_mm_srli_si128(low, 1), _mm_slli_si128(high, 15));
|
||||
const __m128i digits = _mm_sub_epi8(_mm_or_si128(_mm_and_si128(before_point, low), _mm_andnot_si128(before_point, moved)), _mm_set1_epi8('0'));
|
||||
// (16-bit lanes: SSE2 has no multiply-add of bytes)
|
||||
const __m128i tens = _mm_setr_epi16(10, 1, 10, 1, 10, 1, 10, 1);
|
||||
const __m128i twos = _mm_packs_epi32(_mm_madd_epi16(_mm_unpacklo_epi8(digits, _mm_setzero_si128()), tens),
|
||||
_mm_madd_epi16(_mm_unpackhi_epi8(digits, _mm_setzero_si128()), tens));
|
||||
const __m128i fours = _mm_madd_epi16(twos, _mm_setr_epi16(100, 1, 100, 1, 100, 1, 100, 1));
|
||||
const __m128i eights = _mm_madd_epi16(_mm_packs_epi32(fours, fours), _mm_setr_epi16(10000, 1, 10000, 1, 10000, 1, 10000, 1));
|
||||
const auto upper = static_cast<std::uint32_t>(_mm_cvtsi128_si32(eights));
|
||||
const auto lower = static_cast<std::uint32_t>(_mm_cvtsi128_si32(_mm_srli_si128(eights, 4)));
|
||||
// NOLINTEND(portability-simd-intrinsics)
|
||||
return (std::uint64_t{upper} * 100000000u) + lower;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
/*!
|
||||
@brief the digits of a float token with at most 19 digits, from its layout
|
||||
|
||||
The digit layout recorded while parsing says where the integer digits, the
|
||||
fraction digits, and the exponent are, so the digits are read eight at a
|
||||
time without scanning.
|
||||
time without scanning (with SIMD, a token of 16 to 19 digits sixteen at a
|
||||
time).
|
||||
|
||||
@param[in] p first character of the token
|
||||
@param[in] e end of the token
|
||||
@@ -4704,15 +4750,34 @@ NLOHMANN_VIEW_ALWAYS_INLINE float_significand layout_decimal(const unsigned char
|
||||
{
|
||||
const bool negative = *p == '-';
|
||||
p += negative ? 1 : 0;
|
||||
std::uint64_t w = parse_upto19(p, int_digits, limit);
|
||||
p += int_digits;
|
||||
std::uint64_t w = 0;
|
||||
std::int64_t q = 0;
|
||||
if (frac_digits != 0)
|
||||
#if NLOHMANN_VIEW_VECTOR
|
||||
const unsigned count = int_digits + frac_digits;
|
||||
if (count >= 16 && limit - p >= 32)
|
||||
{
|
||||
w = (w * int_pow10(frac_digits)) + parse_upto19(p + 1, frac_digits, limit);
|
||||
p += 1 + frac_digits;
|
||||
// (the point, if any, follows the integer digits; the digits after
|
||||
// the first 16 are at most three)
|
||||
w = sixteen_digits(p, int_digits);
|
||||
for (unsigned i = 16; i < count; ++i)
|
||||
{
|
||||
w = (w * 10) + static_cast<std::uint64_t>(p[i + (i >= int_digits ? 1 : 0)] - '0');
|
||||
}
|
||||
p += count + (frac_digits != 0 ? 1 : 0);
|
||||
q = -static_cast<std::int64_t>(frac_digits);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
w = parse_upto19(p, int_digits, limit);
|
||||
p += int_digits;
|
||||
if (frac_digits != 0)
|
||||
{
|
||||
w = (w * int_pow10(frac_digits)) + parse_upto19(p + 1, frac_digits, limit);
|
||||
p += 1 + frac_digits;
|
||||
q = -static_cast<std::int64_t>(frac_digits);
|
||||
}
|
||||
}
|
||||
if (p != e)
|
||||
{
|
||||
// [eE][+-]digits; huge exponents saturate (the parser rejected
|
||||
@@ -4747,12 +4812,22 @@ NLOHMANN_VIEW_ALWAYS_INLINE float_significand layout_decimal(const unsigned char
|
||||
The result is correctly rounded by the lexer's conversion
|
||||
(detail::decimal_to_float(): Clinger's fast path where both operands are
|
||||
exact, else the Eisel-Lemire algorithm, which needs no fallback for up to 19
|
||||
digits), so it is the value parse() produces.
|
||||
digits), so it is the value parse() produces. Tokens of 16 or more digits go
|
||||
to Eisel-Lemire directly: the fast path takes few of them (none of 17 digits),
|
||||
and its check is a branch that data like GeoJSON coordinates mispredicts.
|
||||
*/
|
||||
template<typename FloatType>
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE FloatType layout_float(const unsigned char* p, const unsigned char* e, unsigned int_digits, unsigned frac_digits, const unsigned char* limit) noexcept
|
||||
{
|
||||
return decimal_to_float<FloatType>(layout_decimal(p, e, int_digits, frac_digits, limit));
|
||||
const float_significand d = layout_decimal(p, e, int_digits, frac_digits, limit);
|
||||
if (int_digits + frac_digits >= 16)
|
||||
{
|
||||
using format = ieee_binary_format<std::numeric_limits<FloatType>::digits>;
|
||||
native_float_t<FloatType> value{};
|
||||
float_from_bits(eisel_lemire<format>(d.exponent, d.w) | (d.negative ? (std::uint64_t{1} << format::sign_bit()) : 0u), value);
|
||||
return value;
|
||||
}
|
||||
return decimal_to_float<FloatType>(d);
|
||||
}
|
||||
|
||||
/// the value of a float set by an edit: its token (the shortest round-trip
|
||||
@@ -4771,6 +4846,80 @@ NLOHMANN_VIEW_NOINLINE FloatType edited_float(const char* token, const node& n)
|
||||
return float_value<FloatType>(token, n);
|
||||
}
|
||||
|
||||
#if NLOHMANN_VIEW_VECTOR
|
||||
/// eisel_lemire() for the token w * 10^q of coordinate_float()
|
||||
template<typename Format>
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t coordinate_bits(std::int64_t q, std::uint64_t w, Format /*format*/) noexcept
|
||||
{
|
||||
return eisel_lemire<Format>(q, w);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief (binary64) eisel_lemire() without the cases that tokens of 16 or 17
|
||||
digits with 5 or more after the point cannot reach: a value of 0, a
|
||||
subnormal, an infinity, or a tie (q < -4)
|
||||
*/
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t coordinate_bits(std::int64_t q, std::uint64_t w, ieee_binary_format<53> format) noexcept
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(w == 0 || q > -5))
|
||||
{
|
||||
return eisel_lemire<ieee_binary_format<53>>(q, w);
|
||||
}
|
||||
static_cast<void>(format);
|
||||
const int lz = count_leading_zeros(w);
|
||||
const std::uint64_t normalized = w << static_cast<unsigned>(lz);
|
||||
const auto index = static_cast<std::size_t>(2 * (q - pow5_128_smallest_power));
|
||||
const uint128_parts product = full_multiplication(normalized, pow5_128()[index]);
|
||||
std::uint64_t high = product.high;
|
||||
if (NLOHMANN_VIEW_UNLIKELY((high & 0x1FFu) == 0x1FFu))
|
||||
{
|
||||
// (the lower bits may carry into the result)
|
||||
const std::uint64_t second = full_multiplication(normalized, pow5_128()[index + 1]).high;
|
||||
high += product.low + second < product.low ? 1u : 0u;
|
||||
}
|
||||
const auto upperbit = static_cast<unsigned>(high >> 63u);
|
||||
std::uint64_t mantissa = high >> (upperbit + 9);
|
||||
std::uint64_t power2 = static_cast<std::uint64_t>(((217706 * q) >> 16) + 63 + static_cast<std::int64_t>(upperbit) - lz + 1023);
|
||||
mantissa = (mantissa + (mantissa & 1u)) >> 1u;
|
||||
const bool carry = mantissa >= (std::uint64_t{2} << 52u);
|
||||
mantissa = carry ? (std::uint64_t{1} << 52u) : mantissa;
|
||||
power2 += carry ? 1u : 0u;
|
||||
return (mantissa & ~(std::uint64_t{1} << 52u)) | (power2 << 52u);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief (binary32 and binary64) the value of a float token of the source with
|
||||
16 or 17 digits, a point, and no exponent, the shape of most coordinates
|
||||
written with full precision; whether the token of n has this shape
|
||||
|
||||
These tokens skip the general layout of layout_decimal(): no digits beyond
|
||||
the 17th, no exponent (and Eisel-Lemire directly, as in layout_float()).
|
||||
*/
|
||||
template<typename FloatType>
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE bool coordinate_float(const document_data& d, const node& n, FloatType& value) noexcept
|
||||
{
|
||||
const unsigned int_digits = n.extra & 0xFFu;
|
||||
const unsigned frac_digits = n.extra >> 8u;
|
||||
const unsigned count = int_digits + frac_digits;
|
||||
const auto* const first = reinterpret_cast<const unsigned char*>(d.src + n.off); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
const unsigned negative = first[0] == '-' ? 1u : 0u;
|
||||
if (count - 16u > 1u || frac_digits == 0 || n.len != negative + count + 1 || d.size - n.off < 33)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const unsigned char* const digits = first + negative;
|
||||
const std::uint64_t w = sixteen_digits(digits, int_digits);
|
||||
// (the 17th digit follows the point)
|
||||
const std::uint64_t w17 = (w * 10) + static_cast<std::uint64_t>(digits[17] - '0');
|
||||
using format = ieee_binary_format<std::numeric_limits<FloatType>::digits>;
|
||||
native_float_t<FloatType> result{};
|
||||
float_from_bits(coordinate_bits(-static_cast<std::int64_t>(frac_digits), count == 17 ? w17 : w, format{}) | (static_cast<std::uint64_t>(negative) << format::sign_bit()), result);
|
||||
value = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// the value of the float token of a node, as parse() converts it; floats and
|
||||
/// doubles with at most 19 digits are converted from the digit layout
|
||||
template<typename FloatType>
|
||||
@@ -4786,6 +4935,13 @@ FloatType float_value(const document_data& d, const node& n)
|
||||
template<typename FloatType>
|
||||
FloatType float_value(const document_data& d, const node& n, std::true_type /*binary32 or binary64*/)
|
||||
{
|
||||
#if NLOHMANN_VIEW_VECTOR
|
||||
FloatType value {};
|
||||
if (coordinate_float(d, n, value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
#endif
|
||||
const unsigned int_digits = n.extra & 0xFFu;
|
||||
const unsigned frac_digits = n.extra >> 8u;
|
||||
if (NLOHMANN_VIEW_LIKELY(int_digits + frac_digits <= 19)) // (255 marks "many")
|
||||
@@ -6357,7 +6513,9 @@ NLOHMANN_JSON_NAMESPACE_END
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint8_t, uint32_t
|
||||
#include <cstring> // memcpy, memset
|
||||
#include <functional> // less
|
||||
#include <limits> // numeric_limits
|
||||
#include <memory> // unique_ptr
|
||||
#include <type_traits> // integral_constant
|
||||
#include <vector> // vector
|
||||
|
||||
@@ -6732,6 +6890,127 @@ struct depth_counter<false>
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief where the double x nearest to a token w * 10^q lies, for 10^15 <= w <
|
||||
10^17 and -17 <= q <= -1 (Eisel-Lemire, then a fixed-point position)
|
||||
|
||||
dump() writes the shortest decimal in the rounding interval of x (the closest
|
||||
one if there are several, see zmij::to_shortest()). Its digits are those of a
|
||||
multiple of 10^g of the token's last digit, with g = dec_exp + 1 - q for x's
|
||||
decimal exponent dec_exp: the multiple of 10^g nearest to x if it lies in the
|
||||
interval (at most one does), else the multiple of 10^(g - 1) nearest to x
|
||||
(see token_digits_1() and token_digits_2()). For g <= 0, the token itself is
|
||||
in the interval.
|
||||
|
||||
The position is exact in a 64-bit fixed-point number with 56 bits after the
|
||||
point (of the token's last digit): x / 10^q = 2c * 5^-q / 2^t for x = c * 2^e2
|
||||
and t = q + 1 - e2, with at most 56 bits after the point here, and |x / 10^q -
|
||||
w| is at most half the spacing of the doubles, below 12 units. So only the low
|
||||
64 bits of 2c * 5^-q are needed. c and e2 come from Eisel-Lemire's product
|
||||
directly (as eisel_lemire() rounds it: no tie is possible for q < -4, and its
|
||||
second product is added where it can carry into the result).
|
||||
|
||||
@param[out] offset x / 10^q - w + 32, with 56 bits after the point
|
||||
@param[out] radius half the spacing of the doubles at x, likewise (+ 1 if
|
||||
the interval is closed)
|
||||
@return g; 3 if the digits are not decided here (a power of two, whose
|
||||
interval is not symmetric, or out of range)
|
||||
*/
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE int locate_token(std::uint64_t w, int q, std::uint64_t& offset, std::uint64_t& radius) noexcept
|
||||
{
|
||||
const int lz = count_leading_zeros(w);
|
||||
const std::uint64_t normalized = w << static_cast<unsigned>(lz);
|
||||
const auto index = static_cast<std::size_t>(2 * (q - pow5_128_smallest_power));
|
||||
const uint128_parts product = full_multiplication(normalized, pow5_128()[index]);
|
||||
std::uint64_t high = product.high;
|
||||
if (NLOHMANN_VIEW_UNLIKELY((high & 0x1FFu) == 0x1FFu))
|
||||
{
|
||||
// (as eisel_lemire(): the lower bits may carry into the result)
|
||||
const std::uint64_t second = full_multiplication(normalized, pow5_128()[index + 1]).high;
|
||||
high += product.low + second < product.low ? 1u : 0u;
|
||||
}
|
||||
const auto upperbit = static_cast<unsigned>(high >> 63u);
|
||||
const std::uint64_t mantissa = high >> (upperbit + 9);
|
||||
const std::uint64_t c = (mantissa + (mantissa & 1u)) >> 1u;
|
||||
// e2 as eisel_lemire() computes the exponent (log2(10) ~ 217706 / 2^16)
|
||||
const int e2 = static_cast<int>((217706 * static_cast<std::int64_t>(q)) >> 16) + 63 + static_cast<int>(upperbit) - lz - 52;
|
||||
const int t = q + 1 - e2;
|
||||
const int g = ((e2 * 315653) >> 20) + 1 - q; // (dec_exp as in zmij::to_shortest(), for a regular x)
|
||||
static const std::array<std::uint64_t, 17> fives = {{1u, 5u, 25u, 125u, 625u, 3125u, 15625u, 78125u, 390625u, 1953125u, 9765625u, 48828125u, 244140625u, 1220703125u, 6103515625u, 30517578125u, 152587890625u}};
|
||||
const std::uint64_t five = fives[static_cast<std::size_t>(-q)]; // 5^-q
|
||||
const unsigned s = static_cast<unsigned>(56 - t) & 63u;
|
||||
offset = ((2 * c * five) << s) - (w << 56u) + (std::uint64_t{32} << 56u);
|
||||
radius = (five << s) + ((c & 1u) == 0 ? 1u : 0u);
|
||||
// (c = 2^52: a power of two; c = 2^53: rounded up to one. With 8 to 16
|
||||
// fraction digits, as the batch's tokens have, t is in [15, 40] and g at
|
||||
// most 2. x lies halfway between two candidates only if its distance from
|
||||
// the token is a whole unit (g == 2) or a half (g == 1): such tokens are
|
||||
// left to the full conversion)
|
||||
const bool decided = c - ((std::uint64_t{1} << 52u) + 1) < (std::uint64_t{1} << 52u) - 1
|
||||
&& (offset & ((std::uint64_t{1} << 55u) - 1)) != 0;
|
||||
return decided ? g : 3;
|
||||
}
|
||||
|
||||
/// the distance of a position (see locate_token()) from the biased point 32
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t biased_distance(std::uint64_t position) noexcept
|
||||
{
|
||||
constexpr std::uint64_t center = std::uint64_t{32} << 56u;
|
||||
return position >= center ? position - center : center - position;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief (g == 1) the digits dump() writes for a token, from the position of its
|
||||
double (see locate_token()): the multiple of 10 nearest to x if it is in the
|
||||
interval, else w rounded to x (an exact tie between two candidates, like
|
||||
754570942.33984373, whose double is 754570942.33984375, is excluded by
|
||||
locate_token())
|
||||
|
||||
With |x - w| at most the radius, which is below half the spacing of the
|
||||
candidates, the multiple of 10 nearest to x is w - r10 or the one above it.
|
||||
*/
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t token_digits_1(std::uint64_t w, std::uint64_t offset, std::uint64_t radius, std::uint64_t r10) noexcept
|
||||
{
|
||||
constexpr std::uint64_t one = std::uint64_t{1} << 56u;
|
||||
const std::uint64_t from = offset + (r10 * one); // x - (w - r10) (+ 32)
|
||||
const bool up = from > 37 * one; // (nearer to the multiple above)
|
||||
const bool shorter = biased_distance(from - (up ? 10 * one : 0)) < radius;
|
||||
const std::uint64_t a = w - r10 + (up ? 10u : 0u);
|
||||
const std::uint64_t b = w + ((offset + (one / 2)) >> 56u) - 32; // w rounded to x
|
||||
return shorter ? a : b;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief (g == 2) the digits dump() writes for a token, as token_digits_1(): the
|
||||
multiple of 100 nearest to x if it is in the interval, else the multiple of 10
|
||||
nearest to x
|
||||
*/
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t token_digits_2(std::uint64_t w, std::uint64_t offset, std::uint64_t radius, std::uint64_t r10, std::uint64_t r100) noexcept
|
||||
{
|
||||
constexpr std::uint64_t one = std::uint64_t{1} << 56u;
|
||||
const std::uint64_t from = offset + (r100 * one);
|
||||
const bool up = from > 82 * one;
|
||||
const bool shorter = biased_distance(from - (up ? 100 * one : 0)) < radius;
|
||||
const std::uint64_t a = w - r100 + (up ? 100u : 0u);
|
||||
// x - (w - r10), in [-12, 21]: the multiple of 10 nearest to x is that one,
|
||||
// or one or two above, or one below
|
||||
const std::uint64_t y = offset + (r10 * one);
|
||||
const std::uint64_t steps = (y > 37 * one ? 1u : 0u) + (y > 47 * one ? 1u : 0u);
|
||||
const bool down = y < 27 * one;
|
||||
const std::uint64_t b = w - r10 + (10 * steps) - (down ? 10u : 0u);
|
||||
return shorter ? a : b;
|
||||
}
|
||||
|
||||
/// the four ASCII digits of x < 10^4, the first in the lowest byte (as
|
||||
/// little-endian memory holds them)
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE std::uint32_t four_digit_text(std::uint32_t x) noexcept
|
||||
{
|
||||
const std::uint32_t ab = (x * 5243u) >> 19u; // x / 100
|
||||
std::uint32_t v = ab | ((x - (ab * 100u)) << 16u);
|
||||
const std::uint32_t a = ((v * 103u) >> 10u) & 0x000F000Fu; // / 10 per 16-bit lane
|
||||
v = a | ((v - (a * 10u)) << 8u);
|
||||
return v + 0x30303030u;
|
||||
}
|
||||
|
||||
/// how the view's dump() writes a value
|
||||
struct dump_style
|
||||
{
|
||||
@@ -7059,6 +7338,7 @@ class view_serializer
|
||||
std::array<frame, 32> buffer; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::vector<frame> heap;
|
||||
small_stack<frame> stack(buffer.data(), buffer.size(), heap);
|
||||
float_batch batch;
|
||||
const char* const src = m_doc.src;
|
||||
const char* const src_end = src + m_doc.size;
|
||||
char* w = m_out.cursor();
|
||||
@@ -7198,7 +7478,7 @@ class view_serializer
|
||||
else if (enabled(fast_double::value))
|
||||
{
|
||||
room(64);
|
||||
w = write_double_at(w, *n);
|
||||
w = write_double_ahead(w, src, n, batch);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -7223,6 +7503,20 @@ class view_serializer
|
||||
case value_t::array:
|
||||
{
|
||||
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object);
|
||||
// (read-only documents, compact output) a pair of floats,
|
||||
// like the coordinates of GeoJSON, without a frame
|
||||
if (!Editable && !Pretty && !SourceNumbers && enabled(fast_double::value) && n->next == 3 && n->len == 2
|
||||
&& n[1].kind == static_cast<std::uint8_t>(value_t::number_float) && n[2].kind == static_cast<std::uint8_t>(value_t::number_float))
|
||||
{
|
||||
room(2 * 64);
|
||||
*w++ = '[';
|
||||
w = write_double_ahead(w, src, n + 1, batch);
|
||||
*w++ = ',';
|
||||
w = write_double_ahead(w, src, n + 2, batch);
|
||||
*w++ = ']';
|
||||
n += 2; // (the element before the next node)
|
||||
break;
|
||||
}
|
||||
room(8);
|
||||
if (n->len == 0)
|
||||
{
|
||||
@@ -7332,6 +7626,247 @@ class view_serializer
|
||||
}
|
||||
}
|
||||
|
||||
/// (float_batch) the mark of digits whose rounding carried beyond the
|
||||
/// token's last four (see mark())
|
||||
static constexpr std::uint64_t carried() noexcept
|
||||
{
|
||||
return std::uint64_t{1} << 63u;
|
||||
}
|
||||
|
||||
/// floats of 16 or 17 digits ahead of the walk, converted together (see
|
||||
/// convert_ahead()), with their digits as mark() keeps them
|
||||
struct float_batch
|
||||
{
|
||||
static constexpr std::size_t capacity() noexcept
|
||||
{
|
||||
return 256;
|
||||
}
|
||||
/// (on the heap, allocated by the first conversion: a frame of more
|
||||
/// than a page made every dump of a small document slower)
|
||||
struct entries
|
||||
{
|
||||
std::array<const node*, 256> at; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::array<std::uint64_t, 256> digits; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
};
|
||||
std::unique_ptr<entries> storage{};
|
||||
const node** at = nullptr;
|
||||
std::uint64_t* digits = nullptr;
|
||||
std::size_t count = 0;
|
||||
std::size_t next = 0;
|
||||
const node* scanned = nullptr; ///< where the last scan of the node array ended
|
||||
};
|
||||
|
||||
/// (doubles) the float at n as dump() writes it, at w (64 bytes of room):
|
||||
/// from the batch if it holds n
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE char* write_double_ahead(char* w, const char* src, const node* n, float_batch& batch)
|
||||
{
|
||||
#if NLOHMANN_VIEW_VECTOR && NLOHMANN_VIEW_LITTLE_ENDIAN
|
||||
// (digits 0: not written by the batch; carried(): rounding carried
|
||||
// beyond the last four digits)
|
||||
if (batch.next != batch.count && batch.at[batch.next] == n && batch.digits[batch.next] - 1 < carried() - 1)
|
||||
{
|
||||
return write_token_digits(w, src, *n, batch.digits[batch.next++]);
|
||||
}
|
||||
return write_double_beside(w, n, batch);
|
||||
#else
|
||||
static_cast<void>(src);
|
||||
static_cast<void>(batch);
|
||||
return write_double_at(w, *n);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if NLOHMANN_VIEW_VECTOR && NLOHMANN_VIEW_LITTLE_ENDIAN
|
||||
/// write_double_ahead() for a float that is not the batch's next one (out
|
||||
/// of line: the walk is faster with less code in its loop)
|
||||
NLOHMANN_VIEW_NOINLINE char* write_double_beside(char* w, const node* n, float_batch& batch)
|
||||
{
|
||||
// (an editable document's walk can skip nodes of the array)
|
||||
const std::less<const node*> before;
|
||||
while (batch.next != batch.count && before(batch.at[batch.next], n))
|
||||
{
|
||||
++batch.next;
|
||||
}
|
||||
if ((batch.next != batch.count && batch.at[batch.next] == n) || convert_ahead(n, batch))
|
||||
{
|
||||
const std::uint64_t digits = batch.digits[batch.next++];
|
||||
if ((digits & carried()) != 0)
|
||||
{
|
||||
return write_carried_digits(w, *n, digits & ~carried());
|
||||
}
|
||||
if (digits != 0)
|
||||
{
|
||||
return write_token_digits(w, m_doc.src, *n, digits);
|
||||
}
|
||||
}
|
||||
return write_double_at(w, *n);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief convert the floats of 16 or 17 digits among the next nodes of the
|
||||
array at once; whether n is the first of them
|
||||
|
||||
Such tokens (like the coordinates of GeoJSON, written with 17 digits) are
|
||||
written as the token with its last four digits replaced
|
||||
(write_token_digits()). Each step (the digits, locate_token(),
|
||||
token_digits_1/2()) is a long chain of dependent operations; one loop per
|
||||
step over many floats lets the processor overlap the floats, which it does
|
||||
not across the steps of one float (on Apple M1, one loop for the digits and
|
||||
locate_token() made the dump of canada.json 12 % slower). Each node of the array
|
||||
is scanned once; a node that the walk does not reach in order (in an
|
||||
editable document) is written without the batch.
|
||||
*/
|
||||
NLOHMANN_VIEW_NOINLINE bool convert_ahead(const node* n, float_batch& batch)
|
||||
{
|
||||
const std::less<const node*> before;
|
||||
const node* const last = m_doc.nodes + m_doc.node_count;
|
||||
if (before(n, batch.scanned) || before(n, m_doc.nodes) || !before(n, last))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const char* const src = m_doc.src;
|
||||
const char* const src_end = src + m_doc.size;
|
||||
const node* const stop = last - n > 1024 ? n + 1024 : last;
|
||||
batch.count = 0;
|
||||
batch.next = 0;
|
||||
// the digits of each token (and its last four from the text)
|
||||
std::array<std::uint64_t, 256> digits; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::array<int, 256> exponent; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::array<std::uint32_t, 256> last_four; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
const node* p = n;
|
||||
if (!batch.storage)
|
||||
{
|
||||
batch.storage.reset(new typename float_batch::entries); // NOLINT(cppcoreguidelines-owning-memory,modernize-make-unique): C++11
|
||||
batch.at = batch.storage->at.data();
|
||||
batch.digits = batch.storage->digits.data();
|
||||
}
|
||||
for (; p != stop && batch.count != float_batch::capacity(); ++p)
|
||||
{
|
||||
// a token of the source: 16 or 17 digits, at least 8 after the
|
||||
// point, no exponent, 32 bytes to read
|
||||
const unsigned frac_digits = p->extra >> 8u;
|
||||
const unsigned int_digits = p->extra & 0xFFu;
|
||||
const unsigned count = int_digits + frac_digits;
|
||||
if (p->kind == static_cast<std::uint8_t>(value_t::number_float) && (p->flags & node_flags::storage) == 0
|
||||
&& count - 16u <= 1u && frac_digits >= 8 && src_end - (src + p->off) >= 33)
|
||||
{
|
||||
const auto* const token = reinterpret_cast<const unsigned char*>(src + p->off); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
const unsigned sign = token[0] == '-' ? 1 : 0;
|
||||
const unsigned char* const first = token + sign;
|
||||
const std::uint64_t w = sixteen_digits(first, int_digits);
|
||||
// (the 17th digit follows the point; a token with an exponent
|
||||
// gets digits outside the range of locate_token(), and is not
|
||||
// written by the batch. A token 0.xxx with 16 significant
|
||||
// digits is: its double is written as "0." and its digits too)
|
||||
const std::uint64_t w17 = (w * 10) + static_cast<std::uint64_t>(first[17] - '0');
|
||||
const std::size_t i = batch.count++;
|
||||
batch.at[i] = p;
|
||||
digits[i] = p->len != sign + count + 1 ? 0 : (count == 17 ? w17 : w);
|
||||
exponent[i] = -static_cast<int>(frac_digits);
|
||||
// (byte 0: the last digit, byte 1: the last two, bits 16-29: the last four)
|
||||
std::uint32_t four = 0;
|
||||
std::memcpy(&four, token + p->len - 4, 4);
|
||||
four -= 0x30303030u;
|
||||
const std::uint32_t pairs = ((four & 0x0F0F0F0Fu) * 2561u) >> 8u; // (byte 0, 2: the pairs)
|
||||
last_four[i] = (four >> 24u) | ((pairs >> 16u & 0xFFu) << 8u) | ((((pairs & 0x00FF00FFu) * 6553601u) >> 16u) << 16u);
|
||||
}
|
||||
}
|
||||
batch.scanned = p;
|
||||
// the position of each double, and the floats of either kind
|
||||
std::array<std::uint64_t, 256> offset; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::array<std::uint64_t, 256> radius; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::array<std::uint8_t, 256> kind_1; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::array<std::uint8_t, 256> kind_2; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::size_t count_1 = 0;
|
||||
std::size_t count_2 = 0;
|
||||
for (std::size_t i = 0; i != batch.count; ++i)
|
||||
{
|
||||
const std::uint64_t w = digits[i];
|
||||
// (the range excludes the values of bytes that are not digits, only
|
||||
// in a damaged image loaded with image_check::bounds)
|
||||
const int g = w - 1000000000000000u < 99000000000000000u ? locate_token(w, exponent[i], offset[i], radius[i]) : 3;
|
||||
batch.digits[i] = g <= 0 ? (last_four[i] >> 16u) + 1 : 0;
|
||||
kind_1[count_1] = static_cast<std::uint8_t>(i);
|
||||
count_1 += g == 1 ? 1 : 0;
|
||||
kind_2[count_2] = static_cast<std::uint8_t>(i);
|
||||
count_2 += g == 2 ? 1 : 0;
|
||||
}
|
||||
// the digits to write, in one loop per kind (see mark())
|
||||
for (std::size_t k = 0; k != count_1; ++k)
|
||||
{
|
||||
const std::size_t i = kind_1[k];
|
||||
const std::uint64_t s = token_digits_1(digits[i], offset[i], radius[i], last_four[i] & 0xFFu);
|
||||
batch.digits[i] = mark(digits[i], s, last_four[i] >> 16u);
|
||||
}
|
||||
for (std::size_t k = 0; k != count_2; ++k)
|
||||
{
|
||||
const std::size_t i = kind_2[k];
|
||||
const std::uint64_t s = token_digits_2(digits[i], offset[i], radius[i], last_four[i] & 0xFFu, (last_four[i] >> 8u) & 0xFFu);
|
||||
batch.digits[i] = mark(digits[i], s, last_four[i] >> 16u);
|
||||
}
|
||||
return batch.count != 0 && batch.at[0] == n;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief a float token of 16 or 17 digits as dump() writes it, at w (64 bytes
|
||||
of room), from the digits of its double (see locate_token()) whose
|
||||
rounding carried beyond the token's last four digits
|
||||
|
||||
(like 43.42027299999999 -> 43.420273): the digits go to the writer of
|
||||
Zmij's results as its 15 or 16 leading digits and one more, which omits
|
||||
the trailing zeros.
|
||||
*/
|
||||
NLOHMANN_VIEW_NOINLINE char* write_carried_digits(char* w, const node& n, std::uint64_t digits) const noexcept
|
||||
{
|
||||
const int exponent = -static_cast<int>(n.extra >> 8u);
|
||||
const std::uint64_t leading = digits / 10;
|
||||
const auto last = static_cast<unsigned char>(digits - (leading * 10));
|
||||
const bool seventeen = digits >= 10000000000000000u;
|
||||
*w = '-';
|
||||
w += m_doc.src[n.off] == '-' ? 1 : 0;
|
||||
return ::nlohmann::detail::dtoa_impl::write_shortest(w, seventeen ? ::nlohmann::detail::zmij::shortest_decimal{leading, exponent, last, last != 0}
|
||||
: ::nlohmann::detail::zmij::shortest_decimal{digits, exponent - 1, 0, false});
|
||||
}
|
||||
|
||||
/// the digits s of a token with the digits w and the last four digits
|
||||
/// r4, as the batch keeps them: the new last four digits + 1 (only those
|
||||
/// are written over the token's), or s marked if it differs from w beyond
|
||||
/// them (0 stays for a float the batch does not write)
|
||||
static std::uint64_t mark(std::uint64_t w, std::uint64_t s, std::uint64_t r4) noexcept
|
||||
{
|
||||
const std::uint64_t last = s - (w - r4);
|
||||
return last < 10000 ? last + 1 : s | carried();
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief a float token of 16 or 17 digits as dump() writes it, at w (64 bytes
|
||||
of room): the token with its last four digits replaced by digits - 1 (see
|
||||
mark(); the others are the token's), and without trailing zeros after the
|
||||
point, but one
|
||||
*/
|
||||
char* write_token_digits(char* w, const char* src, const node& n, std::uint64_t digits)
|
||||
{
|
||||
const char* const token = src + n.off;
|
||||
std::memcpy(w, token, 32);
|
||||
char* const end = w + n.len;
|
||||
const std::uint32_t last = four_digit_text(static_cast<std::uint32_t>(digits - 1));
|
||||
std::memcpy(end - 4, &last, 4);
|
||||
// (the token's text before the new digits, from the source: reading
|
||||
// the output back would wait for the stores)
|
||||
std::uint64_t before = 0;
|
||||
std::memcpy(&before, token + n.len - 12, 8);
|
||||
const std::uint32_t last_zeros = last ^ 0x30303030u;
|
||||
const std::uint64_t before_zeros = before ^ 0x3030303030303030u;
|
||||
if (NLOHMANN_VIEW_UNLIKELY(last_zeros == 0 && before_zeros == 0))
|
||||
{
|
||||
return write_double_at(w, n); // (twelve or more trailing zeros)
|
||||
}
|
||||
const unsigned zeros = last_zeros != 0 ? static_cast<unsigned>(count_leading_zeros(last_zeros)) / 8 - 4
|
||||
: 4 + (static_cast<unsigned>(count_leading_zeros(before_zeros)) / 8);
|
||||
const unsigned frac_digits = n.extra >> 8u;
|
||||
return end - (zeros < frac_digits ? zeros : frac_digits - 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// a float node as dump() writes it
|
||||
void write_float_node(const node& n)
|
||||
{
|
||||
|
||||
@@ -150,6 +150,71 @@ TEST_CASE("json_view dump")
|
||||
CHECK(many_doc.root().dump() == json::parse(many_tokens).dump());
|
||||
}
|
||||
|
||||
// tokens of 16 and 17 digits without an exponent (like GeoJSON
|
||||
// coordinates), which dump() converts in batches: doubles exactly
|
||||
// halfway between two candidates for the shortest digits, random
|
||||
// digits, and digits near a shorter decimal (...99999997,
|
||||
// ...00000003), whose rounding carries; in pairs and among other
|
||||
// values, so that the batches are interrupted; the last token near the
|
||||
// end of the text
|
||||
{
|
||||
std::mt19937_64 tokens(5627); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed)
|
||||
const auto draw = [&tokens](std::size_t n)
|
||||
{
|
||||
const std::uint64_t r = tokens() % n;
|
||||
return static_cast<std::size_t>(r);
|
||||
};
|
||||
std::string coordinates = "[754570942.33984373,257769445.56835938,858106901.94921875,97944964.876953125,"
|
||||
"[0.5,1e-7],[1.5,-0.0,2.5],[1E2,12.5,0.1,3.25,4.5,5.75,6.125,7.0,8.5],[],[1.5],[1,2.5],[2.5,\"x\"],[[1.5],2.5],";
|
||||
for (int i = 0; i < 20000; ++i)
|
||||
{
|
||||
const std::size_t length = 16 + draw(2);
|
||||
std::string digits(1, static_cast<char>('1' + draw(9)));
|
||||
const std::size_t kept = draw(3) == 0 ? length : 2 + draw(length - 3);
|
||||
for (std::size_t k = 1; k < kept; ++k)
|
||||
{
|
||||
digits += static_cast<char>('0' + draw(10));
|
||||
}
|
||||
digits += std::string(length - kept, draw(2) == 0 ? '9' : '0');
|
||||
digits.back() = static_cast<char>('0' + draw(10));
|
||||
const std::size_t point = 1 + draw(9);
|
||||
const std::string token = (draw(2) == 0 ? "-" : "") + digits.substr(0, point) + "." + digits.substr(point);
|
||||
coordinates += (i != 0 ? "," : "") + (draw(8) == 0 ? "{\"x\":" + token + ",\"s\":\"a\"}" : "[" + token + "," + token + "]");
|
||||
}
|
||||
coordinates += ",-65.613616999999977]";
|
||||
const json_document parsed = json_document::parse(coordinates);
|
||||
const ordered_json j = ordered_json::parse(coordinates);
|
||||
CHECK(parsed.root().dump() == j.dump());
|
||||
CHECK(parsed.root().dump(1) == j.dump(1));
|
||||
CHECK(parsed.root().dump(-1, ' ', true) == j.dump(-1, ' ', true));
|
||||
CHECK(parsed.root().back().get<double>() == j.back().get<double>());
|
||||
CHECK(parsed.root().back().dump() == "-65.61361699999998");
|
||||
std::size_t i = 0;
|
||||
for (const json_view v : parsed.root())
|
||||
{
|
||||
const ordered_json& e = j[i++];
|
||||
CHECK(v.dump() == e.dump());
|
||||
if (v.is_array() && v.size() != 0 && v[0].is_number())
|
||||
{
|
||||
CHECK(v[0].get<double>() == e[0].get<double>());
|
||||
}
|
||||
}
|
||||
|
||||
// an editable document: edits move some arrays, so that the walk
|
||||
// leaves the order of the parsed nodes
|
||||
ordered_json_editable_document editable = ordered_json_editable_document::parse(coordinates);
|
||||
ordered_json expected = j;
|
||||
for (std::size_t k = 0; k < 20000; k += 97)
|
||||
{
|
||||
if (expected[k].is_array())
|
||||
{
|
||||
editable.push_back(editable.root()[k], ordered_json(1.5));
|
||||
expected[k].push_back(1.5);
|
||||
}
|
||||
}
|
||||
CHECK(editable.root().dump() == expected.dump());
|
||||
}
|
||||
|
||||
// random doubles, written as parse() and dump() would
|
||||
std::mt19937_64 rng(1170); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed)
|
||||
std::string many = "[";
|
||||
|
||||
Reference in new issue
Block a user