diff --git a/docs/mkdocs/docs/examples/no_exceptions_access.cpp b/docs/mkdocs/docs/examples/no_exceptions_access.cpp new file mode 100644 index 000000000..5c457d9d6 --- /dev/null +++ b/docs/mkdocs/docs/examples/no_exceptions_access.cpp @@ -0,0 +1,42 @@ +#include +#include + +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() << "\"\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* age = j["age"].get_ptr(); + std::cout << "get_ptr: 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'; + } +} diff --git a/docs/mkdocs/docs/examples/no_exceptions_access.output b/docs/mkdocs/docs/examples/no_exceptions_access.output new file mode 100644 index 000000000..38b700296 --- /dev/null +++ b/docs/mkdocs/docs/examples/no_exceptions_access.output @@ -0,0 +1,5 @@ +name: string "Mary" +age: not a string, but number +city: missing +get_ptr: name = 1, age = 0 +[json.exception.type_error.302] type must be number, but is string diff --git a/docs/mkdocs/docs/examples/no_exceptions_convert.c++17.cpp b/docs/mkdocs/docs/examples/no_exceptions_convert.c++17.cpp new file mode 100644 index 000000000..6caca0a76 --- /dev/null +++ b/docs/mkdocs/docs/examples/no_exceptions_convert.c++17.cpp @@ -0,0 +1,46 @@ +#include +#include +#include +#include + +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 to_person(const json& j) +{ + try + { + return j.get(); + } + 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'; + } + } +} diff --git a/docs/mkdocs/docs/examples/no_exceptions_convert.c++17.output b/docs/mkdocs/docs/examples/no_exceptions_convert.c++17.output new file mode 100644 index 000000000..1409aa105 --- /dev/null +++ b/docs/mkdocs/docs/examples/no_exceptions_convert.c++17.output @@ -0,0 +1,3 @@ +person: Mary, 42 +not a person: {"age":"forty-two","name":"Mary"} +not a person: {"name":"Mary"} diff --git a/docs/mkdocs/docs/examples/no_exceptions_parse.cpp b/docs/mkdocs/docs/examples/no_exceptions_parse.cpp new file mode 100644 index 000000000..d65d5c829 --- /dev/null +++ b/docs/mkdocs/docs/examples/no_exceptions_parse.cpp @@ -0,0 +1,28 @@ +#include +#include +#include +#include + +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 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'; +} diff --git a/docs/mkdocs/docs/examples/no_exceptions_parse.output b/docs/mkdocs/docs/examples/no_exceptions_parse.output new file mode 100644 index 000000000..d571e3cbb --- /dev/null +++ b/docs/mkdocs/docs/examples/no_exceptions_parse.output @@ -0,0 +1,4 @@ +parse: discarded = true +accept: false +from_cbor: discarded = true +valid input: {"name":"Mary"} diff --git a/docs/mkdocs/docs/examples/parse_error_position.cpp b/docs/mkdocs/docs/examples/parse_error_position.cpp new file mode 100644 index 000000000..ed3c42e79 --- /dev/null +++ b/docs/mkdocs/docs/examples/parse_error_position.cpp @@ -0,0 +1,42 @@ +#include +#include +#include + +using json = nlohmann::json; + +// line and column (both starting at 1) of a byte index in a text +std::pair 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'; + } +} diff --git a/docs/mkdocs/docs/examples/parse_error_position.output b/docs/mkdocs/docs/examples/parse_error_position.output new file mode 100644 index 000000000..77730d4db --- /dev/null +++ b/docs/mkdocs/docs/examples/parse_error_position.output @@ -0,0 +1,2 @@ +[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 diff --git a/docs/mkdocs/docs/examples/parse_error_position_diagnostic.cpp b/docs/mkdocs/docs/examples/parse_error_position_diagnostic.cpp new file mode 100644 index 000000000..1630b2a59 --- /dev/null +++ b/docs/mkdocs/docs/examples/parse_error_position_diagnostic.cpp @@ -0,0 +1,46 @@ +#define JSON_DIAGNOSTIC_POSITIONS 1 +#include +#include +#include + +using json = nlohmann::json; + +// line and column (both starting at 1) of a byte index in a text +std::pair 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'; + } +} diff --git a/docs/mkdocs/docs/examples/parse_error_position_diagnostic.output b/docs/mkdocs/docs/examples/parse_error_position_diagnostic.output new file mode 100644 index 000000000..2f2f0862d --- /dev/null +++ b/docs/mkdocs/docs/examples/parse_error_position_diagnostic.output @@ -0,0 +1,2 @@ +[json.exception.type_error.302] (bytes 29-40) type must be number, but is string +the value starts at line 3, column 10 diff --git a/docs/mkdocs/docs/examples/partial_input_buffering.cpp b/docs/mkdocs/docs/examples/partial_input_buffering.cpp new file mode 100644 index 000000000..85a3b9fc9 --- /dev/null +++ b/docs/mkdocs/docs/examples/partial_input_buffering.cpp @@ -0,0 +1,46 @@ +#include +#include + +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'; + } +} diff --git a/docs/mkdocs/docs/examples/partial_input_buffering.output b/docs/mkdocs/docs/examples/partial_input_buffering.output new file mode 100644 index 000000000..b1d5a6526 --- /dev/null +++ b/docs/mkdocs/docs/examples/partial_input_buffering.output @@ -0,0 +1,7 @@ +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 diff --git a/docs/mkdocs/docs/examples/partial_input_cbor.cpp b/docs/mkdocs/docs/examples/partial_input_cbor.cpp new file mode 100644 index 000000000..8e2497e26 --- /dev/null +++ b/docs/mkdocs/docs/examples/partial_input_cbor.cpp @@ -0,0 +1,23 @@ +#include +#include +#include + +using json = nlohmann::json; + +int main() +{ + // a CBOR value followed by other data + const std::vector 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'; +} diff --git a/docs/mkdocs/docs/examples/partial_input_cbor.output b/docs/mkdocs/docs/examples/partial_input_cbor.output new file mode 100644 index 000000000..beaf841d1 --- /dev/null +++ b/docs/mkdocs/docs/examples/partial_input_cbor.output @@ -0,0 +1,3 @@ +value: [1,2,3] +bytes used: 4 +remaining input: TRAILING diff --git a/docs/mkdocs/docs/examples/partial_input_concatenated.cpp b/docs/mkdocs/docs/examples/partial_input_concatenated.cpp new file mode 100644 index 000000000..0f7079ee4 --- /dev/null +++ b/docs/mkdocs/docs/examples/partial_input_concatenated.cpp @@ -0,0 +1,18 @@ +#include +#include +#include + +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::eof()) + { + input >> j; + std::cout << j << '\n'; + } +} diff --git a/docs/mkdocs/docs/examples/partial_input_concatenated.output b/docs/mkdocs/docs/examples/partial_input_concatenated.output new file mode 100644 index 000000000..e51277135 --- /dev/null +++ b/docs/mkdocs/docs/examples/partial_input_concatenated.output @@ -0,0 +1,4 @@ +{"a":1} +[2,3] +4 +"s" diff --git a/docs/mkdocs/docs/examples/partial_input_discard_records.cpp b/docs/mkdocs/docs/examples/partial_input_discard_records.cpp new file mode 100644 index 000000000..3aebda1f7 --- /dev/null +++ b/docs/mkdocs/docs/examples/partial_input_discard_records.cpp @@ -0,0 +1,27 @@ +#include +#include +#include + +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'; +} diff --git a/docs/mkdocs/docs/examples/partial_input_discard_records.output b/docs/mkdocs/docs/examples/partial_input_discard_records.output new file mode 100644 index 000000000..7eed01747 --- /dev/null +++ b/docs/mkdocs/docs/examples/partial_input_discard_records.output @@ -0,0 +1,4 @@ +handling {"id":1} +handling {"id":2} +handling {"id":3} +result: [] diff --git a/docs/mkdocs/docs/examples/partial_input_sax_stop.cpp b/docs/mkdocs/docs/examples/partial_input_sax_stop.cpp new file mode 100644 index 000000000..6dbeb0945 --- /dev/null +++ b/docs/mkdocs/docs/examples/partial_input_sax_stop.cpp @@ -0,0 +1,81 @@ +#include +#include + +using json = nlohmann::json; + +struct find_id : nlohmann::json_sax +{ + 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'; +} diff --git a/docs/mkdocs/docs/examples/partial_input_sax_stop.output b/docs/mkdocs/docs/examples/partial_input_sax_stop.output new file mode 100644 index 000000000..947e23ee9 --- /dev/null +++ b/docs/mkdocs/docs/examples/partial_input_sax_stop.output @@ -0,0 +1,2 @@ +parse completed: false +id: 42 diff --git a/docs/mkdocs/docs/examples/type_recipes_chrono.cpp b/docs/mkdocs/docs/examples/type_recipes_chrono.cpp new file mode 100644 index 000000000..2db928ad9 --- /dev/null +++ b/docs/mkdocs/docs/examples/type_recipes_chrono.cpp @@ -0,0 +1,56 @@ +#include +#include +#include + +using json = nlohmann::json; + +namespace nlohmann +{ +template +struct adl_serializer> +{ + static void to_json(json& j, const std::chrono::duration& d) + { + j = d.count(); + } + + static void from_json(const json& j, std::chrono::duration& d) + { + d = std::chrono::duration(j.get()); + } +}; + +template +struct adl_serializer> +{ + static void to_json(json& j, const std::chrono::time_point& t) + { + j = t.time_since_epoch(); + } + + static void from_json(const json& j, std::chrono::time_point& t) + { + t = std::chrono::time_point(j.get()); + } +}; +} // 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().count() << '\n'; + + // a system_clock time point is the time since the Unix epoch + const time_point t(milliseconds(1700000000123)); + const json stamp = t; + std::cout << "time point: " << stamp << '\n'; + + const auto back = stamp.get>(); + std::cout << "round trip: " << (back == t) << '\n'; +} diff --git a/docs/mkdocs/docs/examples/type_recipes_chrono.output b/docs/mkdocs/docs/examples/type_recipes_chrono.output new file mode 100644 index 000000000..59bbd9340 --- /dev/null +++ b/docs/mkdocs/docs/examples/type_recipes_chrono.output @@ -0,0 +1,4 @@ +duration: 1500 +as milliseconds: 1500 +time point: 1700000000123 +round trip: true diff --git a/docs/mkdocs/docs/examples/type_recipes_class_hierarchy.c++14.cpp b/docs/mkdocs/docs/examples/type_recipes_class_hierarchy.c++14.cpp new file mode 100644 index 000000000..a7ac926f3 --- /dev/null +++ b/docs/mkdocs/docs/examples/type_recipes_class_hierarchy.c++14.cpp @@ -0,0 +1,99 @@ +#include +#include +#include +#include +#include + +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> +{ + static void to_json(json& j, const std::unique_ptr& p) + { + j = p ? p->serialize() : json(nullptr); + } + + static void from_json(const json& j, std::unique_ptr& p) + { + if (j.is_null()) + { + p.reset(); + return; + } + + const auto& type = j.at("type").get_ref(); + if (type == "circle") + { + p = std::make_unique(j.at("radius").get()); + } + else if (type == "rectangle") + { + p = std::make_unique(j.at("width").get(), j.at("height").get()); + } + else + { + throw std::invalid_argument("unknown shape type: " + type); + } + } +}; +} // namespace nlohmann + +int main() +{ + std::vector> shapes; + shapes.push_back(std::make_unique(1.5)); + shapes.push_back(std::make_unique(2, 3)); + + const json j = shapes; + std::cout << j << '\n'; + + // reading creates objects of the derived types + const auto back = j.get>>(); + std::cout << std::boolalpha; + std::cout << "first is a circle: " << (dynamic_cast(back[0].get()) != nullptr) << '\n'; + std::cout << "second is a rectangle: " << (dynamic_cast(back[1].get()) != nullptr) << '\n'; + + // an unknown type tag is reported + try + { + json{{"type", "star"}}.get>(); + } + catch (const std::invalid_argument& e) + { + std::cout << e.what() << '\n'; + } +} diff --git a/docs/mkdocs/docs/examples/type_recipes_class_hierarchy.c++14.output b/docs/mkdocs/docs/examples/type_recipes_class_hierarchy.c++14.output new file mode 100644 index 000000000..b04cea385 --- /dev/null +++ b/docs/mkdocs/docs/examples/type_recipes_class_hierarchy.c++14.output @@ -0,0 +1,4 @@ +[{"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 diff --git a/docs/mkdocs/docs/examples/type_recipes_smart_pointers.c++14.cpp b/docs/mkdocs/docs/examples/type_recipes_smart_pointers.c++14.cpp new file mode 100644 index 000000000..d06557df2 --- /dev/null +++ b/docs/mkdocs/docs/examples/type_recipes_smart_pointers.c++14.cpp @@ -0,0 +1,90 @@ +#include +#include +#include +#include + +using json = nlohmann::json; + +namespace nlohmann +{ +template +struct adl_serializer> +{ + static void to_json(json& j, const std::shared_ptr& p) + { + if (p) + { + j = *p; + } + else + { + j = nullptr; + } + } + + static void from_json(const json& j, std::shared_ptr& p) + { + if (j.is_null()) + { + p = nullptr; + } + else + { + p = std::make_shared(j.get::type>()); + } + } +}; + +template +struct adl_serializer> +{ + static void to_json(json& j, const std::unique_ptr& p) + { + if (p) + { + j = *p; + } + else + { + j = nullptr; + } + } + + static void from_json(const json& j, std::unique_ptr& p) + { + if (j.is_null()) + { + p = nullptr; + } + else + { + p = std::make_unique(j.get()); + } + } +}; +} // 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> points = {std::make_shared(point{1, 2}), nullptr}; + const json j = points; + std::cout << j << '\n'; + + // reading allocates new objects + const auto back = j.get>>(); + 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>(); + const auto unique = j[0].get>(); + std::cout << const_ptr->y << ", " << unique->x << '\n'; +} diff --git a/docs/mkdocs/docs/examples/type_recipes_smart_pointers.c++14.output b/docs/mkdocs/docs/examples/type_recipes_smart_pointers.c++14.output new file mode 100644 index 000000000..083b00774 --- /dev/null +++ b/docs/mkdocs/docs/examples/type_recipes_smart_pointers.c++14.output @@ -0,0 +1,3 @@ +[{"x":1,"y":2},null] +1, 2, 1 +2, 1 diff --git a/docs/mkdocs/docs/examples/type_recipes_variant.c++17.cpp b/docs/mkdocs/docs/examples/type_recipes_variant.c++17.cpp new file mode 100644 index 000000000..e774f8a2b --- /dev/null +++ b/docs/mkdocs/docs/examples/type_recipes_variant.c++17.cpp @@ -0,0 +1,63 @@ +#include +#include +#include +#include +#include + +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; + +void to_json(json& j, const figure& f) +{ + std::visit([&j](const auto & alternative) + { + j = alternative; + }, f); + j["type"] = std::holds_alternative(f) ? "circ" : "rect"; +} + +void from_json(const json& j, figure& f) +{ + const auto& type = j.at("type").get_ref(); + if (type == "circ") + { + f = j.get(); + } + else if (type == "rect") + { + f = j.get(); + } + else + { + throw std::invalid_argument("unknown figure type: " + type); + } +} +} // namespace geo + +int main() +{ + const std::vector figures = {geo::circ{2}, geo::rect{3, 4}}; + + const json j = figures; + std::cout << j << '\n'; + + const auto back = j.get>(); + std::cout << "alternatives read back: " << back[0].index() << ", " << back[1].index() << '\n'; +} diff --git a/docs/mkdocs/docs/examples/type_recipes_variant.c++17.output b/docs/mkdocs/docs/examples/type_recipes_variant.c++17.output new file mode 100644 index 000000000..062554cb5 --- /dev/null +++ b/docs/mkdocs/docs/examples/type_recipes_variant.c++17.output @@ -0,0 +1,2 @@ +[{"radius":2.0,"type":"circ"},{"height":4.0,"type":"rect","width":3.0}] +alternatives read back: 0, 1 diff --git a/docs/mkdocs/docs/features/arbitrary_types.md b/docs/mkdocs/docs/features/arbitrary_types.md index 0ca165902..bab37f8fd 100644 --- a/docs/mkdocs/docs/features/arbitrary_types.md +++ b/docs/mkdocs/docs/features/arbitrary_types.md @@ -409,6 +409,32 @@ 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;` the type + *is* `std::array`, 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 {};` + (the function is then found in `Hash`'s namespace), or specialize + [`adl_serializer`](#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{...};` 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 struct box`, write `template void to_json(json&, const box&)`, + 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()`/generic conversion support, so `get()` and @@ -505,6 +531,7 @@ 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 diff --git a/docs/mkdocs/docs/features/assertions.md b/docs/mkdocs/docs/features/assertions.md index dadb0bd87..9ba199fa1 100644 --- a/docs/mkdocs/docs/features/assertions.md +++ b/docs/mkdocs/docs/features/assertions.md @@ -7,7 +7,9 @@ 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. +[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. ## Change assertion behavior diff --git a/docs/mkdocs/docs/features/conversions.md b/docs/mkdocs/docs/features/conversions.md index 5ba42b7cf..11e6a2bf0 100644 --- a/docs/mkdocs/docs/features/conversions.md +++ b/docs/mkdocs/docs/features/conversions.md @@ -112,6 +112,29 @@ 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: 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());`. + + Prefer `get()` 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 diff --git a/docs/mkdocs/docs/features/creating_values.md b/docs/mkdocs/docs/features/creating_values.md index fc35b3401..a3a7ae505 100644 --- a/docs/mkdocs/docs/features/creating_values.md +++ b/docs/mkdocs/docs/features/creating_values.md @@ -59,6 +59,25 @@ 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 diff --git a/docs/mkdocs/docs/features/element_access/index.md b/docs/mkdocs/docs/features/element_access/index.md index 262c057b8..b97abd6df 100644 --- a/docs/mkdocs/docs/features/element_access/index.md +++ b/docs/mkdocs/docs/features/element_access/index.md @@ -21,3 +21,42 @@ flowchart TD A --> F{"just testing first?"} F -->|"yes"| G["contains() / find()"] ``` + +## Does this key exist? + +Use [`contains`](../../api/basic_json/contains.md) to test for a key; it does not modify the value and does not throw: + +```cpp +json j = {{"name", "Mary"}, {"address", {{"city", "Berlin"}}}}; + +j.contains("name"); // true +j.contains("age"); // false +j.contains("city"); // false: contains() only looks at the top level +j["address"].contains("city"); // true +j.contains(json::json_pointer("/address/city")); // true: a JSON pointer tests a nested path in one call +``` + +If the value is needed afterwards, [`find`](../../api/basic_json/find.md) tests and accesses with a single lookup: + +```cpp +if (auto it = j.find("name"); it != j.end()) +{ + std::cout << *it << '\n'; +} +``` + +`contains` also returns `true` for a key whose value is `null`; use `j.contains("k") && !j["k"].is_null()` if a +`null` should count as missing. To read a value with a fallback instead, see [`value`](default_value.md). + +!!! warning "`operator[]` inserts missing keys" + + Like `#!cpp std::map::operator[]`, the non-const [`operator[]`](unchecked_access.md) adds a `null` value if the key + is missing. Testing a key with it therefore *changes the object*: + + ```cpp + json j = {{"foo", 1}}; + bool absent = j["bar"].is_null(); // true, but j is now {"bar": null, "foo": 1} + ``` + + Use `contains`/`find` to test, and [`at`](checked_access.md) to access a value that must exist (it throws an + [`out_of_range`](../../home/exceptions.md#out-of-range) exception otherwise). diff --git a/docs/mkdocs/docs/features/no_exceptions.md b/docs/mkdocs/docs/features/no_exceptions.md new file mode 100644 index 000000000..cfb489876 --- /dev/null +++ b/docs/mkdocs/docs/features/no_exceptions.md @@ -0,0 +1,113 @@ +# 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()` | 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()` | 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()`, 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) diff --git a/docs/mkdocs/docs/features/parsing/index.md b/docs/mkdocs/docs/features/parsing/index.md index 07fb6500d..5561ce75a 100644 --- a/docs/mkdocs/docs/features/parsing/index.md +++ b/docs/mkdocs/docs/features/parsing/index.md @@ -33,7 +33,9 @@ json data = json::parse(f); ``` The input must be encoded in UTF-8; other encodings are not supported. A single input may contain only one JSON value. -Inputs consisting of multiple values separated by newlines are handled by the [JSON Lines](json_lines.md) format. +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). 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). @@ -85,7 +87,7 @@ again without changing any result. ## SAX vs. DOM parsing -The library offers two parsing models: +The library offers two parsing models, and a third, read-only alternative: - **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 @@ -95,6 +97,13 @@ The library offers two parsing models: 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 `, 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. @@ -111,7 +120,9 @@ 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 diff --git a/docs/mkdocs/docs/features/parsing/large_and_partial_input.md b/docs/mkdocs/docs/features/parsing/large_and_partial_input.md new file mode 100644 index 000000000..d24b5fab1 --- /dev/null +++ b/docs/mkdocs/docs/features/parsing/large_and_partial_input.md @@ -0,0 +1,154 @@ +# 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 +`` (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 diff --git a/docs/mkdocs/docs/features/parsing/parse_exceptions.md b/docs/mkdocs/docs/features/parsing/parse_exceptions.md index f524b67d8..eb0ae5f58 100644 --- a/docs/mkdocs/docs/features/parsing/parse_exceptions.md +++ b/docs/mkdocs/docs/features/parsing/parse_exceptions.md @@ -21,6 +21,10 @@ 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 @@ -81,3 +85,61 @@ 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). diff --git a/docs/mkdocs/docs/features/parsing/untrusted_input.md b/docs/mkdocs/docs/features/parsing/untrusted_input.md index 9c5fa4c9e..06f6b8379 100644 --- a/docs/mkdocs/docs/features/parsing/untrusted_input.md +++ b/docs/mkdocs/docs/features/parsing/untrusted_input.md @@ -26,9 +26,11 @@ 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()` -- 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 +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 `JSON_THROW_USER`. ## Nesting depth diff --git a/docs/mkdocs/docs/features/performance.md b/docs/mkdocs/docs/features/performance.md index a15fbf38b..7ad7b187e 100644 --- a/docs/mkdocs/docs/features/performance.md +++ b/docs/mkdocs/docs/features/performance.md @@ -122,7 +122,9 @@ 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. + 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. - **Access without copying.** [`get()`](../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: @@ -151,6 +153,29 @@ 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 `, 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" diff --git a/docs/mkdocs/docs/features/type_recipes.md b/docs/mkdocs/docs/features/type_recipes.md new file mode 100644 index 000000000..3bd1ec7da --- /dev/null +++ b/docs/mkdocs/docs/features/type_recipes.md @@ -0,0 +1,122 @@ +# 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` or `std::unique_ptr` 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` 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>` converts to an array of tagged objects and back, creating a `circle` +and a `rectangle`. The same works for `std::shared_ptr`. 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`; for a variant of only +standard types (for example `#!cpp std::variant`) 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` +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` diff --git a/docs/mkdocs/docs/home/exceptions.md b/docs/mkdocs/docs/home/exceptions.md index 7658fb3cb..c7f5ff54d 100644 --- a/docs/mkdocs/docs/home/exceptions.md +++ b/docs/mkdocs/docs/home/exceptions.md @@ -39,6 +39,8 @@ 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" @@ -192,6 +194,34 @@ 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 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. diff --git a/docs/mkdocs/docs/home/faq.md b/docs/mkdocs/docs/home/faq.md index 2381f4f7e..d27b86ae3 100644 --- a/docs/mkdocs/docs/home/faq.md +++ b/docs/mkdocs/docs/home/faq.md @@ -58,6 +58,208 @@ 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()`](../api/basic_json/get.md), for example `#!cpp int age = j["age"].get();`, 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()`. 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 @@ -202,6 +404,36 @@ 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 @@ -210,7 +442,8 @@ and is a common recommendation for this use case. Is it possible to indicate a parse error without throwing an exception? -Yes, see [Parsing and exceptions](../features/parsing/parse_exceptions.md). +Yes, see [Parsing and exceptions](../features/parsing/parse_exceptions.md) and +[Working without exceptions](../features/no_exceptions.md). ### Key name in exceptions diff --git a/docs/mkdocs/docs/index.md b/docs/mkdocs/docs/index.md index 091eca5e8..f57f8d094 100644 --- a/docs/mkdocs/docs/index.md +++ b/docs/mkdocs/docs/index.md @@ -29,6 +29,10 @@ 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(); + std::cout << "2 * pi = " << 2 * pi << '\n'; + // serialize with an indent of 4 spaces std::cout << j.dump(4) << '\n'; } diff --git a/docs/mkdocs/docs/integration/index.md b/docs/mkdocs/docs/integration/index.md index f022ec6ed..3553c7461 100644 --- a/docs/mkdocs/docs/integration/index.md +++ b/docs/mkdocs/docs/integration/index.md @@ -43,6 +43,21 @@ 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 ` 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 @@ -54,3 +69,21 @@ For the read-only, non-owning [`basic_json_document`](../api/basic_json_document types, additionally include [`single_include/nlohmann/json_view.hpp`](https://github.com/nlohmann/json/blob/develop/single_include/nlohmann/json_view.hpp); see [Zero-copy JSON views](../features/json_view.md). + +## Notes for builds and distribution + +**Reproducible and path-independent builds.** The runtime assertions use `assert`, which embeds the name of the source +file (`__FILE__`) in the binary. Release builds do not contain them, because they define `NDEBUG`. If you compile with +assertions enabled and need binaries that do not depend on the build directory, either define `NDEBUG` or map the +prefix of the path with the compiler option `-ffile-prefix-map==` (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). diff --git a/docs/mkdocs/mkdocs.yml b/docs/mkdocs/mkdocs.yml index 90393ff0d..567e2add8 100644 --- a/docs/mkdocs/mkdocs.yml +++ b/docs/mkdocs/mkdocs.yml @@ -60,6 +60,7 @@ 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 @@ -84,11 +85,13 @@ 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