From 1a90c9463aa5691b4a1fd5b746fedfbecebe57ff Mon Sep 17 00:00:00 2001 From: Florian Albrechtskirchinger Date: Fri, 22 Apr 2022 14:21:16 +0200 Subject: [PATCH 01/30] Disable regression test for #3070 on GCC <8.4 (#3451) --- test/src/unit-regression2.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/src/unit-regression2.cpp b/test/src/unit-regression2.cpp index 3ab9a2a2c..e034593ed 100644 --- a/test/src/unit-regression2.cpp +++ b/test/src/unit-regression2.cpp @@ -48,6 +48,7 @@ using ordered_json = nlohmann::ordered_json; #endif #if JSON_HAS_EXPERIMENTAL_FILESYSTEM +// JSON_HAS_CPP_17 (magic keyword; do not remove) #include namespace nlohmann::detail { @@ -61,7 +62,6 @@ namespace std_fs = std::filesystem; } // namespace nlohmann::detail #endif - #ifdef JSON_HAS_CPP_20 #include #endif @@ -793,8 +793,8 @@ TEST_CASE("regression tests 2") const auto j_path = j.get(); CHECK(j_path == text_path); -#ifndef _MSC_VER - // works everywhere but on MSVC +#if !defined(_MSC_VER) && !(defined(__GNUC__) && __GNUC__ == 8 && __GNUC_MINOR__ < 4) + // works everywhere but on MSVC and GCC <8.4 CHECK_THROWS_WITH_AS(nlohmann::detail::std_fs::path(json(1)), "[json.exception.type_error.302] type must be string, but is number", json::type_error); #endif } From fcc36f99ba1afc7baebe24e0c7429d2039d32d99 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 24 Apr 2022 17:22:04 +0200 Subject: [PATCH 02/30] :arrow_up: cpplint 1.6.0 (#3454) --- third_party/cpplint/README.rst | 5 +---- third_party/cpplint/cpplint.py | 20 ++++++++++++++------ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/third_party/cpplint/README.rst b/third_party/cpplint/README.rst index 18af13c00..9ff67c0eb 100644 --- a/third_party/cpplint/README.rst +++ b/third_party/cpplint/README.rst @@ -1,9 +1,6 @@ cpplint - static code checker for C++ ===================================== -.. image:: https://travis-ci.org/cpplint/cpplint.svg?branch=master - :target: https://travis-ci.org/cpplint/cpplint - .. image:: https://img.shields.io/pypi/v/cpplint.svg :target: https://pypi.python.org/pypi/cpplint @@ -62,7 +59,7 @@ The modifications in this fork are minor fixes and cosmetic changes, such as: * python 3 compatibility * more default file extensions * customizable file extensions with the --extensions argument -* continuous integration on travis +* continuous integration on github * support for recursive file discovery via the --recursive argument * support for excluding files via --exclude * JUnit XML output format diff --git a/third_party/cpplint/cpplint.py b/third_party/cpplint/cpplint.py index 3bf3441a3..6b78b308c 100755 --- a/third_party/cpplint/cpplint.py +++ b/third_party/cpplint/cpplint.py @@ -41,6 +41,11 @@ We do a small hack, which is to ignore //'s with "'s after them on the same line, but it is far from perfect (in either direction). """ +# cpplint predates fstrings +# pylint: disable=consider-using-f-string + +# pylint: disable=invalid-name + import codecs import copy import getopt @@ -59,7 +64,7 @@ import xml.etree.ElementTree # if empty, use defaults _valid_extensions = set([]) -__VERSION__ = '1.5.5' +__VERSION__ = '1.6.0' try: xrange # Python 2 @@ -1915,6 +1920,7 @@ class CleansedLines(object): self.raw_lines = lines self.num_lines = len(lines) self.lines_without_raw_strings = CleanseRawStrings(lines) + # # pylint: disable=consider-using-enumerate for linenum in range(len(self.lines_without_raw_strings)): self.lines.append(CleanseComments( self.lines_without_raw_strings[linenum])) @@ -5068,10 +5074,12 @@ def CheckIncludeLine(filename, clean_lines, linenum, include_state, error): # # We also make an exception for Lua headers, which follow google # naming convention but not the include convention. - match = Match(r'#include\s*"([^/]+\.h)"', line) - if match and not _THIRD_PARTY_HEADERS_PATTERN.match(match.group(1)): - error(filename, linenum, 'build/include_subdir', 4, - 'Include the directory when naming .h files') + match = Match(r'#include\s*"([^/]+\.(.*))"', line) + if match: + if (IsHeaderExtension(match.group(2)) and + not _THIRD_PARTY_HEADERS_PATTERN.match(match.group(1))): + error(filename, linenum, 'build/include_subdir', 4, + 'Include the directory when naming header files') # we shouldn't include a file more than once. actually, there are a # handful of instances where doing so is okay, but in general it's @@ -6523,7 +6531,7 @@ def ProcessConfigOverrides(filename): continue try: - with open(cfg_file) as file_handle: + with open(cfg_file, encoding='utf-8') as file_handle: for line in file_handle: line, _, _ = line.partition('#') # Remove comments. if not line.strip(): From a6ee8bf9d94ef783f21f955d4125d5f3924d2c8e Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Mon, 25 Apr 2022 22:40:45 +0200 Subject: [PATCH 03/30] Overwork documentation (#3444) * :memo: overwork macro documentation * :memo: address review comments * :wrench: add style check to Makefile * :see_no_evil: overwork .gitignore * :pushpin: Pygments 2.12.0 is broken * :pencil2: fix links * :children_crossing: adjust output to cppcheck * :memo: add titles to more admonitions * :pencil2: fix typos * :memo: document future behavior change --- .gitignore | 25 +- README.md | 16 +- ...lohmann_define_type_intrusive_explicit.cpp | 59 +++++ ...mann_define_type_intrusive_explicit.output | 2 + .../nlohmann_define_type_intrusive_macro.cpp | 47 ++++ ...lohmann_define_type_intrusive_macro.output | 2 + ...e_type_intrusive_with_default_explicit.cpp | 54 +++++ ...ype_intrusive_with_default_explicit.output | 2 + ...fine_type_intrusive_with_default_macro.cpp | 41 ++++ ...e_type_intrusive_with_default_macro.output | 2 + ...ann_define_type_non_intrusive_explicit.cpp | 52 +++++ ..._define_type_non_intrusive_explicit.output | 2 + ...ohmann_define_type_non_intrusive_macro.cpp | 40 ++++ ...ann_define_type_non_intrusive_macro.output | 2 + ...pe_non_intrusive_with_default_explicit.cpp | 52 +++++ ...non_intrusive_with_default_explicit.output | 2 + ..._type_non_intrusive_with_default_macro.cpp | 39 ++++ ...pe_non_intrusive_with_default_macro.output | 2 + doc/examples/nlohmann_json_serialize_enum.cpp | 59 +++++ .../nlohmann_json_serialize_enum.output | 3 + .../nlohmann_json_serialize_enum_2.cpp | 33 +++ .../nlohmann_json_serialize_enum_2.output | 3 + doc/mkdocs/Makefile | 19 +- doc/mkdocs/docs/api/basic_json/back.md | 4 +- doc/mkdocs/docs/api/basic_json/basic_json.md | 10 +- doc/mkdocs/docs/api/basic_json/front.md | 4 +- doc/mkdocs/docs/api/basic_json/get.md | 2 +- doc/mkdocs/docs/api/basic_json/get_ptr.md | 2 +- doc/mkdocs/docs/api/basic_json/get_ref.md | 4 +- doc/mkdocs/docs/api/basic_json/index.md | 2 +- .../docs/api/basic_json/is_discarded.md | 4 +- doc/mkdocs/docs/api/basic_json/items.md | 2 +- doc/mkdocs/docs/api/basic_json/meta.md | 4 + .../api/basic_json/object_comparator_t.md | 2 +- doc/mkdocs/docs/api/basic_json/operator[].md | 5 +- .../docs/api/basic_json/operator_ValueType.md | 47 ++-- doc/mkdocs/docs/api/basic_json/value.md | 2 +- doc/mkdocs/docs/api/json_pointer/index.md | 12 +- doc/mkdocs/docs/api/macros/index.md | 49 ++-- doc/mkdocs/docs/api/macros/json_assert.md | 81 ++++++- .../docs/api/macros/json_diagnostics.md | 67 ++++++ doc/mkdocs/docs/api/macros/json_has_cpp_11.md | 28 +++ .../docs/api/macros/json_has_filesystem.md | 30 +++ doc/mkdocs/docs/api/macros/json_no_io.md | 21 ++ .../docs/api/macros/json_noexception.md | 32 +++ .../macros/json_skip_library_version_check.md | 37 +++ .../json_skip_unsupported_compiler_check.md | 20 ++ doc/mkdocs/docs/api/macros/json_throw_user.md | 75 ++++++ .../macros/json_use_implicit_conversions.md | 56 +++++ .../macros/nlohmann_define_type_intrusive.md | 124 ++++++++++ .../nlohmann_define_type_non_intrusive.md | 124 ++++++++++ .../macros/nlohmann_json_serialize_enum.md | 84 +++++++ .../api/macros/nlohmann_json_version_major.md | 23 ++ doc/mkdocs/docs/features/arbitrary_types.md | 17 +- doc/mkdocs/docs/features/assertions.md | 104 +++++++++ .../element_access/unchecked_access.md | 12 +- doc/mkdocs/docs/features/enum_conversion.md | 11 +- doc/mkdocs/docs/features/macros.md | 220 +++--------------- .../docs/features/types/number_handling.md | 2 +- doc/mkdocs/docs/home/exceptions.md | 7 +- doc/mkdocs/mkdocs.yml | 27 ++- doc/mkdocs/requirements.txt | 77 +++--- doc/mkdocs/scripts/check_structure.py | 63 +++-- 63 files changed, 1689 insertions(+), 366 deletions(-) create mode 100644 doc/examples/nlohmann_define_type_intrusive_explicit.cpp create mode 100644 doc/examples/nlohmann_define_type_intrusive_explicit.output create mode 100644 doc/examples/nlohmann_define_type_intrusive_macro.cpp create mode 100644 doc/examples/nlohmann_define_type_intrusive_macro.output create mode 100644 doc/examples/nlohmann_define_type_intrusive_with_default_explicit.cpp create mode 100644 doc/examples/nlohmann_define_type_intrusive_with_default_explicit.output create mode 100644 doc/examples/nlohmann_define_type_intrusive_with_default_macro.cpp create mode 100644 doc/examples/nlohmann_define_type_intrusive_with_default_macro.output create mode 100644 doc/examples/nlohmann_define_type_non_intrusive_explicit.cpp create mode 100644 doc/examples/nlohmann_define_type_non_intrusive_explicit.output create mode 100644 doc/examples/nlohmann_define_type_non_intrusive_macro.cpp create mode 100644 doc/examples/nlohmann_define_type_non_intrusive_macro.output create mode 100644 doc/examples/nlohmann_define_type_non_intrusive_with_default_explicit.cpp create mode 100644 doc/examples/nlohmann_define_type_non_intrusive_with_default_explicit.output create mode 100644 doc/examples/nlohmann_define_type_non_intrusive_with_default_macro.cpp create mode 100644 doc/examples/nlohmann_define_type_non_intrusive_with_default_macro.output create mode 100644 doc/examples/nlohmann_json_serialize_enum.cpp create mode 100644 doc/examples/nlohmann_json_serialize_enum.output create mode 100644 doc/examples/nlohmann_json_serialize_enum_2.cpp create mode 100644 doc/examples/nlohmann_json_serialize_enum_2.output create mode 100644 doc/mkdocs/docs/api/macros/json_diagnostics.md create mode 100644 doc/mkdocs/docs/api/macros/json_has_cpp_11.md create mode 100644 doc/mkdocs/docs/api/macros/json_has_filesystem.md create mode 100644 doc/mkdocs/docs/api/macros/json_no_io.md create mode 100644 doc/mkdocs/docs/api/macros/json_noexception.md create mode 100644 doc/mkdocs/docs/api/macros/json_skip_library_version_check.md create mode 100644 doc/mkdocs/docs/api/macros/json_skip_unsupported_compiler_check.md create mode 100644 doc/mkdocs/docs/api/macros/json_throw_user.md create mode 100644 doc/mkdocs/docs/api/macros/json_use_implicit_conversions.md create mode 100644 doc/mkdocs/docs/api/macros/nlohmann_define_type_intrusive.md create mode 100644 doc/mkdocs/docs/api/macros/nlohmann_define_type_non_intrusive.md create mode 100644 doc/mkdocs/docs/api/macros/nlohmann_json_serialize_enum.md create mode 100644 doc/mkdocs/docs/api/macros/nlohmann_json_version_major.md create mode 100644 doc/mkdocs/docs/features/assertions.md mode change 100644 => 100755 doc/mkdocs/scripts/check_structure.py diff --git a/.gitignore b/.gitignore index a13ad89a5..e4f92117b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,32 +1,21 @@ -json_unit -json_benchmarks -json_benchmarks_simple -fuzz-testing - *.dSYM *.o *.gcno *.gcda - -build -build_coverage -clang_analyze_build - -benchmarks/files/numbers/*.json +.DS_Store .wsjcpp-logs/* .wsjcpp/* -.idea +/.idea /cmake-build-* -test/test-* /.vs -doc/html -doc/mkdocs/venv/ -doc/mkdocs/docs/examples -doc/mkdocs/site -doc/mkdocs/docs/__pycache__/ +/doc/mkdocs/docs/examples/ +/doc/mkdocs/docs/__pycache__/ +/doc/mkdocs/site/ +/doc/mkdocs/venv/ /doc/docset/JSON_for_Modern_C++.docset/ /doc/docset/JSON_for_Modern_C++.tgz +/doc/mkdocs/docs/images/json.gif diff --git a/README.md b/README.md index 9d9bd2c91..20e7c48ea 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ There is also a [**docset**](https://github.com/Kapeli/Dash-User-Contributions/t ## Examples -Beside the examples below, you may want to check the [documentation](https://nlohmann.github.io/json/) where each function contains a separate code example (e.g., check out [`emplace()`](https://nlohmann.github.io/json/api/basic_json/emplace/)). All [example files](https://github.com/nlohmann/json/tree/develop/doc/examples) can be compiled and executed on their own (e.g., file [emplace.cpp](https://github.com/nlohmann/json/blob/develop/doc/examples/emplace.cpp)). +Beside the examples below, you may want to check the [documentation](https://json.nlohmann.me/) where each function contains a separate code example (e.g., check out [`emplace()`](https://json.nlohmann.me/api/basic_json/emplace/)). All [example files](https://github.com/nlohmann/json/tree/develop/doc/examples) can be compiled and executed on their own (e.g., file [emplace.cpp](https://github.com/nlohmann/json/blob/develop/doc/examples/emplace.cpp)). ### JSON as first-class data type @@ -162,7 +162,7 @@ json j2 = { }; ``` -Note that in all these cases, you never need to "tell" the compiler which JSON value type you want to use. If you want to be explicit or express some edge cases, the functions [`json::array()`](https://nlohmann.github.io/json/api/basic_json/array/) and [`json::object()`](https://nlohmann.github.io/json/api/basic_json/object/) will help: +Note that in all these cases, you never need to "tell" the compiler which JSON value type you want to use. If you want to be explicit or express some edge cases, the functions [`json::array()`](https://json.nlohmann.me/api/basic_json/array/) and [`json::object()`](https://json.nlohmann.me/api/basic_json/object/) will help: ```cpp // a way to express the empty array [] @@ -197,7 +197,7 @@ auto j2 = R"( Note that without appending the `_json` suffix, the passed string literal is not parsed, but just used as JSON string value. That is, `json j = "{ \"happy\": true, \"pi\": 3.141 }"` would just store the string `"{ "happy": true, "pi": 3.141 }"` rather than parsing the actual object. -The above example can also be expressed explicitly using [`json::parse()`](https://nlohmann.github.io/json/api/basic_json/parse/): +The above example can also be expressed explicitly using [`json::parse()`](https://json.nlohmann.me/api/basic_json/parse/): ```cpp // parse explicitly @@ -240,9 +240,9 @@ std::cout << cpp_string << " == " << cpp_string2 << " == " << j_string.get()`, `your_type` **MUST** be [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible). (There is a way to bypass this requirement described later.) -* In function `from_json`, use function [`at()`](https://nlohmann.github.io/json/api/basic_json/at/) to access the object values rather than `operator[]`. In case a key does not exist, `at` throws an exception that you can handle, whereas `operator[]` exhibits undefined behavior. +* In function `from_json`, use function [`at()`](https://json.nlohmann.me/api/basic_json/at/) to access the object values rather than `operator[]`. In case a key does not exist, `at` throws an exception that you can handle, whereas `operator[]` exhibits undefined behavior. * You do not need to add serializers or deserializers for STL types like `std::vector`: the library already implements these. #### Simplify your life with macros @@ -1647,7 +1647,7 @@ The library supports **Unicode input** as follows: - [Unicode noncharacters](https://www.unicode.org/faq/private_use.html#nonchar1) will not be replaced by the library. - Invalid surrogates (e.g., incomplete pairs such as `\uDEAD`) will yield parse errors. - The strings stored in the library are UTF-8 encoded. When using the default string type (`std::string`), note that its length/size functions return the number of stored bytes rather than the number of characters or glyphs. -- When you store strings with different encodings in the library, calling [`dump()`](https://nlohmann.github.io/json/api/basic_json/dump/) may throw an exception unless `json::error_handler_t::replace` or `json::error_handler_t::ignore` are used as error handlers. +- When you store strings with different encodings in the library, calling [`dump()`](https://json.nlohmann.me/api/basic_json/dump/) may throw an exception unless `json::error_handler_t::replace` or `json::error_handler_t::ignore` are used as error handlers. - To store wide strings (e.g., `std::wstring`), you need to convert them to a UTF-8 encoded `std::string` before, see [an example](https://json.nlohmann.me/home/faq/#wide-string-handling). ### Comments in JSON @@ -1682,7 +1682,7 @@ Here is a related issue [#1924](https://github.com/nlohmann/json/issues/1924). ### Further notes -- The code contains numerous debug **assertions** which can be switched off by defining the preprocessor macro `NDEBUG`, see the [documentation of `assert`](https://en.cppreference.com/w/cpp/error/assert). In particular, note [`operator[]`](https://nlohmann.github.io/json/api/basic_json/operator%5B%5D/) implements **unchecked access** for const objects: If the given key is not present, the behavior is undefined (think of a dereferenced null pointer) and yields an [assertion failure](https://github.com/nlohmann/json/issues/289) if assertions are switched on. If you are not sure whether an element in an object exists, use checked access with the [`at()` function](https://nlohmann.github.io/json/api/basic_json/at/). Furthermore, you can define `JSON_ASSERT(x)` to replace calls to `assert(x)`. +- The code contains numerous debug **assertions** which can be switched off by defining the preprocessor macro `NDEBUG`, see the [documentation of `assert`](https://en.cppreference.com/w/cpp/error/assert). In particular, note [`operator[]`](https://json.nlohmann.me/api/basic_json/operator%5B%5D/) implements **unchecked access** for const objects: If the given key is not present, the behavior is undefined (think of a dereferenced null pointer) and yields an [assertion failure](https://github.com/nlohmann/json/issues/289) if assertions are switched on. If you are not sure whether an element in an object exists, use checked access with the [`at()` function](https://json.nlohmann.me/api/basic_json/at/). Furthermore, you can define `JSON_ASSERT(x)` to replace calls to `assert(x)`. - As the exact number type is not defined in the [JSON specification](https://tools.ietf.org/html/rfc8259.html), this library tries to choose the best fitting C++ number type automatically. As a result, the type `double` may be used to store numbers which may yield [**floating-point exceptions**](https://github.com/nlohmann/json/issues/181) in certain rare situations if floating-point exceptions have been unmasked in the calling code. These exceptions are not caused by the library and need to be fixed in the calling code, such as by re-masking the exceptions prior to calling library functions. - The code can be compiled without C++ **runtime type identification** features; that is, you can use the `-fno-rtti` compiler flag. - **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`. In this case, exceptions are replaced by `abort()` calls. You can further control this behavior by defining `JSON_THROW_USER` (overriding `throw`), `JSON_TRY_USER` (overriding `try`), and `JSON_CATCH_USER` (overriding `catch`). Note that `JSON_THROW_USER` should leave the current scope (e.g., by throwing or aborting), as continuing after it may yield undefined behavior. Note the explanatory [`what()`](https://en.cppreference.com/w/cpp/error/exception/what) string of exceptions is not available for MSVC if exceptions are disabled, see [#2824](https://github.com/nlohmann/json/discussions/2824). diff --git a/doc/examples/nlohmann_define_type_intrusive_explicit.cpp b/doc/examples/nlohmann_define_type_intrusive_explicit.cpp new file mode 100644 index 000000000..fb7701dec --- /dev/null +++ b/doc/examples/nlohmann_define_type_intrusive_explicit.cpp @@ -0,0 +1,59 @@ +#include +#include + +using json = nlohmann::json; + +namespace ns +{ +class person +{ + private: + std::string name = "John Doe"; + std::string address = "123 Fake St"; + int age = -1; + + public: + person() = default; + person(std::string name_, std::string address_, int age_) + : name(std::move(name_)), address(std::move(address_)), age(age_) + {} + + friend void to_json(nlohmann::json& nlohmann_json_j, const person& nlohmann_json_t) + { + nlohmann_json_j["name"] = nlohmann_json_t.name; + nlohmann_json_j["address"] = nlohmann_json_t.address; + nlohmann_json_j["age"] = nlohmann_json_t.age; + } + + friend void from_json(const nlohmann::json& nlohmann_json_j, person& nlohmann_json_t) + { + nlohmann_json_t.name = nlohmann_json_j.at("name"); + nlohmann_json_t.address = nlohmann_json_j.at("address"); + nlohmann_json_t.age = nlohmann_json_j.at("age"); + } +}; +} // namespace ns + +int main() +{ + ns::person p = {"Ned Flanders", "744 Evergreen Terrace", 60}; + + // serialization: person -> json + json j = p; + std::cout << "serialization: " << j << std::endl; + + // deserialization: json -> person + json j2 = R"({"address": "742 Evergreen Terrace", "age": 40, "name": "Homer Simpson"})"_json; + auto p2 = j2.get(); + + // incomplete deserialization: + json j3 = R"({"address": "742 Evergreen Terrace", "name": "Maggie Simpson"})"_json; + try + { + auto p3 = j3.get(); + } + catch (json::exception& e) + { + std::cout << "deserialization failed: " << e.what() << std::endl; + } +} diff --git a/doc/examples/nlohmann_define_type_intrusive_explicit.output b/doc/examples/nlohmann_define_type_intrusive_explicit.output new file mode 100644 index 000000000..37f4eb414 --- /dev/null +++ b/doc/examples/nlohmann_define_type_intrusive_explicit.output @@ -0,0 +1,2 @@ +serialization: {"address":"744 Evergreen Terrace","age":60,"name":"Ned Flanders"} +deserialization failed: [json.exception.out_of_range.403] key 'age' not found diff --git a/doc/examples/nlohmann_define_type_intrusive_macro.cpp b/doc/examples/nlohmann_define_type_intrusive_macro.cpp new file mode 100644 index 000000000..ce292659a --- /dev/null +++ b/doc/examples/nlohmann_define_type_intrusive_macro.cpp @@ -0,0 +1,47 @@ +#include +#include + +using json = nlohmann::json; + +namespace ns +{ +class person +{ + private: + std::string name = "John Doe"; + std::string address = "123 Fake St"; + int age = -1; + + public: + person() = default; + person(std::string name_, std::string address_, int age_) + : name(std::move(name_)), address(std::move(address_)), age(age_) + {} + + NLOHMANN_DEFINE_TYPE_INTRUSIVE(person, name, address, age) +}; +} // namespace ns + +int main() +{ + ns::person p = {"Ned Flanders", "744 Evergreen Terrace", 60}; + + // serialization: person -> json + json j = p; + std::cout << "serialization: " << j << std::endl; + + // deserialization: json -> person + json j2 = R"({"address": "742 Evergreen Terrace", "age": 40, "name": "Homer Simpson"})"_json; + auto p2 = j2.get(); + + // incomplete deserialization: + json j3 = R"({"address": "742 Evergreen Terrace", "name": "Maggie Simpson"})"_json; + try + { + auto p3 = j3.get(); + } + catch (json::exception& e) + { + std::cout << "deserialization failed: " << e.what() << std::endl; + } +} diff --git a/doc/examples/nlohmann_define_type_intrusive_macro.output b/doc/examples/nlohmann_define_type_intrusive_macro.output new file mode 100644 index 000000000..37f4eb414 --- /dev/null +++ b/doc/examples/nlohmann_define_type_intrusive_macro.output @@ -0,0 +1,2 @@ +serialization: {"address":"744 Evergreen Terrace","age":60,"name":"Ned Flanders"} +deserialization failed: [json.exception.out_of_range.403] key 'age' not found diff --git a/doc/examples/nlohmann_define_type_intrusive_with_default_explicit.cpp b/doc/examples/nlohmann_define_type_intrusive_with_default_explicit.cpp new file mode 100644 index 000000000..621ed1452 --- /dev/null +++ b/doc/examples/nlohmann_define_type_intrusive_with_default_explicit.cpp @@ -0,0 +1,54 @@ +#include +#include + +using json = nlohmann::json; + +namespace ns +{ +class person +{ + private: + std::string name = "John Doe"; + std::string address = "123 Fake St"; + int age = -1; + + public: + person() = default; + person(std::string name_, std::string address_, int age_) + : name(std::move(name_)), address(std::move(address_)), age(age_) + {} + + friend void to_json(nlohmann::json& nlohmann_json_j, const person& nlohmann_json_t) + { + nlohmann_json_j["name"] = nlohmann_json_t.name; + nlohmann_json_j["address"] = nlohmann_json_t.address; + nlohmann_json_j["age"] = nlohmann_json_t.age; + } + + friend void from_json(const nlohmann::json& nlohmann_json_j, person& nlohmann_json_t) + { + person nlohmann_json_default_obj; + nlohmann_json_t.name = nlohmann_json_j.value("name", nlohmann_json_default_obj.name); + nlohmann_json_t.address = nlohmann_json_j.value("address", nlohmann_json_default_obj.address); + nlohmann_json_t.age = nlohmann_json_j.value("age", nlohmann_json_default_obj.age); + } +}; +} // namespace ns + +int main() +{ + ns::person p = {"Ned Flanders", "744 Evergreen Terrace", 60}; + + // serialization: person -> json + json j = p; + std::cout << "serialization: " << j << std::endl; + + // deserialization: json -> person + json j2 = R"({"address": "742 Evergreen Terrace", "age": 40, "name": "Homer Simpson"})"_json; + auto p2 = j2.get(); + + // incomplete deserialization: + json j3 = R"({"address": "742 Evergreen Terrace", "name": "Maggie Simpson"})"_json; + auto p3 = j3.get(); + std::cout << "roundtrip: " << json(p3) << std::endl; +} diff --git a/doc/examples/nlohmann_define_type_intrusive_with_default_explicit.output b/doc/examples/nlohmann_define_type_intrusive_with_default_explicit.output new file mode 100644 index 000000000..1a255f65c --- /dev/null +++ b/doc/examples/nlohmann_define_type_intrusive_with_default_explicit.output @@ -0,0 +1,2 @@ +serialization: {"address":"744 Evergreen Terrace","age":60,"name":"Ned Flanders"} +roundtrip: {"address":"742 Evergreen Terrace","age":-1,"name":"Maggie Simpson"} diff --git a/doc/examples/nlohmann_define_type_intrusive_with_default_macro.cpp b/doc/examples/nlohmann_define_type_intrusive_with_default_macro.cpp new file mode 100644 index 000000000..7851f526e --- /dev/null +++ b/doc/examples/nlohmann_define_type_intrusive_with_default_macro.cpp @@ -0,0 +1,41 @@ +#include +#include + +using json = nlohmann::json; + +namespace ns +{ +class person +{ + private: + std::string name = "John Doe"; + std::string address = "123 Fake St"; + int age = -1; + + public: + person() = default; + person(std::string name_, std::string address_, int age_) + : name(std::move(name_)), address(std::move(address_)), age(age_) + {} + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(person, name, address, age) +}; +} // namespace ns + +int main() +{ + ns::person p = {"Ned Flanders", "744 Evergreen Terrace", 60}; + + // serialization: person -> json + json j = p; + std::cout << "serialization: " << j << std::endl; + + // deserialization: json -> person + json j2 = R"({"address": "742 Evergreen Terrace", "age": 40, "name": "Homer Simpson"})"_json; + auto p2 = j2.get(); + + // incomplete deserialization: + json j3 = R"({"address": "742 Evergreen Terrace", "name": "Maggie Simpson"})"_json; + auto p3 = j3.get(); + std::cout << "roundtrip: " << json(p3) << std::endl; +} diff --git a/doc/examples/nlohmann_define_type_intrusive_with_default_macro.output b/doc/examples/nlohmann_define_type_intrusive_with_default_macro.output new file mode 100644 index 000000000..1a255f65c --- /dev/null +++ b/doc/examples/nlohmann_define_type_intrusive_with_default_macro.output @@ -0,0 +1,2 @@ +serialization: {"address":"744 Evergreen Terrace","age":60,"name":"Ned Flanders"} +roundtrip: {"address":"742 Evergreen Terrace","age":-1,"name":"Maggie Simpson"} diff --git a/doc/examples/nlohmann_define_type_non_intrusive_explicit.cpp b/doc/examples/nlohmann_define_type_non_intrusive_explicit.cpp new file mode 100644 index 000000000..b9d30dd8f --- /dev/null +++ b/doc/examples/nlohmann_define_type_non_intrusive_explicit.cpp @@ -0,0 +1,52 @@ +#include +#include + +using json = nlohmann::json; + +namespace ns +{ +struct person +{ + std::string name; + std::string address; + int age; +}; + +void to_json(nlohmann::json& nlohmann_json_j, const person& nlohmann_json_t) +{ + nlohmann_json_j["name"] = nlohmann_json_t.name; + nlohmann_json_j["address"] = nlohmann_json_t.address; + nlohmann_json_j["age"] = nlohmann_json_t.age; +} + +void from_json(const nlohmann::json& nlohmann_json_j, person& nlohmann_json_t) +{ + nlohmann_json_t.name = nlohmann_json_j.at("name"); + nlohmann_json_t.address = nlohmann_json_j.at("address"); + nlohmann_json_t.age = nlohmann_json_j.at("age"); +} +} // namespace ns + +int main() +{ + ns::person p = {"Ned Flanders", "744 Evergreen Terrace", 60}; + + // serialization: person -> json + json j = p; + std::cout << "serialization: " << j << std::endl; + + // deserialization: json -> person + json j2 = R"({"address": "742 Evergreen Terrace", "age": 40, "name": "Homer Simpson"})"_json; + auto p2 = j2.get(); + + // incomplete deserialization: + json j3 = R"({"address": "742 Evergreen Terrace", "name": "Maggie Simpson"})"_json; + try + { + auto p3 = j3.get(); + } + catch (json::exception& e) + { + std::cout << "deserialization failed: " << e.what() << std::endl; + } +} diff --git a/doc/examples/nlohmann_define_type_non_intrusive_explicit.output b/doc/examples/nlohmann_define_type_non_intrusive_explicit.output new file mode 100644 index 000000000..37f4eb414 --- /dev/null +++ b/doc/examples/nlohmann_define_type_non_intrusive_explicit.output @@ -0,0 +1,2 @@ +serialization: {"address":"744 Evergreen Terrace","age":60,"name":"Ned Flanders"} +deserialization failed: [json.exception.out_of_range.403] key 'age' not found diff --git a/doc/examples/nlohmann_define_type_non_intrusive_macro.cpp b/doc/examples/nlohmann_define_type_non_intrusive_macro.cpp new file mode 100644 index 000000000..b073ef615 --- /dev/null +++ b/doc/examples/nlohmann_define_type_non_intrusive_macro.cpp @@ -0,0 +1,40 @@ +#include +#include + +using json = nlohmann::json; + +namespace ns +{ +struct person +{ + std::string name; + std::string address; + int age; +}; + +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(person, name, address, age) +} // namespace ns + +int main() +{ + ns::person p = {"Ned Flanders", "744 Evergreen Terrace", 60}; + + // serialization: person -> json + json j = p; + std::cout << "serialization: " << j << std::endl; + + // deserialization: json -> person + json j2 = R"({"address": "742 Evergreen Terrace", "age": 40, "name": "Homer Simpson"})"_json; + auto p2 = j2.get(); + + // incomplete deserialization: + json j3 = R"({"address": "742 Evergreen Terrace", "name": "Maggie Simpson"})"_json; + try + { + auto p3 = j3.get(); + } + catch (json::exception& e) + { + std::cout << "deserialization failed: " << e.what() << std::endl; + } +} diff --git a/doc/examples/nlohmann_define_type_non_intrusive_macro.output b/doc/examples/nlohmann_define_type_non_intrusive_macro.output new file mode 100644 index 000000000..37f4eb414 --- /dev/null +++ b/doc/examples/nlohmann_define_type_non_intrusive_macro.output @@ -0,0 +1,2 @@ +serialization: {"address":"744 Evergreen Terrace","age":60,"name":"Ned Flanders"} +deserialization failed: [json.exception.out_of_range.403] key 'age' not found diff --git a/doc/examples/nlohmann_define_type_non_intrusive_with_default_explicit.cpp b/doc/examples/nlohmann_define_type_non_intrusive_with_default_explicit.cpp new file mode 100644 index 000000000..21967b638 --- /dev/null +++ b/doc/examples/nlohmann_define_type_non_intrusive_with_default_explicit.cpp @@ -0,0 +1,52 @@ +#include +#include + +using json = nlohmann::json; + +namespace ns +{ +struct person +{ + std::string name = "John Doe"; + std::string address = "123 Fake St"; + int age = -1; + + person() = default; + person(std::string name_, std::string address_, int age_) + : name(std::move(name_)), address(std::move(address_)), age(age_) + {} +}; + +void to_json(nlohmann::json& nlohmann_json_j, const person& nlohmann_json_t) +{ + nlohmann_json_j["name"] = nlohmann_json_t.name; + nlohmann_json_j["address"] = nlohmann_json_t.address; + nlohmann_json_j["age"] = nlohmann_json_t.age; +} + +void from_json(const nlohmann::json& nlohmann_json_j, person& nlohmann_json_t) +{ + person nlohmann_json_default_obj; + nlohmann_json_t.name = nlohmann_json_j.value("name", nlohmann_json_default_obj.name); + nlohmann_json_t.address = nlohmann_json_j.value("address", nlohmann_json_default_obj.address); + nlohmann_json_t.age = nlohmann_json_j.value("age", nlohmann_json_default_obj.age); +} +} // namespace ns + +int main() +{ + ns::person p = {"Ned Flanders", "744 Evergreen Terrace", 60}; + + // serialization: person -> json + json j = p; + std::cout << "serialization: " << j << std::endl; + + // deserialization: json -> person + json j2 = R"({"address": "742 Evergreen Terrace", "age": 40, "name": "Homer Simpson"})"_json; + auto p2 = j2.get(); + + // incomplete deserialization: + json j3 = R"({"address": "742 Evergreen Terrace", "name": "Maggie Simpson"})"_json; + auto p3 = j3.get(); + std::cout << "roundtrip: " << json(p3) << std::endl; +} diff --git a/doc/examples/nlohmann_define_type_non_intrusive_with_default_explicit.output b/doc/examples/nlohmann_define_type_non_intrusive_with_default_explicit.output new file mode 100644 index 000000000..1a255f65c --- /dev/null +++ b/doc/examples/nlohmann_define_type_non_intrusive_with_default_explicit.output @@ -0,0 +1,2 @@ +serialization: {"address":"744 Evergreen Terrace","age":60,"name":"Ned Flanders"} +roundtrip: {"address":"742 Evergreen Terrace","age":-1,"name":"Maggie Simpson"} diff --git a/doc/examples/nlohmann_define_type_non_intrusive_with_default_macro.cpp b/doc/examples/nlohmann_define_type_non_intrusive_with_default_macro.cpp new file mode 100644 index 000000000..470fed69c --- /dev/null +++ b/doc/examples/nlohmann_define_type_non_intrusive_with_default_macro.cpp @@ -0,0 +1,39 @@ +#include +#include + +using json = nlohmann::json; + +namespace ns +{ +struct person +{ + std::string name = "John Doe"; + std::string address = "123 Fake St"; + int age = -1; + + person() = default; + person(std::string name_, std::string address_, int age_) + : name(std::move(name_)), address(std::move(address_)), age(age_) + {} +}; + +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(person, name, address, age) +} // namespace ns + +int main() +{ + ns::person p = {"Ned Flanders", "744 Evergreen Terrace", 60}; + + // serialization: person -> json + json j = p; + std::cout << "serialization: " << j << std::endl; + + // deserialization: json -> person + json j2 = R"({"address": "742 Evergreen Terrace", "age": 40, "name": "Homer Simpson"})"_json; + auto p2 = j2.get(); + + // incomplete deserialization: + json j3 = R"({"address": "742 Evergreen Terrace", "name": "Maggie Simpson"})"_json; + auto p3 = j3.get(); + std::cout << "roundtrip: " << json(p3) << std::endl; +} diff --git a/doc/examples/nlohmann_define_type_non_intrusive_with_default_macro.output b/doc/examples/nlohmann_define_type_non_intrusive_with_default_macro.output new file mode 100644 index 000000000..1a255f65c --- /dev/null +++ b/doc/examples/nlohmann_define_type_non_intrusive_with_default_macro.output @@ -0,0 +1,2 @@ +serialization: {"address":"744 Evergreen Terrace","age":60,"name":"Ned Flanders"} +roundtrip: {"address":"742 Evergreen Terrace","age":-1,"name":"Maggie Simpson"} diff --git a/doc/examples/nlohmann_json_serialize_enum.cpp b/doc/examples/nlohmann_json_serialize_enum.cpp new file mode 100644 index 000000000..f9e472c64 --- /dev/null +++ b/doc/examples/nlohmann_json_serialize_enum.cpp @@ -0,0 +1,59 @@ +#include +#include + +using json = nlohmann::json; + +namespace ns +{ +enum TaskState +{ + TS_STOPPED, + TS_RUNNING, + TS_COMPLETED, + TS_INVALID = -1 +}; + +NLOHMANN_JSON_SERIALIZE_ENUM(TaskState, +{ + { TS_INVALID, nullptr }, + { TS_STOPPED, "stopped" }, + { TS_RUNNING, "running" }, + { TS_COMPLETED, "completed" } +}) + +enum class Color +{ + red, green, blue, unknown +}; + +NLOHMANN_JSON_SERIALIZE_ENUM(Color, +{ + { Color::unknown, "unknown" }, { Color::red, "red" }, + { Color::green, "green" }, { Color::blue, "blue" } +}) +} // namespace ns + +int main() +{ + // serialization + json j_stopped = ns::TS_STOPPED; + json j_red = ns::Color::red; + std::cout << "ns::TS_STOPPED -> " << j_stopped + << ", ns::Color::red -> " << j_red << std::endl; + + // deserialization + json j_running = "running"; + json j_blue = "blue"; + auto running = j_running.get(); + auto blue = j_blue.get(); + std::cout << j_running << " -> " << running + << ", " << j_blue << " -> " << static_cast(blue) << std::endl; + + // deserializing undefined JSON value to enum + // (where the first map entry above is the default) + json j_pi = 3.14; + auto invalid = j_pi.get(); + auto unknown = j_pi.get(); + std::cout << j_pi << " -> " << invalid << ", " + << j_pi << " -> " << static_cast(unknown) << std::endl; +} diff --git a/doc/examples/nlohmann_json_serialize_enum.output b/doc/examples/nlohmann_json_serialize_enum.output new file mode 100644 index 000000000..f512563dd --- /dev/null +++ b/doc/examples/nlohmann_json_serialize_enum.output @@ -0,0 +1,3 @@ +ns::TS_STOPPED -> "stopped", ns::Color::red -> "red" +"running" -> 1, "blue" -> 2 +3.14 -> -1, 3.14 -> 3 diff --git a/doc/examples/nlohmann_json_serialize_enum_2.cpp b/doc/examples/nlohmann_json_serialize_enum_2.cpp new file mode 100644 index 000000000..fd27226ca --- /dev/null +++ b/doc/examples/nlohmann_json_serialize_enum_2.cpp @@ -0,0 +1,33 @@ +#include +#include + +using json = nlohmann::json; + +namespace ns +{ +enum class Color +{ + red, green, blue, unknown +}; + +NLOHMANN_JSON_SERIALIZE_ENUM(Color, +{ + { Color::unknown, "unknown" }, { Color::red, "red" }, + { Color::green, "green" }, { Color::blue, "blue" }, + { Color::red, "rot" } // a second conversion for Color::red +}) +} + +int main() +{ + // serialization + json j_red = ns::Color::red; + std::cout << static_cast(ns::Color::red) << " -> " << j_red << std::endl; + + // deserialization + json j_rot = "rot"; + auto rot = j_rot.get(); + auto red = j_red.get(); + std::cout << j_rot << " -> " << static_cast(rot) << std::endl; + std::cout << j_red << " -> " << static_cast(red) << std::endl; +} diff --git a/doc/examples/nlohmann_json_serialize_enum_2.output b/doc/examples/nlohmann_json_serialize_enum_2.output new file mode 100644 index 000000000..5dec31b4a --- /dev/null +++ b/doc/examples/nlohmann_json_serialize_enum_2.output @@ -0,0 +1,3 @@ +0 -> "red" +"rot" -> 0 +"red" -> 0 diff --git a/doc/mkdocs/Makefile b/doc/mkdocs/Makefile index 3d3d5e7c6..3f894d098 100644 --- a/doc/mkdocs/Makefile +++ b/doc/mkdocs/Makefile @@ -1,18 +1,21 @@ # serve the site locally -serve: prepare_files +serve: prepare_files style_check venv/bin/mkdocs serve -build: prepare_files +serve_dirty: prepare_files style_check + venv/bin/mkdocs serve --dirtyreload + +build: prepare_files style_check venv/bin/mkdocs build -# create files that are not versioned inside the mkdocs folder +# create files that are not versioned inside the mkdocs folder (images, examples) prepare_files: clean - # create subfolders mkdir docs/examples - # copy images - cp -vr ../json.gif docs/images - # copy examples - cp -vr ../examples/*.cpp ../examples/*.output docs/examples + cp -r ../json.gif docs/images + cp -r ../examples/*.cpp ../examples/*.output docs/examples + +style_check: + @cd docs ; python3 ../scripts/check_structure.py # clean subfolders clean: diff --git a/doc/mkdocs/docs/api/basic_json/back.md b/doc/mkdocs/docs/api/basic_json/back.md index 96e1dec75..1a715284d 100644 --- a/doc/mkdocs/docs/api/basic_json/back.md +++ b/doc/mkdocs/docs/api/basic_json/back.md @@ -35,9 +35,9 @@ Constant. ## Notes -!!! danger +!!! info "Precondition" - Calling `back` on an empty array or object is undefined behavior and is **guarded by an assertion**! + The array or object must not be empty. Calling `back` on an empty array or object yields undefined behavior. ## Examples diff --git a/doc/mkdocs/docs/api/basic_json/basic_json.md b/doc/mkdocs/docs/api/basic_json/basic_json.md index ab3fea470..afa3901d1 100644 --- a/doc/mkdocs/docs/api/basic_json/basic_json.md +++ b/doc/mkdocs/docs/api/basic_json/basic_json.md @@ -250,17 +250,15 @@ basic_json(basic_json&& other) noexcept; !!! info "Preconditions" - - Iterators `first` and `last` must be initialized. **This precondition is enforced with an assertion (see - warning).** If assertions are switched off, a violation of this precondition yields undefined behavior. + - Iterators `first` and `last` must be initialized. **This precondition is enforced with a + [runtime assertion](../../features/assertions.md). - Range `[first, last)` is valid. Usually, this precondition cannot be checked efficiently. Only certain edge cases are detected; see the description of the exceptions above. A violation of this precondition yields undefined behavior. - !!! warning + !!! danger "Runtime assertion" - A precondition is enforced with a runtime assertion that will result in calling `std::abort` if this - precondition is not met. Assertions can be disabled by defining `NDEBUG` at compile time. See - for more information. + A precondition is enforced with a [runtime assertion](../../features/assertions.md). - Overload 8: diff --git a/doc/mkdocs/docs/api/basic_json/front.md b/doc/mkdocs/docs/api/basic_json/front.md index 909f0b59b..e258c36a0 100644 --- a/doc/mkdocs/docs/api/basic_json/front.md +++ b/doc/mkdocs/docs/api/basic_json/front.md @@ -28,9 +28,9 @@ Constant. ## Notes -!!! danger +!!! info "Precondition" - Calling `front` on an empty array or object is undefined behavior and is **guarded by an assertion**! + The array or object must not be empty. Calling `front` on an empty array or object yields undefined behavior. ## Examples diff --git a/doc/mkdocs/docs/api/basic_json/get.md b/doc/mkdocs/docs/api/basic_json/get.md index 0a0bc3bab..96fc221da 100644 --- a/doc/mkdocs/docs/api/basic_json/get.md +++ b/doc/mkdocs/docs/api/basic_json/get.md @@ -90,7 +90,7 @@ Depends on what `json_serializer` `from_json()` method throws ## Notes -!!! warning +!!! danger "Undefined behavior" Writing data to the pointee (overload 3) of the result yields an undefined state. diff --git a/doc/mkdocs/docs/api/basic_json/get_ptr.md b/doc/mkdocs/docs/api/basic_json/get_ptr.md index 72517cd7e..2441e1156 100644 --- a/doc/mkdocs/docs/api/basic_json/get_ptr.md +++ b/doc/mkdocs/docs/api/basic_json/get_ptr.md @@ -33,7 +33,7 @@ Constant. ## Notes -!!! warning +!!! danger "Undefined behavior" Writing data to the pointee of the result yields an undefined state. diff --git a/doc/mkdocs/docs/api/basic_json/get_ref.md b/doc/mkdocs/docs/api/basic_json/get_ref.md index 1140836e4..b1219742c 100644 --- a/doc/mkdocs/docs/api/basic_json/get_ref.md +++ b/doc/mkdocs/docs/api/basic_json/get_ref.md @@ -16,7 +16,7 @@ Implicit reference access to the internally stored JSON value. No copies are mad : reference type; must be a reference to [`array_t`](array_t.md), [`object_t`](object_t.md), [`string_t`](string_t.md), [`boolean_t`](boolean_t.md), [`number_integer_t`](number_integer_t.md), or [`number_unsigned_t`](number_unsigned_t.md), [`number_float_t`](number_float_t.md), or [`binary_t`](binary_t.md). - Enforced by static assertion. + Enforced by a static assertion. ## Return value @@ -38,7 +38,7 @@ Constant. ## Notes -!!! warning +!!! danger "Undefined behavior" Writing data to the referee of the result yields an undefined state. diff --git a/doc/mkdocs/docs/api/basic_json/index.md b/doc/mkdocs/docs/api/basic_json/index.md index f8828dbd8..286fea2b4 100644 --- a/doc/mkdocs/docs/api/basic_json/index.md +++ b/doc/mkdocs/docs/api/basic_json/index.md @@ -292,7 +292,7 @@ Access to the JSON value - [**std::hash<basic_json>**](std_hash.md) - return a hash value for a JSON object - [**std::swap<basic_json>**](std_swap.md) - exchanges the values of two JSON objects -## Example +## Examples ??? example diff --git a/doc/mkdocs/docs/api/basic_json/is_discarded.md b/doc/mkdocs/docs/api/basic_json/is_discarded.md index 6de31c937..663cbf889 100644 --- a/doc/mkdocs/docs/api/basic_json/is_discarded.md +++ b/doc/mkdocs/docs/api/basic_json/is_discarded.md @@ -24,7 +24,7 @@ Constant. ## Notes -!!! note +!!! note "Comparisons" Discarded values are never compared equal with [`operator==`](operator_eq.md). That is, checking whether a JSON value `j` is discarded will only work via: @@ -41,7 +41,7 @@ Constant. will always be `#!cpp false`. -!!! note +!!! note "Removal during parsing with callback functions" When a value is discarded by a callback function (see [`parser_callback_t`](parser_callback_t.md)) during parsing, then it is removed when it is part of a structured value. For instance, if the second value of an array is discarded, diff --git a/doc/mkdocs/docs/api/basic_json/items.md b/doc/mkdocs/docs/api/basic_json/items.md index b388824f9..0b34ddcba 100644 --- a/doc/mkdocs/docs/api/basic_json/items.md +++ b/doc/mkdocs/docs/api/basic_json/items.md @@ -63,7 +63,7 @@ Constant. When iterating over an array, `key()` will return the index of the element as string (see example). For primitive types (e.g., numbers), `key()` returns an empty string. -!!! warning +!!! danger "Lifetime issues" Using `items()` on temporary objects is dangerous. Make sure the object's lifetime exceeds the iteration. See for more information. diff --git a/doc/mkdocs/docs/api/basic_json/meta.md b/doc/mkdocs/docs/api/basic_json/meta.md index e2b312e0c..87767e4d5 100644 --- a/doc/mkdocs/docs/api/basic_json/meta.md +++ b/doc/mkdocs/docs/api/basic_json/meta.md @@ -45,6 +45,10 @@ Constant. --8<-- "examples/meta.output" ``` +## See also + +- [**NLOHMANN_JSON_VERSION_MAJOR**/**NLOHMANN_JSON_VERSION_MINOR**/**NLOHMANN_JSON_VERSION_PATCH**](../macros/nlohmann_json_version_major.md) - library version information + ## Version history - Added in version 2.1.0. diff --git a/doc/mkdocs/docs/api/basic_json/object_comparator_t.md b/doc/mkdocs/docs/api/basic_json/object_comparator_t.md index 44509a94b..e2bc79d05 100644 --- a/doc/mkdocs/docs/api/basic_json/object_comparator_t.md +++ b/doc/mkdocs/docs/api/basic_json/object_comparator_t.md @@ -8,7 +8,7 @@ using object_comparator_t = std::less<>; // since C++14 The comparator used in [`object_t`](object_t.md). -When C++14 is detected, a transparent com parator is used which, when combined with perfect forwarding on find() and +When C++14 is detected, a transparent comparator is used which, when combined with perfect forwarding on find() and count() calls, prevents unnecessary string construction. ## Version history diff --git a/doc/mkdocs/docs/api/basic_json/operator[].md b/doc/mkdocs/docs/api/basic_json/operator[].md index 5b6512a21..cc9eae7f3 100644 --- a/doc/mkdocs/docs/api/basic_json/operator[].md +++ b/doc/mkdocs/docs/api/basic_json/operator[].md @@ -74,10 +74,11 @@ Strong exception safety: if an exception occurs, the original value stays intact ## Notes -!!! danger +!!! danger "Undefined behavior and runtime assertions" 1. If the element with key `idx` does not exist, the behavior is undefined. - 2. If the element with key `key` does not exist, the behavior is undefined and is **guarded by an assertion**! + 2. If the element with key `key` does not exist, the behavior is undefined and is **guarded by a + [runtime assertion](../../features/assertions.md)**! 1. The non-const version may add values: If `idx` is beyond the range of the array (i.e., `idx >= size()`), then the array is silently filled up with `#!json null` values to make `idx` a valid reference to the last stored element. In diff --git a/doc/mkdocs/docs/api/basic_json/operator_ValueType.md b/doc/mkdocs/docs/api/basic_json/operator_ValueType.md index 1eec13553..787588781 100644 --- a/doc/mkdocs/docs/api/basic_json/operator_ValueType.md +++ b/doc/mkdocs/docs/api/basic_json/operator_ValueType.md @@ -27,33 +27,44 @@ Linear in the size of the JSON value. ## Notes -By default `JSON_EXPLICIT` defined to the empty string, so the signature is: +!!! note "Definition of `JSON_EXPLICIT`" -```cpp -template -operator ValueType() const; -``` + By default `JSON_EXPLICIT` is defined to the empty string, so the signature is: + + ```cpp + template + operator ValueType() const; + ``` + + If [`JSON_USE_IMPLICIT_CONVERSIONS`](../macros/json_use_implicit_conversions.md) is set to `0`, + `JSON_EXPLICIT` is defined to `#!cpp explicit`: -If [`JSON_USE_IMPLICIT_CONVERSIONS`](../../features/macros.md#json_use_implicit_conversions) is set to `0`, -`JSON_EXPLICIT` is defined to `#!cpp explicit`: + ```cpp + template + explicit operator ValueType() const; + ``` + + That is, implicit conversions can be switched off by defining + [`JSON_USE_IMPLICIT_CONVERSIONS`](../macros/json_use_implicit_conversions.md) to `0`. -```cpp -template -explicit operator ValueType() const; -``` +!!! info "Future behavior change" + + Implicit conversions will be switched off by default in the next major release of the library. That is, + `JSON_EXPLICIT` will be set to `#!cpp explicit` by default. + + You can prepare existing code by already defining + [`JSON_USE_IMPLICIT_CONVERSIONS`](../macros/json_use_implicit_conversions.md) to `0` and replace any implicit + conversions with calls to [`get`](../basic_json/get.md). -That is, implicit conversions can be switched off by defining -[`JSON_USE_IMPLICIT_CONVERSIONS`](../../features/macros.md#json_use_implicit_conversions) to `0`. ## Examples ??? example - The example below shows several conversions from JSON values - to other types. There a few things to note: (1) Floating-point numbers can - be converted to integers, (2) A JSON array can be converted to a standard - `std::vector`, (3) A JSON object can be converted to C++ - associative containers such as `std::unordered_map`. + The example below shows several conversions from JSON values to other types. There are a few things to note: (1) + Floating-point numbers can be converted to integers, (2) A JSON array can be converted to a standard + `std::vector`, (3) A JSON object can be converted to C++ associative containers such as + `std::unordered_map`. ```cpp --8<-- "examples/operator__ValueType.cpp" diff --git a/doc/mkdocs/docs/api/basic_json/value.md b/doc/mkdocs/docs/api/basic_json/value.md index 0b4f1cc19..1844c41fb 100644 --- a/doc/mkdocs/docs/api/basic_json/value.md +++ b/doc/mkdocs/docs/api/basic_json/value.md @@ -36,7 +36,7 @@ ValueType value(const json_pointer& ptr, } ``` -!!! note +!!! note "Differences to `at` and `operator[]`" - Unlike [`at`](at.md), this function does not throw if the given `key`/`ptr` was not found. - Unlike [`operator[]`](operator[].md), this function does not implicitly add an element to the position defined by diff --git a/doc/mkdocs/docs/api/json_pointer/index.md b/doc/mkdocs/docs/api/json_pointer/index.md index 3504c9ff7..dca9c382c 100644 --- a/doc/mkdocs/docs/api/json_pointer/index.md +++ b/doc/mkdocs/docs/api/json_pointer/index.md @@ -16,7 +16,13 @@ are the base for JSON patches. ## Notes -For backwards compatibility `RefStringType` may also be a specialization of [`basic_json`](../basic_json/index.md) in which case `string_t` will be deduced as [`basic_json::string_t`](../basic_json/string_t.md). This feature is deprecated and may be removed in a future major version. +For backwards compatibility `RefStringType` may also be a specialization of [`basic_json`](../basic_json/index.md) in +which case `string_t` will be deduced as [`basic_json::string_t`](../basic_json/string_t.md). This feature is deprecated +and may be removed in a future major version. + +## Member types + +- [**string_t**](string_t.md) - the string type used for the reference tokens ## Member functions @@ -31,10 +37,6 @@ For backwards compatibility `RefStringType` may also be a specialization of [`ba - [**push_back**](push_back.md) - append an unescaped token at the end of the pointer - [**empty**](empty.md) - return whether pointer points to the root document -## Member types - -- [**string_t**](string_t.md) - the string type used for the reference tokens - ## See also - [operator""_json_pointer](../basic_json/operator_literal_json_pointer.md) - user-defined string literal for JSON pointers diff --git a/doc/mkdocs/docs/api/macros/index.md b/doc/mkdocs/docs/api/macros/index.md index 4c723c6a6..56924da44 100644 --- a/doc/mkdocs/docs/api/macros/index.md +++ b/doc/mkdocs/docs/api/macros/index.md @@ -1,22 +1,37 @@ # Macros -!!! note +Some aspects of the library can be configured by defining preprocessor macros **before** including the `json.hpp` +header. See also the [macro overview page](../../features/macros.md). - This page is under construction. See the [macro overview page](../../features/macros.md) until then. +## Runtime assertions -Some aspects of the library can be configured by defining preprocessor macros before including the `json.hpp` header. +- [**JSON_ASSERT(x)**](json_assert.md) - control behavior of runtime assertions -- [`JSON_ASSERT(x)`](json_assert.md) -- `JSON_CATCH_USER(exception)` -- `JSON_DIAGNOSTICS` -- `JSON_HAS_CPP_11`, `JSON_HAS_CPP_14`, `JSON_HAS_CPP_17`, `JSON_HAS_CPP_20` -- `JSON_NOEXCEPTION` -- `JSON_NO_IO` -- `JSON_SKIP_UNSUPPORTED_COMPILER_CHECK` -- `JSON_THROW_USER(exception)` -- `JSON_TRY_USER` -- `JSON_USE_IMPLICIT_CONVERSIONS` -- `NLOHMANN_DEFINE_TYPE_INTRUSIVE(type, member...)` -- `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(type, member...)` -- `NLOHMANN_JSON_SERIALIZE_ENUM(type, ...)` -- `NLOHMANN_JSON_VERSION_MAJOR`, `NLOHMANN_JSON_VERSION_MINOR`, `NLOHMANN_JSON_VERSION_PATCH` +## Exceptions + +- [**JSON_CATCH_USER(exception)**
**JSON_THROW_USER(exception)**
**JSON_TRY_USER**](json_throw_user.md) - control exceptions +- [**JSON_DIAGNOSTICS**](json_diagnostics.md) - control extended diagnostics +- [**JSON_NOEXCEPTION**](json_noexception.md) - switch off exceptions + +## Language support + +- [**JSON_HAS_CPP_11**
**JSON_HAS_CPP_14**
**JSON_HAS_CPP_17**
**JSON_HAS_CPP_20**](json_has_cpp_11.md) - set supported C++ standard +- [**JSON_HAS_FILESYSTEM**
**JSON_HAS_EXPERIMENTAL_FILESYSTEM**](json_has_filesystem.md) - control `std::filesystem` support +- [**JSON_NO_IO**](json_no_io.md) - switch off functions relying on certain C++ I/O headers +- [**JSON_SKIP_UNSUPPORTED_COMPILER_CHECK**](json_skip_unsupported_compiler_check.md) - do not warn about unsupported compilers + +## Library version + +- [**JSON_SKIP_LIBRARY_VERSION_CHECK**](json_skip_library_version_check.md) - skip library version check +- [**NLOHMANN_JSON_VERSION_MAJOR**
**NLOHMANN_JSON_VERSION_MINOR**
**NLOHMANN_JSON_VERSION_PATCH**](nlohmann_json_version_major.md) - library version information + +## Type conversions + +- [**JSON_USE_IMPLICIT_CONVERSIONS**](json_use_implicit_conversions.md) - control implicit conversions + +## Serialization/deserialization macros + +- [**NLOHMANN_DEFINE_TYPE_INTRUSIVE(type, member...)**
**NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(type, member...)**](nlohmann_define_type_intrusive.md) - serialization/deserialization of types _with_ access to private variables +- [**NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(type, member...)**
**NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(type, member...)**](nlohmann_define_type_non_intrusive.md) - serialization/deserialization of types _without_ access to private variables +- [**NLOHMANN_JSON_SERIALIZE_ENUM(type, ...)**](nlohmann_json_serialize_enum.md) - serialization/deserialization of enum + types diff --git a/doc/mkdocs/docs/api/macros/json_assert.md b/doc/mkdocs/docs/api/macros/json_assert.md index 63d4ae076..a093341a1 100644 --- a/doc/mkdocs/docs/api/macros/json_assert.md +++ b/doc/mkdocs/docs/api/macros/json_assert.md @@ -1,11 +1,84 @@ -# JSON_ASSERT(x) +# JSON_ASSERT ```cpp -JSON_ASSERT(x) +#define JSON_ASSERT(x) /* value */ ``` -## Default implementation +This macro controls which code is executed for [runtime assertions](../../features/assertions.md) of the library. + +## Parameters + +`x` (in) +: expression of scalar type + +## Default definition + +The default value is [`#!cpp assert(x)`](https://en.cppreference.com/w/cpp/error/assert). ```cpp -assert(x); +#define JSON_ASSERT(x) assert(x) ``` + +Therefore, assertions can be switched off by defining `NDEBUG`. + +## Notes + +- The library uses numerous assertions to guarantee invariants and to abort in case of otherwise undefined behavior + (e.g., when calling [operator[]](../basic_json/operator%5B%5D.md) with a missing object key on a `const` object). See + page [runtime assertions](../../features/assertions.md) for more information. +- Defining the macro to code that does not call `std::abort` may leave the library in an undefined state. +- The macro is undefined outside the library. + +## Examples + +??? example "Example 1: default behavior" + + The following code will trigger an assertion at runtime: + + ```cpp + #include + + using json = nlohmann::json; + + int main() + { + const json j = {{"key", "value"}}; + auto v = j["missing"]; + } + ``` + + Output: + + ``` + Assertion failed: (m_value.object->find(key) != m_value.object->end()), function operator[], file json.hpp, line 2144. + ``` + +??? example "Example 2: user-defined behavior" + + The assertion reporting can be changed by defining `JSON_ASSERT(x)` differently. + + ```cpp + #include + #include + #define JSON_ASSERT(x) if(!(x)){fprintf(stderr, "assertion error in %s\n", __FUNCTION__); std::abort();} + + #include + + using json = nlohmann::json; + + int main() + { + const json j = {{"key", "value"}}; + auto v = j["missing"]; + } + ``` + + Output: + + ``` + assertion error in operator[] + ``` + +## Version history + +- Added in version 3.9.0. diff --git a/doc/mkdocs/docs/api/macros/json_diagnostics.md b/doc/mkdocs/docs/api/macros/json_diagnostics.md new file mode 100644 index 000000000..d42025092 --- /dev/null +++ b/doc/mkdocs/docs/api/macros/json_diagnostics.md @@ -0,0 +1,67 @@ +# JSON_DIAGNOSTICS + +```cpp +#define JSON_DIAGNOSTICS /* value */ +``` + +This macro enables [extended diagnostics for exception messages](../../home/exceptions.md#extended-diagnostic-messages). +Possible values are `1` to enable or `0` to disable (default). + +When enabled, exception messages contain a [JSON Pointer](../json_pointer/json_pointer.md) to the JSON value that +triggered the exception. Note that enabling this macro increases the size of every JSON value by one pointer and adds +some runtime overhead. + +The diagnostics messages can also be controlled with the CMake option `JSON_Diagnostics` (`OFF` by default) which sets +`JSON_DIAGNOSTICS` accordingly. + +## Default definition + +The default value is `0` (extended diagnostics are switched off). + +```cpp +#define JSON_DIAGNOSTICS 0 +``` + +When the macro is not defined, the library will define it to its default value. + +## Notes + +!!! danger "ABI incompatibility" + + As this macro changes the definition of the `basic_json` object, it MUST be defined in the same way globally, even + across different compilation units: `basic_json` objects with differently defined `JSON_DIAGNOSTICS` macros are + not compatible! + +## Examples + +??? example "Example 1: default behavior" + + ```cpp + --8<-- "examples/diagnostics_standard.cpp" + ``` + + Output: + + ``` + --8<-- "examples/diagnostics_standard.output" + ``` + + This exception can be hard to debug if storing the value `#!c "12"` and accessing it is further apart. + +??? example "Example 2: extended diagnostic messages" + + ```cpp + --8<-- "examples/diagnostics_extended.cpp" + ``` + + Output: + + ``` + --8<-- "examples/diagnostics_extended.output" + ``` + + Now the exception message contains a JSON Pointer `/address/housenumber` that indicates which value has the wrong type. + +## Version history + +- Added in version 3.10.0. diff --git a/doc/mkdocs/docs/api/macros/json_has_cpp_11.md b/doc/mkdocs/docs/api/macros/json_has_cpp_11.md new file mode 100644 index 000000000..3bee84324 --- /dev/null +++ b/doc/mkdocs/docs/api/macros/json_has_cpp_11.md @@ -0,0 +1,28 @@ +# JSON_HAS_CPP_11, JSON_HAS_CPP_14, JSON_HAS_CPP_17, JSON_HAS_CPP_20 + +```cpp +#define JSON_HAS_CPP_11 +#define JSON_HAS_CPP_14 +#define JSON_HAS_CPP_17 +#define JSON_HAS_CPP_20 +``` + +The library targets C++11, but also supports some features introduced in later C++ versions (e.g., `std::string_view` +support for C++17). For these new features, the library implements some preprocessor checks to determine the C++ +standard. By defining any of these symbols, the internal check is overridden and the provided C++ version is +unconditionally assumed. This can be helpful for compilers that only implement parts of the standard and would be +detected incorrectly. + +## Default definition + +The default value is detected based on preprocessor macros such as `#!cpp __cplusplus`, `#!cpp _HAS_CXX17`, or +`#!cpp _MSVC_LANG`. + +## Notes + +- `#!cpp JSON_HAS_CPP_11` is always defined. +- All macros are undefined outside the library. + +## Version history + +- Added in version 3.10.5. diff --git a/doc/mkdocs/docs/api/macros/json_has_filesystem.md b/doc/mkdocs/docs/api/macros/json_has_filesystem.md new file mode 100644 index 000000000..160ad8c24 --- /dev/null +++ b/doc/mkdocs/docs/api/macros/json_has_filesystem.md @@ -0,0 +1,30 @@ +# JSON_HAS_FILESYSTEM / JSON_HAS_EXPERIMENTAL_FILESYSTEM + +```cpp +#define JSON_HAS_FILESYSTEM /* value */ +#define JSON_HAS_EXPERIMENTAL_FILESYSTEM /* value */ +``` + +When compiling with C++17, the library provides conversions from and to +[`std::filesystem::path`](https://en.cppreference.com/w/cpp/filesystem/path). As compiler support for filesystem is +limited, the library tries to detect whether +[``/`std::filesystem`](https://en.cppreference.com/w/cpp/header/filesystem) (`JSON_HAS_FILESYSTEM`) or +[``/`std::experimental::filesystem`](https://en.cppreference.com/w/cpp/header/experimental/filesystem) +(`JSON_HAS_EXPERIMENTAL_FILESYSTEM`) should be used. To override the built-in check, define `JSON_HAS_FILESYSTEM` or +`JSON_HAS_EXPERIMENTAL_FILESYSTEM` to `1`. + +## Default definition + +The default value is detected based on the preprocessor macros `#!cpp __cpp_lib_filesystem`, +`#!cpp __cpp_lib_experimental_filesystem`, `#!cpp __has_include()`, or +`#!cpp __has_include()`. + +## Notes + +- Note that older compilers or older versions of libstd++ also require the library `stdc++fs` to be linked to for + filesystem support. +- Both macros are undefined outside the library. + +## Version history + +- Added in version 3.10.5. diff --git a/doc/mkdocs/docs/api/macros/json_no_io.md b/doc/mkdocs/docs/api/macros/json_no_io.md new file mode 100644 index 000000000..10ae24c8a --- /dev/null +++ b/doc/mkdocs/docs/api/macros/json_no_io.md @@ -0,0 +1,21 @@ +# JSON_NO_IO + +```cpp +#define JSON_NO_IO +``` + +When defined, headers ``, ``, ``, ``, and `` are not included and parse functions +relying on these headers are excluded. This is relevant for environments where these I/O functions are disallowed for +security reasons (e.g., Intel Software Guard Extensions (SGX)). + +## Default definition + +By default, `#!cpp JSON_NO_IO` is not defined. + +```cpp +#undef JSON_NO_IO +``` + +## Version history + +- Added in version 3.10.0. diff --git a/doc/mkdocs/docs/api/macros/json_noexception.md b/doc/mkdocs/docs/api/macros/json_noexception.md new file mode 100644 index 000000000..0f32b63e9 --- /dev/null +++ b/doc/mkdocs/docs/api/macros/json_noexception.md @@ -0,0 +1,32 @@ +# JSON_NOEXCEPTION + +```cpp +#define JSON_NOEXCEPTION +``` + +Exceptions can be switched off by defining the symbol `JSON_NOEXCEPTION`. When defining `JSON_NOEXCEPTION`, `#!cpp try` +is replaced by `#!cpp if (true)`, `#!cpp catch` is replaced by `#!cpp if (false)`, and `#!cpp throw` is replaced by +`#!cpp std::abort()`. + +The same effect is achieved by setting the compiler flag `-fno-exceptions`. + +## Default definition + +By default, the macro is not defined. + +```cpp +#undef JSON_NOEXCEPTION +``` + +## Notes + +The explanatory [`what()`](https://en.cppreference.com/w/cpp/error/exception/what) string of exceptions is not +available for MSVC if exceptions are disabled, see [#2824](https://github.com/nlohmann/json/discussions/2824). + +## See also + +- [Switch off exceptions](../../home/exceptions.md#switch-off-exceptions) for more information how to switch off exceptions + +## Version history + +Added in version 2.1.0. diff --git a/doc/mkdocs/docs/api/macros/json_skip_library_version_check.md b/doc/mkdocs/docs/api/macros/json_skip_library_version_check.md new file mode 100644 index 000000000..c9a743c18 --- /dev/null +++ b/doc/mkdocs/docs/api/macros/json_skip_library_version_check.md @@ -0,0 +1,37 @@ +# JSON_SKIP_LIBRARY_VERSION_CHECK + +```cpp +#define JSON_SKIP_LIBRARY_VERSION_CHECK +``` + +When defined, the library will not create a compiler warning when a different version of the library was already +included. + +## Default definition + +By default, the macro is not defined. + +```cpp +#undef JSON_SKIP_LIBRARY_VERSION_CHECK +``` + +## Notes + +!!! danger "ABI compatibility" + + Mixing different library versions in the same code can be a problem as the different versions may not be ABI + compatible. + +## Examples + +!!! example + + The following warning will be shown in case a different version of the library was already included: + + ``` + Already included a different version of the library! + ``` + +## Version history + +Added in version 3.11.0. diff --git a/doc/mkdocs/docs/api/macros/json_skip_unsupported_compiler_check.md b/doc/mkdocs/docs/api/macros/json_skip_unsupported_compiler_check.md new file mode 100644 index 000000000..c58d0ea85 --- /dev/null +++ b/doc/mkdocs/docs/api/macros/json_skip_unsupported_compiler_check.md @@ -0,0 +1,20 @@ +# JSON_SKIP_UNSUPPORTED_COMPILER_CHECK + +```cpp +#define JSON_SKIP_UNSUPPORTED_COMPILER_CHECK +``` + +When defined, the library will not create a compile error when a known unsupported compiler is detected. This allows to +use the library with compilers that do not fully support C++11 and may only work if unsupported features are not used. + +## Default definition + +By default, the macro is not defined. + +```cpp +#undef JSON_SKIP_UNSUPPORTED_COMPILER_CHECK +``` + +## Version history + +Added in version 3.2.0. diff --git a/doc/mkdocs/docs/api/macros/json_throw_user.md b/doc/mkdocs/docs/api/macros/json_throw_user.md new file mode 100644 index 000000000..e10db90e4 --- /dev/null +++ b/doc/mkdocs/docs/api/macros/json_throw_user.md @@ -0,0 +1,75 @@ +# JSON_CATCH_USER, JSON_THROW_USER, JSON_TRY_USER + +```cpp +// (1) +#define JSON_CATCH_USER(exception) /* value */ +// (2) +#define JSON_THROW_USER(exception) /* value */ +// (3) +#define JSON_TRY_USER /* value */ +``` + +Controls how exceptions are handled by the library. + +1. This macro overrides [`#!cpp catch`](https://en.cppreference.com/w/cpp/language/try_catch) calls inside the library. + The argument is the type of the exception to catch. As of version 3.8.0, the library only catches `std::out_of_range` + exceptions internally to rethrow them as [`json::out_of_range`](../../home/exceptions.md#out-of-range) exceptions. + The macro is always followed by a scope. +2. This macro overrides `#!cpp throw` calls inside the library. The argument is the exception to be thrown. Note that + `JSON_THROW_USER` should leave the current scope (e.g., by throwing or aborting), as continuing after it may yield + undefined behavior. +3. This macro overrides `#!cpp try` calls inside the library. It has no arguments and is always followed by a scope. + +## Parameters + +`exception` (in) +: an exception type + +## Default definition + +By default, the macros map to their respective C++ keywords: + +```cpp +#define JSON_CATCH_USER(exception) catch(exception) +#define JSON_THROW_USER(exception) throw exception +#define JSON_TRY_USER try +``` + +When exceptions are switched off, the `#!cpp try` block is executed unconditionally, and throwing exceptions is +replaced by calling [`std::abort`](https://en.cppreference.com/w/cpp/utility/program/abort) to make reaching the +`#!cpp throw` branch abort the process. + +```cpp +#define JSON_THROW_USER(exception) std::abort() +#define JSON_TRY_USER if (true) +#define JSON_CATCH_USER(exception) if (false) +``` + +## Examples + +??? example + + The code below switches off exceptions and creates a log entry with a detailed error message in case of errors. + + ```cpp + #include + + #define JSON_TRY_USER if(true) + #define JSON_CATCH_USER(exception) if(false) + #define JSON_THROW_USER(exception) \ + {std::clog << "Error in " << __FILE__ << ":" << __LINE__ \ + << " (function " << __FUNCTION__ << ") - " \ + << (exception).what() << std::endl; \ + std::abort();} + + #include + ``` + +## See also + +- [Switch off exceptions](../../home/exceptions.md#switch-off-exceptions) for more information how to switch off exceptions +- [JSON_NOEXCEPTION](JSON_NOEXCEPTION) - switch off exceptions + +## Version history + +- Added in version 3.1.0. diff --git a/doc/mkdocs/docs/api/macros/json_use_implicit_conversions.md b/doc/mkdocs/docs/api/macros/json_use_implicit_conversions.md new file mode 100644 index 000000000..3ee81b061 --- /dev/null +++ b/doc/mkdocs/docs/api/macros/json_use_implicit_conversions.md @@ -0,0 +1,56 @@ +# JSON_USE_IMPLICIT_CONVERSIONS + +```cpp +#define JSON_USE_IMPLICIT_CONVERSIONS /* value */ +``` + +When defined to `0`, implicit conversions are switched off. By default, implicit conversions are switched on. The +value directly affects [`operator ValueType`](../basic_json/operator_ValueType.md). + +Implicit conversions can also be controlled with the CMake option `JSON_ImplicitConversions` (`ON` by default) which +sets `JSON_USE_IMPLICIT_CONVERSIONS` accordingly. + +## Default definition + +By default, implicit conversions are enabled. + +```cpp +#define JSON_USE_IMPLICIT_CONVERSIONS 1 +``` + +## Notes + +!!! info "Future behavior change" + + Implicit conversions will be switched off by default in the next major release of the library. + + You can prepare existing code by already defining `JSON_USE_IMPLICIT_CONVERSIONS` to `0` and replace any implicit + conversions with calls to [`get`](../basic_json/get.md). + +## Examples + +??? example + + This is an example for an implicit conversion: + + ```cpp + json j = "Hello, world!"; + std::string s = j; + ``` + + When `JSON_USE_IMPLICIT_CONVERSIONS` is defined to `0`, the code above does no longer compile. Instead, it must be + written like this: + + ```cpp + json j = "Hello, world!"; + auto s = j.get(); + ``` + +## See also + +- [**operator ValueType**](../basic_json/operator_ValueType.md) - get a value (implicit) +- [**get**](../basic_json/get.md) - get a value (explicit) + +## Version history + +- Added in version 3.9.0. diff --git a/doc/mkdocs/docs/api/macros/nlohmann_define_type_intrusive.md b/doc/mkdocs/docs/api/macros/nlohmann_define_type_intrusive.md new file mode 100644 index 000000000..7269ef081 --- /dev/null +++ b/doc/mkdocs/docs/api/macros/nlohmann_define_type_intrusive.md @@ -0,0 +1,124 @@ +# NLOHMANN_DEFINE_TYPE_INTRUSIVE, NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT + +```cpp +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(type, member...) // (1) +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(type, member...) // (2) +``` + +These macros can be used to simplify the serialization/deserialization of types if you want to use a JSON object as +serialization and want to use the member variable names as object keys in that object. The macro is to be defined +**inside** the class/struct to create code for. +Unlike [`NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE`](nlohmann_define_type_non_intrusive.md), it can access private members. +The first parameter is the name of the class/struct, and all remaining parameters name the members. + +1. Will use [`at`](../basic_json/at.md) during deserialization and will throw + [`out_of_range.403`](../../home/exceptions.md#jsonexceptionout_of_range403) if a key is missing in the JSON object. +2. Will use [`value`](../basic_json/value.md) during deserialization and fall back to the default value for the + respective type of the member variable if a key in the JSON object is missing. The generated `from_json()` function + default constructs an object and uses its values as the defaults when calling the `value` function. + +## Parameters + +`type` (in) +: name of the type (class, struct) to serialize/deserialize + +`member` (in) +: name of the member variable to serialize/deserialize; up to 64 members can be given as comma-separated list + +## Default definition + +The macros add two friend functions to the class which take care of the serialization and deserialization: + +```cpp +friend void to_json(nlohmann::json&, const type&); +friend void from_json(const nlohmann::json&, type&); +``` + +See examples below for the concrete generated code. + +## Notes + +!!! info "Prerequisites" + + 1. The type `type` must be default constructible. See [How can I use `get()` for non-default constructible/non-copyable types?](../../features/arbitrary_types.md#how-can-i-use-get-for-non-default-constructiblenon-copyable-types) + for how to overcome this limitation. + 2. The macro must be used inside the type (class/struct). + +!!! warning "Implementation limits" + + - The current implementation is limited to at most 64 member variables. If you want to serialize/deserialize types + with more than 64 member variables, you need to define the `to_json`/`from_json` functions manually. + - The macros only work for the [`nlohmann::json`](../json.md) type; other specializations such as + [`nlohmann::ordered_json`](../ordered_json.md) are currently unsupported. + +## Examples + +??? example "Example (1): NLOHMANN_DEFINE_TYPE_INTRUSIVE" + + Consider the following complete example: + + ```cpp hl_lines="21" + --8<-- "examples/nlohmann_define_type_intrusive_macro.cpp" + ``` + + Output: + + ```json + --8<-- "examples/nlohmann_define_type_intrusive_macro.output" + ``` + + Notes: + + - `ns::person` is default-constructible. This is a requirement for using the macro. + - `ns::person` has private member variables. This makes `NLOHMANN_DEFINE_TYPE_INTRUSIVE` applicable, but not + `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE`. + - The macro `NLOHMANN_DEFINE_TYPE_INTRUSIVE` is used _inside_ the class. + - A missing key "age" in the deserialization yields an exception. To fall back to the default value, + `NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT` can be used. + + The macro is equivalent to: + + ```cpp hl_lines="21 22 23 24 25 26 27 28 29 30 31 32 33" + --8<-- "examples/nlohmann_define_type_intrusive_explicit.cpp" + ``` + +??? example "Example (2): NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT" + + Consider the following complete example: + + ```cpp hl_lines="21" + --8<-- "examples/nlohmann_define_type_intrusive_with_default_macro.cpp" + ``` + + Output: + + ```json + --8<-- "examples/nlohmann_define_type_intrusive_with_default_macro.output" + ``` + + Notes: + + - `ns::person` is default-constructible. This is a requirement for using the macro. + - `ns::person` has private member variables. This makes `NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT` applicable, + but not `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT`. + - The macro `NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT` is used _inside_ the class. + - A missing key "age" in the deserialization does not yield an exception. Instead, the default value `-1` is used. + + The macro is equivalent to: + + ```cpp hl_lines="21 22 23 24 25 26 27 28 29 30 31 32 33 34" + --8<-- "examples/nlohmann_define_type_intrusive_with_default_explicit.cpp" + ``` + + Note how a default-initialized `person` object is used in the `from_json` to fill missing values. + +## See also + +- [NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE / NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT](nlohmann_define_type_non_intrusive.md) + for a similar macro that can be defined _outside_ the type. +- [Arbitrary Types Conversions](../../features/arbitrary_types.md) for an overview. + +## Version history + +1. Added in version 3.9.0. +2. Added in version 3.11.0. diff --git a/doc/mkdocs/docs/api/macros/nlohmann_define_type_non_intrusive.md b/doc/mkdocs/docs/api/macros/nlohmann_define_type_non_intrusive.md new file mode 100644 index 000000000..7ed5a6b43 --- /dev/null +++ b/doc/mkdocs/docs/api/macros/nlohmann_define_type_non_intrusive.md @@ -0,0 +1,124 @@ +# NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE, NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT + +```cpp +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(type, member...) // (1) +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(type, member...) // (2) +``` + +These macros can be used to simplify the serialization/deserialization of types if you want to use a JSON object as +serialization and want to use the member variable names as object keys in that object. The macro is to be defined +**outside** the class/struct to create code for, but **inside** its namespace. +Unlike [`NLOHMANN_DEFINE_TYPE_INTRUSIVE`](nlohmann_define_type_intrusive.md), it **cannot** access private members. +The first parameter is the name of the class/struct, and all remaining parameters name the members. + +1. Will use [`at`](../basic_json/at.md) during deserialization and will throw + [`out_of_range.403`](../../home/exceptions.md#jsonexceptionout_of_range403) if a key is missing in the JSON object. +2. Will use [`value`](../basic_json/value.md) during deserialization and fall back to the default value for the + respective type of the member variable if a key in the JSON object is missing. The generated `from_json()` function + default constructs an object and uses its values as the defaults when calling the `value` function. + +## Parameters + +`type` (in) +: name of the type (class, struct) to serialize/deserialize + +`member` (in) +: name of the (public) member variable to serialize/deserialize; up to 64 members can be given as comma-separated list + +## Default definition + +The macros add two functions to the namespace which take care of the serialization and deserialization: + +```cpp +void to_json(nlohmann::json&, const type&); +void from_json(const nlohmann::json&, type&); +``` + +See examples below for the concrete generated code. + +## Notes + +!!! info "Prerequisites" + + 1. The type `type` must be default constructible. See [How can I use `get()` for non-default constructible/non-copyable types?](../../features/arbitrary_types.md#how-can-i-use-get-for-non-default-constructiblenon-copyable-types) + for how to overcome this limitation. + 2. The macro must be used outside the type (class/struct). + 3. The passed members must be public. + +!!! warning "Implementation limits" + + - The current implementation is limited to at most 64 member variables. If you want to serialize/deserialize types + with more than 64 member variables, you need to define the `to_json`/`from_json` functions manually. + - The macros only work for the [`nlohmann::json`](../json.md) type; other specializations such as + [`nlohmann::ordered_json`](../ordered_json.md) are currently unsupported. + +## Examples + +??? example "Example (1): NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE" + + Consider the following complete example: + + ```cpp hl_lines="15" + --8<-- "examples/nlohmann_define_type_non_intrusive_macro.cpp" + ``` + + Output: + + ```json + --8<-- "examples/nlohmann_define_type_non_intrusive_macro.output" + ``` + + Notes: + + - `ns::person` is default-constructible. This is a requirement for using the macro. + - `ns::person` has only public member variables. This makes `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE` applicable. + - The macro `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE` is used _outside_ the class, but _inside_ its namespace `ns`. + - A missing key "age" in the deserialization yields an exception. To fall back to the default value, + `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT` can be used. + + The macro is equivalent to: + + ```cpp hl_lines="15 16 17 18 19 20 21 22 23 24 25 26 27" + --8<-- "examples/nlohmann_define_type_non_intrusive_explicit.cpp" + ``` + +??? example "Example (2): NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT" + + Consider the following complete example: + + ```cpp hl_lines="20" + --8<-- "examples/nlohmann_define_type_non_intrusive_with_default_macro.cpp" + ``` + + Output: + + ```json + --8<-- "examples/nlohmann_define_type_non_intrusive_with_default_macro.output" + ``` + + Notes: + + - `ns::person` is default-constructible. This is a requirement for using the macro. + - `ns::person` has only public member variables. This makes `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT` + applicable. + - The macro `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT` is used _outside_ the class, but _inside_ its namespace `ns`. + - A missing key "age" in the deserialization does not yield an exception. Instead, the default value `-1` is used. + + The macro is equivalent to: + + ```cpp hl_lines="20 21 22 23 24 25 26 27 28 29 30 31 32 33" + --8<-- "examples/nlohmann_define_type_non_intrusive_with_default_explicit.cpp" + ``` + + Note how a default-initialized `person` object is used in the `from_json` to fill missing values. + +## See also + +- [NLOHMANN_DEFINE_TYPE_INTRUSIVE / NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT](nlohmann_define_type_intrusive.md) + for a similar macro that can be defined _inside_ the type. +- [Arbitrary Types Conversions](../../features/arbitrary_types.md) for an overview. + +## Version history + +1. Added in version 3.9.0. +2. Added in version 3.11.0. diff --git a/doc/mkdocs/docs/api/macros/nlohmann_json_serialize_enum.md b/doc/mkdocs/docs/api/macros/nlohmann_json_serialize_enum.md new file mode 100644 index 000000000..7b0f89802 --- /dev/null +++ b/doc/mkdocs/docs/api/macros/nlohmann_json_serialize_enum.md @@ -0,0 +1,84 @@ +# NLOHMANN_JSON_SERIALIZE_ENUM + +```cpp +#define NLOHMANN_JSON_SERIALIZE_ENUM(type, conversion...) +``` + +By default, enum values are serialized to JSON as integers. In some cases this could result in undesired behavior. If an +enum is modified or re-ordered after data has been serialized to JSON, the later de-serialized JSON data may be +undefined or a different enum value than was originally intended. + +The `NLOHMANN_JSON_SERIALIZE_ENUM` allows to define a user-defined serialization for every enumerator. + +## Parameters + +`type` (in) +: name of the enum to serialize/deserialize + +`conversion` (in) +: a pair of an enumerator and a JSON serialization; arbitrary pairs can can be given as comma-separated list + +## Default definition + +The macros add two friend functions to the class which take care of the serialization and deserialization: + +```cpp +template +inline void to_json(BasicJsonType& j, const type& e); +template +inline void from_json(const BasicJsonType& j, type& e); +``` + +## Notes + +!!! info "Prerequisites" + + The macro must be used inside the namespace of the enum. + +!!! important "Important notes" + + - When using [`get()`](../basic_json/get.md), undefined JSON values will default to the first specified + conversion. Select this default pair carefully. See example 1 below. + - If an enum or JSON value is specified in multiple conversions, the first matching conversion from the top of the + list will be returned when converting to or from JSON. See example 2 below. + +## Examples + +??? example "Example 1: Basic usage" + + The example shows how `NLOHMANN_JSON_SERIALIZE_ENUM` can be used to serialize/deserialize both classical enums and + C++11 enum classes: + + ```cpp hl_lines="16 17 18 19 20 21 22 29 30 31 32 33" + --8<-- "examples/nlohmann_json_serialize_enum.cpp" + ``` + + Output: + + ```json + --8<-- "examples/nlohmann_json_serialize_enum.output" + ``` + +??? example "Example 2: Multiple conversions for one enumerator" + + The example shows how to use multiple conversions for a single enumerator. In the example, `Color::red` will always + be *serialized* to `"red"`, because the first occurring conversion. The second conversion, however, offers an + alternative *deserialization* from `"rot"` to `Color::red`. + + ```cpp hl_lines="17" + --8<-- "examples/nlohmann_json_serialize_enum_2.cpp" + ``` + + Output: + + ```json + --8<-- "examples/nlohmann_json_serialize_enum_2.output" + ``` + +## See also + +- [Specializing enum conversion](../../features/enum_conversion.md) + +## Version history + +Added in version 3.4.0. diff --git a/doc/mkdocs/docs/api/macros/nlohmann_json_version_major.md b/doc/mkdocs/docs/api/macros/nlohmann_json_version_major.md new file mode 100644 index 000000000..826785292 --- /dev/null +++ b/doc/mkdocs/docs/api/macros/nlohmann_json_version_major.md @@ -0,0 +1,23 @@ +# NLOHMANN_JSON_VERSION_MAJOR, NLOHMANN_JSON_VERSION_MINOR, NLOHMANN_JSON_VERSION_PATCH + +```cpp +#define NLOHMANN_JSON_VERSION_MAJOR /* value */ +#define NLOHMANN_JSON_VERSION_MINOR /* value */ +#define NLOHMANN_JSON_VERSION_PATCH /* value */ +``` + +These macros are defined by the library and contain the version numbers according to +[Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html). + +## Default definition + +The macros are defined according to the current library version. + +## See also + +- [meta](../basic_json/meta.md) - returns version information on the library +- [JSON_SKIP_LIBRARY_VERSION_CHECK](json_skip_library_version_check.md) - skip library version check + +## Version history + +- Added in version 3.1.0. diff --git a/doc/mkdocs/docs/features/arbitrary_types.md b/doc/mkdocs/docs/features/arbitrary_types.md index 67e8a4554..49a541ef5 100644 --- a/doc/mkdocs/docs/features/arbitrary_types.md +++ b/doc/mkdocs/docs/features/arbitrary_types.md @@ -77,7 +77,7 @@ Some important things: * Those methods **MUST** be in your type's namespace (which can be the global namespace), or the library will not be able to locate them (in this example, they are in namespace `ns`, where `person` is defined). * Those methods **MUST** be available (e.g., proper headers must be included) everywhere you use these conversions. Look at [issue 1108](https://github.com/nlohmann/json/issues/1108) for errors that may occur otherwise. * When using `get()`, `your_type` **MUST** be [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible). (There is a way to bypass this requirement described later.) -* In function `from_json`, use function [`at()`](https://nlohmann.github.io/json/classnlohmann_1_1basic__json_a93403e803947b86f4da2d1fb3345cf2c.html#a93403e803947b86f4da2d1fb3345cf2c) to access the object values rather than `operator[]`. In case a key does not exist, `at` throws an exception that you can handle, whereas `operator[]` exhibits undefined behavior. +* In function `from_json`, use function [`at()`](../api/basic_json/at.md) to access the object values rather than `operator[]`. In case a key does not exist, `at` throws an exception that you can handle, whereas `operator[]` exhibits undefined behavior. * You do not need to add serializers or deserializers for STL types like `std::vector`: the library already implements these. @@ -87,16 +87,19 @@ If you just want to serialize/deserialize some structs, the `to_json`/`from_json There are four macros to make your life easier as long as you (1) want to use a JSON object as serialization and (2) want to use the member variable names as object keys in that object: -- `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(name, member1, member2, ...)` is to be defined inside the namespace of the class/struct to create code for. It will throw an exception in `from_json()` due to a missing value in the JSON object. -- `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(name, member1, member2, ...)` is to be defined inside the namespace of the class/struct to create code for. It will not throw an exception in `from_json()` due to a missing value in the JSON object, but fills in values from object which is default-constructed by the type. -- `NLOHMANN_DEFINE_TYPE_INTRUSIVE(name, member1, member2, ...)` is to be defined inside the class/struct to create code for. This macro can also access private members. It will throw an exception in `from_json()` due to a missing value in the JSON object. -- `NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(name, member1, member2, ...)` is to be defined inside the class/struct to create code for. This macro can also access private members. It will not throw an exception in `from_json()` due to a missing value in the JSON object, but fills in values from object which is default-constructed by the type. +- [`NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(name, member1, member2, ...)`](../api/macros/nlohmann_define_type_non_intrusive.md) is to be defined inside the namespace of the class/struct to create code for. It will throw an exception in `from_json()` due to a missing value in the JSON object. +- [`NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(name, member1, member2, ...)`](../api/macros/nlohmann_define_type_non_intrusive.md) is to be defined inside the namespace of the class/struct to create code for. It will not throw an exception in `from_json()` due to a missing value in the JSON object, but fills in values from object which is default-constructed by the type. +- [`NLOHMANN_DEFINE_TYPE_INTRUSIVE(name, member1, member2, ...)`](../api/macros/nlohmann_define_type_intrusive.md) is to be defined inside the class/struct to create code for. This macro can also access private members. It will throw an exception in `from_json()` due to a missing value in the JSON object. +- [`NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(name, member1, member2, ...)`](../api/macros/nlohmann_define_type_intrusive.md) is to be defined inside the class/struct to create code for. This macro can also access private members. It will not throw an exception in `from_json()` due to a missing value in the JSON object, but fills in values from object which is default-constructed by the type. In all macros, the first parameter is the name of the class/struct, and all remaining parameters name the members. You can read more docs about them starting from [here](macros.md#nlohmann_define_type_intrusivetype-member). -!!! note +!!! info "Implementation limits" - At most 64 member variables can be passed to these macros. + - The current macro implementations are limited to at most 64 member variables. If you want to serialize/deserialize + types with more than 64 member variables, you need to define the `to_json`/`from_json` functions manually. + - The macros only work for the [`nlohmann::json`](../api/json.md) type; other specializations such as + [`nlohmann::ordered_json`](../api/ordered_json.md) are currently unsupported. ??? example diff --git a/doc/mkdocs/docs/features/assertions.md b/doc/mkdocs/docs/features/assertions.md new file mode 100644 index 000000000..a0b118722 --- /dev/null +++ b/doc/mkdocs/docs/features/assertions.md @@ -0,0 +1,104 @@ +# Runtime Assertions + +The code contains numerous debug assertions to ensure class invariants are valid or to detect undefined behavior. +Whereas the former class invariants are nothing to be concerned of, the latter checks for undefined behavior are to +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. + +## Change assertion behavior + +The behavior of runtime assertions can be changes by defining macro [`JSON_ASSERT(x)`](../api/macros/json_assert.md) +before including the `json.hpp` header. + +## Function with runtime assertions + +### Unchecked object access to a const value + +Function [`operator[]`](../api/basic_json/operator%5B%5D.md) implements unchecked access for objects. Whereas a missing +key is added in case of non-const objects, accessing a const object with a missing key is undefined behavior (think of a +dereferenced null pointer) and yields a runtime assertion. + +If you are not sure whether an element in an object exists, use checked access with the +[`at` function](../api/basic_json/at.md) or call the [`contains` function](../api/basic_json/contains.md) before. + +See also the documentation on [element access](element_access/index.md). + +??? example "Example 1: Missing object key" + + The following code will trigger an assertion at runtime: + + ```cpp + #include + + using json = nlohmann::json; + + int main() + { + const json j = {{"key", "value"}}; + auto v = j["missing"]; + } + ``` + + Output: + + ``` + Assertion failed: (m_value.object->find(key) != m_value.object->end()), function operator[], file json.hpp, line 2144. + ``` + +### Constructing from an uninitialized iterator range + +Constructing a JSON value from an iterator range (see [constructor](../api/basic_json/basic_json.md)) with an +uninitialized iterator is undefined behavior and yields a runtime assertion. + +??? example "Example 2: Uninitialized iterator range" + + The following code will trigger an assertion at runtime: + + ```cpp + #include + + using json = nlohmann::json; + + int main() + { + json::iterator it1, it2; + json j(it1, it2); + } + ``` + + Output: + + ``` + Assertion failed: (m_object != nullptr), function operator++, file iter_impl.hpp, line 368. + ``` + +### Operations on uninitialized iterators + +Any operation on uninitialized iterators (i.e., iterators that are not associated with any JSON value) is undefined +behavior and yields a runtime assertion. + +??? example "Example 3: Uninitialized iterator" + + The following code will trigger an assertion at runtime: + + ```cpp + #include + + using json = nlohmann::json; + + int main() + { + json::iterator it; + ++it; + } + ``` + + Output: + + ``` + Assertion failed: (m_object != nullptr), function operator++, file iter_impl.hpp, line 368. + ``` diff --git a/doc/mkdocs/docs/features/element_access/unchecked_access.md b/doc/mkdocs/docs/features/element_access/unchecked_access.md index bb7228777..4fdef35c7 100644 --- a/doc/mkdocs/docs/features/element_access/unchecked_access.md +++ b/doc/mkdocs/docs/features/element_access/unchecked_access.md @@ -94,9 +94,9 @@ When accessing an invalid index (i.e., an index greater than or equal to the arr ## Summary -| scenario | non-const value | const value | -|-----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------| -| access to existing object key | reference to existing value is returned | const reference to existing value is returned | -| access to valid array index | reference to existing value is returned | const reference to existing value is returned | -| access to non-existing object key | reference to newly inserted `#!json null` value is returned | **undefined behavior**; assertion in debug mode | -| access to invalid array index | reference to newly inserted `#!json null` value is returned; any index between previous maximal index and passed index are filled with `#!json null` | **undefined behavior**; assertion in debug mode | +| scenario | non-const value | const value | +|-----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------| +| access to existing object key | reference to existing value is returned | const reference to existing value is returned | +| access to valid array index | reference to existing value is returned | const reference to existing value is returned | +| access to non-existing object key | reference to newly inserted `#!json null` value is returned | **undefined behavior**; [runtime assertion](../assertions.md) in debug mode | +| access to invalid array index | reference to newly inserted `#!json null` value is returned; any index between previous maximal index and passed index are filled with `#!json null` | **undefined behavior**; [runtime assertion](../assertions.md) in debug mode | diff --git a/doc/mkdocs/docs/features/enum_conversion.md b/doc/mkdocs/docs/features/enum_conversion.md index 8f474a702..7e61f67bb 100644 --- a/doc/mkdocs/docs/features/enum_conversion.md +++ b/doc/mkdocs/docs/features/enum_conversion.md @@ -24,8 +24,8 @@ NLOHMANN_JSON_SERIALIZE_ENUM( TaskState, { }) ``` -The `NLOHMANN_JSON_SERIALIZE_ENUM()` macro declares a set of `to_json()` / `from_json()` functions for type `TaskState` -while avoiding repetition and boilerplate serialization code. +The [`NLOHMANN_JSON_SERIALIZE_ENUM()` macro](../api/macros/nlohmann_json_serialize_enum.md) declares a set of +`to_json()` / `from_json()` functions for type `TaskState` while avoiding repetition and boilerplate serialization code. ## Usage @@ -45,10 +45,11 @@ assert(jPi.get() == TS_INVALID ); ## Notes -Just as in [Arbitrary Type Conversions](#arbitrary-types-conversions) above, +Just as in [Arbitrary Type Conversions](arbitrary_types.md) above, -- `NLOHMANN_JSON_SERIALIZE_ENUM()` MUST be declared in your enum type's namespace (which can be the global namespace), - or the library will not be able to locate it, and it will default to integer serialization. +- [`NLOHMANN_JSON_SERIALIZE_ENUM()`](../api/macros/nlohmann_json_serialize_enum.md) MUST be declared in your enum type's + namespace (which can be the global namespace), or the library will not be able to locate it, and it will default to + integer serialization. - It MUST be available (e.g., proper headers must be included) everywhere you use the conversions. Other Important points: diff --git a/doc/mkdocs/docs/features/macros.md b/doc/mkdocs/docs/features/macros.md index 576ec935d..e04a426a5 100644 --- a/doc/mkdocs/docs/features/macros.md +++ b/doc/mkdocs/docs/features/macros.md @@ -1,47 +1,19 @@ # Supported Macros Some aspects of the library can be configured by defining preprocessor macros before including the `json.hpp` header. +See also the [API documentation for macros](../api/macros/index.md) for examples and more information. ## `JSON_ASSERT(x)` -This macro controls which code is executed for runtime assertions of the libraries. +This macro controls which code is executed for [runtime assertions](assertions.md) of the library. -!!! info "Default behavior" - - The default value is [`#!cpp assert(x)`](https://en.cppreference.com/w/cpp/error/assert). - - ```cpp - #define JSON_ASSERT(x) assert(x) - ``` - -The macro was introduced in version 3.9.0. +See [full documentation of `JSON_ASSERT(x)`](../api/macros/json_assert.md). ## `JSON_CATCH_USER(exception)` This macro overrides [`#!cpp catch`](https://en.cppreference.com/w/cpp/language/try_catch) calls inside the library. -The argument is the type of the exception to catch. As of version 3.8.0, the library only catches `std::out_of_range` -exceptions internally to rethrow them as [`json::out_of_range`](../home/exceptions.md#out-of-range) exceptions. The -macro is always followed by a scope. -See [Switch off exceptions](../home/exceptions.md#switch-off-exceptions) for an example. - -!!! info "Default behavior" - - When exceptions are enabled, the default value is - [`#!cpp catch(exception)`](https://en.cppreference.com/w/cpp/language/try_catch). - - ```cpp - #define JSON_CATCH_USER(exception) catch(exception) - ``` - - When exceptions are switched off by the compiler, the default value is `#!cpp if (false)` to make the catch block - unreachable. - - ```cpp - #define JSON_CATCH_USER(exception) if (false) - ``` - -The macro was introduced in version 3.1.0. +See [full documentation of `JSON_CATCH_USER(exception)`](../api/macros/json_throw_user.md). ## `JSON_DIAGNOSTICS` @@ -55,19 +27,7 @@ that enabling this macro increases the size of every JSON value by one pointer a The diagnostics messages can also be controlled with the CMake option `JSON_Diagnostics` (`OFF` by default) which sets `JSON_DIAGNOSTICS` accordingly. -!!! warning - - As this macro changes the definition of the `basic_json` object, it MUST be defined in the same way globally, even - across different compilation units; DO NOT link together code compiled with different definitions of - `JSON_DIAGNOSTICS` as this is a violation of the One Definition Rule and will cause undefined behaviour. - -!!! info "Default behavior" - - ```cpp - #define JSON_DIAGNOSTICS 0 - ``` - -The macro was introduced in version 3.10.0. +See [full documentation of `JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md). ## `JSON_HAS_CPP_11`, `JSON_HAS_CPP_14`, `JSON_HAS_CPP_17`, `JSON_HAS_CPP_20` @@ -77,12 +37,7 @@ standard. By defining any of these symbols, the internal check is overridden and unconditionally assumed. This can be helpful for compilers that only implement parts of the standard and would be detected incorrectly. -!!! info "Default behavior" - - The default value is detected based on the preprocessor macros `#!cpp __cplusplus`, `#!cpp _HAS_CXX17`, or - `#!cpp _MSVC_LANG`. - -The macros were introduced in version 3.10.5. +See [full documentation of `JSON_HAS_CPP_11`, `JSON_HAS_CPP_14`, `JSON_HAS_CPP_17`, and `JSON_HAS_CPP_20`](../api/macros/json_has_cpp_11.md). ## `JSON_HAS_FILESYSTEM`, `JSON_HAS_EXPERIMENTAL_FILESYSTEM` @@ -91,37 +46,13 @@ for filesystem is limited, the library tries to detect whether ``/`s or ``/`std::experimental::filesystem` (`JSON_HAS_EXPERIMENTAL_FILESYSTEM`) should be used. To override the built-in check, define `JSON_HAS_FILESYSTEM` or `JSON_HAS_EXPERIMENTAL_FILESYSTEM` to `1`. -!!! info "Default behavior" - - The default value is detected based on the preprocessor macros `#!cpp __cpp_lib_filesystem`, - `#!cpp __cpp_lib_experimental_filesystem`, `#!cpp __has_include()`, or - `#!cpp __has_include()`. - -Note that older compilers or older versions of libstd++ also require the library `stdc++fs` to be linked to for -filesystem support. - -The macros were introduced in version 3.10.5. +See [full documentation of `JSON_HAS_FILESYSTEM` and `JSON_HAS_EXPERIMENTAL_FILESYSTEM`](../api/macros/json_has_filesystem.md). ## `JSON_NOEXCEPTION` -Exceptions can be switched off by defining the symbol `JSON_NOEXCEPTION`. When defining `JSON_NOEXCEPTION`, `#!cpp try` -is replaced by `#!cpp if (true)`, `#!cpp catch` is replaced by `#!cpp if (false)`, and `#!cpp throw` is replaced by -`#!cpp std::abort()`. +Exceptions can be switched off by defining the symbol `JSON_NOEXCEPTION`. -!!! info "Default behavior" - - By default, the macro is not defined. - - ```cpp - #undef JSON_NOEXCEPTION - ``` - -The same effect is achieved by setting the compiler flag `-fno-exceptions`. - -Note the explanatory [`what()`](https://en.cppreference.com/w/cpp/error/exception/what) string of exceptions is not -available for MSVC if exceptions are disabled, see [#2824](https://github.com/nlohmann/json/discussions/2824). - -The macro was introduced in version 2.1.0. +See [full documentation of `JSON_NOEXCEPTION`](../api/macros/json_noexception.md). ## `JSON_NO_IO` @@ -129,128 +60,39 @@ When defined, headers ``, ``, ``, ``, and `(); - ``` - -Implicit conversions can also be controlled with the CMake option `JSON_ImplicitConversions` (`ON` by default) which -sets `JSON_USE_IMPLICIT_CONVERSIONS` accordingly. - -!!! info "Default behavior" - - ```cpp - #define JSON_USE_IMPLICIT_CONVERSIONS 1 - ``` - -The macro was introduced in version 3.9.0. +See [full documentation of `JSON_USE_IMPLICIT_CONVERSIONS`](../api/macros/json_use_implicit_conversions.md). ## `NLOHMANN_DEFINE_TYPE_INTRUSIVE(type, member...)` @@ -261,18 +103,15 @@ The macro is to be defined inside the class/struct to create code for. Unlike [`NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE`](#nlohmann_define_type_non_intrusivetype-member), it can access private members. The first parameter is the name of the class/struct, and all remaining parameters name the members. -See [Simplify your life with macros](arbitrary_types.md#simplify-your-life-with-macros) for an example. - -The macro was introduced in version 3.9.0. +See [full documentation of `NLOHMANN_DEFINE_TYPE_INTRUSIVE`](../api/macros/nlohmann_define_type_intrusive.md). ## `NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(type, member...)` This macro is similar to `NLOHMANN_DEFINE_TYPE_INTRUSIVE`. It will not throw an exception in `from_json()` due to a -missing value in the JSON object, but can throw due to a mismatched type. In order to support that it requires that the -type be default constructible. The `from_json()` function default constructs an object and uses its values as the -defaults when calling the `value()` function. +missing value in the JSON object, but can throw due to a mismatched type. The `from_json()` function default constructs +an object and uses its values as the defaults when calling the [`value`](../api/basic_json/value.md) function. -The macro was introduced in version 3.11.0. +See [full documentation of `NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT`](../api/macros/nlohmann_define_type_intrusive.md). ## `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(type, member...)` @@ -283,29 +122,26 @@ The macro is to be defined inside the namespace of the class/struct to create co accessed. Use [`NLOHMANN_DEFINE_TYPE_INTRUSIVE`](#nlohmann_define_type_intrusivetype-member) in these scenarios. The first parameter is the name of the class/struct, and all remaining parameters name the members. -See [Simplify your life with macros](arbitrary_types.md#simplify-your-life-with-macros) for an example. - -The macro was introduced in version 3.9.0. +See [full documentation of `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE`](../api/macros/nlohmann_define_type_non_intrusive.md). ## `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(type, member...)` This macro is similar to `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE`. It will not throw an exception in `from_json()` due to a -missing value in the JSON object, but can throw due to a mismatched type. In order to support that it requires that the -type be default constructible. The `from_json()` function default constructs an object and uses its values as the -defaults when calling the `value()` function. +missing value in the JSON object, but can throw due to a mismatched type. The `from_json()` function default constructs +an object and uses its values as the defaults when calling the [`value`](../api/basic_json/value.md) function. -The macro was introduced in version 3.11.0. +See [full documentation of `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT`](../api/macros/nlohmann_define_type_non_intrusive.md). ## `NLOHMANN_JSON_SERIALIZE_ENUM(type, ...)` This macro simplifies the serialization/deserialization of enum types. See [Specializing enum conversion](enum_conversion.md) for more information. -The macro was introduced in version 3.4.0. +See [full documentation of `NLOHMANN_JSON_SERIALIZE_ENUM`](../api/macros/nlohmann_json_serialize_enum.md). ## `NLOHMANN_JSON_VERSION_MAJOR`, `NLOHMANN_JSON_VERSION_MINOR`, `NLOHMANN_JSON_VERSION_PATCH` These macros are defined by the library and contain the version numbers according to -[Semantic Versioning 2.0.0](https://semver.org). +[Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html). -The macros were introduced in version 3.1.0. +See [full documentation of `NLOHMANN_JSON_VERSION_MAJOR`, `NLOHMANN_JSON_VERSION_MINOR`, and `NLOHMANN_JSON_VERSION_PATCH`](../api/macros/nlohmann_json_version_major.md). diff --git a/doc/mkdocs/docs/features/types/number_handling.md b/doc/mkdocs/docs/features/types/number_handling.md index dd6507353..03d8c9c69 100644 --- a/doc/mkdocs/docs/features/types/number_handling.md +++ b/doc/mkdocs/docs/features/types/number_handling.md @@ -275,7 +275,7 @@ The rationale is twofold: ### Determine number types -As the example in [Number conversion](#number_conversion) shows, there are different functions to determine the type of +As the example in [Number conversion](#number-conversion) shows, there are different functions to determine the type of the stored number: - [`is_number()`](../../api/basic_json/is_number.md) returns `#!c true` for any number type diff --git a/doc/mkdocs/docs/home/exceptions.md b/doc/mkdocs/docs/home/exceptions.md index 1241926cf..666d225bd 100644 --- a/doc/mkdocs/docs/home/exceptions.md +++ b/doc/mkdocs/docs/home/exceptions.md @@ -28,9 +28,9 @@ class json::parse_error { ### Switch off exceptions -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`. 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`). +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`). -Note that `JSON_THROW_USER` should leave the current scope (e.g., by throwing or aborting), as continuing after it may yield undefined behavior. +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 @@ -52,6 +52,8 @@ Note that `JSON_THROW_USER` should leave the current scope (e.g., by throwing or Note the explanatory [`what()`](https://en.cppreference.com/w/cpp/error/exception/what) string of exceptions is not available for MSVC if exceptions are disabled, see [#2824](https://github.com/nlohmann/json/discussions/2824). +See [documentation of `JSON_TRY_USER`, `JSON_CATCH_USER` and `JSON_THROW_USER`](../api/macros/json_throw_user.md) for more information. + ### Extended diagnostic messages Exceptions in the library are thrown in the local context of the JSON value they are detected. This makes detailed diagnostics messages, and hence debugging, difficult. @@ -88,6 +90,7 @@ As this global context comes at the price of storing one additional pointer per Now the exception message contains a JSON Pointer `/address/housenumber` that indicates which value has the wrong type. +See [documentation of `JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) for more information. ## Parse errors diff --git a/doc/mkdocs/mkdocs.yml b/doc/mkdocs/mkdocs.yml index ddd55e5c9..59f0ae700 100644 --- a/doc/mkdocs/mkdocs.yml +++ b/doc/mkdocs/mkdocs.yml @@ -64,6 +64,7 @@ nav: - features/parsing/parse_exceptions.md - features/parsing/parser_callbacks.md - features/parsing/sax_interface.md + - features/assertions.md - features/enum_conversion.md - features/macros.md - Types: @@ -232,7 +233,30 @@ nav: - 'ordered_map': api/ordered_map.md - macros: - 'Overview': api/macros/index.md - - 'JSON_ASSERT(x)': api/macros/json_assert.md + - 'JSON_ASSERT': api/macros/json_assert.md + - 'JSON_CATCH_USER': api/macros/json_throw_user.md + - 'JSON_DIAGNOSTICS': api/macros/json_diagnostics.md + - 'JSON_HAS_CPP_11': api/macros/json_has_cpp_11.md + - 'JSON_HAS_CPP_14': api/macros/json_has_cpp_11.md + - 'JSON_HAS_CPP_17': api/macros/json_has_cpp_11.md + - 'JSON_HAS_CPP_20': api/macros/json_has_cpp_11.md + - 'JSON_HAS_EXPERIMENTAL_FILESYSTEM': api/macros/json_has_filesystem.md + - 'JSON_HAS_FILESYSTEM': api/macros/json_has_filesystem.md + - 'JSON_NOEXCEPTION': api/macros/json_noexception.md + - 'JSON_NO_IO': api/macros/json_no_io.md + - 'JSON_SKIP_LIBRARY_VERSION_CHECK': api/macros/json_skip_library_version_check.md + - 'JSON_SKIP_UNSUPPORTED_COMPILER_CHECK': api/macros/json_skip_unsupported_compiler_check.md + - 'JSON_THROW_USER': api/macros/json_throw_user.md + - 'JSON_TRY_USER': api/macros/json_throw_user.md + - 'JSON_USE_IMPLICIT_CONVERSIONS': api/macros/json_use_implicit_conversions.md + - 'NLOHMANN_DEFINE_TYPE_INTRUSIVE': api/macros/nlohmann_define_type_intrusive.md + - 'NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT': api/macros/nlohmann_define_type_intrusive.md + - 'NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE': api/macros/nlohmann_define_type_non_intrusive.md + - 'NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT': api/macros/nlohmann_define_type_non_intrusive.md + - 'NLOHMANN_JSON_SERIALIZE_ENUM': api/macros/nlohmann_json_serialize_enum.md + - 'NLOHMANN_JSON_VERSION_MAJOR': api/macros/nlohmann_json_version_major.md + - 'NLOHMANN_JSON_VERSION_MINOR': api/macros/nlohmann_json_version_major.md + - 'NLOHMANN_JSON_VERSION_PATCH': api/macros/nlohmann_json_version_major.md # Extras extra: @@ -289,6 +313,7 @@ plugins: lang: en - minify: minify_html: true + - git-revision-date-localized extra_javascript: - https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-MML-AM_CHTML diff --git a/doc/mkdocs/requirements.txt b/doc/mkdocs/requirements.txt index b64e9b873..aa9fcaf0e 100644 --- a/doc/mkdocs/requirements.txt +++ b/doc/mkdocs/requirements.txt @@ -1,29 +1,48 @@ -click>=7.1.2 -future>=0.18.2 -htmlmin>=0.1.12 -httplib2>=0.18.1 -importlib-metadata>=1.6.0 -Jinja2>=2.11.2 -joblib>=0.15.1 -jsmin>=2.2.2 -livereload>=2.6.1 -lunr>=0.5.8 -Markdown>=3.2.2 -markdown-include>=0.5.1 -MarkupSafe>=1.1.1 -mkdocs>=1.1.2 -mkdocs-material>=5.2.1 -mkdocs-material-extensions>=1.0 -mkdocs-minify-plugin>=0.3.0 -mkdocs-simple-hooks>=0.1.1 -nltk>=3.5 -plantuml>=0.3.0 -plantuml-markdown>=3.2.2 -Pygments>=2.6.1 -pymdown-extensions>=7.1 -PyYAML>=5.3.1 -regex>=2020.5.14 -six>=1.15.0 -tornado>=6.0.4 -tqdm>=4.46.0 -zipp>=3.1.0 +Babel==2.10.1 +certifi==2021.10.8 +charset-normalizer==2.0.12 +click==8.1.2 +csscompressor==0.9.5 +future==0.18.2 +ghp-import==2.0.2 +gitdb==4.0.9 +GitPython==3.1.27 +htmlmin==0.1.12 +httplib2==0.20.4 +idna==3.3 +importlib-metadata==4.11.3 +Jinja2==3.1.1 +joblib==1.1.0 +jsmin==3.0.1 +livereload==2.6.3 +lunr==0.6.2 +Markdown==3.3.6 +markdown-include==0.6.0 +MarkupSafe==2.1.1 +mergedeep==1.3.4 +mkdocs==1.3.0 +mkdocs-git-revision-date-localized-plugin==1.0.1 +mkdocs-material==8.2.10 +mkdocs-material-extensions==1.0.3 +mkdocs-minify-plugin==0.5.0 +mkdocs-simple-hooks==0.1.5 +nltk==3.7 +packaging==21.3 +plantuml==0.3.0 +plantuml-markdown==3.5.2 +Pygments==2.11.0 +pymdown-extensions==9.3 +pyparsing==3.0.8 +python-dateutil==2.8.2 +pytz==2022.1 +PyYAML==6.0 +pyyaml_env_tag==0.1 +regex==2022.4.24 +requests==2.27.1 +six==1.16.0 +smmap==5.0.0 +tornado==6.1 +tqdm==4.64.0 +urllib3==1.26.9 +watchdog==2.1.7 +zipp==3.8.0 diff --git a/doc/mkdocs/scripts/check_structure.py b/doc/mkdocs/scripts/check_structure.py old mode 100644 new mode 100755 index cacc51cb4..92f6ff182 --- a/doc/mkdocs/scripts/check_structure.py +++ b/doc/mkdocs/scripts/check_structure.py @@ -2,10 +2,19 @@ import glob import os.path +import re + +warnings = 0 + + +def report(rule, location, description): + global warnings + warnings += 1 + print(f'{warnings:3}. {location}: {description} [{rule}]') def check_structure(): - expected_headers = [ + expected_sections = [ 'Template parameters', 'Specializations', 'Iterator invalidation', @@ -23,13 +32,14 @@ def check_structure(): 'Exceptions', 'Complexity', 'Possible implementation', + 'Default definition', 'Notes', 'Examples', 'See also', 'Version history' ] - required_headers = [ + required_sections = [ 'Examples', 'Version history' ] @@ -37,8 +47,8 @@ def check_structure(): files = sorted(glob.glob('api/**/*.md', recursive=True)) for file in files: with open(file) as file_content: - header_idx = -1 - existing_headers = [] + section_idx = -1 + existing_sections = [] in_initial_code_example = False previous_line = None h1sections = 0 @@ -51,50 +61,55 @@ def check_structure(): # there should only be one top-level title if h1sections > 1: - print(f'{file}:{lineno+1}: Error: unexpected top-level title "{line}"!') + report('structure/unexpected_section', f'{file}:{lineno+1}', f'unexpected top-level title "{line}"') h1sections = 1 # Overview pages should have a better title if line == '# Overview': - print(f'{file}:{lineno+1}: Error: overview pages should have a better title!') + report('style/title', f'{file}:{lineno+1}', 'overview pages should have a better title than "Overview"') # lines longer than 160 characters are bad (unless they are tables) if len(line) > 160 and '|' not in line: - print(f'{file}:{lineno+1}: Error: line is too long ({len(line)} vs. 160 chars)!') + report('whitespace/line_length', f'{file}:{lineno+1}', f'line is too long ({len(line)} vs. 160 chars)') - # check if headers are correct + # check if sections are correct if line.startswith('## '): - header = line.strip('## ') - existing_headers.append(header) + current_section = line.strip('## ') + existing_sections.append(current_section) - if header in expected_headers: - idx = expected_headers.index(header) - if idx <= header_idx: - print(f'{file}:{lineno+1}: Error: header "{header}" is in an unexpected order (should be before "{expected_headers[header_idx]}")!') - header_idx = idx + if current_section in expected_sections: + idx = expected_sections.index(current_section) + if idx <= section_idx: + report('structure/section_order', f'{file}:{lineno+1}', f'section "{current_section}" is in an unexpected order (should be before "{expected_sections[section_idx]}")') + section_idx = idx else: - print(f'{file}:{lineno+1}: Error: header "{header}" is not part of the expected headers!') + report('structure/unknown_section', f'{file}:{lineno+1}', f'section "{current_section}" is not part of the expected sections') # code example - if line == '```cpp' and header_idx == -1: + if line == '```cpp' and section_idx == -1: in_initial_code_example = True if in_initial_code_example and line.startswith('//'): if any(map(str.isdigit, line)) and '(' not in line: - print(f'{file}:{lineno+1}: Number should be in parentheses: {line}') + report('style/numbering', f'{file}:{lineno+1}', 'number should be in parentheses: {line}') if line == '```' and in_initial_code_example: in_initial_code_example = False # consecutive blank lines are bad if line == '' and previous_line == '': - print(f'{file}:{lineno}-{lineno+1}: Error: Consecutive blank lines!') + report('whitespace/blank_lines', f'{file}:{lineno}-{lineno+1}', 'consecutive blank lines') + + # check that non-example admonitions have titles + untitled_admonition = re.match(r'^(\?\?\?|!!!) ([^ ]+)$', line) + if untitled_admonition and untitled_admonition.group(2) != 'example': + report('style/admonition_title', f'{file}:{lineno}', f'"{untitled_admonition.group(2)}" admonitions should have a title') previous_line = line - for required_header in required_headers: - if required_header not in existing_headers: - print(f'{file}:{lineno+1}: Error: required header "{required_header}" was not found!') + for required_section in required_sections: + if required_section not in existing_sections: + report('structure/missing_section', f'{file}:{lineno+1}', f'required section "{required_section}" was not found') def check_examples(): @@ -113,9 +128,11 @@ def check_examples(): break if not found: - print(f'{example_file}: Error: example file is not used in any documentation file!') + report('examples/missing', f'{example_file}', 'example file is not used in any documentation file') if __name__ == '__main__': + print(120 * '-') check_structure() check_examples() + print(120 * '-') From ee516614813c4ef5762d436f26f2138f09c96410 Mon Sep 17 00:00:00 2001 From: Qianqian Fang Date: Fri, 29 Apr 2022 15:17:30 -0400 Subject: [PATCH 04/30] Support UBJSON-derived Binary JData (BJData) format (#3336) * support UBJSON-derived Binary JData (BJData) format * fix Codacy warning * partially fix VS compilation errors * fix additional VS errors * fix more VS compilation errors * fix additional warnings and errors for clang and msvc * add more tests to cover the new bjdata types * add tests for optimized ndarray, improve coverage, fix clang/gcc warnings * gcc warn useless conversion but msvc gives an error * fix ci_test errors * complete test coverage, fix ci_test errors * add half precision error test * fix No newline at end of file error by clang * simplify endian condition, format unit-bjdata * remove broken test due to alloc limit * full coverage, I hope * move bjdata new markers from default to the same level as ubjson markers * fix ci errors, add tests for new bjdata switch structure * make is_bjdata const after using initializer list * remove the unwanted assert * move is_bjdata to an optional param to write_ubjson * pass use_bjdata via output adapter * revert order to avoid msvc 2015 unreferenced formal param error * update BJData Spect V1 Draft-2 URL after spec release * amalgamate code * code polishing following @gregmarr's feedback * make use_bjdata a non-default parameter * fix ci error, remove unwanted param comment * encode and decode bjdata ndarray in jdata annotations, enable roundtrip tests * partially fix ci errors, add tests to improve coverage * polish patch to remove ci errors * fix a ndarray dim vector condition * fix clang tidy error * add sax test cases for ndarray * add additional sax event tests * adjust sax event numbering * fix sax tests * ndarray can only be used with array containers, discard if used in object * complete test coverage * disable [{SHTFNZ in optimized type due to security risks in #2793 and hampered readability * fix ci error * move OutputIsLittleEndian from tparam to param to replace use_bjdata * fix ci clang gcc error * fix ci static analysis error * update json_test_data to 3.1.0, enable file-based bjdata unit tests * fix stack overflow error on msvc 2019 and 2022 * use https link, update sax_parse_error after rebase * make input_format const and use initializer * return bool for write_bjdata_ndarray * test write_bjdata_ndarray return value as boolean * fix ci error --- README.md | 6 +- cmake/ci.cmake | 2 +- cmake/download_test_data.cmake | 2 +- .../nlohmann/detail/input/binary_reader.hpp | 420 ++- .../nlohmann/detail/input/input_adapters.hpp | 2 +- .../nlohmann/detail/output/binary_writer.hpp | 291 +- include/nlohmann/json.hpp | 111 +- single_include/nlohmann/json.hpp | 824 +++- test/CMakeLists.txt | 2 +- test/Makefile | 5 +- test/src/fuzzer-parse_bjdata.cpp | 84 + test/src/unit-bjdata.cpp | 3355 +++++++++++++++++ 12 files changed, 4877 insertions(+), 227 deletions(-) create mode 100644 test/src/fuzzer-parse_bjdata.cpp create mode 100644 test/src/unit-bjdata.cpp diff --git a/README.md b/README.md index 20e7c48ea..c3990a69b 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ - [Implicit conversions](#implicit-conversions) - [Conversions to/from arbitrary types](#arbitrary-types-conversions) - [Specializing enum conversion](#specializing-enum-conversion) - - [Binary formats (BSON, CBOR, MessagePack, and UBJSON)](#binary-formats-bson-cbor-messagepack-and-ubjson) + - [Binary formats (BSON, CBOR, MessagePack, UBJSON, and BJData)](#binary-formats-bson-cbor-messagepack-ubjson-and-bjdata) - [Supported compilers](#supported-compilers) - [Integration](#integration) - [CMake](#cmake) @@ -961,9 +961,9 @@ Other Important points: - When using `get()`, undefined JSON values will default to the first pair specified in your map. Select this default pair carefully. - If an enum or JSON value is specified more than once in your map, the first matching occurrence from the top of the map will be returned when converting to or from JSON. -### Binary formats (BSON, CBOR, MessagePack, and UBJSON) +### Binary formats (BSON, CBOR, MessagePack, UBJSON, and BJData) -Though JSON is a ubiquitous data format, it is not a very compact format suitable for data exchange, for instance over a network. Hence, the library supports [BSON](https://bsonspec.org) (Binary JSON), [CBOR](https://cbor.io) (Concise Binary Object Representation), [MessagePack](https://msgpack.org), and [UBJSON](https://ubjson.org) (Universal Binary JSON Specification) to efficiently encode JSON values to byte vectors and to decode such vectors. +Though JSON is a ubiquitous data format, it is not a very compact format suitable for data exchange, for instance over a network. Hence, the library supports [BSON](https://bsonspec.org) (Binary JSON), [CBOR](https://cbor.io) (Concise Binary Object Representation), [MessagePack](https://msgpack.org), [UBJSON](https://ubjson.org) (Universal Binary JSON Specification) and [BJData](https://neurojson.org/bjdata) (Binary JData) to efficiently encode JSON values to byte vectors and to decode such vectors. ```cpp // create a JSON value diff --git a/cmake/ci.cmake b/cmake/ci.cmake index be564fa99..5ae05a9d9 100644 --- a/cmake/ci.cmake +++ b/cmake/ci.cmake @@ -683,7 +683,7 @@ add_custom_target(ci_infer add_custom_target(ci_offline_testdata COMMAND mkdir -p ${PROJECT_BINARY_DIR}/build_offline_testdata/test_data - COMMAND cd ${PROJECT_BINARY_DIR}/build_offline_testdata/test_data && ${GIT_TOOL} clone -c advice.detachedHead=false --branch v3.0.0 https://github.com/nlohmann/json_test_data.git --quiet --depth 1 + COMMAND cd ${PROJECT_BINARY_DIR}/build_offline_testdata/test_data && ${GIT_TOOL} clone -c advice.detachedHead=false --branch v3.1.0 https://github.com/nlohmann/json_test_data.git --quiet --depth 1 COMMAND ${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE=Debug -GNinja -DJSON_BuildTests=ON -DJSON_FastTests=ON -DJSON_TestDataDirectory=${PROJECT_BINARY_DIR}/build_offline_testdata/test_data/json_test_data diff --git a/cmake/download_test_data.cmake b/cmake/download_test_data.cmake index f516a7c3b..1bb998dae 100644 --- a/cmake/download_test_data.cmake +++ b/cmake/download_test_data.cmake @@ -1,5 +1,5 @@ set(JSON_TEST_DATA_URL https://github.com/nlohmann/json_test_data) -set(JSON_TEST_DATA_VERSION 3.0.0) +set(JSON_TEST_DATA_VERSION 3.1.0) # if variable is set, use test data from given directory rather than downloading them if(JSON_TestDataDirectory) diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index 4041cedba..6474b8b05 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -12,6 +12,7 @@ #include // char_traits, string #include // make_pair, move #include // vector +#include // map #include #include @@ -74,7 +75,7 @@ class binary_reader @param[in] adapter input adapter to read from */ - explicit binary_reader(InputAdapterType&& adapter) noexcept : ia(std::move(adapter)) + explicit binary_reader(InputAdapterType&& adapter, const input_format_t format = input_format_t::json) noexcept : ia(std::move(adapter)), input_format(format) { (void)detail::is_sax_static_asserts {}; } @@ -118,6 +119,7 @@ class binary_reader break; case input_format_t::ubjson: + case input_format_t::bjdata: result = parse_ubjson_internal(); break; @@ -129,7 +131,7 @@ class binary_reader // strict mode: next byte must be EOF if (result && strict) { - if (format == input_format_t::ubjson) + if (input_format == input_format_t::ubjson || input_format == input_format_t::bjdata) { get_ignore_noop(); } @@ -141,7 +143,7 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY(current != std::char_traits::eof())) { return sax->parse_error(chars_read, get_token_string(), parse_error::create(110, chars_read, - exception_message(format, concat("expected end of input; last byte: 0x", get_token_string()), "value"), nullptr)); + exception_message(input_format, concat("expected end of input; last byte: 0x", get_token_string()), "value"), nullptr)); } } @@ -1844,7 +1846,7 @@ class binary_reader get(); // TODO(niels): may we ignore N here? } - if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "value"))) + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "value"))) { return false; } @@ -1854,52 +1856,154 @@ class binary_reader case 'U': { std::uint8_t len{}; - return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); + return get_number(input_format, len) && get_string(input_format, len, result); } case 'i': { std::int8_t len{}; - return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); + return get_number(input_format, len) && get_string(input_format, len, result); } case 'I': { std::int16_t len{}; - return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); + return get_number(input_format, len) && get_string(input_format, len, result); } case 'l': { std::int32_t len{}; - return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); + return get_number(input_format, len) && get_string(input_format, len, result); } case 'L': { std::int64_t len{}; - return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); + return get_number(input_format, len) && get_string(input_format, len, result); + } + + case 'u': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint16_t len{}; + return get_number(input_format, len) && get_string(input_format, len, result); + } + + case 'm': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint32_t len{}; + return get_number(input_format, len) && get_string(input_format, len, result); + } + + case 'M': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint64_t len{}; + return get_number(input_format, len) && get_string(input_format, len, result); } default: - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(input_format_t::ubjson, concat("expected length type specification (U, i, I, l, L); last byte: 0x", last_token), "string"), nullptr)); + break; } + auto last_token = get_token_string(); + std::string message; + + if (input_format != input_format_t::bjdata) + { + message = "expected length type specification (U, i, I, l, L); last byte: 0x" + last_token; + } + else + { + message = "expected length type specification (U, i, u, I, m, l, M, L); last byte: 0x" + last_token; + } + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format, message, "string"), nullptr)); + } + + /*! + @param[out] dim an integer vector storing the ND array dimensions + @return whether reading ND array size vector is successful + */ + bool get_ubjson_ndarray_size(std::vector& dim) + { + std::pair size_and_type; + size_t dimlen = 0; + + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type))) + { + return false; + } + + if (size_and_type.first != string_t::npos) + { + if (size_and_type.second != 0) + { + if (size_and_type.second != 'N') + { + for (std::size_t i = 0; i < size_and_type.first; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, size_and_type.second))) + { + return false; + } + dim.push_back(dimlen); + } + } + } + else + { + for (std::size_t i = 0; i < size_and_type.first; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen))) + { + return false; + } + dim.push_back(dimlen); + } + } + } + else + { + while (current != ']') + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, current))) + { + return false; + } + dim.push_back(dimlen); + get_ignore_noop(); + } + } + return true; } /*! @param[out] result determined size @return whether size determination completed */ - bool get_ubjson_size_value(std::size_t& result) + bool get_ubjson_size_value(std::size_t& result, char_int_type prefix = 0) { - switch (get_ignore_noop()) + if (prefix == 0) + { + prefix = get_ignore_noop(); + } + + switch (prefix) { case 'U': { std::uint8_t number{}; - if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) { return false; } @@ -1910,7 +2014,7 @@ class binary_reader case 'i': { std::int8_t number{}; - if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) { return false; } @@ -1921,7 +2025,7 @@ class binary_reader case 'I': { std::int16_t number{}; - if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) { return false; } @@ -1932,7 +2036,7 @@ class binary_reader case 'l': { std::int32_t number{}; - if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) { return false; } @@ -1943,7 +2047,7 @@ class binary_reader case 'L': { std::int64_t number{}; - if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) { return false; } @@ -1951,13 +2055,105 @@ class binary_reader return true; } - default: + case 'u': { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(input_format_t::ubjson, concat("expected length type specification (U, i, I, l, L) after '#'; last byte: 0x", last_token), "size"), nullptr)); + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint16_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) + { + return false; + } + result = static_cast(number); + return true; } + + case 'm': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint32_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) + { + return false; + } + result = static_cast(number); + return true; + } + + case 'M': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint64_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) + { + return false; + } + result = detail::conditional_static_cast(number); + return true; + } + + case '[': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::vector dim; + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_ndarray_size(dim))) + { + return false; + } + if (dim.size() == 1 || (dim.size() == 2 && dim.at(0) == 1)) // return normal array size if 1D row vector + { + result = dim.at(dim.size() - 1); + return true; + } + if (!dim.empty()) // if ndarray, convert to an object in JData annotated array format + { + string_t key = "_ArraySize_"; + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(3) || !sax->key(key) || !sax->start_array(dim.size()))) + { + return false; + } + result = 1; + for (auto i : dim) + { + result *= i; + if (JSON_HEDLEY_UNLIKELY(!sax->number_integer(static_cast(i)))) + { + return false; + } + } + result |= (1ull << (sizeof(result) * 8 - 1)); // low 63 bit of result stores the total element count, sign-bit indicates ndarray + return sax->end_array(); + } + result = 0; + return true; + } + + default: + break; } + auto last_token = get_token_string(); + std::string message; + + if (input_format != input_format_t::bjdata) + { + message = "expected length type specification (U, i, I, l, L) after '#'; last byte: 0x" + last_token; + } + else + { + message = "expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x" + last_token; + } + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format, message, "size"), nullptr)); } /*! @@ -1979,8 +2175,10 @@ class binary_reader if (current == '$') { + std::vector bjdx = {'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z'}; // excluded markers in bjdata optimized type + result.second = get(); // must not ignore 'N', because 'N' maybe the type - if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "type"))) + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "type") || (input_format == input_format_t::bjdata && std::find(bjdx.begin(), bjdx.end(), result.second) != bjdx.end() ))) { return false; } @@ -1988,13 +2186,13 @@ class binary_reader get_ignore_noop(); if (JSON_HEDLEY_UNLIKELY(current != '#')) { - if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "value"))) + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "value"))) { return false; } auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(input_format_t::ubjson, concat("expected '#' after type information; last byte: 0x", last_token), "size"), nullptr)); + exception_message(input_format, concat("expected '#' after type information; last byte: 0x", last_token), "size"), nullptr)); } return get_ubjson_size_value(result.first); @@ -2017,7 +2215,7 @@ class binary_reader switch (prefix) { case std::char_traits::eof(): // EOF - return unexpect_eof(input_format_t::ubjson, "value"); + return unexpect_eof(input_format, "value"); case 'T': // true return sax->boolean(true); @@ -2030,43 +2228,125 @@ class binary_reader case 'U': { std::uint8_t number{}; - return get_number(input_format_t::ubjson, number) && sax->number_unsigned(number); + return get_number(input_format, number) && sax->number_unsigned(number); } case 'i': { std::int8_t number{}; - return get_number(input_format_t::ubjson, number) && sax->number_integer(number); + return get_number(input_format, number) && sax->number_integer(number); } case 'I': { std::int16_t number{}; - return get_number(input_format_t::ubjson, number) && sax->number_integer(number); + return get_number(input_format, number) && sax->number_integer(number); } case 'l': { std::int32_t number{}; - return get_number(input_format_t::ubjson, number) && sax->number_integer(number); + return get_number(input_format, number) && sax->number_integer(number); } case 'L': { std::int64_t number{}; - return get_number(input_format_t::ubjson, number) && sax->number_integer(number); + return get_number(input_format, number) && sax->number_integer(number); + } + + case 'u': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint16_t number{}; + return get_number(input_format, number) && sax->number_unsigned(number); + } + + case 'm': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint32_t number{}; + return get_number(input_format, number) && sax->number_unsigned(number); + } + + case 'M': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint64_t number{}; + return get_number(input_format, number) && sax->number_unsigned(number); + } + + case 'h': + { + if (input_format != input_format_t::bjdata) + { + break; + } + const auto byte1_raw = get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "number"))) + { + return false; + } + const auto byte2_raw = get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "number"))) + { + return false; + } + + const auto byte1 = static_cast(byte1_raw); + const auto byte2 = static_cast(byte2_raw); + + // code from RFC 7049, Appendix D, Figure 3: + // As half-precision floating-point numbers were only added + // to IEEE 754 in 2008, today's programming platforms often + // still only have limited support for them. It is very + // easy to include at least decoding support for them even + // without such support. An example of a small decoder for + // half-precision floating-point numbers in the C language + // is shown in Fig. 3. + const auto half = static_cast((byte2 << 8u) + byte1); + const double val = [&half] + { + const int exp = (half >> 10u) & 0x1Fu; + const unsigned int mant = half & 0x3FFu; + JSON_ASSERT(0 <= exp&& exp <= 32); + JSON_ASSERT(mant <= 1024); + switch (exp) + { + case 0: + return std::ldexp(mant, -24); + case 31: + return (mant == 0) + ? std::numeric_limits::infinity() + : std::numeric_limits::quiet_NaN(); + default: + return std::ldexp(mant + 1024, exp - 25); + } + }(); + return sax->number_float((half & 0x8000u) != 0 + ? static_cast(-val) + : static_cast(val), ""); } case 'd': { float number{}; - return get_number(input_format_t::ubjson, number) && sax->number_float(static_cast(number), ""); + return get_number(input_format, number) && sax->number_float(static_cast(number), ""); } case 'D': { double number{}; - return get_number(input_format_t::ubjson, number) && sax->number_float(static_cast(number), ""); + return get_number(input_format, number) && sax->number_float(static_cast(number), ""); } case 'H': @@ -2077,7 +2357,7 @@ class binary_reader case 'C': // char { get(); - if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "char"))) + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "char"))) { return false; } @@ -2085,7 +2365,7 @@ class binary_reader { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(input_format_t::ubjson, concat("byte after 'C' must be in range 0x00..0x7F; last byte: 0x", last_token), "char"), nullptr)); + exception_message(input_format, concat("byte after 'C' must be in range 0x00..0x7F; last byte: 0x", last_token), "char"), nullptr)); } string_t s(1, static_cast(current)); return sax->string(s); @@ -2104,12 +2384,10 @@ class binary_reader return get_ubjson_object(); default: // anything else - { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(input_format_t::ubjson, concat("invalid byte: 0x", last_token), "value"), nullptr)); - } + break; } + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format, "invalid byte: 0x" + last_token, "value"), nullptr)); } /*! @@ -2123,6 +2401,44 @@ class binary_reader return false; } + // detect and encode bjdata ndarray as an object in JData annotated array format (https://github.com/NeuroJSON/jdata): + // {"_ArrayType_" : "typeid", "_ArraySize_" : [n1, n2, ...], "_ArrayData_" : [v1, v2, ...]} + + if (input_format == input_format_t::bjdata && size_and_type.first != string_t::npos && size_and_type.first >= (1ull << (sizeof(std::size_t) * 8 - 1))) + { + std::map bjdtype = {{'U', "uint8"}, {'i', "int8"}, {'u', "uint16"}, {'I', "int16"}, + {'m', "uint32"}, {'l', "int32"}, {'M', "uint64"}, {'L', "int64"}, {'d', "single"}, {'D', "double"}, {'C', "char"} + }; + + string_t key = "_ArrayType_"; + if (JSON_HEDLEY_UNLIKELY(bjdtype.count(size_and_type.second) == 0 || !sax->key(key) || !sax->string(bjdtype[size_and_type.second]) )) + { + return false; + } + + if (size_and_type.second == 'C') + { + size_and_type.second = 'U'; + } + + size_and_type.first &= ~(1ull << (sizeof(std::size_t) * 8 - 1)); + key = "_ArrayData_"; + if (JSON_HEDLEY_UNLIKELY(!sax->key(key) || !sax->start_array(size_and_type.first) )) + { + return false; + } + + for (std::size_t i = 0; i < size_and_type.first; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second))) + { + return false; + } + } + + return (sax->end_array() && sax->end_object()); + } + if (size_and_type.first != string_t::npos) { if (JSON_HEDLEY_UNLIKELY(!sax->start_array(size_and_type.first))) @@ -2185,6 +2501,11 @@ class binary_reader return false; } + if (input_format == input_format_t::bjdata && size_and_type.first != string_t::npos && size_and_type.first >= (1ull << (sizeof(std::size_t) * 8 - 1))) + { + return false; + } + string_t key; if (size_and_type.first != string_t::npos) { @@ -2267,7 +2588,7 @@ class binary_reader for (std::size_t i = 0; i < size; ++i) { get(); - if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "number"))) + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "number"))) { return false; } @@ -2286,7 +2607,7 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY(result_remainder != token_type::end_of_input)) { return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read, - exception_message(input_format_t::ubjson, concat("invalid number text: ", number_lexer.get_token_string()), "high-precision number"), nullptr)); + exception_message(input_format, concat("invalid number text: ", number_lexer.get_token_string()), "high-precision number"), nullptr)); } switch (result_number) @@ -2313,7 +2634,7 @@ class binary_reader case token_type::literal_or_value: default: return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read, - exception_message(input_format_t::ubjson, concat("invalid number text: ", number_lexer.get_token_string()), "high-precision number"), nullptr)); + exception_message(input_format, concat("invalid number text: ", number_lexer.get_token_string()), "high-precision number"), nullptr)); } } @@ -2362,6 +2683,8 @@ class binary_reader @note This function needs to respect the system's endianness, because bytes in CBOR, MessagePack, and UBJSON are stored in network order (big endian) and therefore need reordering on little endian systems. + On the other hand, BSON and BJData use little endian and should reorder + on big endian systems. */ template bool get_number(const input_format_t format, NumberType& result) @@ -2377,7 +2700,7 @@ class binary_reader } // reverse byte order prior to conversion if necessary - if (is_little_endian != InputIsLittleEndian) + if (is_little_endian != (InputIsLittleEndian || format == input_format_t::bjdata)) { vec[sizeof(NumberType) - i - 1] = static_cast(current); } @@ -2514,6 +2837,10 @@ class binary_reader error_msg += "BSON"; break; + case input_format_t::bjdata: + error_msg += "BJData"; + break; + case input_format_t::json: // LCOV_EXCL_LINE default: // LCOV_EXCL_LINE JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE @@ -2535,6 +2862,9 @@ class binary_reader /// whether we can assume little endianness const bool is_little_endian = little_endianness(); + /// input format + const input_format_t input_format = input_format_t::json; + /// the SAX parser json_sax_t* sax = nullptr; }; diff --git a/include/nlohmann/detail/input/input_adapters.hpp b/include/nlohmann/detail/input/input_adapters.hpp index d196aec54..4c1ad3cef 100644 --- a/include/nlohmann/detail/input/input_adapters.hpp +++ b/include/nlohmann/detail/input/input_adapters.hpp @@ -23,7 +23,7 @@ namespace nlohmann namespace detail { /// the supported input formats -enum class input_format_t { json, cbor, msgpack, ubjson, bson }; +enum class input_format_t { json, cbor, msgpack, ubjson, bson, bjdata }; //////////////////// // input adapters // diff --git a/include/nlohmann/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp index c018b8ccd..bb44a1811 100644 --- a/include/nlohmann/detail/output/binary_writer.hpp +++ b/include/nlohmann/detail/output/binary_writer.hpp @@ -2,12 +2,14 @@ #include // reverse #include // array +#include // map #include // isnan, isinf #include // uint8_t, uint16_t, uint32_t, uint64_t #include // memcpy #include // numeric_limits #include // string #include // move +#include // vector #include #include @@ -724,9 +726,11 @@ class binary_writer @param[in] use_count whether to use '#' prefixes (optimized format) @param[in] use_type whether to use '$' prefixes (optimized format) @param[in] add_prefix whether prefixes need to be used for this value + @param[in] use_bjdata whether write in BJData format, default is false */ void write_ubjson(const BasicJsonType& j, const bool use_count, - const bool use_type, const bool add_prefix = true) + const bool use_type, const bool add_prefix = true, + const bool use_bjdata = false) { switch (j.type()) { @@ -752,19 +756,19 @@ class binary_writer case value_t::number_integer: { - write_number_with_ubjson_prefix(j.m_value.number_integer, add_prefix); + write_number_with_ubjson_prefix(j.m_value.number_integer, add_prefix, use_bjdata); break; } case value_t::number_unsigned: { - write_number_with_ubjson_prefix(j.m_value.number_unsigned, add_prefix); + write_number_with_ubjson_prefix(j.m_value.number_unsigned, add_prefix, use_bjdata); break; } case value_t::number_float: { - write_number_with_ubjson_prefix(j.m_value.number_float, add_prefix); + write_number_with_ubjson_prefix(j.m_value.number_float, add_prefix, use_bjdata); break; } @@ -774,7 +778,7 @@ class binary_writer { oa->write_character(to_char_type('S')); } - write_number_with_ubjson_prefix(j.m_value.string->size(), true); + write_number_with_ubjson_prefix(j.m_value.string->size(), true, use_bjdata); oa->write_characters( reinterpret_cast(j.m_value.string->c_str()), j.m_value.string->size()); @@ -792,14 +796,16 @@ class binary_writer if (use_type && !j.m_value.array->empty()) { JSON_ASSERT(use_count); - const CharType first_prefix = ubjson_prefix(j.front()); + const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata); const bool same_prefix = std::all_of(j.begin() + 1, j.end(), - [this, first_prefix](const BasicJsonType & v) + [this, first_prefix, use_bjdata](const BasicJsonType & v) { - return ubjson_prefix(v) == first_prefix; + return ubjson_prefix(v, use_bjdata) == first_prefix; }); - if (same_prefix) + std::vector bjdx = {'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z'}; // excluded markers in bjdata optimized type + + if (same_prefix && !(use_bjdata && std::find(bjdx.begin(), bjdx.end(), first_prefix) != bjdx.end())) { prefix_required = false; oa->write_character(to_char_type('$')); @@ -810,12 +816,12 @@ class binary_writer if (use_count) { oa->write_character(to_char_type('#')); - write_number_with_ubjson_prefix(j.m_value.array->size(), true); + write_number_with_ubjson_prefix(j.m_value.array->size(), true, use_bjdata); } for (const auto& el : *j.m_value.array) { - write_ubjson(el, use_count, use_type, prefix_required); + write_ubjson(el, use_count, use_type, prefix_required, use_bjdata); } if (!use_count) @@ -843,7 +849,7 @@ class binary_writer if (use_count) { oa->write_character(to_char_type('#')); - write_number_with_ubjson_prefix(j.m_value.binary->size(), true); + write_number_with_ubjson_prefix(j.m_value.binary->size(), true, use_bjdata); } if (use_type) @@ -871,6 +877,14 @@ class binary_writer case value_t::object: { + if (use_bjdata && j.m_value.object->size() == 3 && j.m_value.object->find("_ArrayType_") != j.m_value.object->end() && j.m_value.object->find("_ArraySize_") != j.m_value.object->end() && j.m_value.object->find("_ArrayData_") != j.m_value.object->end()) + { + if (!write_bjdata_ndarray(*j.m_value.object, use_count, use_type)) // decode bjdata ndarray in the JData format (https://github.com/NeuroJSON/jdata) + { + break; + } + } + if (add_prefix) { oa->write_character(to_char_type('{')); @@ -880,14 +894,16 @@ class binary_writer if (use_type && !j.m_value.object->empty()) { JSON_ASSERT(use_count); - const CharType first_prefix = ubjson_prefix(j.front()); + const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata); const bool same_prefix = std::all_of(j.begin(), j.end(), - [this, first_prefix](const BasicJsonType & v) + [this, first_prefix, use_bjdata](const BasicJsonType & v) { - return ubjson_prefix(v) == first_prefix; + return ubjson_prefix(v, use_bjdata) == first_prefix; }); - if (same_prefix) + std::vector bjdx = {'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z'}; // excluded markers in bjdata optimized type + + if (same_prefix && !(use_bjdata && std::find(bjdx.begin(), bjdx.end(), first_prefix) != bjdx.end())) { prefix_required = false; oa->write_character(to_char_type('$')); @@ -898,16 +914,16 @@ class binary_writer if (use_count) { oa->write_character(to_char_type('#')); - write_number_with_ubjson_prefix(j.m_value.object->size(), true); + write_number_with_ubjson_prefix(j.m_value.object->size(), true, use_bjdata); } for (const auto& el : *j.m_value.object) { - write_number_with_ubjson_prefix(el.first.size(), true); + write_number_with_ubjson_prefix(el.first.size(), true, use_bjdata); oa->write_characters( reinterpret_cast(el.first.c_str()), el.first.size()); - write_ubjson(el.second, use_count, use_type, prefix_required); + write_ubjson(el.second, use_count, use_type, prefix_required, use_bjdata); } if (!use_count) @@ -974,7 +990,7 @@ class binary_writer const double value) { write_bson_entry_header(name, 0x01); - write_number(value); + write_number(value, true); } /*! @@ -993,7 +1009,7 @@ class binary_writer { write_bson_entry_header(name, 0x02); - write_number(static_cast(value.size() + 1ul)); + write_number(static_cast(value.size() + 1ul), true); oa->write_characters( reinterpret_cast(value.c_str()), value.size() + 1); @@ -1026,12 +1042,12 @@ class binary_writer if ((std::numeric_limits::min)() <= value && value <= (std::numeric_limits::max)()) { write_bson_entry_header(name, 0x10); // int32 - write_number(static_cast(value)); + write_number(static_cast(value), true); } else { write_bson_entry_header(name, 0x12); // int64 - write_number(static_cast(value)); + write_number(static_cast(value), true); } } @@ -1054,12 +1070,12 @@ class binary_writer if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) { write_bson_entry_header(name, 0x10 /* int32 */); - write_number(static_cast(j.m_value.number_unsigned)); + write_number(static_cast(j.m_value.number_unsigned), true); } else if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) { write_bson_entry_header(name, 0x12 /* int64 */); - write_number(static_cast(j.m_value.number_unsigned)); + write_number(static_cast(j.m_value.number_unsigned), true); } else { @@ -1107,7 +1123,7 @@ class binary_writer const typename BasicJsonType::array_t& value) { write_bson_entry_header(name, 0x04); // array - write_number(static_cast(calc_bson_array_size(value))); + write_number(static_cast(calc_bson_array_size(value)), true); std::size_t array_index = 0ul; @@ -1127,7 +1143,7 @@ class binary_writer { write_bson_entry_header(name, 0x05); - write_number(static_cast(value.size())); + write_number(static_cast(value.size()), true); write_number(value.has_subtype() ? static_cast(value.subtype()) : static_cast(0x00)); oa->write_characters(reinterpret_cast(value.data()), value.size()); @@ -1249,7 +1265,7 @@ class binary_writer */ void write_bson_object(const typename BasicJsonType::object_t& value) { - write_number(static_cast(calc_bson_object_size(value))); + write_number(static_cast(calc_bson_object_size(value)), true); for (const auto& el : value) { @@ -1295,20 +1311,22 @@ class binary_writer template::value, int>::type = 0> void write_number_with_ubjson_prefix(const NumberType n, - const bool add_prefix) + const bool add_prefix, + const bool use_bjdata) { if (add_prefix) { oa->write_character(get_ubjson_float_prefix(n)); } - write_number(n); + write_number(n, use_bjdata); } // UBJSON: write number (unsigned integer) template::value, int>::type = 0> void write_number_with_ubjson_prefix(const NumberType n, - const bool add_prefix) + const bool add_prefix, + const bool use_bjdata) { if (n <= static_cast((std::numeric_limits::max)())) { @@ -1316,7 +1334,7 @@ class binary_writer { oa->write_character(to_char_type('i')); // int8 } - write_number(static_cast(n)); + write_number(static_cast(n), use_bjdata); } else if (n <= (std::numeric_limits::max)()) { @@ -1324,7 +1342,7 @@ class binary_writer { oa->write_character(to_char_type('U')); // uint8 } - write_number(static_cast(n)); + write_number(static_cast(n), use_bjdata); } else if (n <= static_cast((std::numeric_limits::max)())) { @@ -1332,7 +1350,15 @@ class binary_writer { oa->write_character(to_char_type('I')); // int16 } - write_number(static_cast(n)); + write_number(static_cast(n), use_bjdata); + } + else if (use_bjdata && n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(to_char_type('u')); // uint16 - bjdata only + } + write_number(static_cast(n), use_bjdata); } else if (n <= static_cast((std::numeric_limits::max)())) { @@ -1340,7 +1366,15 @@ class binary_writer { oa->write_character(to_char_type('l')); // int32 } - write_number(static_cast(n)); + write_number(static_cast(n), use_bjdata); + } + else if (use_bjdata && n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(to_char_type('m')); // uint32 - bjdata only + } + write_number(static_cast(n), use_bjdata); } else if (n <= static_cast((std::numeric_limits::max)())) { @@ -1348,7 +1382,15 @@ class binary_writer { oa->write_character(to_char_type('L')); // int64 } - write_number(static_cast(n)); + write_number(static_cast(n), use_bjdata); + } + else if (use_bjdata && n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(to_char_type('M')); // uint64 - bjdata only + } + write_number(static_cast(n), use_bjdata); } else { @@ -1358,7 +1400,7 @@ class binary_writer } const auto number = BasicJsonType(n).dump(); - write_number_with_ubjson_prefix(number.size(), true); + write_number_with_ubjson_prefix(number.size(), true, use_bjdata); for (std::size_t i = 0; i < number.size(); ++i) { oa->write_character(to_char_type(static_cast(number[i]))); @@ -1371,7 +1413,8 @@ class binary_writer std::is_signed::value&& !std::is_floating_point::value, int >::type = 0 > void write_number_with_ubjson_prefix(const NumberType n, - const bool add_prefix) + const bool add_prefix, + const bool use_bjdata) { if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) { @@ -1379,7 +1422,7 @@ class binary_writer { oa->write_character(to_char_type('i')); // int8 } - write_number(static_cast(n)); + write_number(static_cast(n), use_bjdata); } else if (static_cast((std::numeric_limits::min)()) <= n && n <= static_cast((std::numeric_limits::max)())) { @@ -1387,7 +1430,7 @@ class binary_writer { oa->write_character(to_char_type('U')); // uint8 } - write_number(static_cast(n)); + write_number(static_cast(n), use_bjdata); } else if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) { @@ -1395,7 +1438,15 @@ class binary_writer { oa->write_character(to_char_type('I')); // int16 } - write_number(static_cast(n)); + write_number(static_cast(n), use_bjdata); + } + else if (use_bjdata && (static_cast((std::numeric_limits::min)()) <= n && n <= static_cast((std::numeric_limits::max)()))) + { + if (add_prefix) + { + oa->write_character(to_char_type('u')); // uint16 - bjdata only + } + write_number(static_cast(n), use_bjdata); } else if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) { @@ -1403,7 +1454,15 @@ class binary_writer { oa->write_character(to_char_type('l')); // int32 } - write_number(static_cast(n)); + write_number(static_cast(n), use_bjdata); + } + else if (use_bjdata && (static_cast((std::numeric_limits::min)()) <= n && n <= static_cast((std::numeric_limits::max)()))) + { + if (add_prefix) + { + oa->write_character(to_char_type('m')); // uint32 - bjdata only + } + write_number(static_cast(n), use_bjdata); } else if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) { @@ -1411,7 +1470,7 @@ class binary_writer { oa->write_character(to_char_type('L')); // int64 } - write_number(static_cast(n)); + write_number(static_cast(n), use_bjdata); } // LCOV_EXCL_START else @@ -1422,7 +1481,7 @@ class binary_writer } const auto number = BasicJsonType(n).dump(); - write_number_with_ubjson_prefix(number.size(), true); + write_number_with_ubjson_prefix(number.size(), true, use_bjdata); for (std::size_t i = 0; i < number.size(); ++i) { oa->write_character(to_char_type(static_cast(number[i]))); @@ -1434,7 +1493,7 @@ class binary_writer /*! @brief determine the type prefix of container values */ - CharType ubjson_prefix(const BasicJsonType& j) const noexcept + CharType ubjson_prefix(const BasicJsonType& j, const bool use_bjdata) const noexcept { switch (j.type()) { @@ -1458,10 +1517,18 @@ class binary_writer { return 'I'; } + if (use_bjdata && ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)())) + { + return 'u'; + } if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) { return 'l'; } + if (use_bjdata && ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)())) + { + return 'm'; + } if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) { return 'L'; @@ -1484,14 +1551,26 @@ class binary_writer { return 'I'; } + if (use_bjdata && j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) + { + return 'u'; + } if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) { return 'l'; } + if (use_bjdata && j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) + { + return 'm'; + } if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) { return 'L'; } + if (use_bjdata && j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + return 'M'; + } // anything else is treated as high-precision number return 'H'; // LCOV_EXCL_LINE } @@ -1525,6 +1604,118 @@ class binary_writer return 'D'; // float 64 } + /*! + @return false if the object is successfully converted to a bjdata ndarray, true if the type or size is invalid + */ + bool write_bjdata_ndarray(const typename BasicJsonType::object_t& value, const bool use_count, const bool use_type) + { + std::map bjdtype = {{"uint8", 'U'}, {"int8", 'i'}, {"uint16", 'u'}, {"int16", 'I'}, + {"uint32", 'm'}, {"int32", 'l'}, {"uint64", 'M'}, {"int64", 'L'}, {"single", 'd'}, {"double", 'D'}, {"char", 'C'} + }; + + string_t key = "_ArrayType_"; + auto it = bjdtype.find(static_cast(value.at(key))); + if (it == bjdtype.end()) + { + return true; + } + CharType dtype = it->second; + + key = "_ArraySize_"; + std::size_t len = (value.at(key).empty() ? 0 : 1); + for (const auto& el : value.at(key)) + { + len *= static_cast(el.m_value.number_unsigned); + } + + key = "_ArrayData_"; + if (value.at(key).size() != len) + { + return true; + } + + oa->write_character('['); + oa->write_character('$'); + oa->write_character(dtype); + oa->write_character('#'); + + key = "_ArraySize_"; + write_ubjson(value.at(key), use_count, use_type, true, true); + + key = "_ArrayData_"; + if (dtype == 'U' || dtype == 'C') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast(el.m_value.number_unsigned), true); + } + } + else if (dtype == 'i') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast(el.m_value.number_integer), true); + } + } + else if (dtype == 'u') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast(el.m_value.number_unsigned), true); + } + } + else if (dtype == 'I') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast(el.m_value.number_integer), true); + } + } + else if (dtype == 'm') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast(el.m_value.number_unsigned), true); + } + } + else if (dtype == 'l') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast(el.m_value.number_integer), true); + } + } + else if (dtype == 'M') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast(el.m_value.number_unsigned), true); + } + } + else if (dtype == 'L') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast(el.m_value.number_integer), true); + } + } + else if (dtype == 'd') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast(el.m_value.number_float), true); + } + } + else if (dtype == 'D') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast(el.m_value.number_float), true); + } + } + return false; + } + /////////////////////// // Utility functions // /////////////////////// @@ -1532,16 +1723,18 @@ class binary_writer /* @brief write a number to output input @param[in] n number of type @a NumberType - @tparam NumberType the type of the number - @tparam OutputIsLittleEndian Set to true if output data is + @param[in] OutputIsLittleEndian Set to true if output data is required to be little endian + @tparam NumberType the type of the number @note This function needs to respect the system's endianness, because bytes in CBOR, MessagePack, and UBJSON are stored in network order (big endian) and therefore need reordering on little endian systems. + On the other hand, BSON and BJData use little endian and should reorder + on big endian systems. */ - template - void write_number(const NumberType n) + template + void write_number(const NumberType n, const bool OutputIsLittleEndian = false) { // step 1: write number to array of length NumberType std::array vec{}; diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 1a2da8d45..0a18f1634 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -3773,7 +3773,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec auto ia = detail::input_adapter(std::forward(i)); return format == input_format_t::json ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) - : detail::binary_reader(std::move(ia)).sax_parse(format, sax, strict); + : detail::binary_reader(std::move(ia), format).sax_parse(format, sax, strict); } /// @brief generate SAX events @@ -3788,7 +3788,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec auto ia = detail::input_adapter(std::move(first), std::move(last)); return format == input_format_t::json ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) - : detail::binary_reader(std::move(ia)).sax_parse(format, sax, strict); + : detail::binary_reader(std::move(ia), format).sax_parse(format, sax, strict); } /// @brief generate SAX events @@ -3809,7 +3809,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - : detail::binary_reader(std::move(ia)).sax_parse(format, sax, strict); + : detail::binary_reader(std::move(ia), format).sax_parse(format, sax, strict); } #ifndef JSON_NO_IO /// @brief deserialize from stream @@ -3965,6 +3965,33 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec binary_writer(o).write_ubjson(j, use_size, use_type); } + /// @brief create a BJData serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ + static std::vector to_bjdata(const basic_json& j, + const bool use_size = false, + const bool use_type = false) + { + std::vector result; + to_bjdata(j, result, use_size, use_type); + return result; + } + + /// @brief create a BJData serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ + static void to_bjdata(const basic_json& j, detail::output_adapter o, + const bool use_size = false, const bool use_type = false) + { + binary_writer(o).write_ubjson(j, use_size, use_type, true, true); + } + + /// @brief create a BJData serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ + static void to_bjdata(const basic_json& j, detail::output_adapter o, + const bool use_size = false, const bool use_type = false) + { + binary_writer(o).write_ubjson(j, use_size, use_type, true, true); + } + /// @brief create a BSON serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_bson/ static std::vector to_bson(const basic_json& j) @@ -4000,7 +4027,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); + const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); return res ? result : basic_json(value_t::discarded); } @@ -4016,7 +4043,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); + const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); return res ? result : basic_json(value_t::discarded); } @@ -4043,7 +4070,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); + const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); return res ? result : basic_json(value_t::discarded); } @@ -4058,7 +4085,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::msgpack, &sdp, strict); + const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); return res ? result : basic_json(value_t::discarded); } @@ -4073,7 +4100,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::msgpack, &sdp, strict); + const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); return res ? result : basic_json(value_t::discarded); } @@ -4097,7 +4124,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::msgpack, &sdp, strict); + const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); return res ? result : basic_json(value_t::discarded); } @@ -4112,7 +4139,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::ubjson, &sdp, strict); + const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); return res ? result : basic_json(value_t::discarded); } @@ -4127,7 +4154,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::ubjson, &sdp, strict); + const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); return res ? result : basic_json(value_t::discarded); } @@ -4151,10 +4178,64 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::ubjson, &sdp, strict); + const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); return res ? result : basic_json(value_t::discarded); } + + /// @brief create a JSON value from an input in BJData format + /// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_bjdata(InputType&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::forward(i)); + const bool res = binary_reader(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + /// @brief create a JSON value from an input in BJData format + /// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_bjdata(IteratorType first, IteratorType last, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::move(first), std::move(last)); + const bool res = binary_reader(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_bjdata(const T* ptr, std::size_t len, + const bool strict = true, + const bool allow_exceptions = true) + { + return from_bjdata(ptr, ptr + len, strict, allow_exceptions); + } + + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_bjdata(detail::span_input_adapter&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = i.get(); + // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) + const bool res = binary_reader(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + /// @brief create a JSON value from an input in BSON format /// @sa https://json.nlohmann.me/api/basic_json/from_bson/ template @@ -4166,7 +4247,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::bson, &sdp, strict); + const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); return res ? result : basic_json(value_t::discarded); } @@ -4181,7 +4262,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::bson, &sdp, strict); + const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); return res ? result : basic_json(value_t::discarded); } @@ -4205,7 +4286,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::bson, &sdp, strict); + const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); return res ? result : basic_json(value_t::discarded); } /// @} diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index a29c52997..d596180c6 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -5402,6 +5402,7 @@ std::size_t hash(const BasicJsonType& j) #include // char_traits, string #include // make_pair, move #include // vector +#include // map // #include @@ -5433,7 +5434,7 @@ namespace nlohmann namespace detail { /// the supported input formats -enum class input_format_t { json, cbor, msgpack, ubjson, bson }; +enum class input_format_t { json, cbor, msgpack, ubjson, bson, bjdata }; //////////////////// // input adapters // @@ -8450,7 +8451,7 @@ class binary_reader @param[in] adapter input adapter to read from */ - explicit binary_reader(InputAdapterType&& adapter) noexcept : ia(std::move(adapter)) + explicit binary_reader(InputAdapterType&& adapter, const input_format_t format = input_format_t::json) noexcept : ia(std::move(adapter)), input_format(format) { (void)detail::is_sax_static_asserts {}; } @@ -8494,6 +8495,7 @@ class binary_reader break; case input_format_t::ubjson: + case input_format_t::bjdata: result = parse_ubjson_internal(); break; @@ -8505,7 +8507,7 @@ class binary_reader // strict mode: next byte must be EOF if (result && strict) { - if (format == input_format_t::ubjson) + if (input_format == input_format_t::ubjson || input_format == input_format_t::bjdata) { get_ignore_noop(); } @@ -8517,7 +8519,7 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY(current != std::char_traits::eof())) { return sax->parse_error(chars_read, get_token_string(), parse_error::create(110, chars_read, - exception_message(format, concat("expected end of input; last byte: 0x", get_token_string()), "value"), nullptr)); + exception_message(input_format, concat("expected end of input; last byte: 0x", get_token_string()), "value"), nullptr)); } } @@ -10220,7 +10222,7 @@ class binary_reader get(); // TODO(niels): may we ignore N here? } - if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "value"))) + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "value"))) { return false; } @@ -10230,52 +10232,154 @@ class binary_reader case 'U': { std::uint8_t len{}; - return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); + return get_number(input_format, len) && get_string(input_format, len, result); } case 'i': { std::int8_t len{}; - return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); + return get_number(input_format, len) && get_string(input_format, len, result); } case 'I': { std::int16_t len{}; - return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); + return get_number(input_format, len) && get_string(input_format, len, result); } case 'l': { std::int32_t len{}; - return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); + return get_number(input_format, len) && get_string(input_format, len, result); } case 'L': { std::int64_t len{}; - return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); + return get_number(input_format, len) && get_string(input_format, len, result); + } + + case 'u': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint16_t len{}; + return get_number(input_format, len) && get_string(input_format, len, result); + } + + case 'm': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint32_t len{}; + return get_number(input_format, len) && get_string(input_format, len, result); + } + + case 'M': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint64_t len{}; + return get_number(input_format, len) && get_string(input_format, len, result); } default: - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(input_format_t::ubjson, concat("expected length type specification (U, i, I, l, L); last byte: 0x", last_token), "string"), nullptr)); + break; } + auto last_token = get_token_string(); + std::string message; + + if (input_format != input_format_t::bjdata) + { + message = "expected length type specification (U, i, I, l, L); last byte: 0x" + last_token; + } + else + { + message = "expected length type specification (U, i, u, I, m, l, M, L); last byte: 0x" + last_token; + } + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format, message, "string"), nullptr)); + } + + /*! + @param[out] dim an integer vector storing the ND array dimensions + @return whether reading ND array size vector is successful + */ + bool get_ubjson_ndarray_size(std::vector& dim) + { + std::pair size_and_type; + size_t dimlen = 0; + + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type))) + { + return false; + } + + if (size_and_type.first != string_t::npos) + { + if (size_and_type.second != 0) + { + if (size_and_type.second != 'N') + { + for (std::size_t i = 0; i < size_and_type.first; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, size_and_type.second))) + { + return false; + } + dim.push_back(dimlen); + } + } + } + else + { + for (std::size_t i = 0; i < size_and_type.first; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen))) + { + return false; + } + dim.push_back(dimlen); + } + } + } + else + { + while (current != ']') + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, current))) + { + return false; + } + dim.push_back(dimlen); + get_ignore_noop(); + } + } + return true; } /*! @param[out] result determined size @return whether size determination completed */ - bool get_ubjson_size_value(std::size_t& result) + bool get_ubjson_size_value(std::size_t& result, char_int_type prefix = 0) { - switch (get_ignore_noop()) + if (prefix == 0) + { + prefix = get_ignore_noop(); + } + + switch (prefix) { case 'U': { std::uint8_t number{}; - if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) { return false; } @@ -10286,7 +10390,7 @@ class binary_reader case 'i': { std::int8_t number{}; - if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) { return false; } @@ -10297,7 +10401,7 @@ class binary_reader case 'I': { std::int16_t number{}; - if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) { return false; } @@ -10308,7 +10412,7 @@ class binary_reader case 'l': { std::int32_t number{}; - if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) { return false; } @@ -10319,7 +10423,7 @@ class binary_reader case 'L': { std::int64_t number{}; - if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) { return false; } @@ -10327,13 +10431,105 @@ class binary_reader return true; } - default: + case 'u': { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(input_format_t::ubjson, concat("expected length type specification (U, i, I, l, L) after '#'; last byte: 0x", last_token), "size"), nullptr)); + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint16_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) + { + return false; + } + result = static_cast(number); + return true; } + + case 'm': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint32_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) + { + return false; + } + result = static_cast(number); + return true; + } + + case 'M': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint64_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) + { + return false; + } + result = detail::conditional_static_cast(number); + return true; + } + + case '[': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::vector dim; + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_ndarray_size(dim))) + { + return false; + } + if (dim.size() == 1 || (dim.size() == 2 && dim.at(0) == 1)) // return normal array size if 1D row vector + { + result = dim.at(dim.size() - 1); + return true; + } + if (!dim.empty()) // if ndarray, convert to an object in JData annotated array format + { + string_t key = "_ArraySize_"; + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(3) || !sax->key(key) || !sax->start_array(dim.size()))) + { + return false; + } + result = 1; + for (auto i : dim) + { + result *= i; + if (JSON_HEDLEY_UNLIKELY(!sax->number_integer(static_cast(i)))) + { + return false; + } + } + result |= (1ull << (sizeof(result) * 8 - 1)); // low 63 bit of result stores the total element count, sign-bit indicates ndarray + return sax->end_array(); + } + result = 0; + return true; + } + + default: + break; } + auto last_token = get_token_string(); + std::string message; + + if (input_format != input_format_t::bjdata) + { + message = "expected length type specification (U, i, I, l, L) after '#'; last byte: 0x" + last_token; + } + else + { + message = "expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x" + last_token; + } + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format, message, "size"), nullptr)); } /*! @@ -10355,8 +10551,10 @@ class binary_reader if (current == '$') { + std::vector bjdx = {'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z'}; // excluded markers in bjdata optimized type + result.second = get(); // must not ignore 'N', because 'N' maybe the type - if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "type"))) + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "type") || (input_format == input_format_t::bjdata && std::find(bjdx.begin(), bjdx.end(), result.second) != bjdx.end() ))) { return false; } @@ -10364,13 +10562,13 @@ class binary_reader get_ignore_noop(); if (JSON_HEDLEY_UNLIKELY(current != '#')) { - if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "value"))) + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "value"))) { return false; } auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(input_format_t::ubjson, concat("expected '#' after type information; last byte: 0x", last_token), "size"), nullptr)); + exception_message(input_format, concat("expected '#' after type information; last byte: 0x", last_token), "size"), nullptr)); } return get_ubjson_size_value(result.first); @@ -10393,7 +10591,7 @@ class binary_reader switch (prefix) { case std::char_traits::eof(): // EOF - return unexpect_eof(input_format_t::ubjson, "value"); + return unexpect_eof(input_format, "value"); case 'T': // true return sax->boolean(true); @@ -10406,43 +10604,125 @@ class binary_reader case 'U': { std::uint8_t number{}; - return get_number(input_format_t::ubjson, number) && sax->number_unsigned(number); + return get_number(input_format, number) && sax->number_unsigned(number); } case 'i': { std::int8_t number{}; - return get_number(input_format_t::ubjson, number) && sax->number_integer(number); + return get_number(input_format, number) && sax->number_integer(number); } case 'I': { std::int16_t number{}; - return get_number(input_format_t::ubjson, number) && sax->number_integer(number); + return get_number(input_format, number) && sax->number_integer(number); } case 'l': { std::int32_t number{}; - return get_number(input_format_t::ubjson, number) && sax->number_integer(number); + return get_number(input_format, number) && sax->number_integer(number); } case 'L': { std::int64_t number{}; - return get_number(input_format_t::ubjson, number) && sax->number_integer(number); + return get_number(input_format, number) && sax->number_integer(number); + } + + case 'u': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint16_t number{}; + return get_number(input_format, number) && sax->number_unsigned(number); + } + + case 'm': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint32_t number{}; + return get_number(input_format, number) && sax->number_unsigned(number); + } + + case 'M': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint64_t number{}; + return get_number(input_format, number) && sax->number_unsigned(number); + } + + case 'h': + { + if (input_format != input_format_t::bjdata) + { + break; + } + const auto byte1_raw = get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "number"))) + { + return false; + } + const auto byte2_raw = get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "number"))) + { + return false; + } + + const auto byte1 = static_cast(byte1_raw); + const auto byte2 = static_cast(byte2_raw); + + // code from RFC 7049, Appendix D, Figure 3: + // As half-precision floating-point numbers were only added + // to IEEE 754 in 2008, today's programming platforms often + // still only have limited support for them. It is very + // easy to include at least decoding support for them even + // without such support. An example of a small decoder for + // half-precision floating-point numbers in the C language + // is shown in Fig. 3. + const auto half = static_cast((byte2 << 8u) + byte1); + const double val = [&half] + { + const int exp = (half >> 10u) & 0x1Fu; + const unsigned int mant = half & 0x3FFu; + JSON_ASSERT(0 <= exp&& exp <= 32); + JSON_ASSERT(mant <= 1024); + switch (exp) + { + case 0: + return std::ldexp(mant, -24); + case 31: + return (mant == 0) + ? std::numeric_limits::infinity() + : std::numeric_limits::quiet_NaN(); + default: + return std::ldexp(mant + 1024, exp - 25); + } + }(); + return sax->number_float((half & 0x8000u) != 0 + ? static_cast(-val) + : static_cast(val), ""); } case 'd': { float number{}; - return get_number(input_format_t::ubjson, number) && sax->number_float(static_cast(number), ""); + return get_number(input_format, number) && sax->number_float(static_cast(number), ""); } case 'D': { double number{}; - return get_number(input_format_t::ubjson, number) && sax->number_float(static_cast(number), ""); + return get_number(input_format, number) && sax->number_float(static_cast(number), ""); } case 'H': @@ -10453,7 +10733,7 @@ class binary_reader case 'C': // char { get(); - if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "char"))) + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "char"))) { return false; } @@ -10461,7 +10741,7 @@ class binary_reader { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(input_format_t::ubjson, concat("byte after 'C' must be in range 0x00..0x7F; last byte: 0x", last_token), "char"), nullptr)); + exception_message(input_format, concat("byte after 'C' must be in range 0x00..0x7F; last byte: 0x", last_token), "char"), nullptr)); } string_t s(1, static_cast(current)); return sax->string(s); @@ -10480,12 +10760,10 @@ class binary_reader return get_ubjson_object(); default: // anything else - { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(input_format_t::ubjson, concat("invalid byte: 0x", last_token), "value"), nullptr)); - } + break; } + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format, "invalid byte: 0x" + last_token, "value"), nullptr)); } /*! @@ -10499,6 +10777,44 @@ class binary_reader return false; } + // detect and encode bjdata ndarray as an object in JData annotated array format (https://github.com/NeuroJSON/jdata): + // {"_ArrayType_" : "typeid", "_ArraySize_" : [n1, n2, ...], "_ArrayData_" : [v1, v2, ...]} + + if (input_format == input_format_t::bjdata && size_and_type.first != string_t::npos && size_and_type.first >= (1ull << (sizeof(std::size_t) * 8 - 1))) + { + std::map bjdtype = {{'U', "uint8"}, {'i', "int8"}, {'u', "uint16"}, {'I', "int16"}, + {'m', "uint32"}, {'l', "int32"}, {'M', "uint64"}, {'L', "int64"}, {'d', "single"}, {'D', "double"}, {'C', "char"} + }; + + string_t key = "_ArrayType_"; + if (JSON_HEDLEY_UNLIKELY(bjdtype.count(size_and_type.second) == 0 || !sax->key(key) || !sax->string(bjdtype[size_and_type.second]) )) + { + return false; + } + + if (size_and_type.second == 'C') + { + size_and_type.second = 'U'; + } + + size_and_type.first &= ~(1ull << (sizeof(std::size_t) * 8 - 1)); + key = "_ArrayData_"; + if (JSON_HEDLEY_UNLIKELY(!sax->key(key) || !sax->start_array(size_and_type.first) )) + { + return false; + } + + for (std::size_t i = 0; i < size_and_type.first; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second))) + { + return false; + } + } + + return (sax->end_array() && sax->end_object()); + } + if (size_and_type.first != string_t::npos) { if (JSON_HEDLEY_UNLIKELY(!sax->start_array(size_and_type.first))) @@ -10561,6 +10877,11 @@ class binary_reader return false; } + if (input_format == input_format_t::bjdata && size_and_type.first != string_t::npos && size_and_type.first >= (1ull << (sizeof(std::size_t) * 8 - 1))) + { + return false; + } + string_t key; if (size_and_type.first != string_t::npos) { @@ -10643,7 +10964,7 @@ class binary_reader for (std::size_t i = 0; i < size; ++i) { get(); - if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "number"))) + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "number"))) { return false; } @@ -10662,7 +10983,7 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY(result_remainder != token_type::end_of_input)) { return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read, - exception_message(input_format_t::ubjson, concat("invalid number text: ", number_lexer.get_token_string()), "high-precision number"), nullptr)); + exception_message(input_format, concat("invalid number text: ", number_lexer.get_token_string()), "high-precision number"), nullptr)); } switch (result_number) @@ -10689,7 +11010,7 @@ class binary_reader case token_type::literal_or_value: default: return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read, - exception_message(input_format_t::ubjson, concat("invalid number text: ", number_lexer.get_token_string()), "high-precision number"), nullptr)); + exception_message(input_format, concat("invalid number text: ", number_lexer.get_token_string()), "high-precision number"), nullptr)); } } @@ -10738,6 +11059,8 @@ class binary_reader @note This function needs to respect the system's endianness, because bytes in CBOR, MessagePack, and UBJSON are stored in network order (big endian) and therefore need reordering on little endian systems. + On the other hand, BSON and BJData use little endian and should reorder + on big endian systems. */ template bool get_number(const input_format_t format, NumberType& result) @@ -10753,7 +11076,7 @@ class binary_reader } // reverse byte order prior to conversion if necessary - if (is_little_endian != InputIsLittleEndian) + if (is_little_endian != (InputIsLittleEndian || format == input_format_t::bjdata)) { vec[sizeof(NumberType) - i - 1] = static_cast(current); } @@ -10890,6 +11213,10 @@ class binary_reader error_msg += "BSON"; break; + case input_format_t::bjdata: + error_msg += "BJData"; + break; + case input_format_t::json: // LCOV_EXCL_LINE default: // LCOV_EXCL_LINE JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE @@ -10911,6 +11238,9 @@ class binary_reader /// whether we can assume little endianness const bool is_little_endian = little_endianness(); + /// input format + const input_format_t input_format = input_format_t::json; + /// the SAX parser json_sax_t* sax = nullptr; }; @@ -13427,12 +13757,14 @@ class json_ref #include // reverse #include // array +#include // map #include // isnan, isinf #include // uint8_t, uint16_t, uint32_t, uint64_t #include // memcpy #include // numeric_limits #include // string #include // move +#include // vector // #include @@ -14292,9 +14624,11 @@ class binary_writer @param[in] use_count whether to use '#' prefixes (optimized format) @param[in] use_type whether to use '$' prefixes (optimized format) @param[in] add_prefix whether prefixes need to be used for this value + @param[in] use_bjdata whether write in BJData format, default is false */ void write_ubjson(const BasicJsonType& j, const bool use_count, - const bool use_type, const bool add_prefix = true) + const bool use_type, const bool add_prefix = true, + const bool use_bjdata = false) { switch (j.type()) { @@ -14320,19 +14654,19 @@ class binary_writer case value_t::number_integer: { - write_number_with_ubjson_prefix(j.m_value.number_integer, add_prefix); + write_number_with_ubjson_prefix(j.m_value.number_integer, add_prefix, use_bjdata); break; } case value_t::number_unsigned: { - write_number_with_ubjson_prefix(j.m_value.number_unsigned, add_prefix); + write_number_with_ubjson_prefix(j.m_value.number_unsigned, add_prefix, use_bjdata); break; } case value_t::number_float: { - write_number_with_ubjson_prefix(j.m_value.number_float, add_prefix); + write_number_with_ubjson_prefix(j.m_value.number_float, add_prefix, use_bjdata); break; } @@ -14342,7 +14676,7 @@ class binary_writer { oa->write_character(to_char_type('S')); } - write_number_with_ubjson_prefix(j.m_value.string->size(), true); + write_number_with_ubjson_prefix(j.m_value.string->size(), true, use_bjdata); oa->write_characters( reinterpret_cast(j.m_value.string->c_str()), j.m_value.string->size()); @@ -14360,14 +14694,16 @@ class binary_writer if (use_type && !j.m_value.array->empty()) { JSON_ASSERT(use_count); - const CharType first_prefix = ubjson_prefix(j.front()); + const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata); const bool same_prefix = std::all_of(j.begin() + 1, j.end(), - [this, first_prefix](const BasicJsonType & v) + [this, first_prefix, use_bjdata](const BasicJsonType & v) { - return ubjson_prefix(v) == first_prefix; + return ubjson_prefix(v, use_bjdata) == first_prefix; }); - if (same_prefix) + std::vector bjdx = {'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z'}; // excluded markers in bjdata optimized type + + if (same_prefix && !(use_bjdata && std::find(bjdx.begin(), bjdx.end(), first_prefix) != bjdx.end())) { prefix_required = false; oa->write_character(to_char_type('$')); @@ -14378,12 +14714,12 @@ class binary_writer if (use_count) { oa->write_character(to_char_type('#')); - write_number_with_ubjson_prefix(j.m_value.array->size(), true); + write_number_with_ubjson_prefix(j.m_value.array->size(), true, use_bjdata); } for (const auto& el : *j.m_value.array) { - write_ubjson(el, use_count, use_type, prefix_required); + write_ubjson(el, use_count, use_type, prefix_required, use_bjdata); } if (!use_count) @@ -14411,7 +14747,7 @@ class binary_writer if (use_count) { oa->write_character(to_char_type('#')); - write_number_with_ubjson_prefix(j.m_value.binary->size(), true); + write_number_with_ubjson_prefix(j.m_value.binary->size(), true, use_bjdata); } if (use_type) @@ -14439,6 +14775,14 @@ class binary_writer case value_t::object: { + if (use_bjdata && j.m_value.object->size() == 3 && j.m_value.object->find("_ArrayType_") != j.m_value.object->end() && j.m_value.object->find("_ArraySize_") != j.m_value.object->end() && j.m_value.object->find("_ArrayData_") != j.m_value.object->end()) + { + if (!write_bjdata_ndarray(*j.m_value.object, use_count, use_type)) // decode bjdata ndarray in the JData format (https://github.com/NeuroJSON/jdata) + { + break; + } + } + if (add_prefix) { oa->write_character(to_char_type('{')); @@ -14448,14 +14792,16 @@ class binary_writer if (use_type && !j.m_value.object->empty()) { JSON_ASSERT(use_count); - const CharType first_prefix = ubjson_prefix(j.front()); + const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata); const bool same_prefix = std::all_of(j.begin(), j.end(), - [this, first_prefix](const BasicJsonType & v) + [this, first_prefix, use_bjdata](const BasicJsonType & v) { - return ubjson_prefix(v) == first_prefix; + return ubjson_prefix(v, use_bjdata) == first_prefix; }); - if (same_prefix) + std::vector bjdx = {'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z'}; // excluded markers in bjdata optimized type + + if (same_prefix && !(use_bjdata && std::find(bjdx.begin(), bjdx.end(), first_prefix) != bjdx.end())) { prefix_required = false; oa->write_character(to_char_type('$')); @@ -14466,16 +14812,16 @@ class binary_writer if (use_count) { oa->write_character(to_char_type('#')); - write_number_with_ubjson_prefix(j.m_value.object->size(), true); + write_number_with_ubjson_prefix(j.m_value.object->size(), true, use_bjdata); } for (const auto& el : *j.m_value.object) { - write_number_with_ubjson_prefix(el.first.size(), true); + write_number_with_ubjson_prefix(el.first.size(), true, use_bjdata); oa->write_characters( reinterpret_cast(el.first.c_str()), el.first.size()); - write_ubjson(el.second, use_count, use_type, prefix_required); + write_ubjson(el.second, use_count, use_type, prefix_required, use_bjdata); } if (!use_count) @@ -14542,7 +14888,7 @@ class binary_writer const double value) { write_bson_entry_header(name, 0x01); - write_number(value); + write_number(value, true); } /*! @@ -14561,7 +14907,7 @@ class binary_writer { write_bson_entry_header(name, 0x02); - write_number(static_cast(value.size() + 1ul)); + write_number(static_cast(value.size() + 1ul), true); oa->write_characters( reinterpret_cast(value.c_str()), value.size() + 1); @@ -14594,12 +14940,12 @@ class binary_writer if ((std::numeric_limits::min)() <= value && value <= (std::numeric_limits::max)()) { write_bson_entry_header(name, 0x10); // int32 - write_number(static_cast(value)); + write_number(static_cast(value), true); } else { write_bson_entry_header(name, 0x12); // int64 - write_number(static_cast(value)); + write_number(static_cast(value), true); } } @@ -14622,12 +14968,12 @@ class binary_writer if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) { write_bson_entry_header(name, 0x10 /* int32 */); - write_number(static_cast(j.m_value.number_unsigned)); + write_number(static_cast(j.m_value.number_unsigned), true); } else if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) { write_bson_entry_header(name, 0x12 /* int64 */); - write_number(static_cast(j.m_value.number_unsigned)); + write_number(static_cast(j.m_value.number_unsigned), true); } else { @@ -14675,7 +15021,7 @@ class binary_writer const typename BasicJsonType::array_t& value) { write_bson_entry_header(name, 0x04); // array - write_number(static_cast(calc_bson_array_size(value))); + write_number(static_cast(calc_bson_array_size(value)), true); std::size_t array_index = 0ul; @@ -14695,7 +15041,7 @@ class binary_writer { write_bson_entry_header(name, 0x05); - write_number(static_cast(value.size())); + write_number(static_cast(value.size()), true); write_number(value.has_subtype() ? static_cast(value.subtype()) : static_cast(0x00)); oa->write_characters(reinterpret_cast(value.data()), value.size()); @@ -14817,7 +15163,7 @@ class binary_writer */ void write_bson_object(const typename BasicJsonType::object_t& value) { - write_number(static_cast(calc_bson_object_size(value))); + write_number(static_cast(calc_bson_object_size(value)), true); for (const auto& el : value) { @@ -14863,20 +15209,22 @@ class binary_writer template::value, int>::type = 0> void write_number_with_ubjson_prefix(const NumberType n, - const bool add_prefix) + const bool add_prefix, + const bool use_bjdata) { if (add_prefix) { oa->write_character(get_ubjson_float_prefix(n)); } - write_number(n); + write_number(n, use_bjdata); } // UBJSON: write number (unsigned integer) template::value, int>::type = 0> void write_number_with_ubjson_prefix(const NumberType n, - const bool add_prefix) + const bool add_prefix, + const bool use_bjdata) { if (n <= static_cast((std::numeric_limits::max)())) { @@ -14884,7 +15232,7 @@ class binary_writer { oa->write_character(to_char_type('i')); // int8 } - write_number(static_cast(n)); + write_number(static_cast(n), use_bjdata); } else if (n <= (std::numeric_limits::max)()) { @@ -14892,7 +15240,7 @@ class binary_writer { oa->write_character(to_char_type('U')); // uint8 } - write_number(static_cast(n)); + write_number(static_cast(n), use_bjdata); } else if (n <= static_cast((std::numeric_limits::max)())) { @@ -14900,7 +15248,15 @@ class binary_writer { oa->write_character(to_char_type('I')); // int16 } - write_number(static_cast(n)); + write_number(static_cast(n), use_bjdata); + } + else if (use_bjdata && n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(to_char_type('u')); // uint16 - bjdata only + } + write_number(static_cast(n), use_bjdata); } else if (n <= static_cast((std::numeric_limits::max)())) { @@ -14908,7 +15264,15 @@ class binary_writer { oa->write_character(to_char_type('l')); // int32 } - write_number(static_cast(n)); + write_number(static_cast(n), use_bjdata); + } + else if (use_bjdata && n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(to_char_type('m')); // uint32 - bjdata only + } + write_number(static_cast(n), use_bjdata); } else if (n <= static_cast((std::numeric_limits::max)())) { @@ -14916,7 +15280,15 @@ class binary_writer { oa->write_character(to_char_type('L')); // int64 } - write_number(static_cast(n)); + write_number(static_cast(n), use_bjdata); + } + else if (use_bjdata && n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(to_char_type('M')); // uint64 - bjdata only + } + write_number(static_cast(n), use_bjdata); } else { @@ -14926,7 +15298,7 @@ class binary_writer } const auto number = BasicJsonType(n).dump(); - write_number_with_ubjson_prefix(number.size(), true); + write_number_with_ubjson_prefix(number.size(), true, use_bjdata); for (std::size_t i = 0; i < number.size(); ++i) { oa->write_character(to_char_type(static_cast(number[i]))); @@ -14939,7 +15311,8 @@ class binary_writer std::is_signed::value&& !std::is_floating_point::value, int >::type = 0 > void write_number_with_ubjson_prefix(const NumberType n, - const bool add_prefix) + const bool add_prefix, + const bool use_bjdata) { if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) { @@ -14947,7 +15320,7 @@ class binary_writer { oa->write_character(to_char_type('i')); // int8 } - write_number(static_cast(n)); + write_number(static_cast(n), use_bjdata); } else if (static_cast((std::numeric_limits::min)()) <= n && n <= static_cast((std::numeric_limits::max)())) { @@ -14955,7 +15328,7 @@ class binary_writer { oa->write_character(to_char_type('U')); // uint8 } - write_number(static_cast(n)); + write_number(static_cast(n), use_bjdata); } else if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) { @@ -14963,7 +15336,15 @@ class binary_writer { oa->write_character(to_char_type('I')); // int16 } - write_number(static_cast(n)); + write_number(static_cast(n), use_bjdata); + } + else if (use_bjdata && (static_cast((std::numeric_limits::min)()) <= n && n <= static_cast((std::numeric_limits::max)()))) + { + if (add_prefix) + { + oa->write_character(to_char_type('u')); // uint16 - bjdata only + } + write_number(static_cast(n), use_bjdata); } else if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) { @@ -14971,7 +15352,15 @@ class binary_writer { oa->write_character(to_char_type('l')); // int32 } - write_number(static_cast(n)); + write_number(static_cast(n), use_bjdata); + } + else if (use_bjdata && (static_cast((std::numeric_limits::min)()) <= n && n <= static_cast((std::numeric_limits::max)()))) + { + if (add_prefix) + { + oa->write_character(to_char_type('m')); // uint32 - bjdata only + } + write_number(static_cast(n), use_bjdata); } else if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) { @@ -14979,7 +15368,7 @@ class binary_writer { oa->write_character(to_char_type('L')); // int64 } - write_number(static_cast(n)); + write_number(static_cast(n), use_bjdata); } // LCOV_EXCL_START else @@ -14990,7 +15379,7 @@ class binary_writer } const auto number = BasicJsonType(n).dump(); - write_number_with_ubjson_prefix(number.size(), true); + write_number_with_ubjson_prefix(number.size(), true, use_bjdata); for (std::size_t i = 0; i < number.size(); ++i) { oa->write_character(to_char_type(static_cast(number[i]))); @@ -15002,7 +15391,7 @@ class binary_writer /*! @brief determine the type prefix of container values */ - CharType ubjson_prefix(const BasicJsonType& j) const noexcept + CharType ubjson_prefix(const BasicJsonType& j, const bool use_bjdata) const noexcept { switch (j.type()) { @@ -15026,10 +15415,18 @@ class binary_writer { return 'I'; } + if (use_bjdata && ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)())) + { + return 'u'; + } if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) { return 'l'; } + if (use_bjdata && ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)())) + { + return 'm'; + } if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) { return 'L'; @@ -15052,14 +15449,26 @@ class binary_writer { return 'I'; } + if (use_bjdata && j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) + { + return 'u'; + } if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) { return 'l'; } + if (use_bjdata && j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) + { + return 'm'; + } if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) { return 'L'; } + if (use_bjdata && j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + return 'M'; + } // anything else is treated as high-precision number return 'H'; // LCOV_EXCL_LINE } @@ -15093,6 +15502,118 @@ class binary_writer return 'D'; // float 64 } + /*! + @return false if the object is successfully converted to a bjdata ndarray, true if the type or size is invalid + */ + bool write_bjdata_ndarray(const typename BasicJsonType::object_t& value, const bool use_count, const bool use_type) + { + std::map bjdtype = {{"uint8", 'U'}, {"int8", 'i'}, {"uint16", 'u'}, {"int16", 'I'}, + {"uint32", 'm'}, {"int32", 'l'}, {"uint64", 'M'}, {"int64", 'L'}, {"single", 'd'}, {"double", 'D'}, {"char", 'C'} + }; + + string_t key = "_ArrayType_"; + auto it = bjdtype.find(static_cast(value.at(key))); + if (it == bjdtype.end()) + { + return true; + } + CharType dtype = it->second; + + key = "_ArraySize_"; + std::size_t len = (value.at(key).empty() ? 0 : 1); + for (const auto& el : value.at(key)) + { + len *= static_cast(el.m_value.number_unsigned); + } + + key = "_ArrayData_"; + if (value.at(key).size() != len) + { + return true; + } + + oa->write_character('['); + oa->write_character('$'); + oa->write_character(dtype); + oa->write_character('#'); + + key = "_ArraySize_"; + write_ubjson(value.at(key), use_count, use_type, true, true); + + key = "_ArrayData_"; + if (dtype == 'U' || dtype == 'C') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast(el.m_value.number_unsigned), true); + } + } + else if (dtype == 'i') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast(el.m_value.number_integer), true); + } + } + else if (dtype == 'u') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast(el.m_value.number_unsigned), true); + } + } + else if (dtype == 'I') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast(el.m_value.number_integer), true); + } + } + else if (dtype == 'm') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast(el.m_value.number_unsigned), true); + } + } + else if (dtype == 'l') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast(el.m_value.number_integer), true); + } + } + else if (dtype == 'M') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast(el.m_value.number_unsigned), true); + } + } + else if (dtype == 'L') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast(el.m_value.number_integer), true); + } + } + else if (dtype == 'd') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast(el.m_value.number_float), true); + } + } + else if (dtype == 'D') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast(el.m_value.number_float), true); + } + } + return false; + } + /////////////////////// // Utility functions // /////////////////////// @@ -15100,16 +15621,18 @@ class binary_writer /* @brief write a number to output input @param[in] n number of type @a NumberType - @tparam NumberType the type of the number - @tparam OutputIsLittleEndian Set to true if output data is + @param[in] OutputIsLittleEndian Set to true if output data is required to be little endian + @tparam NumberType the type of the number @note This function needs to respect the system's endianness, because bytes in CBOR, MessagePack, and UBJSON are stored in network order (big endian) and therefore need reordering on little endian systems. + On the other hand, BSON and BJData use little endian and should reorder + on big endian systems. */ - template - void write_number(const NumberType n) + template + void write_number(const NumberType n, const bool OutputIsLittleEndian = false) { // step 1: write number to array of length NumberType std::array vec{}; @@ -21221,7 +21744,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec auto ia = detail::input_adapter(std::forward(i)); return format == input_format_t::json ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) - : detail::binary_reader(std::move(ia)).sax_parse(format, sax, strict); + : detail::binary_reader(std::move(ia), format).sax_parse(format, sax, strict); } /// @brief generate SAX events @@ -21236,7 +21759,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec auto ia = detail::input_adapter(std::move(first), std::move(last)); return format == input_format_t::json ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) - : detail::binary_reader(std::move(ia)).sax_parse(format, sax, strict); + : detail::binary_reader(std::move(ia), format).sax_parse(format, sax, strict); } /// @brief generate SAX events @@ -21257,7 +21780,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - : detail::binary_reader(std::move(ia)).sax_parse(format, sax, strict); + : detail::binary_reader(std::move(ia), format).sax_parse(format, sax, strict); } #ifndef JSON_NO_IO /// @brief deserialize from stream @@ -21413,6 +21936,33 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec binary_writer(o).write_ubjson(j, use_size, use_type); } + /// @brief create a BJData serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ + static std::vector to_bjdata(const basic_json& j, + const bool use_size = false, + const bool use_type = false) + { + std::vector result; + to_bjdata(j, result, use_size, use_type); + return result; + } + + /// @brief create a BJData serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ + static void to_bjdata(const basic_json& j, detail::output_adapter o, + const bool use_size = false, const bool use_type = false) + { + binary_writer(o).write_ubjson(j, use_size, use_type, true, true); + } + + /// @brief create a BJData serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ + static void to_bjdata(const basic_json& j, detail::output_adapter o, + const bool use_size = false, const bool use_type = false) + { + binary_writer(o).write_ubjson(j, use_size, use_type, true, true); + } + /// @brief create a BSON serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_bson/ static std::vector to_bson(const basic_json& j) @@ -21448,7 +21998,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); + const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); return res ? result : basic_json(value_t::discarded); } @@ -21464,7 +22014,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); + const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); return res ? result : basic_json(value_t::discarded); } @@ -21491,7 +22041,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); + const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); return res ? result : basic_json(value_t::discarded); } @@ -21506,7 +22056,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::msgpack, &sdp, strict); + const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); return res ? result : basic_json(value_t::discarded); } @@ -21521,7 +22071,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::msgpack, &sdp, strict); + const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); return res ? result : basic_json(value_t::discarded); } @@ -21545,7 +22095,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::msgpack, &sdp, strict); + const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); return res ? result : basic_json(value_t::discarded); } @@ -21560,7 +22110,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::ubjson, &sdp, strict); + const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); return res ? result : basic_json(value_t::discarded); } @@ -21575,7 +22125,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::ubjson, &sdp, strict); + const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); return res ? result : basic_json(value_t::discarded); } @@ -21599,10 +22149,64 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::ubjson, &sdp, strict); + const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); return res ? result : basic_json(value_t::discarded); } + + /// @brief create a JSON value from an input in BJData format + /// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_bjdata(InputType&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::forward(i)); + const bool res = binary_reader(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + /// @brief create a JSON value from an input in BJData format + /// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_bjdata(IteratorType first, IteratorType last, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::move(first), std::move(last)); + const bool res = binary_reader(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_bjdata(const T* ptr, std::size_t len, + const bool strict = true, + const bool allow_exceptions = true) + { + return from_bjdata(ptr, ptr + len, strict, allow_exceptions); + } + + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_bjdata(detail::span_input_adapter&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = i.get(); + // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) + const bool res = binary_reader(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + /// @brief create a JSON value from an input in BSON format /// @sa https://json.nlohmann.me/api/basic_json/from_bson/ template @@ -21614,7 +22218,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::bson, &sdp, strict); + const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); return res ? result : basic_json(value_t::discarded); } @@ -21629,7 +22233,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::bson, &sdp, strict); + const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); return res ? result : basic_json(value_t::discarded); } @@ -21653,7 +22257,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::bson, &sdp, strict); + const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); return res ? result : basic_json(value_t::discarded); } /// @} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2f06def50..1d5c78697 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -72,7 +72,7 @@ endif() if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") # avoid stack overflow, see https://github.com/nlohmann/json/issues/2955 - json_test_set_test_options("test-cbor;test-msgpack;test-ubjson" LINK_OPTIONS /STACK:4000000) + json_test_set_test_options("test-cbor;test-msgpack;test-ubjson;test-bjdata" LINK_OPTIONS /STACK:4000000) endif() # disable exceptions for test-disabled_exceptions diff --git a/test/Makefile b/test/Makefile index 2d08a8f7b..3a11ce7dd 100644 --- a/test/Makefile +++ b/test/Makefile @@ -10,7 +10,7 @@ CXXFLAGS += -std=c++11 CPPFLAGS += -I ../single_include FUZZER_ENGINE = src/fuzzer-driver_afl.cpp -FUZZERS = parse_afl_fuzzer parse_bson_fuzzer parse_cbor_fuzzer parse_msgpack_fuzzer parse_ubjson_fuzzer +FUZZERS = parse_afl_fuzzer parse_bson_fuzzer parse_cbor_fuzzer parse_msgpack_fuzzer parse_ubjson_fuzzer parse_bjdata_fuzzer fuzzers: $(FUZZERS) parse_afl_fuzzer: @@ -27,3 +27,6 @@ parse_msgpack_fuzzer: parse_ubjson_fuzzer: $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(FUZZER_ENGINE) src/fuzzer-parse_ubjson.cpp -o $@ + +parse_bjdata_fuzzer: + $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(FUZZER_ENGINE) src/fuzzer-parse_bjdata.cpp -o $@ diff --git a/test/src/fuzzer-parse_bjdata.cpp b/test/src/fuzzer-parse_bjdata.cpp new file mode 100644 index 000000000..99e799bd3 --- /dev/null +++ b/test/src/fuzzer-parse_bjdata.cpp @@ -0,0 +1,84 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ (fuzz test support) +| | |__ | | | | | | version 3.10.5 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +This file implements a parser test suitable for fuzz testing. Given a byte +array data, it performs the following steps: + +- j1 = from_bjdata(data) +- vec = to_bjdata(j1) +- j2 = from_bjdata(vec) +- assert(j1 == j2) +- vec2 = to_bjdata(j1, use_size = true, use_type = false) +- j3 = from_bjdata(vec2) +- assert(j1 == j3) +- vec3 = to_bjdata(j1, use_size = true, use_type = true) +- j4 = from_bjdata(vec3) +- assert(j1 == j4) + +The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer +drivers. + +Licensed under the MIT License . +*/ + +#include +#include +#include + +using json = nlohmann::json; + +// see http://llvm.org/docs/LibFuzzer.html +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + try + { + // step 1: parse input + std::vector vec1(data, data + size); + json j1 = json::from_bjdata(vec1); + + try + { + // step 2.1: round trip without adding size annotations to container types + std::vector vec2 = json::to_bjdata(j1, false, false); + + // step 2.2: round trip with adding size annotations but without adding type annonations to container types + std::vector vec3 = json::to_bjdata(j1, true, false); + + // step 2.3: round trip with adding size as well as type annotations to container types + std::vector vec4 = json::to_bjdata(j1, true, true); + + // parse serialization + json j2 = json::from_bjdata(vec2); + json j3 = json::from_bjdata(vec3); + json j4 = json::from_bjdata(vec4); + + // serializations must match + assert(json::to_bjdata(j2, false, false) == vec2); + assert(json::to_bjdata(j3, true, false) == vec3); + assert(json::to_bjdata(j4, true, true) == vec4); + } + catch (const json::parse_error&) + { + // parsing a BJData serialization must not fail + assert(false); + } + } + catch (const json::parse_error&) + { + // parse errors are ok, because input may be random bytes + } + catch (const json::type_error&) + { + // type errors can occur during parsing, too + } + catch (const json::out_of_range&) + { + // out of range errors may happen if provided sizes are excessive + } + + // return 0 - non-zero return values are reserved for future use + return 0; +} diff --git a/test/src/unit-bjdata.cpp b/test/src/unit-bjdata.cpp new file mode 100644 index 000000000..bc6c52833 --- /dev/null +++ b/test/src/unit-bjdata.cpp @@ -0,0 +1,3355 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ (test suite) +| | |__ | | | | | | version 3.10.5 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +Licensed under the MIT License . +SPDX-License-Identifier: MIT +Copyright (c) 2013-2022 Niels Lohmann . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "doctest_compatibility.h" + +#include +using nlohmann::json; + +#include +#include +#include +#include +#include "test_utils.hpp" + +namespace +{ +class SaxCountdown +{ + public: + explicit SaxCountdown(const int count) : events_left(count) + {} + + bool null() + { + return events_left-- > 0; + } + + bool boolean(bool /*unused*/) + { + return events_left-- > 0; + } + + bool number_integer(json::number_integer_t /*unused*/) + { + return events_left-- > 0; + } + + bool number_unsigned(json::number_unsigned_t /*unused*/) + { + return events_left-- > 0; + } + + bool number_float(json::number_float_t /*unused*/, const std::string& /*unused*/) + { + return events_left-- > 0; + } + + bool string(std::string& /*unused*/) + { + return events_left-- > 0; + } + + bool binary(std::vector& /*unused*/) + { + return events_left-- > 0; + } + + bool start_object(std::size_t /*unused*/) + { + return events_left-- > 0; + } + + bool key(std::string& /*unused*/) + { + return events_left-- > 0; + } + + bool end_object() + { + return events_left-- > 0; + } + + bool start_array(std::size_t /*unused*/) + { + return events_left-- > 0; + } + + bool end_array() + { + return events_left-- > 0; + } + + bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const json::exception& /*unused*/) // NOLINT(readability-convert-member-functions-to-static) + { + return false; + } + + private: + int events_left = 0; +}; +} // namespace + +TEST_CASE("BJData") +{ + SECTION("individual values") + { + SECTION("discarded") + { + // discarded values are not serialized + json j = json::value_t::discarded; + const auto result = json::to_bjdata(j); + CHECK(result.empty()); + } + + SECTION("null") + { + json j = nullptr; + std::vector expected = {'Z'}; + const auto result = json::to_bjdata(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + + SECTION("boolean") + { + SECTION("true") + { + json j = true; + std::vector expected = {'T'}; + const auto result = json::to_bjdata(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + + SECTION("false") + { + json j = false; + std::vector expected = {'F'}; + const auto result = json::to_bjdata(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + + SECTION("number") + { + SECTION("signed") + { + SECTION("-9223372036854775808..-2147483649 (int64)") + { + std::vector numbers; + numbers.push_back((std::numeric_limits::min)()); + numbers.push_back(-1000000000000000000LL); + numbers.push_back(-100000000000000000LL); + numbers.push_back(-10000000000000000LL); + numbers.push_back(-1000000000000000LL); + numbers.push_back(-100000000000000LL); + numbers.push_back(-10000000000000LL); + numbers.push_back(-1000000000000LL); + numbers.push_back(-100000000000LL); + numbers.push_back(-10000000000LL); + numbers.push_back(-2147483649LL); + for (auto i : numbers) + { + CAPTURE(i) + + // create JSON value with integer number + json j = i; + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(static_cast('L')); + expected.push_back(static_cast(i & 0xff)); + expected.push_back(static_cast((i >> 8) & 0xff)); + expected.push_back(static_cast((i >> 16) & 0xff)); + expected.push_back(static_cast((i >> 24) & 0xff)); + expected.push_back(static_cast((i >> 32) & 0xff)); + expected.push_back(static_cast((i >> 40) & 0xff)); + expected.push_back(static_cast((i >> 48) & 0xff)); + expected.push_back(static_cast((i >> 56) & 0xff)); + + // compare result + size + const auto result = json::to_bjdata(j); + CHECK(result == expected); + CHECK(result.size() == 9); + + // check individual bytes + CHECK(result[0] == 'L'); + int64_t restored = (static_cast(result[8]) << 070) + + (static_cast(result[7]) << 060) + + (static_cast(result[6]) << 050) + + (static_cast(result[5]) << 040) + + (static_cast(result[4]) << 030) + + (static_cast(result[3]) << 020) + + (static_cast(result[2]) << 010) + + static_cast(result[1]); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + + SECTION("-2147483648..-32769 (int32)") + { + std::vector numbers; + numbers.push_back(-32769); + numbers.push_back(-100000); + numbers.push_back(-1000000); + numbers.push_back(-10000000); + numbers.push_back(-100000000); + numbers.push_back(-1000000000); + numbers.push_back(-2147483647 - 1); // https://stackoverflow.com/a/29356002/266378 + for (auto i : numbers) + { + CAPTURE(i) + + // create JSON value with integer number + json j = i; + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(static_cast('l')); + expected.push_back(static_cast(i & 0xff)); + expected.push_back(static_cast((i >> 8) & 0xff)); + expected.push_back(static_cast((i >> 16) & 0xff)); + expected.push_back(static_cast((i >> 24) & 0xff)); + + // compare result + size + const auto result = json::to_bjdata(j); + CHECK(result == expected); + CHECK(result.size() == 5); + + // check individual bytes + CHECK(result[0] == 'l'); + int32_t restored = (static_cast(result[4]) << 030) + + (static_cast(result[3]) << 020) + + (static_cast(result[2]) << 010) + + static_cast(result[1]); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + + SECTION("-32768..-129 (int16)") + { + for (int32_t i = -32768; i <= -129; ++i) + { + CAPTURE(i) + + // create JSON value with integer number + json j = i; + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(static_cast('I')); + expected.push_back(static_cast(i & 0xff)); + expected.push_back(static_cast((i >> 8) & 0xff)); + + // compare result + size + const auto result = json::to_bjdata(j); + CHECK(result == expected); + CHECK(result.size() == 3); + + // check individual bytes + CHECK(result[0] == 'I'); + auto restored = static_cast(((result[2] << 8) + result[1])); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + + SECTION("-9263 (int16)") + { + json j = -9263; + std::vector expected = {'I', 0xd1, 0xdb}; + + // compare result + size + const auto result = json::to_bjdata(j); + CHECK(result == expected); + CHECK(result.size() == 3); + + // check individual bytes + CHECK(result[0] == 'I'); + auto restored = static_cast(((result[2] << 8) + result[1])); + CHECK(restored == -9263); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + + SECTION("-128..-1 (int8)") + { + for (auto i = -128; i <= -1; ++i) + { + CAPTURE(i) + + // create JSON value with integer number + json j = i; + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back('i'); + expected.push_back(static_cast(i)); + + // compare result + size + const auto result = json::to_bjdata(j); + CHECK(result == expected); + CHECK(result.size() == 2); + + // check individual bytes + CHECK(result[0] == 'i'); + CHECK(static_cast(result[1]) == i); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + + SECTION("0..127 (int8)") + { + for (size_t i = 0; i <= 127; ++i) + { + CAPTURE(i) + + // create JSON value with integer number + json j = -1; + j.get_ref() = static_cast(i); + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(static_cast('i')); + expected.push_back(static_cast(i)); + + // compare result + size + const auto result = json::to_bjdata(j); + CHECK(result == expected); + CHECK(result.size() == 2); + + // check individual bytes + CHECK(result[0] == 'i'); + CHECK(result[1] == i); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + + SECTION("128..255 (uint8)") + { + for (size_t i = 128; i <= 255; ++i) + { + CAPTURE(i) + + // create JSON value with integer number + json j = -1; + j.get_ref() = static_cast(i); + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(static_cast('U')); + expected.push_back(static_cast(i)); + + // compare result + size + const auto result = json::to_bjdata(j); + CHECK(result == expected); + CHECK(result.size() == 2); + + // check individual bytes + CHECK(result[0] == 'U'); + CHECK(result[1] == i); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + + SECTION("256..32767 (int16)") + { + for (size_t i = 256; i <= 32767; ++i) + { + CAPTURE(i) + + // create JSON value with integer number + json j = -1; + j.get_ref() = static_cast(i); + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(static_cast('I')); + expected.push_back(static_cast(i & 0xff)); + expected.push_back(static_cast((i >> 8) & 0xff)); + + // compare result + size + const auto result = json::to_bjdata(j); + CHECK(result == expected); + CHECK(result.size() == 3); + + // check individual bytes + CHECK(result[0] == 'I'); + auto restored = static_cast(static_cast(result[2]) * 256 + static_cast(result[1])); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + + SECTION("32768..65535 (uint16)") + { + for (uint32_t i : + { + 32768u, 55555u, 65535u + }) + { + CAPTURE(i) + + // create JSON value with integer number + json j = -1; + j.get_ref() = static_cast(i); + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(static_cast('u')); + expected.push_back(static_cast(i & 0xff)); + expected.push_back(static_cast((i >> 8) & 0xff)); + + // compare result + size + const auto result = json::to_bjdata(j); + CHECK(result == expected); + CHECK(result.size() == 3); + + // check individual bytes + CHECK(result[0] == 'u'); + auto restored = static_cast(static_cast(result[2]) * 256 + static_cast(result[1])); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + + SECTION("65536..2147483647 (int32)") + { + for (uint32_t i : + { + 65536u, 77777u, 2147483647u + }) + { + CAPTURE(i) + + // create JSON value with integer number + json j = -1; + j.get_ref() = static_cast(i); + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back('l'); + expected.push_back(static_cast(i & 0xff)); + expected.push_back(static_cast((i >> 8) & 0xff)); + expected.push_back(static_cast((i >> 16) & 0xff)); + expected.push_back(static_cast((i >> 24) & 0xff)); + + // compare result + size + const auto result = json::to_bjdata(j); + CHECK(result == expected); + CHECK(result.size() == 5); + + // check individual bytes + CHECK(result[0] == 'l'); + uint32_t restored = (static_cast(result[4]) << 030) + + (static_cast(result[3]) << 020) + + (static_cast(result[2]) << 010) + + static_cast(result[1]); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + + SECTION("2147483648..4294967295 (uint32)") + { + for (uint32_t i : + { + 2147483648u, 3333333333u, 4294967295u + }) + { + CAPTURE(i) + + // create JSON value with integer number + json j = -1; + j.get_ref() = static_cast(i); + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back('m'); + expected.push_back(static_cast(i & 0xff)); + expected.push_back(static_cast((i >> 8) & 0xff)); + expected.push_back(static_cast((i >> 16) & 0xff)); + expected.push_back(static_cast((i >> 24) & 0xff)); + + // compare result + size + const auto result = json::to_bjdata(j); + CHECK(result == expected); + CHECK(result.size() == 5); + + // check individual bytes + CHECK(result[0] == 'm'); + uint32_t restored = (static_cast(result[4]) << 030) + + (static_cast(result[3]) << 020) + + (static_cast(result[2]) << 010) + + static_cast(result[1]); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + + SECTION("4294967296..9223372036854775807 (int64)") + { + std::vector v = {4294967296LU, 9223372036854775807LU}; + for (uint64_t i : v) + { + CAPTURE(i) + + // create JSON value with integer number + json j = -1; + j.get_ref() = static_cast(i); + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back('L'); + expected.push_back(static_cast(i & 0xff)); + expected.push_back(static_cast((i >> 010) & 0xff)); + expected.push_back(static_cast((i >> 020) & 0xff)); + expected.push_back(static_cast((i >> 030) & 0xff)); + expected.push_back(static_cast((i >> 040) & 0xff)); + expected.push_back(static_cast((i >> 050) & 0xff)); + expected.push_back(static_cast((i >> 060) & 0xff)); + expected.push_back(static_cast((i >> 070) & 0xff)); + + // compare result + size + const auto result = json::to_bjdata(j); + CHECK(result == expected); + CHECK(result.size() == 9); + + // check individual bytes + CHECK(result[0] == 'L'); + uint64_t restored = (static_cast(result[8]) << 070) + + (static_cast(result[7]) << 060) + + (static_cast(result[6]) << 050) + + (static_cast(result[5]) << 040) + + (static_cast(result[4]) << 030) + + (static_cast(result[3]) << 020) + + (static_cast(result[2]) << 010) + + static_cast(result[1]); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + + SECTION("9223372036854775808..18446744073709551615 (uint64)") + { + std::vector v = {9223372036854775808ull, 18446744073709551615ull}; + for (uint64_t i : v) + { + CAPTURE(i) + + // create JSON value with integer number + json j = i; + + // check type + CHECK(j.is_number_unsigned()); + + // create expected byte vector + std::vector expected; + expected.push_back('M'); + expected.push_back(static_cast(i & 0xff)); + expected.push_back(static_cast((i >> 010) & 0xff)); + expected.push_back(static_cast((i >> 020) & 0xff)); + expected.push_back(static_cast((i >> 030) & 0xff)); + expected.push_back(static_cast((i >> 040) & 0xff)); + expected.push_back(static_cast((i >> 050) & 0xff)); + expected.push_back(static_cast((i >> 060) & 0xff)); + expected.push_back(static_cast((i >> 070) & 0xff)); + + // compare result + size + const auto result = json::to_bjdata(j); + CHECK(result == expected); + CHECK(result.size() == 9); + + // check individual bytes + CHECK(result[0] == 'M'); + uint64_t restored = (static_cast(result[8]) << 070) + + (static_cast(result[7]) << 060) + + (static_cast(result[6]) << 050) + + (static_cast(result[5]) << 040) + + (static_cast(result[4]) << 030) + + (static_cast(result[3]) << 020) + + (static_cast(result[2]) << 010) + + static_cast(result[1]); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + } + + SECTION("unsigned") + { + SECTION("0..127 (int8)") + { + for (size_t i = 0; i <= 127; ++i) + { + CAPTURE(i) + + // create JSON value with unsigned integer number + json j = i; + + // check type + CHECK(j.is_number_unsigned()); + + // create expected byte vector + std::vector expected; + expected.push_back('i'); + expected.push_back(static_cast(i)); + + // compare result + size + const auto result = json::to_bjdata(j); + CHECK(result == expected); + CHECK(result.size() == 2); + + // check individual bytes + CHECK(result[0] == 'i'); + auto restored = static_cast(result[1]); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + + SECTION("128..255 (uint8)") + { + for (size_t i = 128; i <= 255; ++i) + { + CAPTURE(i) + + // create JSON value with unsigned integer number + json j = i; + + // check type + CHECK(j.is_number_unsigned()); + + // create expected byte vector + std::vector expected; + expected.push_back('U'); + expected.push_back(static_cast(i)); + + // compare result + size + const auto result = json::to_bjdata(j); + CHECK(result == expected); + CHECK(result.size() == 2); + + // check individual bytes + CHECK(result[0] == 'U'); + auto restored = static_cast(result[1]); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + + SECTION("256..32767 (int16)") + { + for (size_t i = 256; i <= 32767; ++i) + { + CAPTURE(i) + + // create JSON value with unsigned integer number + json j = i; + + // check type + CHECK(j.is_number_unsigned()); + + // create expected byte vector + std::vector expected; + expected.push_back('I'); + expected.push_back(static_cast(i & 0xff)); + expected.push_back(static_cast((i >> 8) & 0xff)); + + // compare result + size + const auto result = json::to_bjdata(j); + CHECK(result == expected); + CHECK(result.size() == 3); + + // check individual bytes + CHECK(result[0] == 'I'); + auto restored = static_cast(static_cast(result[2]) * 256 + static_cast(result[1])); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + + SECTION("32768..65535 (uint16)") + { + for (uint32_t i : + { + 32768u, 55555u, 65535u + }) + { + CAPTURE(i) + + // create JSON value with unsigned integer number + json j = i; + + // check type + CHECK(j.is_number_unsigned()); + + // create expected byte vector + std::vector expected; + expected.push_back('u'); + expected.push_back(static_cast(i & 0xff)); + expected.push_back(static_cast((i >> 8) & 0xff)); + + // compare result + size + const auto result = json::to_bjdata(j); + CHECK(result == expected); + CHECK(result.size() == 3); + + // check individual bytes + CHECK(result[0] == 'u'); + auto restored = static_cast(static_cast(result[2]) * 256 + static_cast(result[1])); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + SECTION("65536..2147483647 (int32)") + { + for (uint32_t i : + { + 65536u, 77777u, 2147483647u + }) + { + CAPTURE(i) + + // create JSON value with unsigned integer number + json j = i; + + // check type + CHECK(j.is_number_unsigned()); + + // create expected byte vector + std::vector expected; + expected.push_back('l'); + expected.push_back(static_cast(i & 0xff)); + expected.push_back(static_cast((i >> 8) & 0xff)); + expected.push_back(static_cast((i >> 16) & 0xff)); + expected.push_back(static_cast((i >> 24) & 0xff)); + + // compare result + size + const auto result = json::to_bjdata(j); + CHECK(result == expected); + CHECK(result.size() == 5); + + // check individual bytes + CHECK(result[0] == 'l'); + uint32_t restored = (static_cast(result[4]) << 030) + + (static_cast(result[3]) << 020) + + (static_cast(result[2]) << 010) + + static_cast(result[1]); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + + SECTION("2147483648..4294967295 (uint32)") + { + for (uint32_t i : + { + 2147483648u, 3333333333u, 4294967295u + }) + { + CAPTURE(i) + + // create JSON value with unsigned integer number + json j = i; + + // check type + CHECK(j.is_number_unsigned()); + + // create expected byte vector + std::vector expected; + expected.push_back('m'); + expected.push_back(static_cast(i & 0xff)); + expected.push_back(static_cast((i >> 8) & 0xff)); + expected.push_back(static_cast((i >> 16) & 0xff)); + expected.push_back(static_cast((i >> 24) & 0xff)); + + // compare result + size + const auto result = json::to_bjdata(j); + CHECK(result == expected); + CHECK(result.size() == 5); + + // check individual bytes + CHECK(result[0] == 'm'); + uint32_t restored = (static_cast(result[4]) << 030) + + (static_cast(result[3]) << 020) + + (static_cast(result[2]) << 010) + + static_cast(result[1]); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + + SECTION("4294967296..9223372036854775807 (int64)") + { + std::vector v = {4294967296ul, 9223372036854775807ul}; + for (uint64_t i : v) + { + CAPTURE(i) + + // create JSON value with integer number + json j = i; + + // check type + CHECK(j.is_number_unsigned()); + + // create expected byte vector + std::vector expected; + expected.push_back('L'); + expected.push_back(static_cast(i & 0xff)); + expected.push_back(static_cast((i >> 010) & 0xff)); + expected.push_back(static_cast((i >> 020) & 0xff)); + expected.push_back(static_cast((i >> 030) & 0xff)); + expected.push_back(static_cast((i >> 040) & 0xff)); + expected.push_back(static_cast((i >> 050) & 0xff)); + expected.push_back(static_cast((i >> 060) & 0xff)); + expected.push_back(static_cast((i >> 070) & 0xff)); + + // compare result + size + const auto result = json::to_bjdata(j); + CHECK(result == expected); + CHECK(result.size() == 9); + + // check individual bytes + CHECK(result[0] == 'L'); + uint64_t restored = (static_cast(result[8]) << 070) + + (static_cast(result[7]) << 060) + + (static_cast(result[6]) << 050) + + (static_cast(result[5]) << 040) + + (static_cast(result[4]) << 030) + + (static_cast(result[3]) << 020) + + (static_cast(result[2]) << 010) + + static_cast(result[1]); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + + SECTION("9223372036854775808..18446744073709551615 (uint64)") + { + std::vector v = {9223372036854775808ull, 18446744073709551615ull}; + for (uint64_t i : v) + { + CAPTURE(i) + + // create JSON value with integer number + json j = i; + + // check type + CHECK(j.is_number_unsigned()); + + // create expected byte vector + std::vector expected; + expected.push_back('M'); + expected.push_back(static_cast(i & 0xff)); + expected.push_back(static_cast((i >> 010) & 0xff)); + expected.push_back(static_cast((i >> 020) & 0xff)); + expected.push_back(static_cast((i >> 030) & 0xff)); + expected.push_back(static_cast((i >> 040) & 0xff)); + expected.push_back(static_cast((i >> 050) & 0xff)); + expected.push_back(static_cast((i >> 060) & 0xff)); + expected.push_back(static_cast((i >> 070) & 0xff)); + + // compare result + size + const auto result = json::to_bjdata(j); + CHECK(result == expected); + CHECK(result.size() == 9); + + // check individual bytes + CHECK(result[0] == 'M'); + uint64_t restored = (static_cast(result[8]) << 070) + + (static_cast(result[7]) << 060) + + (static_cast(result[6]) << 050) + + (static_cast(result[5]) << 040) + + (static_cast(result[4]) << 030) + + (static_cast(result[3]) << 020) + + (static_cast(result[2]) << 010) + + static_cast(result[1]); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + } + SECTION("float64") + { + SECTION("3.1415925") + { + double v = 3.1415925; + json j = v; + std::vector expected = + { + 'D', 0xfc, 0xde, 0xa6, 0x3f, 0xfb, 0x21, 0x09, 0x40 + }; + const auto result = json::to_bjdata(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result) == v); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + + SECTION("half-precision float") + { + SECTION("simple half floats") + { + CHECK(json::parse("0.0") == json::from_bjdata(std::vector({'h', 0x00, 0x00}))); + CHECK(json::parse("-0.0") == json::from_bjdata(std::vector({'h', 0x00, 0x80}))); + CHECK(json::parse("1.0") == json::from_bjdata(std::vector({'h', 0x00, 0x3c}))); + CHECK(json::parse("1.5") == json::from_bjdata(std::vector({'h', 0x00, 0x3e}))); + CHECK(json::parse("65504.0") == json::from_bjdata(std::vector({'h', 0xff, 0x7b}))); + } + + SECTION("errors") + { + SECTION("no byte follows") + { + json _; + std::vector vec0 = {'h'}; + CHECK_THROWS_AS(_ = json::from_bjdata(vec0), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(vec0), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BJData number: unexpected end of input"); + CHECK(json::from_bjdata(vec0, true, false).is_discarded()); + } + + SECTION("only one byte follows") + { + json _; + std::vector vec1 = {'h', 0x00}; + CHECK_THROWS_AS(_ = json::from_bjdata(vec1), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(vec1), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BJData number: unexpected end of input"); + CHECK(json::from_bjdata(vec1, true, false).is_discarded()); + } + } + } + + SECTION("half-precision float (edge cases)") + { + SECTION("exp = 0b00000") + { + SECTION("0 (0 00000 0000000000)") + { + json j = json::from_bjdata(std::vector({'h', 0x00, 0x00})); + json::number_float_t d{j}; + CHECK(d == 0.0); + } + + SECTION("-0 (1 00000 0000000000)") + { + json j = json::from_bjdata(std::vector({'h', 0x00, 0x80})); + json::number_float_t d{j}; + CHECK(d == -0.0); + } + + SECTION("2**-24 (0 00000 0000000001)") + { + json j = json::from_bjdata(std::vector({'h', 0x01, 0x00})); + json::number_float_t d{j}; + CHECK(d == std::pow(2.0, -24.0)); + } + } + + SECTION("exp = 0b11111") + { + SECTION("infinity (0 11111 0000000000)") + { + json j = json::from_bjdata(std::vector({'h', 0x00, 0x7c})); + json::number_float_t d{j}; + CHECK(d == std::numeric_limits::infinity()); + CHECK(j.dump() == "null"); + } + + SECTION("-infinity (1 11111 0000000000)") + { + json j = json::from_bjdata(std::vector({'h', 0x00, 0xfc})); + json::number_float_t d{j}; + CHECK(d == -std::numeric_limits::infinity()); + CHECK(j.dump() == "null"); + } + } + + SECTION("other values from https://en.wikipedia.org/wiki/Half-precision_floating-point_format") + { + SECTION("1 (0 01111 0000000000)") + { + json j = json::from_bjdata(std::vector({'h', 0x00, 0x3c})); + json::number_float_t d{j}; + CHECK(d == 1); + } + + SECTION("-2 (1 10000 0000000000)") + { + json j = json::from_bjdata(std::vector({'h', 0x00, 0xc0})); + json::number_float_t d{j}; + CHECK(d == -2); + } + + SECTION("65504 (0 11110 1111111111)") + { + json j = json::from_bjdata(std::vector({'h', 0xff, 0x7b})); + json::number_float_t d{j}; + CHECK(d == 65504); + } + } + + SECTION("infinity") + { + json j = json::from_bjdata(std::vector({'h', 0x00, 0x7c})); + json::number_float_t d{j}; + CHECK(!std::isfinite(d)); + CHECK(j.dump() == "null"); + } + + SECTION("NaN") + { + json j = json::from_bjdata(std::vector({'h', 0x00, 0x7e })); + json::number_float_t d{j}; + CHECK(std::isnan(d)); + CHECK(j.dump() == "null"); + } + } + + SECTION("high-precision number") + { + SECTION("unsigned integer number") + { + std::vector vec = {'H', 'i', 0x14, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}; + const auto j = json::from_bjdata(vec); + CHECK(j.is_number_unsigned()); + CHECK(j.dump() == "12345678901234567890"); + } + + SECTION("signed integer number") + { + std::vector vec = {'H', 'i', 0x13, '-', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8'}; + const auto j = json::from_bjdata(vec); + CHECK(j.is_number_integer()); + CHECK(j.dump() == "-123456789012345678"); + } + + SECTION("floating-point number") + { + std::vector vec = {'H', 'i', 0x16, '3', '.', '1', '4', '1', '5', '9', '2', '6', '5', '3', '5', '8', '9', '7', '9', '3', '2', '3', '8', '4', '6'}; + const auto j = json::from_bjdata(vec); + CHECK(j.is_number_float()); + CHECK(j.dump() == "3.141592653589793"); + } + + SECTION("errors") + { + // error while parsing length + std::vector vec0 = {'H', 'i'}; + CHECK(json::from_bjdata(vec0, true, false).is_discarded()); + // error while parsing string + std::vector vec1 = {'H', 'i', '1'}; + CHECK(json::from_bjdata(vec1, true, false).is_discarded()); + + json _; + std::vector vec2 = {'H', 'i', 2, '1', 'A', '3'}; + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec2), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing BJData high-precision number: invalid number text: 1A", json::parse_error); + std::vector vec3 = {'H', 'i', 2, '1', '.'}; + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec3), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing BJData high-precision number: invalid number text: 1.", json::parse_error); + std::vector vec4 = {'H', 2, '1', '0'}; + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec4), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing BJData size: expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x02", json::parse_error); + } + } + } + + SECTION("string") + { + SECTION("N = 0..127") + { + for (size_t N = 0; N <= 127; ++N) + { + CAPTURE(N) + + // create JSON value with string containing of N * 'x' + const auto s = std::string(N, 'x'); + json j = s; + + // create expected byte vector + std::vector expected; + expected.push_back('S'); + expected.push_back('i'); + expected.push_back(static_cast(N)); + for (size_t i = 0; i < N; ++i) + { + expected.push_back('x'); + } + + // compare result + size + const auto result = json::to_bjdata(j); + CHECK(result == expected); + CHECK(result.size() == N + 3); + // check that no null byte is appended + if (N > 0) + { + CHECK(result.back() != '\x00'); + } + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + + SECTION("N = 128..255") + { + for (size_t N = 128; N <= 255; ++N) + { + CAPTURE(N) + + // create JSON value with string containing of N * 'x' + const auto s = std::string(N, 'x'); + json j = s; + + // create expected byte vector + std::vector expected; + expected.push_back('S'); + expected.push_back('U'); + expected.push_back(static_cast(N)); + for (size_t i = 0; i < N; ++i) + { + expected.push_back('x'); + } + + // compare result + size + const auto result = json::to_bjdata(j); + CHECK(result == expected); + CHECK(result.size() == N + 3); + // check that no null byte is appended + CHECK(result.back() != '\x00'); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + + SECTION("N = 256..32767") + { + for (size_t N : + { + 256u, 999u, 1025u, 3333u, 2048u, 32767u + }) + { + CAPTURE(N) + + // create JSON value with string containing of N * 'x' + const auto s = std::string(N, 'x'); + json j = s; + + // create expected byte vector (hack: create string first) + std::vector expected(N, 'x'); + // reverse order of commands, because we insert at begin() + expected.insert(expected.begin(), static_cast((N >> 8) & 0xff)); + expected.insert(expected.begin(), static_cast(N & 0xff)); + expected.insert(expected.begin(), 'I'); + expected.insert(expected.begin(), 'S'); + + // compare result + size + const auto result = json::to_bjdata(j); + CHECK(result == expected); + CHECK(result.size() == N + 4); + // check that no null byte is appended + CHECK(result.back() != '\x00'); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + + SECTION("N = 32768..65535") + { + for (size_t N : + { + 32768u, 55555u, 65535u + }) + { + CAPTURE(N) + + // create JSON value with string containing of N * 'x' + const auto s = std::string(N, 'x'); + json j = s; + + // create expected byte vector (hack: create string first) + std::vector expected(N, 'x'); + // reverse order of commands, because we insert at begin() + expected.insert(expected.begin(), static_cast((N >> 8) & 0xff)); + expected.insert(expected.begin(), static_cast(N & 0xff)); + expected.insert(expected.begin(), 'u'); + expected.insert(expected.begin(), 'S'); + + // compare result + size + const auto result = json::to_bjdata(j); + CHECK(result == expected); + CHECK(result.size() == N + 4); + // check that no null byte is appended + CHECK(result.back() != '\x00'); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + + SECTION("N = 65536..2147483647") + { + for (size_t N : + { + 65536u, 77777u, 1048576u + }) + { + CAPTURE(N) + + // create JSON value with string containing of N * 'x' + const auto s = std::string(N, 'x'); + json j = s; + + // create expected byte vector (hack: create string first) + std::vector expected(N, 'x'); + // reverse order of commands, because we insert at begin() + expected.insert(expected.begin(), static_cast((N >> 24) & 0xff)); + expected.insert(expected.begin(), static_cast((N >> 16) & 0xff)); + expected.insert(expected.begin(), static_cast((N >> 8) & 0xff)); + expected.insert(expected.begin(), static_cast(N & 0xff)); + expected.insert(expected.begin(), 'l'); + expected.insert(expected.begin(), 'S'); + + // compare result + size + const auto result = json::to_bjdata(j); + CHECK(result == expected); + CHECK(result.size() == N + 6); + // check that no null byte is appended + CHECK(result.back() != '\x00'); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + } + + + SECTION("binary") + { + SECTION("N = 0..127") + { + for (std::size_t N = 0; N <= 127; ++N) + { + CAPTURE(N) + + // create JSON value with byte array containing of N * 'x' + const auto s = std::vector(N, 'x'); + json j = json::binary(s); + + // create expected byte vector + std::vector expected; + expected.push_back(static_cast('[')); + if (N != 0) + { + expected.push_back(static_cast('$')); + expected.push_back(static_cast('U')); + } + expected.push_back(static_cast('#')); + expected.push_back(static_cast('i')); + expected.push_back(static_cast(N)); + for (size_t i = 0; i < N; ++i) + { + expected.push_back(0x78); + } + + // compare result + size + const auto result = json::to_bjdata(j, true, true); + CHECK(result == expected); + if (N == 0) + { + CHECK(result.size() == N + 4); + } + else + { + CHECK(result.size() == N + 6); + } + + // check that no null byte is appended + if (N > 0) + { + CHECK(result.back() != '\x00'); + } + + // roundtrip only works to an array of numbers + json j_out = s; + CHECK(json::from_bjdata(result) == j_out); + CHECK(json::from_bjdata(result, true, false) == j_out); + } + } + + SECTION("N = 128..255") + { + for (std::size_t N = 128; N <= 255; ++N) + { + CAPTURE(N) + + // create JSON value with byte array containing of N * 'x' + const auto s = std::vector(N, 'x'); + json j = json::binary(s); + + // create expected byte vector + std::vector expected; + expected.push_back(static_cast('[')); + expected.push_back(static_cast('$')); + expected.push_back(static_cast('U')); + expected.push_back(static_cast('#')); + expected.push_back(static_cast('U')); + expected.push_back(static_cast(N)); + for (size_t i = 0; i < N; ++i) + { + expected.push_back(0x78); + } + + // compare result + size + const auto result = json::to_bjdata(j, true, true); + CHECK(result == expected); + CHECK(result.size() == N + 6); + // check that no null byte is appended + CHECK(result.back() != '\x00'); + + // roundtrip only works to an array of numbers + json j_out = s; + CHECK(json::from_bjdata(result) == j_out); + CHECK(json::from_bjdata(result, true, false) == j_out); + } + } + + SECTION("N = 256..32767") + { + for (std::size_t N : + { + 256u, 999u, 1025u, 3333u, 2048u, 32767u + }) + { + CAPTURE(N) + + // create JSON value with byte array containing of N * 'x' + const auto s = std::vector(N, 'x'); + json j = json::binary(s); + + // create expected byte vector + std::vector expected(N + 7, 'x'); + expected[0] = '['; + expected[1] = '$'; + expected[2] = 'U'; + expected[3] = '#'; + expected[4] = 'I'; + expected[5] = static_cast(N & 0xFF); + expected[6] = static_cast((N >> 8) & 0xFF); + + // compare result + size + const auto result = json::to_bjdata(j, true, true); + CHECK(result == expected); + CHECK(result.size() == N + 7); + // check that no null byte is appended + CHECK(result.back() != '\x00'); + + // roundtrip only works to an array of numbers + json j_out = s; + CHECK(json::from_bjdata(result) == j_out); + CHECK(json::from_bjdata(result, true, false) == j_out); + } + } + + SECTION("N = 32768..65535") + { + for (std::size_t N : + { + 32768u, 55555u, 65535u + }) + { + CAPTURE(N) + + // create JSON value with byte array containing of N * 'x' + const auto s = std::vector(N, 'x'); + json j = json::binary(s); + + // create expected byte vector + std::vector expected(N + 7, 'x'); + expected[0] = '['; + expected[1] = '$'; + expected[2] = 'U'; + expected[3] = '#'; + expected[4] = 'u'; + expected[5] = static_cast(N & 0xFF); + expected[6] = static_cast((N >> 8) & 0xFF); + + // compare result + size + const auto result = json::to_bjdata(j, true, true); + CHECK(result == expected); + CHECK(result.size() == N + 7); + // check that no null byte is appended + CHECK(result.back() != '\x00'); + + // roundtrip only works to an array of numbers + json j_out = s; + CHECK(json::from_bjdata(result) == j_out); + CHECK(json::from_bjdata(result, true, false) == j_out); + } + } + + SECTION("N = 65536..2147483647") + { + for (std::size_t N : + { + 65536u, 77777u, 1048576u + }) + { + CAPTURE(N) + + // create JSON value with byte array containing of N * 'x' + const auto s = std::vector(N, 'x'); + json j = json::binary(s); + + // create expected byte vector + std::vector expected(N + 9, 'x'); + expected[0] = '['; + expected[1] = '$'; + expected[2] = 'U'; + expected[3] = '#'; + expected[4] = 'l'; + expected[5] = static_cast(N & 0xFF); + expected[6] = static_cast((N >> 8) & 0xFF); + expected[7] = static_cast((N >> 16) & 0xFF); + expected[8] = static_cast((N >> 24) & 0xFF); + + // compare result + size + const auto result = json::to_bjdata(j, true, true); + CHECK(result == expected); + CHECK(result.size() == N + 9); + // check that no null byte is appended + CHECK(result.back() != '\x00'); + + // roundtrip only works to an array of numbers + json j_out = s; + CHECK(json::from_bjdata(result) == j_out); + CHECK(json::from_bjdata(result, true, false) == j_out); + } + } + + SECTION("Other Serializations") + { + const std::size_t N = 10; + const auto s = std::vector(N, 'x'); + json j = json::binary(s); + + SECTION("No Count No Type") + { + std::vector expected; + expected.push_back(static_cast('[')); + for (std::size_t i = 0; i < N; ++i) + { + expected.push_back(static_cast('U')); + expected.push_back(static_cast(0x78)); + } + expected.push_back(static_cast(']')); + + // compare result + size + const auto result = json::to_bjdata(j, false, false); + CHECK(result == expected); + CHECK(result.size() == N + 12); + // check that no null byte is appended + CHECK(result.back() != '\x00'); + + // roundtrip only works to an array of numbers + json j_out = s; + CHECK(json::from_bjdata(result) == j_out); + CHECK(json::from_bjdata(result, true, false) == j_out); + } + + SECTION("Yes Count No Type") + { + std::vector expected; + expected.push_back(static_cast('[')); + expected.push_back(static_cast('#')); + expected.push_back(static_cast('i')); + expected.push_back(static_cast(N)); + + for (size_t i = 0; i < N; ++i) + { + expected.push_back(static_cast('U')); + expected.push_back(static_cast(0x78)); + } + + // compare result + size + const auto result = json::to_bjdata(j, true, false); + CHECK(result == expected); + CHECK(result.size() == N + 14); + // check that no null byte is appended + CHECK(result.back() != '\x00'); + + // roundtrip only works to an array of numbers + json j_out = s; + CHECK(json::from_bjdata(result) == j_out); + CHECK(json::from_bjdata(result, true, false) == j_out); + } + } + } + SECTION("array") + { + SECTION("empty") + { + SECTION("size=false type=false") + { + json j = json::array(); + std::vector expected = {'[', ']'}; + const auto result = json::to_bjdata(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + + SECTION("size=true type=false") + { + json j = json::array(); + std::vector expected = {'[', '#', 'i', 0}; + const auto result = json::to_bjdata(j, true); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + + SECTION("size=true type=true") + { + json j = json::array(); + std::vector expected = {'[', '#', 'i', 0}; + const auto result = json::to_bjdata(j, true, true); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + + SECTION("[null]") + { + SECTION("size=false type=false") + { + json j = {nullptr}; + std::vector expected = {'[', 'Z', ']'}; + const auto result = json::to_bjdata(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + + SECTION("size=true type=false") + { + json j = {nullptr}; + std::vector expected = {'[', '#', 'i', 1, 'Z'}; + const auto result = json::to_bjdata(j, true); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + + SECTION("size=true type=true") + { + json j = {nullptr}; + std::vector expected = {'[', '#', 'i', 1, 'Z'}; + const auto result = json::to_bjdata(j, true, true); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + + SECTION("[1,2,3,4,5]") + { + SECTION("size=false type=false") + { + json j = json::parse("[1,2,3,4,5]"); + std::vector expected = {'[', 'i', 1, 'i', 2, 'i', 3, 'i', 4, 'i', 5, ']'}; + const auto result = json::to_bjdata(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + + SECTION("size=true type=false") + { + json j = json::parse("[1,2,3,4,5]"); + std::vector expected = {'[', '#', 'i', 5, 'i', 1, 'i', 2, 'i', 3, 'i', 4, 'i', 5}; + const auto result = json::to_bjdata(j, true); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + + SECTION("size=true type=true") + { + json j = json::parse("[1,2,3,4,5]"); + std::vector expected = {'[', '$', 'i', '#', 'i', 5, 1, 2, 3, 4, 5}; + const auto result = json::to_bjdata(j, true, true); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + + SECTION("[[[[]]]]") + { + SECTION("size=false type=false") + { + json j = json::parse("[[[[]]]]"); + std::vector expected = {'[', '[', '[', '[', ']', ']', ']', ']'}; + const auto result = json::to_bjdata(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + + SECTION("size=true type=false") + { + json j = json::parse("[[[[]]]]"); + std::vector expected = {'[', '#', 'i', 1, '[', '#', 'i', 1, '[', '#', 'i', 1, '[', '#', 'i', 0}; + const auto result = json::to_bjdata(j, true); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + + SECTION("size=true type=true") + { + json j = json::parse("[[[[]]]]"); + std::vector expected = {'[', '#', 'i', 1, '[', '#', 'i', 1, '[', '#', 'i', 1, '[', '#', 'i', 0}; + const auto result = json::to_bjdata(j, true, true); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + + SECTION("array with int16_t elements") + { + SECTION("size=false type=false") + { + json j(257, nullptr); + std::vector expected(j.size() + 2, 'Z'); // all null + expected[0] = '['; // opening array + expected[258] = ']'; // closing array + const auto result = json::to_bjdata(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + + SECTION("size=true type=false") + { + json j(257, nullptr); + std::vector expected(j.size() + 5, 'Z'); // all null + expected[0] = '['; // opening array + expected[1] = '#'; // array size + expected[2] = 'I'; // int16 + expected[3] = 0x01; // 0x0101, first byte + expected[4] = 0x01; // 0x0101, second byte + const auto result = json::to_bjdata(j, true); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + + SECTION("array with uint16_t elements") + { + SECTION("size=false type=false") + { + json j(32768, nullptr); + std::vector expected(j.size() + 2, 'Z'); // all null + expected[0] = '['; // opening array + expected[32769] = ']'; // closing array + const auto result = json::to_bjdata(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + + SECTION("size=true type=false") + { + json j(32768, nullptr); + std::vector expected(j.size() + 5, 'Z'); // all null + expected[0] = '['; // opening array + expected[1] = '#'; // array size + expected[2] = 'u'; // int16 + expected[3] = 0x00; // 0x0101, first byte + expected[4] = 0x80; // 0x0101, second byte + const auto result = json::to_bjdata(j, true); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + + SECTION("array with int32_t elements") + { + SECTION("size=false type=false") + { + json j(65793, nullptr); + std::vector expected(j.size() + 2, 'Z'); // all null + expected[0] = '['; // opening array + expected[65794] = ']'; // closing array + const auto result = json::to_bjdata(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + + SECTION("size=true type=false") + { + json j(65793, nullptr); + std::vector expected(j.size() + 7, 'Z'); // all null + expected[0] = '['; // opening array + expected[1] = '#'; // array size + expected[2] = 'l'; // int32 + expected[3] = 0x01; // 0x00010101, fourth byte + expected[4] = 0x01; // 0x00010101, third byte + expected[5] = 0x01; // 0x00010101, second byte + expected[6] = 0x00; // 0x00010101, first byte + const auto result = json::to_bjdata(j, true); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + } + + SECTION("object") + { + SECTION("empty") + { + SECTION("size=false type=false") + { + json j = json::object(); + std::vector expected = {'{', '}'}; + const auto result = json::to_bjdata(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + + SECTION("size=true type=false") + { + json j = json::object(); + std::vector expected = {'{', '#', 'i', 0}; + const auto result = json::to_bjdata(j, true); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + + SECTION("size=true type=true") + { + json j = json::object(); + std::vector expected = {'{', '#', 'i', 0}; + const auto result = json::to_bjdata(j, true, true); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + + SECTION("{\"\":null}") + { + SECTION("size=false type=false") + { + json j = {{"", nullptr}}; + std::vector expected = {'{', 'i', 0, 'Z', '}'}; + const auto result = json::to_bjdata(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + + SECTION("size=true type=false") + { + json j = {{"", nullptr}}; + std::vector expected = {'{', '#', 'i', 1, 'i', 0, 'Z'}; + const auto result = json::to_bjdata(j, true); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + + SECTION("{\"a\": {\"b\": {\"c\": {}}}}") + { + SECTION("size=false type=false") + { + json j = json::parse(R"({"a": {"b": {"c": {}}}})"); + std::vector expected = + { + '{', 'i', 1, 'a', '{', 'i', 1, 'b', '{', 'i', 1, 'c', '{', '}', '}', '}', '}' + }; + const auto result = json::to_bjdata(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + + SECTION("size=true type=false") + { + json j = json::parse(R"({"a": {"b": {"c": {}}}})"); + std::vector expected = + { + '{', '#', 'i', 1, 'i', 1, 'a', '{', '#', 'i', 1, 'i', 1, 'b', '{', '#', 'i', 1, 'i', 1, 'c', '{', '#', 'i', 0 + }; + const auto result = json::to_bjdata(j, true); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + + SECTION("size=true type=true ignore object type marker") + { + json j = json::parse(R"({"a": {"b": {"c": {}}}})"); + std::vector expected = + { + '{', '#', 'i', 1, 'i', 1, 'a', '{', '#', 'i', 1, 'i', 1, 'b', '{', '#', 'i', 1, 'i', 1, 'c', '{', '#', 'i', 0 + }; + const auto result = json::to_bjdata(j, true, true); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + } + } + } + + SECTION("errors") + { + SECTION("strict mode") + { + std::vector vec = {'Z', 'Z'}; + SECTION("non-strict mode") + { + const auto result = json::from_bjdata(vec, false); + CHECK(result == json()); + } + + SECTION("strict mode") + { + json _; + CHECK_THROWS_AS(_ = json::from_bjdata(vec), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(vec), + "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BJData value: expected end of input; last byte: 0x5A"); + } + } + } + + SECTION("SAX aborts") + { + SECTION("start_array()") + { + std::vector v = {'[', 'T', 'F', ']'}; + SaxCountdown scp(0); + CHECK(!json::sax_parse(v, &scp, json::input_format_t::bjdata)); + } + + SECTION("start_object()") + { + std::vector v = {'{', 'i', 3, 'f', 'o', 'o', 'F', '}'}; + SaxCountdown scp(0); + CHECK(!json::sax_parse(v, &scp, json::input_format_t::bjdata)); + } + + SECTION("key() in object") + { + std::vector v = {'{', 'i', 3, 'f', 'o', 'o', 'F', '}'}; + SaxCountdown scp(1); + CHECK(!json::sax_parse(v, &scp, json::input_format_t::bjdata)); + } + + SECTION("start_array(len)") + { + std::vector v = {'[', '#', 'i', '2', 'T', 'F'}; + SaxCountdown scp(0); + CHECK(!json::sax_parse(v, &scp, json::input_format_t::bjdata)); + } + + SECTION("start_object(len)") + { + std::vector v = {'{', '#', 'i', '1', 3, 'f', 'o', 'o', 'F'}; + SaxCountdown scp(0); + CHECK(!json::sax_parse(v, &scp, json::input_format_t::bjdata)); + } + + SECTION("key() in object with length") + { + std::vector v = {'{', 'i', 3, 'f', 'o', 'o', 'F', '}'}; + SaxCountdown scp(1); + CHECK(!json::sax_parse(v, &scp, json::input_format_t::bjdata)); + } + + SECTION("start_array() in ndarray _ArraySize_") + { + std::vector v = {'[', '$', 'i', '#', '[', '$', 'i', '#', 'i', 2, 2, 1, 1, 2}; + SaxCountdown scp(2); + CHECK(!json::sax_parse(v, &scp, json::input_format_t::bjdata)); + } + + SECTION("number_integer() in ndarray _ArraySize_") + { + std::vector v = {'[', '$', 'U', '#', '[', '$', 'i', '#', 'i', 2, 2, 1, 1, 2}; + SaxCountdown scp(3); + CHECK(!json::sax_parse(v, &scp, json::input_format_t::bjdata)); + } + + SECTION("key() in ndarray _ArrayType_") + { + std::vector v = {'[', '$', 'U', '#', '[', '$', 'U', '#', 'i', 2, 2, 2, 1, 2, 3, 4}; + SaxCountdown scp(8); + CHECK(!json::sax_parse(v, &scp, json::input_format_t::bjdata)); + } + + SECTION("string() in ndarray _ArrayType_") + { + std::vector v = {'[', '$', 'U', '#', '[', '$', 'i', '#', 'i', 2, 3, 2, 6, 5, 4, 3, 2, 1}; + SaxCountdown scp(11); + CHECK(!json::sax_parse(v, &scp, json::input_format_t::bjdata)); + } + + SECTION("start_array() in ndarray _ArrayData_") + { + std::vector v = {'[', '$', 'U', '#', '[', 'i', 2, 'i', 3, ']', 6, 5, 4, 3, 2, 1}; + SaxCountdown scp(13); + CHECK(!json::sax_parse(v, &scp, json::input_format_t::bjdata)); + } + } + + SECTION("parsing values") + { + SECTION("strings") + { + // create a single-character string for all number types + std::vector s_i = {'S', 'i', 1, 'a'}; + std::vector s_U = {'S', 'U', 1, 'a'}; + std::vector s_I = {'S', 'I', 1, 0, 'a'}; + std::vector s_u = {'S', 'u', 1, 0, 'a'}; + std::vector s_l = {'S', 'l', 1, 0, 0, 0, 'a'}; + std::vector s_m = {'S', 'm', 1, 0, 0, 0, 'a'}; + std::vector s_L = {'S', 'L', 1, 0, 0, 0, 0, 0, 0, 0, 'a'}; + std::vector s_M = {'S', 'M', 1, 0, 0, 0, 0, 0, 0, 0, 'a'}; + + // check if string is parsed correctly to "a" + CHECK(json::from_bjdata(s_i) == "a"); + CHECK(json::from_bjdata(s_U) == "a"); + CHECK(json::from_bjdata(s_I) == "a"); + CHECK(json::from_bjdata(s_u) == "a"); + CHECK(json::from_bjdata(s_l) == "a"); + CHECK(json::from_bjdata(s_m) == "a"); + CHECK(json::from_bjdata(s_L) == "a"); + CHECK(json::from_bjdata(s_M) == "a"); + + // roundtrip: output should be optimized + CHECK(json::to_bjdata(json::from_bjdata(s_i)) == s_i); + CHECK(json::to_bjdata(json::from_bjdata(s_U)) == s_i); + CHECK(json::to_bjdata(json::from_bjdata(s_I)) == s_i); + CHECK(json::to_bjdata(json::from_bjdata(s_u)) == s_i); + CHECK(json::to_bjdata(json::from_bjdata(s_l)) == s_i); + CHECK(json::to_bjdata(json::from_bjdata(s_m)) == s_i); + CHECK(json::to_bjdata(json::from_bjdata(s_L)) == s_i); + CHECK(json::to_bjdata(json::from_bjdata(s_M)) == s_i); + } + + SECTION("number") + { + SECTION("float") + { + // float32 + std::vector v_d = {'d', 0xd0, 0x0f, 0x49, 0x40}; + CHECK(json::from_bjdata(v_d) == 3.14159f); + + // float64 + std::vector v_D = {'D', 0x6e, 0x86, 0x1b, 0xf0, 0xf9, 0x21, 0x09, 0x40}; + CHECK(json::from_bjdata(v_D) == 3.14159); + + // float32 is serialized as float64 as the library does not support float32 + CHECK(json::to_bjdata(json::from_bjdata(v_d)) == json::to_bjdata(3.14159f)); + } + } + + SECTION("array") + { + SECTION("optimized version (length only)") + { + // create vector with two elements of the same type + std::vector v_TU = {'[', '#', 'U', 2, 'T', 'T'}; + std::vector v_T = {'[', '#', 'i', 2, 'T', 'T'}; + std::vector v_F = {'[', '#', 'i', 2, 'F', 'F'}; + std::vector v_Z = {'[', '#', 'i', 2, 'Z', 'Z'}; + std::vector v_i = {'[', '#', 'i', 2, 'i', 0x7F, 'i', 0x7F}; + std::vector v_U = {'[', '#', 'i', 2, 'U', 0xFF, 'U', 0xFF}; + std::vector v_I = {'[', '#', 'i', 2, 'I', 0xFF, 0x7F, 'I', 0xFF, 0x7F}; + std::vector v_u = {'[', '#', 'i', 2, 'u', 0x0F, 0xA7, 'u', 0x0F, 0xA7}; + std::vector v_l = {'[', '#', 'i', 2, 'l', 0xFF, 0xFF, 0xFF, 0x7F, 'l', 0xFF, 0xFF, 0xFF, 0x7F}; + std::vector v_m = {'[', '#', 'i', 2, 'm', 0xFF, 0xC9, 0x9A, 0xBB, 'm', 0xFF, 0xC9, 0x9A, 0xBB}; + std::vector v_L = {'[', '#', 'i', 2, 'L', 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 'L', 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}; + std::vector v_M = {'[', '#', 'i', 2, 'M', 0xFF, 0xFF, 0x63, 0xA7, 0xB3, 0xB6, 0xE0, 0x8D, 'M', 0xFF, 0xFF, 0x63, 0xA7, 0xB3, 0xB6, 0xE0, 0x8D}; + std::vector v_D = {'[', '#', 'i', 2, 'D', 0x4a, 0xd8, 0x12, 0x4d, 0xfb, 0x21, 0x09, 0x40, 'D', 0x4a, 0xd8, 0x12, 0x4d, 0xfb, 0x21, 0x09, 0x40}; + std::vector v_S = {'[', '#', 'i', 2, 'S', 'i', 1, 'a', 'S', 'i', 1, 'a'}; + std::vector v_C = {'[', '#', 'i', 2, 'C', 'a', 'C', 'a'}; + + // check if vector is parsed correctly + CHECK(json::from_bjdata(v_TU) == json({true, true})); + CHECK(json::from_bjdata(v_T) == json({true, true})); + CHECK(json::from_bjdata(v_F) == json({false, false})); + CHECK(json::from_bjdata(v_Z) == json({nullptr, nullptr})); + CHECK(json::from_bjdata(v_i) == json({127, 127})); + CHECK(json::from_bjdata(v_U) == json({255, 255})); + CHECK(json::from_bjdata(v_I) == json({32767, 32767})); + CHECK(json::from_bjdata(v_u) == json({42767, 42767})); + CHECK(json::from_bjdata(v_l) == json({2147483647, 2147483647})); + CHECK(json::from_bjdata(v_m) == json({3147483647, 3147483647})); + CHECK(json::from_bjdata(v_L) == json({9223372036854775807, 9223372036854775807})); + CHECK(json::from_bjdata(v_M) == json({10223372036854775807ull, 10223372036854775807ull})); + CHECK(json::from_bjdata(v_D) == json({3.1415926, 3.1415926})); + CHECK(json::from_bjdata(v_S) == json({"a", "a"})); + CHECK(json::from_bjdata(v_C) == json({"a", "a"})); + + // roundtrip: output should be optimized + CHECK(json::to_bjdata(json::from_bjdata(v_T), true) == v_T); + CHECK(json::to_bjdata(json::from_bjdata(v_F), true) == v_F); + CHECK(json::to_bjdata(json::from_bjdata(v_Z), true) == v_Z); + CHECK(json::to_bjdata(json::from_bjdata(v_i), true) == v_i); + CHECK(json::to_bjdata(json::from_bjdata(v_U), true) == v_U); + CHECK(json::to_bjdata(json::from_bjdata(v_I), true) == v_I); + CHECK(json::to_bjdata(json::from_bjdata(v_u), true) == v_u); + CHECK(json::to_bjdata(json::from_bjdata(v_l), true) == v_l); + CHECK(json::to_bjdata(json::from_bjdata(v_m), true) == v_m); + CHECK(json::to_bjdata(json::from_bjdata(v_L), true) == v_L); + CHECK(json::to_bjdata(json::from_bjdata(v_M), true) == v_M); + CHECK(json::to_bjdata(json::from_bjdata(v_D), true) == v_D); + CHECK(json::to_bjdata(json::from_bjdata(v_S), true) == v_S); + CHECK(json::to_bjdata(json::from_bjdata(v_C), true) == v_S); // char is serialized to string + } + + SECTION("optimized version (type and length)") + { + // create vector with two elements of the same type + std::vector v_i = {'[', '$', 'i', '#', 'i', 2, 0x7F, 0x7F}; + std::vector v_U = {'[', '$', 'U', '#', 'i', 2, 0xFF, 0xFF}; + std::vector v_I = {'[', '$', 'I', '#', 'i', 2, 0xFF, 0x7F, 0xFF, 0x7F}; + std::vector v_u = {'[', '$', 'u', '#', 'i', 2, 0x0F, 0xA7, 0x0F, 0xA7}; + std::vector v_l = {'[', '$', 'l', '#', 'i', 2, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0x7F}; + std::vector v_m = {'[', '$', 'm', '#', 'i', 2, 0xFF, 0xC9, 0x9A, 0xBB, 0xFF, 0xC9, 0x9A, 0xBB}; + std::vector v_L = {'[', '$', 'L', '#', 'i', 2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}; + std::vector v_M = {'[', '$', 'M', '#', 'i', 2, 0xFF, 0xFF, 0x63, 0xA7, 0xB3, 0xB6, 0xE0, 0x8D, 0xFF, 0xFF, 0x63, 0xA7, 0xB3, 0xB6, 0xE0, 0x8D}; + std::vector v_D = {'[', '$', 'D', '#', 'i', 2, 0x4a, 0xd8, 0x12, 0x4d, 0xfb, 0x21, 0x09, 0x40, 0x4a, 0xd8, 0x12, 0x4d, 0xfb, 0x21, 0x09, 0x40}; + std::vector v_S = {'[', '#', 'i', 2, 'S', 'i', 1, 'a', 'S', 'i', 1, 'a'}; + std::vector v_C = {'[', '$', 'C', '#', 'i', 2, 'a', 'a'}; + + // check if vector is parsed correctly + CHECK(json::from_bjdata(v_i) == json({127, 127})); + CHECK(json::from_bjdata(v_U) == json({255, 255})); + CHECK(json::from_bjdata(v_I) == json({32767, 32767})); + CHECK(json::from_bjdata(v_u) == json({42767, 42767})); + CHECK(json::from_bjdata(v_l) == json({2147483647, 2147483647})); + CHECK(json::from_bjdata(v_m) == json({3147483647, 3147483647})); + CHECK(json::from_bjdata(v_L) == json({9223372036854775807, 9223372036854775807})); + CHECK(json::from_bjdata(v_M) == json({10223372036854775807ull, 10223372036854775807ull})); + CHECK(json::from_bjdata(v_D) == json({3.1415926, 3.1415926})); + CHECK(json::from_bjdata(v_S) == json({"a", "a"})); + CHECK(json::from_bjdata(v_C) == json({"a", "a"})); + + // roundtrip: output should be optimized + std::vector v_empty = {'[', '#', 'i', 0}; + CHECK(json::to_bjdata(json::from_bjdata(v_i), true, true) == v_i); + CHECK(json::to_bjdata(json::from_bjdata(v_U), true, true) == v_U); + CHECK(json::to_bjdata(json::from_bjdata(v_I), true, true) == v_I); + CHECK(json::to_bjdata(json::from_bjdata(v_u), true, true) == v_u); + CHECK(json::to_bjdata(json::from_bjdata(v_l), true, true) == v_l); + CHECK(json::to_bjdata(json::from_bjdata(v_m), true, true) == v_m); + CHECK(json::to_bjdata(json::from_bjdata(v_L), true, true) == v_L); + CHECK(json::to_bjdata(json::from_bjdata(v_M), true, true) == v_M); + CHECK(json::to_bjdata(json::from_bjdata(v_D), true, true) == v_D); + CHECK(json::to_bjdata(json::from_bjdata(v_S), true, true) == v_S); + CHECK(json::to_bjdata(json::from_bjdata(v_C), true, true) == v_S); // char is serialized to string + } + + SECTION("optimized ndarray (type and vector-size as optimized 1D array)") + { + // create vector with two elements of the same type + std::vector v_0 = {'[', '$', 'i', '#', '[', '$', 'i', '#', 'i', 1, 0}; + std::vector v_1 = {'[', '$', 'i', '#', '[', '$', 'i', '#', 'i', 1, 2, 0x7F, 0x7F}; + std::vector v_i = {'[', '$', 'i', '#', '[', '$', 'i', '#', 'i', 2, 1, 2, 0x7F, 0x7F}; + std::vector v_U = {'[', '$', 'U', '#', '[', '$', 'i', '#', 'i', 2, 1, 2, 0xFF, 0xFF}; + std::vector v_I = {'[', '$', 'I', '#', '[', '$', 'i', '#', 'i', 2, 1, 2, 0xFF, 0x7F, 0xFF, 0x7F}; + std::vector v_u = {'[', '$', 'u', '#', '[', '$', 'i', '#', 'i', 2, 1, 2, 0x0F, 0xA7, 0x0F, 0xA7}; + std::vector v_l = {'[', '$', 'l', '#', '[', '$', 'i', '#', 'i', 2, 1, 2, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0x7F}; + std::vector v_m = {'[', '$', 'm', '#', '[', '$', 'i', '#', 'i', 2, 1, 2, 0xFF, 0xC9, 0x9A, 0xBB, 0xFF, 0xC9, 0x9A, 0xBB}; + std::vector v_L = {'[', '$', 'L', '#', '[', '$', 'i', '#', 'i', 2, 1, 2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}; + std::vector v_M = {'[', '$', 'M', '#', '[', '$', 'i', '#', 'i', 2, 1, 2, 0xFF, 0xFF, 0x63, 0xA7, 0xB3, 0xB6, 0xE0, 0x8D, 0xFF, 0xFF, 0x63, 0xA7, 0xB3, 0xB6, 0xE0, 0x8D}; + std::vector v_D = {'[', '$', 'D', '#', '[', '$', 'i', '#', 'i', 2, 1, 2, 0x4a, 0xd8, 0x12, 0x4d, 0xfb, 0x21, 0x09, 0x40, 0x4a, 0xd8, 0x12, 0x4d, 0xfb, 0x21, 0x09, 0x40}; + std::vector v_S = {'[', '#', '[', '$', 'i', '#', 'i', 2, 1, 2, 'S', 'i', 1, 'a', 'S', 'i', 1, 'a'}; + std::vector v_C = {'[', '$', 'C', '#', '[', '$', 'i', '#', 'i', 2, 1, 2, 'a', 'a'}; + + // check if vector is parsed correctly + CHECK(json::from_bjdata(v_0) == json::array()); + CHECK(json::from_bjdata(v_1) == json({127, 127})); + CHECK(json::from_bjdata(v_i) == json({127, 127})); + CHECK(json::from_bjdata(v_U) == json({255, 255})); + CHECK(json::from_bjdata(v_I) == json({32767, 32767})); + CHECK(json::from_bjdata(v_u) == json({42767, 42767})); + CHECK(json::from_bjdata(v_l) == json({2147483647, 2147483647})); + CHECK(json::from_bjdata(v_m) == json({3147483647, 3147483647})); + CHECK(json::from_bjdata(v_L) == json({9223372036854775807, 9223372036854775807})); + CHECK(json::from_bjdata(v_M) == json({10223372036854775807ull, 10223372036854775807ull})); + CHECK(json::from_bjdata(v_D) == json({3.1415926, 3.1415926})); + CHECK(json::from_bjdata(v_S) == json({"a", "a"})); + CHECK(json::from_bjdata(v_C) == json({"a", "a"})); + } + + SECTION("optimized ndarray (type and vector-size ndarray with JData annotations)") + { + // create vector with 0, 1, 2 elements of the same type + std::vector v_e = {'[', '$', 'U', '#', '[', '$', 'i', '#', 'i', 2, 2, 1, 0xFE, 0xFF}; + std::vector v_U = {'[', '$', 'U', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06}; + std::vector v_i = {'[', '$', 'i', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06}; + std::vector v_u = {'[', '$', 'u', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00}; + std::vector v_I = {'[', '$', 'I', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00}; + std::vector v_m = {'[', '$', 'm', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00}; + std::vector v_l = {'[', '$', 'l', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00}; + std::vector v_M = {'[', '$', 'M', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + std::vector v_L = {'[', '$', 'L', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + std::vector v_d = {'[', '$', 'd', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 0xA0, 0x40, 0x00, 0x00, 0xC0, 0x40}; + std::vector v_D = {'[', '$', 'D', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x40}; + std::vector v_C = {'[', '$', 'C', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 'a', 'b', 'c', 'd', 'e', 'f'}; + + // check if vector is parsed correctly + CHECK(json::from_bjdata(v_e) == json({{"_ArrayData_", {254, 255}}, {"_ArraySize_", {2, 1}}, {"_ArrayType_", "uint8"}})); + CHECK(json::from_bjdata(v_U) == json({{"_ArrayData_", {1, 2, 3, 4, 5, 6}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "uint8"}})); + CHECK(json::from_bjdata(v_i) == json({{"_ArrayData_", {1, 2, 3, 4, 5, 6}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "int8"}})); + CHECK(json::from_bjdata(v_i) == json({{"_ArrayData_", {1, 2, 3, 4, 5, 6}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "int8"}})); + CHECK(json::from_bjdata(v_u) == json({{"_ArrayData_", {1, 2, 3, 4, 5, 6}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "uint16"}})); + CHECK(json::from_bjdata(v_I) == json({{"_ArrayData_", {1, 2, 3, 4, 5, 6}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "int16"}})); + CHECK(json::from_bjdata(v_m) == json({{"_ArrayData_", {1, 2, 3, 4, 5, 6}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "uint32"}})); + CHECK(json::from_bjdata(v_l) == json({{"_ArrayData_", {1, 2, 3, 4, 5, 6}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "int32"}})); + CHECK(json::from_bjdata(v_M) == json({{"_ArrayData_", {1, 2, 3, 4, 5, 6}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "uint64"}})); + CHECK(json::from_bjdata(v_L) == json({{"_ArrayData_", {1, 2, 3, 4, 5, 6}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "int64"}})); + CHECK(json::from_bjdata(v_d) == json({{"_ArrayData_", {1.f, 2.f, 3.f, 4.f, 5.f, 6.f}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "single"}})); + CHECK(json::from_bjdata(v_D) == json({{"_ArrayData_", {1., 2., 3., 4., 5., 6.}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "double"}})); + CHECK(json::from_bjdata(v_C) == json({{"_ArrayData_", {'a', 'b', 'c', 'd', 'e', 'f'}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "char"}})); + + // roundtrip: output should be optimized + CHECK(json::to_bjdata(json::from_bjdata(v_e), true, true) == v_e); + CHECK(json::to_bjdata(json::from_bjdata(v_U), true, true) == v_U); + CHECK(json::to_bjdata(json::from_bjdata(v_i), true, true) == v_i); + CHECK(json::to_bjdata(json::from_bjdata(v_u), true, true) == v_u); + CHECK(json::to_bjdata(json::from_bjdata(v_I), true, true) == v_I); + CHECK(json::to_bjdata(json::from_bjdata(v_m), true, true) == v_m); + CHECK(json::to_bjdata(json::from_bjdata(v_l), true, true) == v_l); + CHECK(json::to_bjdata(json::from_bjdata(v_M), true, true) == v_M); + CHECK(json::to_bjdata(json::from_bjdata(v_L), true, true) == v_L); + CHECK(json::to_bjdata(json::from_bjdata(v_d), true, true) == v_d); + CHECK(json::to_bjdata(json::from_bjdata(v_D), true, true) == v_D); + CHECK(json::to_bjdata(json::from_bjdata(v_C), true, true) == v_C); + } + + SECTION("optimized ndarray (type and vector-size as 1D array)") + { + // create vector with two elements of the same type + std::vector v_0 = {'[', '$', 'i', '#', '[', ']'}; + std::vector v_i = {'[', '$', 'i', '#', '[', 'i', 1, 'i', 2, ']', 0x7F, 0x7F}; + std::vector v_U = {'[', '$', 'U', '#', '[', 'i', 1, 'i', 2, ']', 0xFF, 0xFF}; + std::vector v_I = {'[', '$', 'I', '#', '[', 'i', 1, 'i', 2, ']', 0xFF, 0x7F, 0xFF, 0x7F}; + std::vector v_u = {'[', '$', 'u', '#', '[', 'i', 1, 'i', 2, ']', 0x0F, 0xA7, 0x0F, 0xA7}; + std::vector v_l = {'[', '$', 'l', '#', '[', 'i', 1, 'i', 2, ']', 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0x7F}; + std::vector v_m = {'[', '$', 'm', '#', '[', 'i', 1, 'i', 2, ']', 0xFF, 0xC9, 0x9A, 0xBB, 0xFF, 0xC9, 0x9A, 0xBB}; + std::vector v_L = {'[', '$', 'L', '#', '[', 'i', 1, 'i', 2, ']', 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}; + std::vector v_M = {'[', '$', 'M', '#', '[', 'i', 1, 'i', 2, ']', 0xFF, 0xFF, 0x63, 0xA7, 0xB3, 0xB6, 0xE0, 0x8D, 0xFF, 0xFF, 0x63, 0xA7, 0xB3, 0xB6, 0xE0, 0x8D}; + std::vector v_D = {'[', '$', 'D', '#', '[', 'i', 1, 'i', 2, ']', 0x4a, 0xd8, 0x12, 0x4d, 0xfb, 0x21, 0x09, 0x40, 0x4a, 0xd8, 0x12, 0x4d, 0xfb, 0x21, 0x09, 0x40}; + std::vector v_S = {'[', '#', '[', 'i', 1, 'i', 2, ']', 'S', 'i', 1, 'a', 'S', 'i', 1, 'a'}; + std::vector v_C = {'[', '$', 'C', '#', '[', 'i', 1, 'i', 2, ']', 'a', 'a'}; + + // check if vector is parsed correctly + CHECK(json::from_bjdata(v_0) == json::array()); + CHECK(json::from_bjdata(v_i) == json({127, 127})); + CHECK(json::from_bjdata(v_U) == json({255, 255})); + CHECK(json::from_bjdata(v_I) == json({32767, 32767})); + CHECK(json::from_bjdata(v_u) == json({42767, 42767})); + CHECK(json::from_bjdata(v_l) == json({2147483647, 2147483647})); + CHECK(json::from_bjdata(v_m) == json({3147483647, 3147483647})); + CHECK(json::from_bjdata(v_L) == json({9223372036854775807, 9223372036854775807})); + CHECK(json::from_bjdata(v_M) == json({10223372036854775807ull, 10223372036854775807ull})); + CHECK(json::from_bjdata(v_D) == json({3.1415926, 3.1415926})); + CHECK(json::from_bjdata(v_S) == json({"a", "a"})); + CHECK(json::from_bjdata(v_C) == json({"a", "a"})); + } + + SECTION("optimized ndarray (type and vector-size as size-optimized array)") + { + // create vector with two elements of the same type + std::vector v_i = {'[', '$', 'i', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2, 0x7F, 0x7F}; + std::vector v_U = {'[', '$', 'U', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2, 0xFF, 0xFF}; + std::vector v_I = {'[', '$', 'I', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2, 0xFF, 0x7F, 0xFF, 0x7F}; + std::vector v_u = {'[', '$', 'u', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2, 0x0F, 0xA7, 0x0F, 0xA7}; + std::vector v_l = {'[', '$', 'l', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0x7F}; + std::vector v_m = {'[', '$', 'm', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2, 0xFF, 0xC9, 0x9A, 0xBB, 0xFF, 0xC9, 0x9A, 0xBB}; + std::vector v_L = {'[', '$', 'L', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}; + std::vector v_M = {'[', '$', 'M', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2, 0xFF, 0xFF, 0x63, 0xA7, 0xB3, 0xB6, 0xE0, 0x8D, 0xFF, 0xFF, 0x63, 0xA7, 0xB3, 0xB6, 0xE0, 0x8D}; + std::vector v_D = {'[', '$', 'D', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2, 0x4a, 0xd8, 0x12, 0x4d, 0xfb, 0x21, 0x09, 0x40, 0x4a, 0xd8, 0x12, 0x4d, 0xfb, 0x21, 0x09, 0x40}; + std::vector v_S = {'[', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2, 'S', 'i', 1, 'a', 'S', 'i', 1, 'a'}; + std::vector v_C = {'[', '$', 'C', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2, 'a', 'a'}; + + // check if vector is parsed correctly + CHECK(json::from_bjdata(v_i) == json({127, 127})); + CHECK(json::from_bjdata(v_U) == json({255, 255})); + CHECK(json::from_bjdata(v_I) == json({32767, 32767})); + CHECK(json::from_bjdata(v_u) == json({42767, 42767})); + CHECK(json::from_bjdata(v_l) == json({2147483647, 2147483647})); + CHECK(json::from_bjdata(v_m) == json({3147483647, 3147483647})); + CHECK(json::from_bjdata(v_L) == json({9223372036854775807, 9223372036854775807})); + CHECK(json::from_bjdata(v_M) == json({10223372036854775807ull, 10223372036854775807ull})); + CHECK(json::from_bjdata(v_D) == json({3.1415926, 3.1415926})); + CHECK(json::from_bjdata(v_S) == json({"a", "a"})); + CHECK(json::from_bjdata(v_C) == json({"a", "a"})); + } + + SECTION("invalid ndarray annotations remains as object") + { + // check if invalid ND array annotations stay as object + json j_type = json({{"_ArrayData_", {1, 2, 3, 4, 5, 6}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "invalidtype"}}); + json j_size = json({{"_ArrayData_", {1, 2, 3, 4, 5}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "uint8"}}); + + // roundtrip: output should stay as object + CHECK(json::from_bjdata(json::to_bjdata(j_type), true, true) == j_type); + CHECK(json::from_bjdata(json::to_bjdata(j_size), true, true) == j_size); + } + + SECTION("do not accept NTFZ markers in ndarray optimized type") + { + json _; + std::vector v_N = {'[', '$', 'N', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2}; + std::vector v_T = {'[', '$', 'T', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2}; + std::vector v_F = {'[', '$', 'F', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2}; + std::vector v_Z = {'[', '$', 'Z', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2}; + + CHECK(json::from_bjdata(v_N, true, true).is_discarded()); + CHECK(json::from_bjdata(v_T, true, true).is_discarded()); + CHECK(json::from_bjdata(v_F, true, true).is_discarded()); + CHECK(json::from_bjdata(v_Z, true, true).is_discarded()); + } + + SECTION("do not accept NTFZ markers in ndarray optimized type") + { + json _; + std::vector v_N = {'[', '$', 'N', '#', '[', 'i', 1, 'i', 2, ']'}; + std::vector v_T = {'[', '$', 'T', '#', '[', 'i', 1, 'i', 2, ']'}; + std::vector v_F = {'[', '$', 'F', '#', '[', 'i', 1, 'i', 2, ']'}; + std::vector v_Z = {'[', '$', 'Z', '#', '[', 'i', 1, 'i', 2, ']'}; + + CHECK(json::from_bjdata(v_N, true, true).is_discarded()); + CHECK(json::from_bjdata(v_T, true, true).is_discarded()); + CHECK(json::from_bjdata(v_F, true, true).is_discarded()); + CHECK(json::from_bjdata(v_Z, true, true).is_discarded()); + } + } + } + + SECTION("parse errors") + { + SECTION("empty byte vector") + { + json _; + CHECK_THROWS_AS(_ = json::from_bjdata(std::vector()), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(std::vector()), + "[json.exception.parse_error.110] parse error at byte 1: syntax error while parsing BJData value: unexpected end of input"); + } + + SECTION("char") + { + SECTION("eof after C byte") + { + std::vector v = {'C'}; + json _; + CHECK_THROWS_AS(_ = json::from_bjdata(v), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(v), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BJData char: unexpected end of input"); + } + + SECTION("byte out of range") + { + std::vector v = {'C', 130}; + json _; + CHECK_THROWS_AS(_ = json::from_bjdata(v), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(v), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing BJData char: byte after 'C' must be in range 0x00..0x7F; last byte: 0x82"); + } + } + + SECTION("strings") + { + SECTION("eof after S byte") + { + std::vector v = {'S'}; + json _; + CHECK_THROWS_AS(_ = json::from_bjdata(v), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(v), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BJData value: unexpected end of input"); + } + + SECTION("invalid byte") + { + std::vector v = {'S', '1', 'a'}; + json _; + CHECK_THROWS_AS(_ = json::from_bjdata(v), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(v), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing BJData string: expected length type specification (U, i, u, I, m, l, M, L); last byte: 0x31"); + } + + SECTION("parse bjdata markers in ubjson") + { + // create a single-character string for all number types + std::vector s_u = {'S', 'u', 1, 0, 'a'}; + std::vector s_m = {'S', 'm', 1, 0, 0, 0, 'a'}; + std::vector s_M = {'S', 'M', 1, 0, 0, 0, 0, 0, 0, 0, 'a'}; + + json _; + // check if string is parsed correctly to "a" + CHECK_THROWS_AS(_ = json::from_ubjson(s_u), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_ubjson(s_u), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON string: expected length type specification (U, i, I, l, L); last byte: 0x75"); + + CHECK_THROWS_AS(_ = json::from_ubjson(s_m), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_ubjson(s_m), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON string: expected length type specification (U, i, I, l, L); last byte: 0x6D"); + + CHECK_THROWS_AS(_ = json::from_ubjson(s_M), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_ubjson(s_M), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON string: expected length type specification (U, i, I, l, L); last byte: 0x4D"); + } + } + + SECTION("array") + { + SECTION("optimized array: no size following type") + { + std::vector v = {'[', '$', 'i', 2}; + json _; + CHECK_THROWS_AS(_ = json::from_bjdata(v), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(v), "[json.exception.parse_error.112] parse error at byte 4: syntax error while parsing BJData size: expected '#' after type information; last byte: 0x02"); + } + } + + SECTION("strings") + { + std::vector vS = {'S'}; + json _; + CHECK_THROWS_AS(_ = json::from_bjdata(vS), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(vS), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BJData value: unexpected end of input"); + CHECK(json::from_bjdata(vS, true, false).is_discarded()); + + std::vector v = {'S', 'i', '2', 'a'}; + CHECK_THROWS_AS(_ = json::from_bjdata(v), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(v), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing BJData string: unexpected end of input"); + CHECK(json::from_bjdata(v, true, false).is_discarded()); + + std::vector vC = {'C'}; + CHECK_THROWS_AS(_ = json::from_bjdata(vC), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(vC), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BJData char: unexpected end of input"); + CHECK(json::from_bjdata(vC, true, false).is_discarded()); + } + + SECTION("sizes") + { + std::vector vU = {'[', '#', 'U'}; + json _; + CHECK_THROWS_AS(_ = json::from_bjdata(vU), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(vU), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData number: unexpected end of input"); + CHECK(json::from_bjdata(vU, true, false).is_discarded()); + + std::vector vi = {'[', '#', 'i'}; + CHECK_THROWS_AS(_ = json::from_bjdata(vi), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(vi), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData number: unexpected end of input"); + CHECK(json::from_bjdata(vi, true, false).is_discarded()); + + std::vector vI = {'[', '#', 'I'}; + CHECK_THROWS_AS(_ = json::from_bjdata(vI), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(vI), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData number: unexpected end of input"); + CHECK(json::from_bjdata(vI, true, false).is_discarded()); + + std::vector vu = {'[', '#', 'u'}; + CHECK_THROWS_AS(_ = json::from_bjdata(vu), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(vu), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData number: unexpected end of input"); + CHECK(json::from_bjdata(vu, true, false).is_discarded()); + + std::vector vl = {'[', '#', 'l'}; + CHECK_THROWS_AS(_ = json::from_bjdata(vl), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(vl), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData number: unexpected end of input"); + CHECK(json::from_bjdata(vl, true, false).is_discarded()); + + std::vector vm = {'[', '#', 'm'}; + CHECK_THROWS_AS(_ = json::from_bjdata(vm), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(vm), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData number: unexpected end of input"); + CHECK(json::from_bjdata(vm, true, false).is_discarded()); + + std::vector vL = {'[', '#', 'L'}; + CHECK_THROWS_AS(_ = json::from_bjdata(vL), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(vL), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData number: unexpected end of input"); + CHECK(json::from_bjdata(vL, true, false).is_discarded()); + + std::vector vM = {'[', '#', 'M'}; + CHECK_THROWS_AS(_ = json::from_bjdata(vM), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(vM), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData number: unexpected end of input"); + CHECK(json::from_bjdata(vM, true, false).is_discarded()); + + std::vector v0 = {'[', '#', 'T', ']'}; + CHECK_THROWS_AS(_ = json::from_bjdata(v0), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(v0), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing BJData size: expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x54"); + CHECK(json::from_bjdata(v0, true, false).is_discarded()); + } + + SECTION("parse bjdata markers as array size in ubjson") + { + json _; + std::vector vu = {'[', '#', 'u'}; + CHECK_THROWS_AS(_ = json::from_ubjson(vu), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_ubjson(vu), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing UBJSON size: expected length type specification (U, i, I, l, L) after '#'; last byte: 0x75"); + CHECK(json::from_ubjson(vu, true, false).is_discarded()); + + std::vector vm = {'[', '#', 'm'}; + CHECK_THROWS_AS(_ = json::from_ubjson(vm), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_ubjson(vm), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing UBJSON size: expected length type specification (U, i, I, l, L) after '#'; last byte: 0x6D"); + CHECK(json::from_ubjson(vm, true, false).is_discarded()); + + std::vector vM = {'[', '#', 'M'}; + CHECK_THROWS_AS(_ = json::from_ubjson(vM), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_ubjson(vM), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing UBJSON size: expected length type specification (U, i, I, l, L) after '#'; last byte: 0x4D"); + CHECK(json::from_ubjson(vM, true, false).is_discarded()); + + std::vector v0 = {'[', '#', '['}; + CHECK_THROWS_AS(_ = json::from_ubjson(v0), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_ubjson(v0), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing UBJSON size: expected length type specification (U, i, I, l, L) after '#'; last byte: 0x5B"); + CHECK(json::from_ubjson(v0, true, false).is_discarded()); + } + + SECTION("types") + { + std::vector v0 = {'[', '$'}; + json _; + CHECK_THROWS_AS(_ = json::from_bjdata(v0), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(v0), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BJData type: unexpected end of input"); + CHECK(json::from_bjdata(v0, true, false).is_discarded()); + + std::vector vi = {'[', '$', '#'}; + CHECK_THROWS_AS(_ = json::from_bjdata(vi), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(vi), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData value: unexpected end of input"); + CHECK(json::from_bjdata(vi, true, false).is_discarded()); + + std::vector vU = {'[', '$', 'U'}; + CHECK_THROWS_AS(_ = json::from_bjdata(vU), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(vU), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData value: unexpected end of input"); + CHECK(json::from_bjdata(vU, true, false).is_discarded()); + } + + SECTION("arrays") + { + std::vector vST = {'[', '$', 'i', '#', 'i', 2, 1}; + json _; + CHECK_THROWS_AS(_ = json::from_bjdata(vST), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(vST), "[json.exception.parse_error.110] parse error at byte 8: syntax error while parsing BJData number: unexpected end of input"); + CHECK(json::from_bjdata(vST, true, false).is_discarded()); + + std::vector vS = {'[', '#', 'i', 2, 'i', 1}; + CHECK_THROWS_AS(_ = json::from_bjdata(vS), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(vS), "[json.exception.parse_error.110] parse error at byte 7: syntax error while parsing BJData value: unexpected end of input"); + CHECK(json::from_bjdata(vS, true, false).is_discarded()); + + std::vector v = {'[', 'i', 2, 'i', 1}; + CHECK_THROWS_AS(_ = json::from_bjdata(v), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(v), "[json.exception.parse_error.110] parse error at byte 6: syntax error while parsing BJData value: unexpected end of input"); + CHECK(json::from_bjdata(v, true, false).is_discarded()); + } + + SECTION("ndarrays") + { + std::vector vST = {'[', '$', 'i', '#', '[', '$', 'i', '#'}; + json _; + CHECK_THROWS_AS(_ = json::from_bjdata(vST), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(vST), "[json.exception.parse_error.113] parse error at byte 9: syntax error while parsing BJData size: expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0xFF"); + CHECK(json::from_bjdata(vST, true, false).is_discarded()); + + std::vector v = {'[', '$', 'i', '#', '[', '$', 'i', '#', 'i', 2, 1, 2}; + CHECK_THROWS_AS(_ = json::from_bjdata(v), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(v), "[json.exception.parse_error.110] parse error at byte 13: syntax error while parsing BJData number: unexpected end of input"); + CHECK(json::from_bjdata(v, true, false).is_discarded()); + + std::vector vS0 = {'[', '$', 'i', '#', '[', '$', 'i', '#', 'i', 2, 1}; + CHECK_THROWS_AS(_ = json::from_bjdata(vS0), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(vS0), "[json.exception.parse_error.110] parse error at byte 12: syntax error while parsing BJData number: unexpected end of input"); + CHECK(json::from_bjdata(vS0, true, false).is_discarded()); + + std::vector vS = {'[', '$', 'i', '#', '[', '#', 'i', 2, 1, 2, 1}; + CHECK_THROWS_AS(_ = json::from_bjdata(vS), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(vS), "[json.exception.parse_error.113] parse error at byte 9: syntax error while parsing BJData size: expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x01"); + CHECK(json::from_bjdata(vS, true, false).is_discarded()); + + std::vector vT = {'[', '$', 'i', '#', '[', 'i', 2, 'i'}; + CHECK_THROWS_AS(_ = json::from_bjdata(vT), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(vT), "[json.exception.parse_error.110] parse error at byte 9: syntax error while parsing BJData number: unexpected end of input"); + CHECK(json::from_bjdata(vT, true, false).is_discarded()); + + std::vector vT0 = {'[', '$', 'i', '#', '[', 'i'}; + CHECK_THROWS_AS(_ = json::from_bjdata(vT0), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(vT0), "[json.exception.parse_error.110] parse error at byte 7: syntax error while parsing BJData number: unexpected end of input"); + CHECK(json::from_bjdata(vT0, true, false).is_discarded()); + + std::vector vu = {'[', '$', 'i', '#', '[', '$', 'i', '#', 'u', 1, 0}; + CHECK_THROWS_AS(_ = json::from_bjdata(vu), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(vu), "[json.exception.parse_error.110] parse error at byte 12: syntax error while parsing BJData number: unexpected end of input"); + CHECK(json::from_bjdata(vu, true, false).is_discarded()); + + std::vector vm = {'[', '$', 'i', '#', '[', '$', 'i', '#', 'm', 1, 0, 0, 0}; + CHECK_THROWS_AS(_ = json::from_bjdata(vm), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(vm), "[json.exception.parse_error.110] parse error at byte 14: syntax error while parsing BJData number: unexpected end of input"); + CHECK(json::from_bjdata(vm, true, false).is_discarded()); + + std::vector vM = {'[', '$', 'i', '#', '[', '$', 'i', '#', 'M', 1, 0, 0, 0, 0, 0, 0, 0}; + CHECK_THROWS_AS(_ = json::from_bjdata(vM), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(vM), "[json.exception.parse_error.110] parse error at byte 18: syntax error while parsing BJData number: unexpected end of input"); + CHECK(json::from_bjdata(vM, true, false).is_discarded()); + + std::vector vU = {'[', '$', 'U', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 1, 2, 3, 4, 5}; + CHECK_THROWS_AS(_ = json::from_bjdata(vU), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(vU), "[json.exception.parse_error.110] parse error at byte 18: syntax error while parsing BJData number: unexpected end of input"); + CHECK(json::from_bjdata(vU, true, false).is_discarded()); + + std::vector vT1 = {'[', '$', 'T', '#', '[', '$', 'i', '#', 'i', 2, 2, 3}; + CHECK(json::from_bjdata(vT1, true, false).is_discarded()); + + std::vector vh = {'[', '$', 'h', '#', '[', '$', 'i', '#', 'i', 2, 2, 3}; + CHECK(json::from_bjdata(vh, true, false).is_discarded()); + } + + SECTION("objects") + { + std::vector vST = {'{', '$', 'i', '#', 'i', 2, 'i', 1, 'a', 1}; + json _; + CHECK_THROWS_AS(_ = json::from_bjdata(vST), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(vST), "[json.exception.parse_error.110] parse error at byte 11: syntax error while parsing BJData value: unexpected end of input"); + CHECK(json::from_bjdata(vST, true, false).is_discarded()); + + std::vector vT = {'{', '$', 'i', 'i', 1, 'a', 1}; + CHECK_THROWS_AS(_ = json::from_bjdata(vT), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(vT), "[json.exception.parse_error.112] parse error at byte 4: syntax error while parsing BJData size: expected '#' after type information; last byte: 0x69"); + CHECK(json::from_bjdata(vT, true, false).is_discarded()); + + std::vector vS = {'{', '#', 'i', 2, 'i', 1, 'a', 'i', 1}; + CHECK_THROWS_AS(_ = json::from_bjdata(vS), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(vS), "[json.exception.parse_error.110] parse error at byte 10: syntax error while parsing BJData value: unexpected end of input"); + CHECK(json::from_bjdata(vS, true, false).is_discarded()); + + std::vector v = {'{', 'i', 1, 'a', 'i', 1}; + CHECK_THROWS_AS(_ = json::from_bjdata(v), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(v), "[json.exception.parse_error.110] parse error at byte 7: syntax error while parsing BJData value: unexpected end of input"); + CHECK(json::from_bjdata(v, true, false).is_discarded()); + + std::vector v2 = {'{', 'i', 1, 'a', 'i', 1, 'i'}; + CHECK_THROWS_AS(_ = json::from_bjdata(v2), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(v2), "[json.exception.parse_error.110] parse error at byte 8: syntax error while parsing BJData number: unexpected end of input"); + CHECK(json::from_bjdata(v2, true, false).is_discarded()); + + std::vector v3 = {'{', 'i', 1, 'a'}; + CHECK_THROWS_AS(_ = json::from_bjdata(v3), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(v3), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing BJData value: unexpected end of input"); + CHECK(json::from_bjdata(v3, true, false).is_discarded()); + + std::vector vST1 = {'{', '$', 'd', '#', 'i', 2, 'i', 1, 'a'}; + CHECK_THROWS_AS(_ = json::from_bjdata(vST1), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(vST1), "[json.exception.parse_error.110] parse error at byte 10: syntax error while parsing BJData number: unexpected end of input"); + CHECK(json::from_bjdata(vST1, true, false).is_discarded()); + + std::vector vST2 = {'{', '#', 'i', 2, 'i', 1, 'a'}; + CHECK_THROWS_AS(_ = json::from_bjdata(vST2), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(vST2), "[json.exception.parse_error.110] parse error at byte 8: syntax error while parsing BJData value: unexpected end of input"); + CHECK(json::from_bjdata(vST2, true, false).is_discarded()); + + std::vector vO = {'{', '#', '[', 'i', 2, 'i', 1, ']', 'i', 1, 'a', 'i', 1, 'i', 1, 'b', 'i', 2}; + CHECK(json::from_bjdata(vO, true, false).is_discarded()); + } + } + + SECTION("writing optimized values") + { + SECTION("integer") + { + SECTION("array of i") + { + json j = {1, -1}; + std::vector expected = {'[', '$', 'i', '#', 'i', 2, 1, 0xff}; + CHECK(json::to_bjdata(j, true, true) == expected); + } + + SECTION("array of U") + { + json j = {200, 201}; + std::vector expected = {'[', '$', 'U', '#', 'i', 2, 0xC8, 0xC9}; + CHECK(json::to_bjdata(j, true, true) == expected); + } + + SECTION("array of I") + { + json j = {30000, -30000}; + std::vector expected = {'[', '$', 'I', '#', 'i', 2, 0x30, 0x75, 0xd0, 0x8a}; + CHECK(json::to_bjdata(j, true, true) == expected); + } + + SECTION("array of u") + { + json j = {50000, 50001}; + std::vector expected = {'[', '$', 'u', '#', 'i', 2, 0x50, 0xC3, 0x51, 0xC3}; + CHECK(json::to_bjdata(j, true, true) == expected); + } + + SECTION("array of l") + { + json j = {70000, -70000}; + std::vector expected = {'[', '$', 'l', '#', 'i', 2, 0x70, 0x11, 0x01, 0x00, 0x90, 0xEE, 0xFE, 0xFF}; + CHECK(json::to_bjdata(j, true, true) == expected); + } + + SECTION("array of m") + { + json j = {3147483647, 3147483648}; + std::vector expected = {'[', '$', 'm', '#', 'i', 2, 0xFF, 0xC9, 0x9A, 0xBB, 0x00, 0xCA, 0x9A, 0xBB}; + CHECK(json::to_bjdata(j, true, true) == expected); + } + + SECTION("array of L") + { + json j = {5000000000, -5000000000}; + std::vector expected = {'[', '$', 'L', '#', 'i', 2, 0x00, 0xF2, 0x05, 0x2A, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0E, 0xFA, 0xD5, 0xFE, 0xFF, 0xFF, 0xFF}; + CHECK(json::to_bjdata(j, true, true) == expected); + } + } + + SECTION("unsigned integer") + { + SECTION("array of i") + { + json j = {1u, 2u}; + std::vector expected = {'[', '$', 'i', '#', 'i', 2, 1, 2}; + std::vector expected_size = {'[', '#', 'i', 2, 'i', 1, 'i', 2}; + CHECK(json::to_bjdata(j, true, true) == expected); + CHECK(json::to_bjdata(j, true) == expected_size); + } + + SECTION("array of U") + { + json j = {200u, 201u}; + std::vector expected = {'[', '$', 'U', '#', 'i', 2, 0xC8, 0xC9}; + std::vector expected_size = {'[', '#', 'i', 2, 'U', 0xC8, 'U', 0xC9}; + CHECK(json::to_bjdata(j, true, true) == expected); + CHECK(json::to_bjdata(j, true) == expected_size); + } + + SECTION("array of I") + { + json j = {30000u, 30001u}; + std::vector expected = {'[', '$', 'I', '#', 'i', 2, 0x30, 0x75, 0x31, 0x75}; + std::vector expected_size = {'[', '#', 'i', 2, 'I', 0x30, 0x75, 'I', 0x31, 0x75}; + CHECK(json::to_bjdata(j, true, true) == expected); + CHECK(json::to_bjdata(j, true) == expected_size); + } + + SECTION("array of u") + { + json j = {50000u, 50001u}; + std::vector expected = {'[', '$', 'u', '#', 'i', 2, 0x50, 0xC3, 0x51, 0xC3}; + std::vector expected_size = {'[', '#', 'i', 2, 'u', 0x50, 0xC3, 'u', 0x51, 0xC3}; + CHECK(json::to_bjdata(j, true, true) == expected); + CHECK(json::to_bjdata(j, true) == expected_size); + } + + SECTION("array of l") + { + json j = {70000u, 70001u}; + std::vector expected = {'[', '$', 'l', '#', 'i', 2, 0x70, 0x11, 0x01, 0x00, 0x71, 0x11, 0x01, 0x00}; + std::vector expected_size = {'[', '#', 'i', 2, 'l', 0x70, 0x11, 0x01, 0x00, 'l', 0x71, 0x11, 0x01, 0x00}; + CHECK(json::to_bjdata(j, true, true) == expected); + CHECK(json::to_bjdata(j, true) == expected_size); + } + + SECTION("array of m") + { + json j = {3147483647u, 3147483648u}; + std::vector expected = {'[', '$', 'm', '#', 'i', 2, 0xFF, 0xC9, 0x9A, 0xBB, 0x00, 0xCA, 0x9A, 0xBB}; + std::vector expected_size = {'[', '#', 'i', 2, 'm', 0xFF, 0xC9, 0x9A, 0xBB, 'm', 0x00, 0xCA, 0x9A, 0xBB}; + CHECK(json::to_bjdata(j, true, true) == expected); + CHECK(json::to_bjdata(j, true) == expected_size); + } + + SECTION("array of L") + { + json j = {5000000000u, 5000000001u}; + std::vector expected = {'[', '$', 'L', '#', 'i', 2, 0x00, 0xF2, 0x05, 0x2A, 0x01, 0x00, 0x00, 0x00, 0x01, 0xF2, 0x05, 0x2A, 0x01, 0x00, 0x00, 0x00}; + std::vector expected_size = {'[', '#', 'i', 2, 'L', 0x00, 0xF2, 0x05, 0x2A, 0x01, 0x00, 0x00, 0x00, 'L', 0x01, 0xF2, 0x05, 0x2A, 0x01, 0x00, 0x00, 0x00}; + CHECK(json::to_bjdata(j, true, true) == expected); + CHECK(json::to_bjdata(j, true) == expected_size); + } + + SECTION("array of M") + { + json j = {10223372036854775807ull, 10223372036854775808ull}; + std::vector expected = {'[', '$', 'M', '#', 'i', 2, 0xFF, 0xFF, 0x63, 0xA7, 0xB3, 0xB6, 0xE0, 0x8D, 0x00, 0x00, 0x64, 0xA7, 0xB3, 0xB6, 0xE0, 0x8D}; + std::vector expected_size = {'[', '#', 'i', 2, 'M', 0xFF, 0xFF, 0x63, 0xA7, 0xB3, 0xB6, 0xE0, 0x8D, 'M', 0x00, 0x00, 0x64, 0xA7, 0xB3, 0xB6, 0xE0, 0x8D}; + CHECK(json::to_bjdata(j, true, true) == expected); + CHECK(json::to_bjdata(j, true) == expected_size); + } + } + } +} + +TEST_CASE("Universal Binary JSON Specification Examples 1") +{ + SECTION("Null Value") + { + json j = {{"passcode", nullptr}}; + std::vector v = {'{', 'i', 8, 'p', 'a', 's', 's', 'c', 'o', 'd', 'e', 'Z', '}'}; + CHECK(json::to_bjdata(j) == v); + CHECK(json::from_bjdata(v) == j); + } + + SECTION("No-Op Value") + { + json j = {"foo", "bar", "baz"}; + std::vector v = {'[', 'S', 'i', 3, 'f', 'o', 'o', + 'S', 'i', 3, 'b', 'a', 'r', + 'S', 'i', 3, 'b', 'a', 'z', ']' + }; + std::vector v2 = {'[', 'S', 'i', 3, 'f', 'o', 'o', 'N', + 'S', 'i', 3, 'b', 'a', 'r', 'N', 'N', 'N', + 'S', 'i', 3, 'b', 'a', 'z', 'N', 'N', ']' + }; + CHECK(json::to_bjdata(j) == v); + CHECK(json::from_bjdata(v) == j); + CHECK(json::from_bjdata(v2) == j); + } + + SECTION("Boolean Types") + { + json j = {{"authorized", true}, {"verified", false}}; + std::vector v = {'{', 'i', 10, 'a', 'u', 't', 'h', 'o', 'r', 'i', 'z', 'e', 'd', 'T', + 'i', 8, 'v', 'e', 'r', 'i', 'f', 'i', 'e', 'd', 'F', '}' + }; + CHECK(json::to_bjdata(j) == v); + CHECK(json::from_bjdata(v) == j); + } + + SECTION("Numeric Types") + { + json j = + { + {"int8", 16}, + {"uint8", 255}, + {"int16", 32767}, + {"uint16", 42767}, + {"int32", 2147483647}, + {"uint32", 3147483647}, + {"int64", 9223372036854775807}, + {"uint64", 10223372036854775807ull}, + {"float64", 113243.7863123} + }; + std::vector v = {'{', + 'i', 7, 'f', 'l', 'o', 'a', 't', '6', '4', 'D', 0xcf, 0x34, 0xbc, 0x94, 0xbc, 0xa5, 0xfb, 0x40, + 'i', 5, 'i', 'n', 't', '1', '6', 'I', 0xff, 0x7f, + 'i', 5, 'i', 'n', 't', '3', '2', 'l', 0xff, 0xff, 0xff, 0x7f, + 'i', 5, 'i', 'n', 't', '6', '4', 'L', 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, + 'i', 4, 'i', 'n', 't', '8', 'i', 16, + 'i', 6, 'u', 'i', 'n', 't', '1', '6', 'u', 0x0F, 0xA7, + 'i', 6, 'u', 'i', 'n', 't', '3', '2', 'm', 0xFF, 0xC9, 0x9A, 0xBB, + 'i', 6, 'u', 'i', 'n', 't', '6', '4', 'M', 0xFF, 0xFF, 0x63, 0xA7, 0xB3, 0xB6, 0xE0, 0x8D, + 'i', 5, 'u', 'i', 'n', 't', '8', 'U', 0xff, + '}' + }; + CHECK(json::to_bjdata(j) == v); + CHECK(json::from_bjdata(v) == j); + } + + SECTION("Char Type") + { + json j = {{"rolecode", "a"}, {"delim", ";"}}; + std::vector v = {'{', 'i', 5, 'd', 'e', 'l', 'i', 'm', 'C', ';', 'i', 8, 'r', 'o', 'l', 'e', 'c', 'o', 'd', 'e', 'C', 'a', '}'}; + //CHECK(json::to_bjdata(j) == v); + CHECK(json::from_bjdata(v) == j); + } + + SECTION("String Type") + { + SECTION("English") + { + json j = "hello"; + std::vector v = {'S', 'i', 5, 'h', 'e', 'l', 'l', 'o'}; + CHECK(json::to_bjdata(j) == v); + CHECK(json::from_bjdata(v) == j); + } + + SECTION("Russian") + { + json j = "привет"; + std::vector v = {'S', 'i', 12, 0xD0, 0xBF, 0xD1, 0x80, 0xD0, 0xB8, 0xD0, 0xB2, 0xD0, 0xB5, 0xD1, 0x82}; + CHECK(json::to_bjdata(j) == v); + CHECK(json::from_bjdata(v) == j); + } + + SECTION("Russian") + { + json j = "مرحبا"; + std::vector v = {'S', 'i', 10, 0xD9, 0x85, 0xD8, 0xB1, 0xD8, 0xAD, 0xD8, 0xA8, 0xD8, 0xA7}; + CHECK(json::to_bjdata(j) == v); + CHECK(json::from_bjdata(v) == j); + } + } + + SECTION("Array Type") + { + SECTION("size=false type=false") + { + // note the float has been replaced by a double + json j = {nullptr, true, false, 4782345193, 153.132, "ham"}; + std::vector v = {'[', 'Z', 'T', 'F', 'L', 0xE9, 0xCB, 0x0C, 0x1D, 0x01, 0x00, 0x00, 0x00, 'D', 0x4e, 0x62, 0x10, 0x58, 0x39, 0x24, 0x63, 0x40, 'S', 'i', 3, 'h', 'a', 'm', ']'}; + CHECK(json::to_bjdata(j) == v); + CHECK(json::from_bjdata(v) == j); + } + + SECTION("size=true type=false") + { + // note the float has been replaced by a double + json j = {nullptr, true, false, 4782345193, 153.132, "ham"}; + std::vector v = {'[', '#', 'i', 6, 'Z', 'T', 'F', 'L', 0xE9, 0xCB, 0x0C, 0x1D, 0x01, 0x00, 0x00, 0x00, 'D', 0x4e, 0x62, 0x10, 0x58, 0x39, 0x24, 0x63, 0x40, 'S', 'i', 3, 'h', 'a', 'm'}; + CHECK(json::to_bjdata(j, true) == v); + CHECK(json::from_bjdata(v) == j); + } + + SECTION("size=true type=true") + { + // note the float has been replaced by a double + json j = {nullptr, true, false, 4782345193, 153.132, "ham"}; + std::vector v = {'[', '#', 'i', 6, 'Z', 'T', 'F', 'L', 0xE9, 0xCB, 0x0C, 0x1D, 0x01, 0x00, 0x00, 0x00, 'D', 0x4e, 0x62, 0x10, 0x58, 0x39, 0x24, 0x63, 0x40, 'S', 'i', 3, 'h', 'a', 'm'}; + CHECK(json::to_bjdata(j, true, true) == v); + CHECK(json::from_bjdata(v) == j); + } + } + + SECTION("Object Type") + { + SECTION("size=false type=false") + { + json j = + { + { + "post", { + {"id", 1137}, + {"author", "rkalla"}, + {"timestamp", 1364482090592}, + {"body", "I totally agree!"} + } + } + }; + std::vector v = {'{', 'i', 4, 'p', 'o', 's', 't', '{', + 'i', 6, 'a', 'u', 't', 'h', 'o', 'r', 'S', 'i', 6, 'r', 'k', 'a', 'l', 'l', 'a', + 'i', 4, 'b', 'o', 'd', 'y', 'S', 'i', 16, 'I', ' ', 't', 'o', 't', 'a', 'l', 'l', 'y', ' ', 'a', 'g', 'r', 'e', 'e', '!', + 'i', 2, 'i', 'd', 'I', 0x71, 0x04, + 'i', 9, 't', 'i', 'm', 'e', 's', 't', 'a', 'm', 'p', 'L', 0x60, 0x66, 0x78, 0xB1, 0x3D, 0x01, 0x00, 0x00, + '}', '}' + }; + CHECK(json::to_bjdata(j) == v); + CHECK(json::from_bjdata(v) == j); + } + + SECTION("size=true type=false") + { + json j = + { + { + "post", { + {"id", 1137}, + {"author", "rkalla"}, + {"timestamp", 1364482090592}, + {"body", "I totally agree!"} + } + } + }; + std::vector v = {'{', '#', 'i', 1, 'i', 4, 'p', 'o', 's', 't', '{', '#', 'i', 4, + 'i', 6, 'a', 'u', 't', 'h', 'o', 'r', 'S', 'i', 6, 'r', 'k', 'a', 'l', 'l', 'a', + 'i', 4, 'b', 'o', 'd', 'y', 'S', 'i', 16, 'I', ' ', 't', 'o', 't', 'a', 'l', 'l', 'y', ' ', 'a', 'g', 'r', 'e', 'e', '!', + 'i', 2, 'i', 'd', 'I', 0x71, 0x04, + 'i', 9, 't', 'i', 'm', 'e', 's', 't', 'a', 'm', 'p', 'L', 0x60, 0x66, 0x78, 0xB1, 0x3D, 0x01, 0x00, 0x00, + }; + CHECK(json::to_bjdata(j, true) == v); + CHECK(json::from_bjdata(v) == j); + } + + SECTION("size=true type=true") + { + json j = + { + { + "post", { + {"id", 1137}, + {"author", "rkalla"}, + {"timestamp", 1364482090592}, + {"body", "I totally agree!"} + } + } + }; + std::vector v = {'{', '#', 'i', 1, 'i', 4, 'p', 'o', 's', 't', '{', '#', 'i', 4, + 'i', 6, 'a', 'u', 't', 'h', 'o', 'r', 'S', 'i', 6, 'r', 'k', 'a', 'l', 'l', 'a', + 'i', 4, 'b', 'o', 'd', 'y', 'S', 'i', 16, 'I', ' ', 't', 'o', 't', 'a', 'l', 'l', 'y', ' ', 'a', 'g', 'r', 'e', 'e', '!', + 'i', 2, 'i', 'd', 'I', 0x71, 0x04, + 'i', 9, 't', 'i', 'm', 'e', 's', 't', 'a', 'm', 'p', 'L', 0x60, 0x66, 0x78, 0xB1, 0x3D, 0x01, 0x00, 0x00, + }; + CHECK(json::to_bjdata(j, true, true) == v); + CHECK(json::from_bjdata(v) == j); + } + } + + SECTION("Optimized Format") + { + SECTION("Array Example") + { + SECTION("No Optimization") + { + // note the floats have been replaced by doubles + json j = {29.97, 31.13, 67.0, 2.113, 23.888}; + std::vector v = {'[', + 'D', 0xb8, 0x1e, 0x85, 0xeb, 0x51, 0xf8, 0x3d, 0x40, + 'D', 0xe1, 0x7a, 0x14, 0xae, 0x47, 0x21, 0x3f, 0x40, + 'D', 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x50, 0x40, + 'D', 0x81, 0x95, 0x43, 0x8b, 0x6c, 0xe7, 0x00, 0x40, + 'D', 0x17, 0xd9, 0xce, 0xf7, 0x53, 0xe3, 0x37, 0x40, + ']' + }; + CHECK(json::to_bjdata(j) == v); + CHECK(json::from_bjdata(v) == j); + } + + SECTION("Optimized with count") + { + // note the floats have been replaced by doubles + json j = {29.97, 31.13, 67.0, 2.113, 23.888}; + std::vector v = {'[', '#', 'i', 5, + 'D', 0xb8, 0x1e, 0x85, 0xeb, 0x51, 0xf8, 0x3d, 0x40, + 'D', 0xe1, 0x7a, 0x14, 0xae, 0x47, 0x21, 0x3f, 0x40, + 'D', 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x50, 0x40, + 'D', 0x81, 0x95, 0x43, 0x8b, 0x6c, 0xe7, 0x00, 0x40, + 'D', 0x17, 0xd9, 0xce, 0xf7, 0x53, 0xe3, 0x37, 0x40, + }; + CHECK(json::to_bjdata(j, true) == v); + CHECK(json::from_bjdata(v) == j); + } + + SECTION("Optimized with type & count") + { + // note the floats have been replaced by doubles + json j = {29.97, 31.13, 67.0, 2.113, 23.888}; + std::vector v = {'[', '$', 'D', '#', 'i', 5, + 0xb8, 0x1e, 0x85, 0xeb, 0x51, 0xf8, 0x3d, 0x40, + 0xe1, 0x7a, 0x14, 0xae, 0x47, 0x21, 0x3f, 0x40, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x50, 0x40, + 0x81, 0x95, 0x43, 0x8b, 0x6c, 0xe7, 0x00, 0x40, + 0x17, 0xd9, 0xce, 0xf7, 0x53, 0xe3, 0x37, 0x40, + }; + CHECK(json::to_bjdata(j, true, true) == v); + CHECK(json::from_bjdata(v) == j); + } + } + + SECTION("Object Example") + { + SECTION("No Optimization") + { + // note the floats have been replaced by doubles + json j = { {"lat", 29.976}, {"long", 31.131}, {"alt", 67.0} }; + std::vector v = {'{', + 'i', 3, 'a', 'l', 't', 'D', 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x50, 0x40, + 'i', 3, 'l', 'a', 't', 'D', 0x60, 0xe5, 0xd0, 0x22, 0xdb, 0xf9, 0x3d, 0x40, + 'i', 4, 'l', 'o', 'n', 'g', 'D', 0xa8, 0xc6, 0x4b, 0x37, 0x89, 0x21, 0x3f, 0x40, + '}' + }; + CHECK(json::to_bjdata(j) == v); + CHECK(json::from_bjdata(v) == j); + } + + SECTION("Optimized with count") + { + // note the floats have been replaced by doubles + json j = { {"lat", 29.976}, {"long", 31.131}, {"alt", 67.0} }; + std::vector v = {'{', '#', 'i', 3, + 'i', 3, 'a', 'l', 't', 'D', 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x50, 0x40, + 'i', 3, 'l', 'a', 't', 'D', 0x60, 0xe5, 0xd0, 0x22, 0xdb, 0xf9, 0x3d, 0x40, + 'i', 4, 'l', 'o', 'n', 'g', 'D', 0xa8, 0xc6, 0x4b, 0x37, 0x89, 0x21, 0x3f, 0x40, + }; + CHECK(json::to_bjdata(j, true) == v); + CHECK(json::from_bjdata(v) == j); + } + + SECTION("Optimized with type & count") + { + // note the floats have been replaced by doubles + json j = { {"lat", 29.976}, {"long", 31.131}, {"alt", 67.0} }; + std::vector v = {'{', '$', 'D', '#', 'i', 3, + 'i', 3, 'a', 'l', 't', 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x50, 0x40, + 'i', 3, 'l', 'a', 't', 0x60, 0xe5, 0xd0, 0x22, 0xdb, 0xf9, 0x3d, 0x40, + 'i', 4, 'l', 'o', 'n', 'g', 0xa8, 0xc6, 0x4b, 0x37, 0x89, 0x21, 0x3f, 0x40, + }; + CHECK(json::to_bjdata(j, true, true) == v); + CHECK(json::from_bjdata(v) == j); + } + } + + SECTION("Special Cases (Null, No-Op and Boolean)") + { + SECTION("Array") + { + std::vector v = {'[', '$', 'N', '#', 'I', 0x00, 0x02}; + CHECK(json::from_bjdata(v, true, true).is_discarded()); + } + + SECTION("Object") + { + std::vector v = {'{', '$', 'Z', '#', 'i', 3, 'i', 4, 'n', 'a', 'm', 'e', 'i', 8, 'p', 'a', 's', 's', 'w', 'o', 'r', 'd', 'i', 5, 'e', 'm', 'a', 'i', 'l'}; + CHECK(json::from_bjdata(v, true, true).is_discarded()); + } + } + } +} + +#if !defined(JSON_NOEXCEPTION) +TEST_CASE("all BJData first bytes") +{ + // these bytes will fail immediately with exception parse_error.112 + std::set supported = + { + 'T', 'F', 'Z', 'U', 'i', 'I', 'l', 'L', 'd', 'D', 'C', 'S', '[', '{', 'N', 'H', 'u', 'm', 'M', 'h' + }; + + for (auto i = 0; i < 256; ++i) + { + const auto byte = static_cast(i); + CAPTURE(byte) + + try + { + auto res = json::from_bjdata(std::vector(1, byte)); + } + catch (const json::parse_error& e) + { + // check that parse_error.112 is only thrown if the + // first byte is not in the supported set + INFO_WITH_TEMP(e.what()); + if (supported.find(byte) == supported.end()) + { + CHECK(e.id == 112); + } + else + { + CHECK(e.id != 112); + } + } + } +} +#endif + +TEST_CASE("BJData roundtrips" * doctest::skip()) +{ + SECTION("input from self-generated BJData files") + { + for (std::string filename : + { + TEST_DATA_DIRECTORY "/json_nlohmann_tests/all_unicode.json", + TEST_DATA_DIRECTORY "/json.org/1.json", + TEST_DATA_DIRECTORY "/json.org/2.json", + TEST_DATA_DIRECTORY "/json.org/3.json", + TEST_DATA_DIRECTORY "/json.org/4.json", + TEST_DATA_DIRECTORY "/json.org/5.json", + TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip01.json", + TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip02.json", + TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip03.json", + TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip04.json", + TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip05.json", + TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip06.json", + TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip07.json", + TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip08.json", + TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip09.json", + TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip10.json", + TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip11.json", + TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip12.json", + TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip13.json", + TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip14.json", + TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip15.json", + TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip16.json", + TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip17.json", + TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip18.json", + TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip19.json", + TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip20.json", + TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip21.json", + TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip22.json", + TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip23.json", + TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip24.json", + TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip25.json", + TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip26.json", + TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip27.json", + TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip28.json", + TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip29.json", + TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip30.json", + TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip31.json", + TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip32.json", + TEST_DATA_DIRECTORY "/json_testsuite/sample.json", + TEST_DATA_DIRECTORY "/json_tests/pass1.json", + TEST_DATA_DIRECTORY "/json_tests/pass2.json", + TEST_DATA_DIRECTORY "/json_tests/pass3.json" + }) + { + CAPTURE(filename) + + { + INFO_WITH_TEMP(filename + ": std::vector"); + // parse JSON file + std::ifstream f_json(filename); + json j1 = json::parse(f_json); + + // parse BJData file + auto packed = utils::read_binary_file(filename + ".bjdata"); + json j2; + CHECK_NOTHROW(j2 = json::from_bjdata(packed)); + + // compare parsed JSON values + CHECK(j1 == j2); + } + + { + INFO_WITH_TEMP(filename + ": std::ifstream"); + // parse JSON file + std::ifstream f_json(filename); + json j1 = json::parse(f_json); + + // parse BJData file + std::ifstream f_bjdata(filename + ".bjdata", std::ios::binary); + json j2; + CHECK_NOTHROW(j2 = json::from_bjdata(f_bjdata)); + + // compare parsed JSON values + CHECK(j1 == j2); + } + + { + INFO_WITH_TEMP(filename + ": uint8_t* and size"); + // parse JSON file + std::ifstream f_json(filename); + json j1 = json::parse(f_json); + + // parse BJData file + auto packed = utils::read_binary_file(filename + ".bjdata"); + json j2; + CHECK_NOTHROW(j2 = json::from_bjdata({packed.data(), packed.size()})); + + // compare parsed JSON values + CHECK(j1 == j2); + } + + { + INFO_WITH_TEMP(filename + ": output to output adapters"); + // parse JSON file + std::ifstream f_json(filename); + json j1 = json::parse(f_json); + + // parse BJData file + auto packed = utils::read_binary_file(filename + ".bjdata"); + + { + INFO_WITH_TEMP(filename + ": output adapters: std::vector"); + std::vector vec; + json::to_bjdata(j1, vec); + CHECK(vec == packed); + } + } + } + } +} From 5352856f04730204123285ed928fc720ee8348b3 Mon Sep 17 00:00:00 2001 From: Florian Albrechtskirchinger Date: Fri, 29 Apr 2022 21:40:02 +0200 Subject: [PATCH 05/30] Implement support for string_view (attempt no. 3) (#3423) * Add key_compare member to ordered_map * Replace == with key_compare in ordered_map * Expose the actual comparison function used by object_t nlohmann::ordered_map uses a different comparison function than the one provided via template parameter. * Introduce a type trait to detect if object_t has a key_compare member. * Rename object_comparator_t to default_object_comparator_t. * Add object_comparator_t to be conditionally defined as object_t::key_compare, if available, or default_object_comparator_t otherwise. * Update the documentation accordingly. Co-authored-by: Niels Lohmann * Add type traits to check if a type is usable as object key Add type trait to check: * if a type is a specialization of a template. * if a type is a json_pointer. * if a type is a basic_json::{const_,}iterator. * if two types are comparable using a given comparison functor. * if a type is comparable to basic_json::object_t::key_type. * if a type has a member type is_transparent. * if a type is usable as object key. * if a type has an erase() function accepting a given KeyType. Co-authored-by: Niels Lohmann * Rework basic_json element access to accept more key types Rework basic_json element access member functions and operators to accept any type that meets the requirements defined by type trait detail::is_usable_as_key_type. Member functions and operators: * at() * operator[] * value() * erase() * find() * count() * contains() Update documentation to reflect these changes. Add unit tests to excercise the new functions using std::string_view. Co-authored-by: Niels Lohmann Co-authored-by: Niels Lohmann --- doc/mkdocs/docs/api/basic_json/at.md | 41 +- doc/mkdocs/docs/api/basic_json/basic_json.md | 10 +- doc/mkdocs/docs/api/basic_json/contains.md | 32 +- doc/mkdocs/docs/api/basic_json/count.md | 23 +- .../basic_json/default_object_comparator_t.md | 19 + doc/mkdocs/docs/api/basic_json/erase.md | 39 +- doc/mkdocs/docs/api/basic_json/find.md | 31 +- doc/mkdocs/docs/api/basic_json/index.md | 1 + doc/mkdocs/docs/api/basic_json/insert.md | 2 +- .../api/basic_json/object_comparator_t.md | 16 +- doc/mkdocs/docs/api/basic_json/object_t.md | 4 +- doc/mkdocs/docs/api/basic_json/operator[].md | 52 +- doc/mkdocs/docs/api/basic_json/to_bson.md | 2 +- doc/mkdocs/docs/api/basic_json/to_cbor.md | 2 +- doc/mkdocs/docs/api/basic_json/to_msgpack.md | 2 +- doc/mkdocs/docs/api/basic_json/to_ubjson.md | 2 +- doc/mkdocs/docs/api/basic_json/value.md | 32 +- doc/mkdocs/docs/api/ordered_map.md | 7 + doc/mkdocs/mkdocs.yml | 1 + include/nlohmann/detail/macro_scope.hpp | 6 + include/nlohmann/detail/macro_unscope.hpp | 1 + include/nlohmann/detail/meta/type_traits.hpp | 95 +++- include/nlohmann/json.hpp | 370 +++++++++---- include/nlohmann/ordered_map.hpp | 26 +- single_include/nlohmann/json.hpp | 498 +++++++++++++---- test/src/unit-element_access2.cpp | 508 ++++++++++++++++++ 26 files changed, 1517 insertions(+), 305 deletions(-) create mode 100644 doc/mkdocs/docs/api/basic_json/default_object_comparator_t.md diff --git a/doc/mkdocs/docs/api/basic_json/at.md b/doc/mkdocs/docs/api/basic_json/at.md index fc518f46e..dbd4cb6c9 100644 --- a/doc/mkdocs/docs/api/basic_json/at.md +++ b/doc/mkdocs/docs/api/basic_json/at.md @@ -10,13 +10,28 @@ reference at(const typename object_t::key_type& key); const_reference at(const typename object_t::key_type& key) const; // (3) +template +reference at(KeyType&& key); +template +const_reference at(KeyType&& key) const; + +// (4) reference at(const json_pointer& ptr); const_reference at(const json_pointer& ptr) const; ``` 1. Returns a reference to the array element at specified location `idx`, with bounds checking. -2. Returns a reference to the object element at with specified key `key`, with bounds checking. -3. Returns a reference to the element at with specified JSON pointer `ptr`, with bounds checking. +2. Returns a reference to the object element with specified key `key`, with bounds checking. +3. See 2. This overload is only available if `KeyType` is comparable with `#!cpp typename object_t::key_type` and + `#!cpp typename object_comparator_t::is_transparent` denotes a type. +4. Returns a reference to the element at specified JSON pointer `ptr`, with bounds checking. + +## Template parameters + +`KeyType` +: A type for an object key other than [`json_pointer`](../json_pointer/index.md) that is comparable with + [`string_t`](string_t.md) using [`object_comparator_t`](object_comparator_t.md). + This can also be a string view (C++17). ## Parameters @@ -25,15 +40,16 @@ const_reference at(const json_pointer& ptr) const; `key` (in) : object key of the elements to access - + `ptr` (in) : JSON pointer to the desired element - + ## Return value 1. reference to the element at index `idx` 2. reference to the element at key `key` -3. reference to the element pointed to by `ptr` +3. reference to the element at key `key` +4. reference to the element pointed to by `ptr` ## Exception safety @@ -51,7 +67,8 @@ Strong exception safety: if an exception occurs, the original value stays intact in this case, calling `at` with a key makes no sense. See example below. - Throws [`out_of_range.403`](../../home/exceptions.md#jsonexceptionout_of_range403) if the key `key` is not stored in the object; that is, `find(key) == end()`. See example below. -3. The function can throw the following exceptions: +3. See 2. +4. The function can throw the following exceptions: - Throws [`parse_error.106`](../../home/exceptions.md#jsonexceptionparse_error106) if an array index in the passed JSON pointer `ptr` begins with '0'. See example below. - Throws [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) if an array index in the passed @@ -68,9 +85,10 @@ Strong exception safety: if an exception occurs, the original value stays intact ## Complexity -1. Constant +1. Constant. 2. Logarithmic in the size of the container. -3. Constant +3. Logarithmic in the size of the container. +4. Logarithmic in the size of the container. ## Examples @@ -134,7 +152,7 @@ Strong exception safety: if an exception occurs, the original value stays intact --8<-- "examples/at__object_t_key_type_const.output" ``` -??? example "Example (3) access specified element via JSON Pointer" +??? example "Example (4) access specified element via JSON Pointer" The example below shows how object elements can be read and written using `at()`. It also demonstrates the different exceptions that can be thrown. @@ -149,7 +167,7 @@ Strong exception safety: if an exception occurs, the original value stays intact --8<-- "examples/at_json_pointer.output" ``` -??? example "Example (3) access specified element via JSON Pointer" +??? example "Example (4) access specified element via JSON Pointer" The example below shows how object elements can be read using `at()`. It also demonstrates the different exceptions that can be thrown. @@ -173,4 +191,5 @@ Strong exception safety: if an exception occurs, the original value stays intact 1. Added in version 1.0.0. 2. Added in version 1.0.0. -3. Added in version 2.0.0. +3. Added in version 3.11.0. +4. Added in version 2.0.0. diff --git a/doc/mkdocs/docs/api/basic_json/basic_json.md b/doc/mkdocs/docs/api/basic_json/basic_json.md index afa3901d1..9a289d6e9 100644 --- a/doc/mkdocs/docs/api/basic_json/basic_json.md +++ b/doc/mkdocs/docs/api/basic_json/basic_json.md @@ -201,16 +201,16 @@ basic_json(basic_json&& other) noexcept; ## Exceptions -1. / +1. (none) 2. The function does not throw exceptions. -3. / -4. / +3. (none) +4. (none) 5. The function can throw the following exceptions: - Throws [`type_error.301`](../../home/exceptions.md#jsonexceptiontype_error301) if `type_deduction` is `#!cpp false`, `manual_type` is `value_t::object`, but `init` contains an element which is not a pair whose first element is a string. In this case, the constructor could not create an object. If `type_deduction` would have been `#!cpp true`, an array would have been created. See `object(initializer_list_t)` for an example. -6. / +6. (none) 7. The function can throw the following exceptions: - Throws [`invalid_iterator.201`](../../home/exceptions.md#jsonexceptioninvalid_iterator201) if iterators `first` and `last` are not compatible (i.e., do not belong to the same JSON value). In this case, the range @@ -220,7 +220,7 @@ basic_json(basic_json&& other) noexcept; element anymore. In this case, the range `[first, last)` is undefined. See example code below. - Throws [`invalid_iterator.206`](../../home/exceptions.md#jsonexceptioninvalid_iterator206) if iterators `first` and `last` belong to a `#!json null` value. In this case, the range `[first, last)` is undefined. -8. / +8. (none) 9. The function does not throw exceptions. ## Complexity diff --git a/doc/mkdocs/docs/api/basic_json/contains.md b/doc/mkdocs/docs/api/basic_json/contains.md index 8463f4ea9..67a5ffcc0 100644 --- a/doc/mkdocs/docs/api/basic_json/contains.md +++ b/doc/mkdocs/docs/api/basic_json/contains.md @@ -2,21 +2,28 @@ ```cpp // (1) -template -bool contains(KeyT && key) const; +bool contains(const typename object_t::key_type& key) const; // (2) +template +bool contains(KeyType&& key) const; + +// (3) bool contains(const json_pointer& ptr) const; ``` -1. Check whether an element exists in a JSON object with key equivalent to `key`. If the element is not found or the +1. Check whether an element exists in a JSON object with a key equivalent to `key`. If the element is not found or the JSON value is not an object, `#!cpp false` is returned. -2. Check whether the given JSON pointer `ptr` can be resolved in the current JSON value. +2. See 1. This overload is only available if `KeyType` is comparable with `#!cpp typename object_t::key_type` and + `#!cpp typename object_comparator_t::is_transparent` denotes a type. +3. Check whether the given JSON pointer `ptr` can be resolved in the current JSON value. ## Template parameters -`KeyT` -: A type for an object key other than `basic_json::json_pointer`. +`KeyType` +: A type for an object key other than [`json_pointer`](../json_pointer/index.md) that is comparable with + [`string_t`](string_t.md) using [`object_comparator_t`](object_comparator_t.md). + This can also be a string view (C++17). ## Parameters @@ -30,7 +37,8 @@ bool contains(const json_pointer& ptr) const; 1. `#!cpp true` if an element with specified `key` exists. If no such element with such key is found or the JSON value is not an object, `#!cpp false` is returned. -2. `#!cpp true` if the JSON pointer can be resolved to a stored value, `#!cpp false` otherwise. +2. See 1. +3. `#!cpp true` if the JSON pointer can be resolved to a stored value, `#!cpp false` otherwise. ## Exception safety @@ -39,7 +47,8 @@ Strong exception safety: if an exception occurs, the original value stays intact ## Exceptions 1. The function does not throw exceptions. -2. The function can throw the following exceptions: +2. The function does not throw exceptions. +3. The function can throw the following exceptions: - Throws [`parse_error.106`](../../home/exceptions.md#jsonexceptionparse_error106) if an array index begins with `0`. - Throws [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) if an array index was not a @@ -74,7 +83,7 @@ Logarithmic in the size of the JSON object. --8<-- "examples/contains.output" ``` -??? example "Example (1) check with JSON pointer" +??? example "Example (3) check with JSON pointer" The example shows how `contains()` is used. @@ -90,5 +99,6 @@ Logarithmic in the size of the JSON object. ## Version history -1. Added in version 3.6.0. -2. Added in version 3.7.0. +1. Added in version 3.11.0. +2. Added in version 3.6.0. Extended template `KeyType` to support comparable types in version 3.11.0. +3. Added in version 3.7.0. diff --git a/doc/mkdocs/docs/api/basic_json/count.md b/doc/mkdocs/docs/api/basic_json/count.md index fcfef8673..de135be21 100644 --- a/doc/mkdocs/docs/api/basic_json/count.md +++ b/doc/mkdocs/docs/api/basic_json/count.md @@ -1,17 +1,25 @@ # nlohmann::basic_json::count ```cpp -template -size_type count(KeyT&& key) const; +// (1) +size_type count(const typename object_t::key_type& key) const; + +// (2) +template +size_type count(KeyType&& key) const; ``` -Returns the number of elements with key `key`. If `ObjectType` is the default `std::map` type, the return value will -always be `0` (`key` was not found) or `1` (`key` was found). +1. Returns the number of elements with key `key`. If `ObjectType` is the default `std::map` type, the return value will + always be `0` (`key` was not found) or `1` (`key` was found). +2. See 1. This overload is only available if `KeyType` is comparable with `#!cpp typename object_t::key_type` and + `#!cpp typename object_comparator_t::is_transparent` denotes a type. ## Template parameters -`KeyT` -: A type for an object key. +`KeyType` +: A type for an object key other than [`json_pointer`](../json_pointer/index.md) that is comparable with + [`string_t`](string_t.md) using [`object_comparator_t`](object_comparator_t.md). + This can also be a string view (C++17). ## Parameters @@ -52,4 +60,5 @@ This method always returns `0` when executed on a JSON type that is not an objec ## Version history -- Added in version 1.0.0. +1. Added in version 3.11.0. +2. Added in version 1.0.0. Changed parameter `key` type to `KeyType&&` in version 3.11.0. diff --git a/doc/mkdocs/docs/api/basic_json/default_object_comparator_t.md b/doc/mkdocs/docs/api/basic_json/default_object_comparator_t.md new file mode 100644 index 000000000..9e5f6c5bd --- /dev/null +++ b/doc/mkdocs/docs/api/basic_json/default_object_comparator_t.md @@ -0,0 +1,19 @@ +# nlohmann::basic_json::default_object_comparator_t + +```cpp +using default_object_comparator_t = std::less; // until C++14 + +using default_object_comparator_t = std::less<>; // since C++14 +``` + +The default comparator used by [`object_t`](object_t.md). + +Since C++14 a transparent comparator is used which prevents unnecessary string construction +when looking up a key in an object. + +The actual comparator used depends on [`object_t`](object_t.md) and can be obtained via +[`object_comparator_t`](object_comparator_t.md). + +## Version history + +- Added in version 3.11.0. diff --git a/doc/mkdocs/docs/api/basic_json/erase.md b/doc/mkdocs/docs/api/basic_json/erase.md index d94c25b7f..6cb749b8c 100644 --- a/doc/mkdocs/docs/api/basic_json/erase.md +++ b/doc/mkdocs/docs/api/basic_json/erase.md @@ -13,6 +13,10 @@ const_iterator erase(const_iterator first, const_iterator last); size_type erase(const typename object_t::key_type& key); // (4) +template +size_type erase(KeyType&& key); + +// (5) void erase(const size_type idx); ``` @@ -29,7 +33,17 @@ void erase(const size_type idx); 3. Removes an element from a JSON object by key. -4. Removes an element from a JSON array by index. +4. See 3. This overload is only available if `KeyType` is comparable with `#!cpp typename object_t::key_type` and + `#!cpp typename object_comparator_t::is_transparent` denotes a type. + +5. Removes an element from a JSON array by index. + +## Template parameters + +`KeyType` +: A type for an object key other than [`json_pointer`](../json_pointer/index.md) that is comparable with + [`string_t`](string_t.md) using [`object_comparator_t`](object_comparator_t.md). + This can also be a string view (C++17). ## Parameters @@ -56,7 +70,8 @@ void erase(const size_type idx); is returned. 3. Number of elements removed. If `ObjectType` is the default `std::map` type, the return value will always be `0` (`key` was not found) or `1` (`key` was found). -4. / +4. See 3. +5. (none) ## Exception safety @@ -83,7 +98,8 @@ Strong exception safety: if an exception occurs, the original value stays intact 3. The function can throw the following exceptions: - Throws [`type_error.307`](../../home/exceptions.md#jsonexceptiontype_error307) when called on a type other than JSON object; example: `"cannot use erase() with null"` -4. The function can throw the following exceptions: +4. See 3. +5. The function can throw the following exceptions: - Throws [`type_error.307`](../../home/exceptions.md#jsonexceptiontype_error307) when called on a type other than JSON object; example: `"cannot use erase() with null"` - Throws [`out_of_range.401`](../../home/exceptions.md#jsonexceptionout_of_range401) when `idx >= size()`; example: @@ -103,14 +119,16 @@ Strong exception safety: if an exception occurs, the original value stays intact - strings and binary: linear in the length of the member - other types: constant 3. `log(size()) + count(key)` -4. Linear in distance between `idx` and the end of the container. +4. `log(size()) + count(key)` +5. Linear in distance between `idx` and the end of the container. ## Notes 1. Invalidates iterators and references at or after the point of the `erase`, including the `end()` iterator. -2. / +2. (none) 3. References and iterators to the erased elements are invalidated. Other references and iterators are not affected. -4. / +4. See 3. +5. (none) ## Examples @@ -156,7 +174,7 @@ Strong exception safety: if an exception occurs, the original value stays intact --8<-- "examples/erase__key_type.output" ``` -??? example "Example: (4) remove element from a JSON array given an index" +??? example "Example: (5) remove element from a JSON array given an index" The example shows the effect of `erase()` using an array index. @@ -172,5 +190,8 @@ Strong exception safety: if an exception occurs, the original value stays intact ## Version history -- Added in version 1.0.0. -- Added support for binary types in version 3.8.0. +1. Added in version 1.0.0. Added support for binary types in version 3.8.0. +2. Added in version 1.0.0. Added support for binary types in version 3.8.0. +3. Added in version 1.0.0. +4. Added in version 3.11.0. +5. Added in version 1.0.0. diff --git a/doc/mkdocs/docs/api/basic_json/find.md b/doc/mkdocs/docs/api/basic_json/find.md index af4cb2972..d4fddc579 100644 --- a/doc/mkdocs/docs/api/basic_json/find.md +++ b/doc/mkdocs/docs/api/basic_json/find.md @@ -1,20 +1,28 @@ # nlohmann::basic_json::find ```cpp -template -iterator find(KeyT&& key); +// (1) +iterator find(const typename object_t::key_type& key); +const_iterator find(const typename object_t::key_type& key) const; -template -const_iterator find(KeyT&& key) const; +// (2) +template +iterator find(KeyType&& key); +template +const_iterator find(KeyType&& key) const; ``` -Finds an element in a JSON object with key equivalent to `key`. If the element is not found or the JSON value is not an -object, `end()` is returned. +1. Finds an element in a JSON object with a key equivalent to `key`. If the element is not found or the + JSON value is not an object, `end()` is returned. +2. See 1. This overload is only available if `KeyType` is comparable with `#!cpp typename object_t::key_type` and + `#!cpp typename object_comparator_t::is_transparent` denotes a type. ## Template parameters -`KeyT` -: A type for an object key. +`KeyType` +: A type for an object key other than [`json_pointer`](../json_pointer/index.md) that is comparable with + [`string_t`](string_t.md) using [`object_comparator_t`](object_comparator_t.md). + This can also be a string view (C++17). ## Parameters @@ -23,8 +31,8 @@ object, `end()` is returned. ## Return value -Iterator to an element with key equivalent to `key`. If no such element is found or the JSON value is not an object, -past-the-end (see `end()`) iterator is returned. +Iterator to an element with a key equivalent to `key`. If no such element is found or the JSON value is not an object, +a past-the-end iterator (see `end()`) is returned. ## Exception safety @@ -60,4 +68,5 @@ This method always returns `end()` when executed on a JSON type that is not an o ## Version history -- Added in version 1.0.0. +1. Added in version 3.11.0. +2. Added in version 1.0.0. Changed to support comparable types in version 3.11.0. diff --git a/doc/mkdocs/docs/api/basic_json/index.md b/doc/mkdocs/docs/api/basic_json/index.md index 286fea2b4..68ac063ff 100644 --- a/doc/mkdocs/docs/api/basic_json/index.md +++ b/doc/mkdocs/docs/api/basic_json/index.md @@ -128,6 +128,7 @@ The class satisfies the following concept requirements: - [**array_t**](array_t.md) - type for arrays - [**binary_t**](binary_t.md) - type for binary arrays - [**boolean_t**](boolean_t.md) - type for booleans +- [**default_object_comparator_t**](default_object_comparator_t.md) - default comparator for objects - [**number_float_t**](number_float_t.md) - type for numbers (floating-point) - [**number_integer_t**](number_integer_t.md) - type for numbers (integer) - [**number_unsigned_t**](number_unsigned_t.md) - type for numbers (unsigned) diff --git a/doc/mkdocs/docs/api/basic_json/insert.md b/doc/mkdocs/docs/api/basic_json/insert.md index fdd8fe6b5..2e6b29301 100644 --- a/doc/mkdocs/docs/api/basic_json/insert.md +++ b/doc/mkdocs/docs/api/basic_json/insert.md @@ -50,7 +50,7 @@ void insert(const_iterator first, const_iterator last); 2. iterator pointing to the first element inserted, or `pos` if `#!cpp cnt==0` 3. iterator pointing to the first element inserted, or `pos` if `#!cpp first==last` 4. iterator pointing to the first element inserted, or `pos` if `ilist` is empty -5. / +5. (none) ## Exception safety diff --git a/doc/mkdocs/docs/api/basic_json/object_comparator_t.md b/doc/mkdocs/docs/api/basic_json/object_comparator_t.md index e2bc79d05..6c64b6453 100644 --- a/doc/mkdocs/docs/api/basic_json/object_comparator_t.md +++ b/doc/mkdocs/docs/api/basic_json/object_comparator_t.md @@ -1,16 +1,16 @@ # nlohmann::basic_json::object_comparator_t -```cpp -using object_comparator_t = std::less; // until C++14 -using object_comparator_t = std::less<>; // since C++14 +```cpp +using object_comparator_t = typename object_t::key_compare; +// or +using object_comparator_t = default_object_comparator_t; ``` -The comparator used in [`object_t`](object_t.md). - -When C++14 is detected, a transparent comparator is used which, when combined with perfect forwarding on find() and -count() calls, prevents unnecessary string construction. +The comparator used by [`object_t`](object_t.md). Defined as `#!cpp typename object_t::key_compare` if available, +and [`default_object_comparator_t`](default_object_comparator_t.md) otherwise. ## Version history -- Unknown. +- Added in version 3.0.0. +- Changed to be conditionally defined as `#!cpp typename object_t::key_compare` or `default_object_comparator_t` in version 3.11.0. diff --git a/doc/mkdocs/docs/api/basic_json/object_t.md b/doc/mkdocs/docs/api/basic_json/object_t.md index d4bea15aa..67b3bb78c 100644 --- a/doc/mkdocs/docs/api/basic_json/object_t.md +++ b/doc/mkdocs/docs/api/basic_json/object_t.md @@ -3,7 +3,7 @@ ```cpp using object_t = ObjectType>>; ``` @@ -52,7 +52,7 @@ std::map< > ``` -See [`object_comparator_t`](object_comparator_t.md) for more information. +See [`default_object_comparator_t`](default_object_comparator_t.md) for more information. #### Behavior diff --git a/doc/mkdocs/docs/api/basic_json/operator[].md b/doc/mkdocs/docs/api/basic_json/operator[].md index cc9eae7f3..cd5638b97 100644 --- a/doc/mkdocs/docs/api/basic_json/operator[].md +++ b/doc/mkdocs/docs/api/basic_json/operator[].md @@ -6,26 +6,32 @@ reference operator[](size_type idx); const_reference operator[](size_type idx) const; // (2) -reference operator[](const typename object_t::key_type& key); +reference operator[](typename object_t::key_type key); const_reference operator[](const typename object_t::key_type& key) const; -template -reference operator[](T* key); -template -const_reference operator[](T* key) const; // (3) +template +reference operator[](KeyType&& key); +template +const_reference operator[](KeyType&& key) const; + +// (4) reference operator[](const json_pointer& ptr); const_reference operator[](const json_pointer& ptr) const; ``` 1. Returns a reference to the array element at specified location `idx`. -2. Returns a reference to the object element at with specified key `key`. -3. Returns a reference to the element at with specified JSON pointer `ptr`. +2. Returns a reference to the object element with specified key `key`. The non-const qualified overload takes the key by value. +3. See 2. This overload is only available if `KeyType` is comparable with `#!cpp typename object_t::key_type` and + `#!cpp typename object_comparator_t::is_transparent` denotes a type. +4. Returns a reference to the element with specified JSON pointer `ptr`. ## Template parameters -`T` -: string literal convertible to `object_t::key_type` +`KeyType` +: A type for an object key other than [`json_pointer`](../json_pointer/index.md) that is comparable with + [`string_t`](string_t.md) using [`object_comparator_t`](object_comparator_t.md). + This can also be a string view (C++17). ## Parameters @@ -40,9 +46,10 @@ const_reference operator[](const json_pointer& ptr) const; ## Return value -1. reference to the element at index `idx` -2. reference to the element at key `key` -3. reference to the element pointed to by `ptr` +1. (const) reference to the element at index `idx` +2. (const) reference to the element at key `key` +3. (const) reference to the element at key `key` +4. (const) reference to the element pointed to by `ptr` ## Exception safety @@ -56,7 +63,8 @@ Strong exception safety: if an exception occurs, the original value stays intact 2. The function can throw the following exceptions: - Throws [`type_error.305`](../../home/exceptions.md#jsonexceptiontype_error305) if the JSON value is not an object or null; in that case, using the `[]` operator with a key makes no sense. -3. The function can throw the following exceptions: +3. See 2. +4. The function can throw the following exceptions: - Throws [`parse_error.106`](../../home/exceptions.md#jsonexceptionparse_error106) if an array index in the passed JSON pointer `ptr` begins with '0'. - Throws [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) if an array index in the passed @@ -70,7 +78,8 @@ Strong exception safety: if an exception occurs, the original value stays intact 1. Constant if `idx` is in the range of the array. Otherwise, linear in `idx - size()`. 2. Logarithmic in the size of the container. -3. Constant +3. Logarithmic in the size of the container. +4. Logarithmic in the size of the container. ## Notes @@ -87,7 +96,9 @@ Strong exception safety: if an exception occurs, the original value stays intact 2. If `key` is not found in the object, then it is silently added to the object and filled with a `#!json null` value to make `key` a valid reference. In case the value was `#!json null` before, it is converted to an object. -3. `null` values are created in arrays and objects if necessary. +3. See 2. + +4. `null` values are created in arrays and objects if necessary. In particular: @@ -143,7 +154,7 @@ Strong exception safety: if an exception occurs, the original value stays intact --8<-- "examples/operatorarray__key_type.output" ``` -??? example "Example (2): access specified object element" +??? example "Example (2): access specified object element (const)" The example below shows how object elements can be read using the `[]` operator. @@ -157,7 +168,7 @@ Strong exception safety: if an exception occurs, the original value stays intact --8<-- "examples/operatorarray__key_type_const.output" ``` -??? example "Example (3): access specified element via JSON Pointer" +??? example "Example (4): access specified element via JSON Pointer" The example below shows how values can be read and written using JSON Pointers. @@ -171,7 +182,7 @@ Strong exception safety: if an exception occurs, the original value stays intact --8<-- "examples/operatorjson_pointer.output" ``` -??? example "Example (3): access specified element via JSON Pointer" +??? example "Example (4): access specified element via JSON Pointer (const)" The example below shows how values can be read using JSON Pointers. @@ -193,5 +204,6 @@ Strong exception safety: if an exception occurs, the original value stays intact ## Version history 1. Added in version 1.0.0. -2. Added in version 1.0.0. Overloads for `T* key` added in version 1.1.0. -3. Added in version 2.0.0. +2. Added in version 1.0.0. Added overloads for `T* key` in version 1.1.0. Removed overloads for `T* key` (replaced by 3) in version 3.11.0. +3. Added in version 3.11.0. +4. Added in version 2.0.0. diff --git a/doc/mkdocs/docs/api/basic_json/to_bson.md b/doc/mkdocs/docs/api/basic_json/to_bson.md index 664dd0e20..5c4324a3f 100644 --- a/doc/mkdocs/docs/api/basic_json/to_bson.md +++ b/doc/mkdocs/docs/api/basic_json/to_bson.md @@ -28,7 +28,7 @@ The exact mapping and its limitations is described on a [dedicated page](../../f ## Return value 1. BSON serialization as byte vector -2. / +2. (none) ## Exception safety diff --git a/doc/mkdocs/docs/api/basic_json/to_cbor.md b/doc/mkdocs/docs/api/basic_json/to_cbor.md index 05d85ed85..0f944c481 100644 --- a/doc/mkdocs/docs/api/basic_json/to_cbor.md +++ b/doc/mkdocs/docs/api/basic_json/to_cbor.md @@ -29,7 +29,7 @@ The exact mapping and its limitations is described on a [dedicated page](../../f ## Return value 1. CBOR serialization as byte vector -2. / +2. (none) ## Exception safety diff --git a/doc/mkdocs/docs/api/basic_json/to_msgpack.md b/doc/mkdocs/docs/api/basic_json/to_msgpack.md index fb4b40bd0..7d40981d5 100644 --- a/doc/mkdocs/docs/api/basic_json/to_msgpack.md +++ b/doc/mkdocs/docs/api/basic_json/to_msgpack.md @@ -28,7 +28,7 @@ The exact mapping and its limitations is described on a [dedicated page](../../f ## Return value 1. MessagePack serialization as byte vector -2. / +2. (none) ## Exception safety diff --git a/doc/mkdocs/docs/api/basic_json/to_ubjson.md b/doc/mkdocs/docs/api/basic_json/to_ubjson.md index 0a3d87e54..e3cd5d62b 100644 --- a/doc/mkdocs/docs/api/basic_json/to_ubjson.md +++ b/doc/mkdocs/docs/api/basic_json/to_ubjson.md @@ -39,7 +39,7 @@ The exact mapping and its limitations is described on a [dedicated page](../../f ## Return value 1. UBJSON serialization as byte vector -2. / +2. (none) ## Exception safety diff --git a/doc/mkdocs/docs/api/basic_json/value.md b/doc/mkdocs/docs/api/basic_json/value.md index 1844c41fb..6a1f3481d 100644 --- a/doc/mkdocs/docs/api/basic_json/value.md +++ b/doc/mkdocs/docs/api/basic_json/value.md @@ -4,9 +4,14 @@ // (1) template ValueType value(const typename object_t::key_type& key, - const ValueType& default_value) const; + ValueType&& default_value) const; // (2) +template +ValueType value(KeyType&& key, + ValueType&& default_value) const; + +// (3) template ValueType value(const json_pointer& ptr, const ValueType& default_value) const; @@ -24,7 +29,10 @@ ValueType value(const json_pointer& ptr, } ``` -2. Returns either a copy of an object's element at the specified JSON pointer `ptr` or a given default value if no value +2. See 1. This overload is only available if `KeyType` is comparable with `#!cpp typename object_t::key_type` and + `#!cpp typename object_comparator_t::is_transparent` denotes a type. + +3. Returns either a copy of an object's element at the specified JSON pointer `ptr` or a given default value if no value at `ptr` exists. The function is basically equivalent to executing @@ -44,6 +52,10 @@ ValueType value(const json_pointer& ptr, ## Template parameters +`KeyType` +: A type for an object key other than [`json_pointer`](../json_pointer/index.md) that is comparable with + [`string_t`](string_t.md) using [`object_comparator_t`](object_comparator_t.md). + This can also be a string view (C++17). `ValueType` : type compatible to JSON values, for instance `#!cpp int` for JSON integer numbers, `#!cpp bool` for JSON booleans, or `#!cpp std::vector` types for JSON arrays. Note the type of the expected value at `key`/`ptr` and the default @@ -55,7 +67,7 @@ ValueType value(const json_pointer& ptr, : key of the element to access `default_value` (in) -: the value to return if key/ptr found no value +: the value to return if `key`/`ptr` found no value `ptr` (in) : a JSON pointer to the element to access @@ -63,7 +75,8 @@ ValueType value(const json_pointer& ptr, ## Return value 1. copy of the element at key `key` or `default_value` if `key` is not found -1. copy of the element at JSON Pointer `ptr` or `default_value` if no value for `ptr` is found +2. copy of the element at key `key` or `default_value` if `key` is not found +3. copy of the element at JSON Pointer `ptr` or `default_value` if no value for `ptr` is found ## Exception safety @@ -77,7 +90,8 @@ changes to any JSON value. the type of the value at `key` - Throws [`type_error.306`](../../home/exceptions.md#jsonexceptiontype_error306) if the JSON value is not an object; in that case, using `value()` with a key makes no sense. -2. The function can throw the following exceptions: +2. See 1. +3. The function can throw the following exceptions: - Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if `default_value` does not match the type of the value at `ptr` - Throws [`type_error.306`](../../home/exceptions.md#jsonexceptiontype_error306) if the JSON value is not an object; @@ -87,6 +101,7 @@ changes to any JSON value. 1. Logarithmic in the size of the container. 2. Logarithmic in the size of the container. +3. Logarithmic in the size of the container. ## Examples @@ -104,7 +119,7 @@ changes to any JSON value. --8<-- "examples/basic_json__value.output" ``` -??? example "Example (2): access specified object element via JSON Pointer with default value" +??? example "Example (3): access specified object element via JSON Pointer with default value" The example below shows how object elements can be queried with a default value. @@ -125,5 +140,6 @@ changes to any JSON value. ## Version history -1. Added in version 1.0.0. -2. Added in version 2.0.2. +1. Added in version 1.0.0. Changed parameter `default_value` type from `const ValueType&` to `ValueType&&` in version 3.11.0. +2. Added in version 3.11.0. +3. Added in version 2.0.2. diff --git a/doc/mkdocs/docs/api/ordered_map.md b/doc/mkdocs/docs/api/ordered_map.md index 74b248ff2..160b85c28 100644 --- a/doc/mkdocs/docs/api/ordered_map.md +++ b/doc/mkdocs/docs/api/ordered_map.md @@ -32,6 +32,12 @@ A minimal map-like container that preserves insertion order for use within [`nlo - **const_iterator** - **size_type** - **value_type** +- **key_compare** - key comparison function +```cpp +std::equal_to // until C++14 + +std::equal_to<> // since C++14 +``` ## Member functions @@ -68,3 +74,4 @@ A minimal map-like container that preserves insertion order for use within [`nlo ## Version history - Added in version 3.9.0 to implement [`nlohmann::ordered_json`](ordered_json.md). +- Added **key_compare** member in version 3.11.0. diff --git a/doc/mkdocs/mkdocs.yml b/doc/mkdocs/mkdocs.yml index 59f0ae700..302e827cf 100644 --- a/doc/mkdocs/mkdocs.yml +++ b/doc/mkdocs/mkdocs.yml @@ -97,6 +97,7 @@ nav: - 'count': api/basic_json/count.md - 'crbegin': api/basic_json/crbegin.md - 'crend': api/basic_json/crend.md + - 'default_object_comparator_t': api/basic_json/default_object_comparator_t.md - 'diff': api/basic_json/diff.md - 'dump': api/basic_json/dump.md - 'emplace': api/basic_json/emplace.md diff --git a/include/nlohmann/detail/macro_scope.hpp b/include/nlohmann/detail/macro_scope.hpp index f636b908a..cc9ac5fc7 100644 --- a/include/nlohmann/detail/macro_scope.hpp +++ b/include/nlohmann/detail/macro_scope.hpp @@ -106,6 +106,12 @@ #endif #endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(no_unique_address) + #define JSON_NO_UNIQUE_ADDRESS [[no_unique_address]] +#else + #define JSON_NO_UNIQUE_ADDRESS +#endif + // disable documentation warnings on clang #if defined(__clang__) #pragma clang diagnostic push diff --git a/include/nlohmann/detail/macro_unscope.hpp b/include/nlohmann/detail/macro_unscope.hpp index 377d3f11d..ec57b02cc 100644 --- a/include/nlohmann/detail/macro_unscope.hpp +++ b/include/nlohmann/detail/macro_unscope.hpp @@ -14,6 +14,7 @@ #undef NLOHMANN_BASIC_JSON_TPL #undef JSON_EXPLICIT #undef NLOHMANN_CAN_CALL_STD_FUNC_IMPL +#undef JSON_NO_UNIQUE_ADDRESS #ifndef JSON_TEST_KEEP_MACROS #undef JSON_CATCH diff --git a/include/nlohmann/detail/meta/type_traits.hpp b/include/nlohmann/detail/meta/type_traits.hpp index 376c00a09..2cc13f3ac 100644 --- a/include/nlohmann/detail/meta/type_traits.hpp +++ b/include/nlohmann/detail/meta/type_traits.hpp @@ -54,11 +54,6 @@ struct is_basic_json_context : || std::is_same::value > {}; -template struct is_json_pointer : std::false_type {}; - -template -struct is_json_pointer> : std::true_type {}; - ////////////////////// // json_ref helpers // ////////////////////// @@ -160,6 +155,24 @@ struct has_to_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> T>::value; }; +template +using detect_key_compare = typename T::key_compare; + +template +struct has_key_compare : std::integral_constant::value> {}; + +// obtains the actual object key comparator +template +struct actual_object_comparator +{ + using object_t = typename BasicJsonType::object_t; + using object_comparator_t = typename BasicJsonType::default_object_comparator_t; + using type = typename std::conditional < has_key_compare::value, + typename object_t::key_compare, object_comparator_t>::type; +}; + +template +using actual_object_comparator_t = typename actual_object_comparator::type; /////////////////// // is_ functions // @@ -454,6 +467,78 @@ struct is_constructible_tuple : std::false_type {}; template struct is_constructible_tuple> : conjunction...> {}; +template +struct is_json_iterator_of : std::false_type {}; + +template +struct is_json_iterator_of : std::true_type {}; + +template +struct is_json_iterator_of : std::true_type +{}; + +// checks if a given type T is a template specialization of Primary +template