From 07ec1a4e318606620cb672cb4a613646afd510c2 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sat, 10 Oct 2026 12:10:11 +0200 Subject: [PATCH 1/9] Generate the docset index and pages with Python (#5799) Replace the hand-maintained docs/docset/docSet.sql and the bash/sed Makefile recipe with docs/docset/generate_docset.py: - The search index is generated from the mkdocs.yml nav and each page's H1 and declaration. Pages documenting several entities (e.g. JSON_HAS_CPP_11..26) get one entry per name; all non-API pages become guides. New API pages no longer need a manual entry. - Page titles are set from the index names. - The docset is self-contained: the mermaid loader no longer points to https://json.nlohmann.me/assets/..., the repository widget no longer queries api.github.com, remaining remote images are downloaded, and the build fails if any remote resource load remains. - The CSS that hides the site navigation now uses Material's classes; the element selectors lost against them, so the header was shown. check_structure.py's docset check is replaced by running the generator in `make style_check`. Signed-off-by: Niels Lohmann --- .github/CONTRIBUTING.md | 6 +- docs/docset/Makefile | 66 +--- docs/docset/README.md | 4 +- docs/docset/docSet.sql | 289 -------------- docs/docset/generate_docset.py | 528 +++++++++++++++++++++++++ docs/mkdocs/Makefile | 1 + docs/mkdocs/scripts/check_structure.py | 31 -- 7 files changed, 547 insertions(+), 378 deletions(-) delete mode 100644 docs/docset/docSet.sql create mode 100755 docs/docset/generate_docset.py diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 9c3bb2c04..1e42fe769 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -149,8 +149,10 @@ make build -C docs/mkdocs # strict build: fails on broken links, anchor make check_mermaid -C docs/mkdocs # checks the Mermaid diagrams (requires Node.js) ``` -A new API page also needs an entry in [`docs/docset/docSet.sql`](https://github.com/nlohmann/json/blob/develop/docs/docset/docSet.sql), -the search index of the docset; `make build` reports missing entries. +The search index of the docset is generated from [`mkdocs.yml`](https://github.com/nlohmann/json/blob/develop/docs/mkdocs/mkdocs.yml) +and each page's title (H1) and declaration by +[`docs/docset/generate_docset.py`](https://github.com/nlohmann/json/blob/develop/docs/docset/generate_docset.py); +`make build` reports API pages that cannot be classified. ### Amalgamate the source code diff --git a/docs/docset/Makefile b/docs/docset/Makefile index b638390db..0f86c57cb 100644 --- a/docs/docset/Makefile +++ b/docs/docset/Makefile @@ -1,48 +1,24 @@ SHELL=/usr/bin/env bash -SED ?= $(shell which gsed 2>/dev/null || which sed) - -MKDOCS_PAGES=$(shell cd ../mkdocs/docs/ && find * -type f -name '*.md' | sort) +PYTHON=../mkdocs/venv/bin/python3 .PHONY: all all: JSON_for_Modern_C++.tgz -docSet.dsidx: docSet.sql - # generate index - sqlite3 docSet.dsidx > "$$(ls JSON_for_Modern_C++.docset/Contents/Resources/Documents/assets/stylesheets/main.*.min.css)" - # fix spacing - echo -e "\n\ndiv.md-sidebar div.md-sidebar--secondary, div.md-main__inner { top: 0; margin-top: 0 }" >> "$$(ls JSON_for_Modern_C++.docset/Contents/Resources/Documents/assets/stylesheets/main.*.min.css)" - # remove "JSON for Modern C++" from page titles (fallback) - find JSON_for_Modern_C++.docset/Contents/Resources/Documents -type f -exec $(SED) -i 's| - JSON for Modern C++||' {} + - # replace page titles with name from index, if available - for page in $(MKDOCS_PAGES); do \ - case "$$page" in \ - */index.md) path=$${page/\/index.md/} ;; \ - *) path=$${page/.md/} ;; \ - esac; \ - title=$$(sqlite3 docSet.dsidx "SELECT name FROM searchIndex WHERE path='$$path/index.html'" | tr '\n' ',' | $(SED) -e 's/,/, /g' -e 's/, $$/\n/'); \ - if [ "x$$title" != "x" ]; then \ - $(SED) -i "s%.*%$$title%" "JSON_for_Modern_C++.docset/Contents/Resources/Documents/$$path/index.html"; \ - fi \ - done - # clean up - rm JSON_for_Modern_C++.docset/Contents/Resources/Documents/sitemap.* - # copy index - cp docSet.dsidx JSON_for_Modern_C++.docset/Contents/Resources/ + $(PYTHON) generate_docset.py docset ../mkdocs/site . +.PHONY: JSON_for_Modern_C++.tgz JSON_for_Modern_C++.tgz: JSON_for_Modern_C++.docset - tar --exclude='.DS_Store' -cvzf JSON_for_Modern_C++.tgz JSON_for_Modern_C++.docset + $(PYTHON) generate_docset.py tgz . # install docset for Zeal documentation browser (https://zealdocs.org/) .PHONY: install_docset_zeal @@ -52,26 +28,6 @@ install_docset_zeal: JSON_for_Modern_C++.docset mkdir -p $$docset_root; \ cp -r JSON_for_Modern_C++.docset $$docset_root/ -# both targets below compare the docset search index with the mkdocs page -# set. They share the same normalization (docs/foo/index.md and -# docs/foo.md both become foo/index.html, the URL mkdocs itself would -# give the page; the top-level index.md is excluded, as it is not part -# of the hand-curated docSet.sql) and use comm(1) on two sorted lists -# instead of running a sqlite3 query, or an O(n*m) nested shell loop, -# once per page. -DOCSET_INDEX_PATHS=$(shell sqlite3 docSet.dsidx "SELECT DISTINCT path FROM searchIndex" | sort) -DOCSET_PAGE_PATHS=$(shell echo '$(MKDOCS_PAGES)' | tr ' ' '\n' | grep -v '^index\.md$$' | $(SED) -E 's@/index\.md$$@/index.html@; s@\.md$$@/index.html@' | sort) - -# list mkdocs pages missing from the docset index -.PHONY: list_missing_pages -list_missing_pages: docSet.dsidx - @comm -23 <(echo '$(DOCSET_PAGE_PATHS)' | tr ' ' '\n') <(echo '$(DOCSET_INDEX_PATHS)' | tr ' ' '\n') - -# list paths in the docset index without a corresponding mkdocs page -.PHONY: list_removed_paths -list_removed_paths: docSet.dsidx - @comm -13 <(echo '$(DOCSET_PAGE_PATHS)' | tr ' ' '\n') <(echo '$(DOCSET_INDEX_PATHS)' | tr ' ' '\n') - .PHONY: clean clean: rm -f docSet.dsidx diff --git a/docs/docset/README.md b/docs/docset/README.md index 9d962a91c..38021f1fc 100644 --- a/docs/docset/README.md +++ b/docs/docset/README.md @@ -4,7 +4,8 @@ The folder contains the required files to create a [docset](https://kapeli.com/d documentation browsers like [Dash](https://kapeli.com/dash), [Velocity](https://velocity.silverlakesoftware.com), or [Zeal](https://zealdocs.org). -The docset can be created with +The docset (pages and search index) is generated by `generate_docset.py` from the mkdocs site and `mkdocs.yml`. It +can be created with ```sh make JSON_for_Modern_C++.docset @@ -12,6 +13,7 @@ make JSON_for_Modern_C++.docset The generated folder `JSON_for_Modern_C++.docset` can then be opened in the documentation browser. `make all` builds a `JSON_for_Modern_C++.tgz` archive instead, and `make install_docset_zeal` installs the docset for Zeal directly. +`make docSet.dsidx` builds only the search index. A recent version is also part of the [Dash user contributions](https://github.com/Kapeli/Dash-User-Contributions/tree/master/docsets/JSON_for_Modern_C%2B%2B). diff --git a/docs/docset/docSet.sql b/docs/docset/docSet.sql deleted file mode 100644 index 5549b55d0..000000000 --- a/docs/docset/docSet.sql +++ /dev/null @@ -1,289 +0,0 @@ -DROP TABLE IF EXISTS searchIndex; -CREATE TABLE searchIndex(id INTEGER PRIMARY KEY, name TEXT, type TEXT, path TEXT); -CREATE UNIQUE INDEX anchor ON searchIndex (name, type, path); - --- API -INSERT INTO searchIndex(name, type, path) VALUES ('adl_serializer', 'Class', 'api/adl_serializer/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('adl_serializer::from_json', 'Function', 'api/adl_serializer/from_json/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('adl_serializer::to_json', 'Function', 'api/adl_serializer/to_json/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('byte_container_with_subtype', 'Class', 'api/byte_container_with_subtype/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('byte_container_with_subtype::byte_container_with_subtype', 'Constructor', 'api/byte_container_with_subtype/byte_container_with_subtype/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('byte_container_with_subtype::clear_subtype', 'Method', 'api/byte_container_with_subtype/clear_subtype/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('byte_container_with_subtype::has_subtype', 'Method', 'api/byte_container_with_subtype/has_subtype/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('byte_container_with_subtype::operator!=', 'Operator', 'api/byte_container_with_subtype/operator_ne/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('byte_container_with_subtype::operator==', 'Operator', 'api/byte_container_with_subtype/operator_eq/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('byte_container_with_subtype::set_subtype', 'Method', 'api/byte_container_with_subtype/set_subtype/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('byte_container_with_subtype::subtype', 'Method', 'api/byte_container_with_subtype/subtype/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json', 'Class', 'api/basic_json/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('format_as', 'Function', 'api/basic_json/format_as/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::accept', 'Function', 'api/basic_json/accept/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::array', 'Function', 'api/basic_json/array/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::array_t', 'Type', 'api/basic_json/array_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::as_base_class', 'Method', 'api/basic_json/as_base_class/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::at', 'Method', 'api/basic_json/at/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::back', 'Method', 'api/basic_json/back/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::basic_json', 'Constructor', 'api/basic_json/basic_json/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::begin', 'Method', 'api/basic_json/begin/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::binary', 'Function', 'api/basic_json/binary/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::binary_t', 'Type', 'api/basic_json/binary_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::boolean_t', 'Type', 'api/basic_json/boolean_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::cbegin', 'Method', 'api/basic_json/cbegin/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::cbor_tag_handler_t', 'Enum', 'api/basic_json/cbor_tag_handler_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::cend', 'Method', 'api/basic_json/cend/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::clear', 'Method', 'api/basic_json/clear/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::contains', 'Method', 'api/basic_json/contains/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::count', 'Method', 'api/basic_json/count/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::crbegin', 'Method', 'api/basic_json/crbegin/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::crend', 'Method', 'api/basic_json/crend/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::default_object_comparator_t', 'Type', 'api/basic_json/default_object_comparator_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::diff', 'Function', 'api/basic_json/diff/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::dump', 'Method', 'api/basic_json/dump/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::emplace', 'Method', 'api/basic_json/emplace/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::emplace_back', 'Method', 'api/basic_json/emplace_back/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::empty', 'Method', 'api/basic_json/empty/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::end', 'Method', 'api/basic_json/end/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::end_pos', 'Method', 'api/basic_json/end_pos/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::erase', 'Method', 'api/basic_json/erase/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::error_handler_t', 'Enum', 'api/basic_json/error_handler_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::exception', 'Class', 'api/basic_json/exception/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::find', 'Method', 'api/basic_json/find/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::flatten', 'Method', 'api/basic_json/flatten/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::from_bjdata', 'Function', 'api/basic_json/from_bjdata/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::from_bson', 'Function', 'api/basic_json/from_bson/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::from_cbor', 'Function', 'api/basic_json/from_cbor/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::from_msgpack', 'Function', 'api/basic_json/from_msgpack/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::from_bon8', 'Function', 'api/basic_json/from_bon8/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::from_ubjson', 'Function', 'api/basic_json/from_ubjson/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::front', 'Method', 'api/basic_json/front/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::get', 'Method', 'api/basic_json/get/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::get_allocator', 'Function', 'api/basic_json/get_allocator/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::get_binary', 'Method', 'api/basic_json/get_binary/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::get_ptr', 'Method', 'api/basic_json/get_ptr/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::get_ref', 'Method', 'api/basic_json/get_ref/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::get_to', 'Method', 'api/basic_json/get_to/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::input_format_t', 'Enum', 'api/basic_json/input_format_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::insert', 'Method', 'api/basic_json/insert/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::invalid_iterator', 'Class', 'api/basic_json/invalid_iterator/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::is_array', 'Method', 'api/basic_json/is_array/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::is_binary', 'Method', 'api/basic_json/is_binary/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::is_boolean', 'Method', 'api/basic_json/is_boolean/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::is_discarded', 'Method', 'api/basic_json/is_discarded/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::is_null', 'Method', 'api/basic_json/is_null/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::is_number', 'Method', 'api/basic_json/is_number/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::is_number_float', 'Method', 'api/basic_json/is_number_float/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::is_number_integer', 'Method', 'api/basic_json/is_number_integer/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::is_number_unsigned', 'Method', 'api/basic_json/is_number_unsigned/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::is_object', 'Method', 'api/basic_json/is_object/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::is_primitive', 'Method', 'api/basic_json/is_primitive/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::is_string', 'Method', 'api/basic_json/is_string/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::is_structured', 'Method', 'api/basic_json/is_structured/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::items', 'Method', 'api/basic_json/items/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::json_base_class_t', 'Type', 'api/basic_json/json_base_class_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::json_serializer', 'Class', 'api/basic_json/json_serializer/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::max_size', 'Method', 'api/basic_json/max_size/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::merge_patch', 'Method', 'api/basic_json/merge_patch/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::meta', 'Function', 'api/basic_json/meta/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::number_float_t', 'Type', 'api/basic_json/number_float_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::number_integer_t', 'Type', 'api/basic_json/number_integer_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::number_unsigned_t', 'Type', 'api/basic_json/number_unsigned_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::object', 'Function', 'api/basic_json/object/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::object_comparator_t', 'Type', 'api/basic_json/object_comparator_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::object_t', 'Type', 'api/basic_json/object_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::operator ValueType', 'Operator', 'api/basic_json/operator_ValueType/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::operator value_t', 'Operator', 'api/basic_json/operator_value_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::operator[]', 'Operator', 'api/basic_json/operator[]/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::operator=', 'Operator', 'api/basic_json/operator=/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::operator+=', 'Operator', 'api/basic_json/operator+=/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::operator==', 'Operator', 'api/basic_json/operator_eq/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::operator!=', 'Operator', 'api/basic_json/operator_ne/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::operator<', 'Operator', 'api/basic_json/operator_lt/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::operator<=', 'Operator', 'api/basic_json/operator_le/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::operator>', 'Operator', 'api/basic_json/operator_gt/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::operator>=', 'Operator', 'api/basic_json/operator_ge/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::operator<=>', 'Operator', 'api/basic_json/operator_spaceship/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::out_of_range', 'Class', 'api/basic_json/out_of_range/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::other_error', 'Class', 'api/basic_json/other_error/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::parse', 'Function', 'api/basic_json/parse/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::parse_error', 'Class', 'api/basic_json/parse_error/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::parse_event_t', 'Enum', 'api/basic_json/parse_event_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::parser_callback_t', 'Type', 'api/basic_json/parser_callback_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::patch', 'Method', 'api/basic_json/patch/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::patch_inplace', 'Method', 'api/basic_json/patch_inplace/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::push_back', 'Method', 'api/basic_json/push_back/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::rbegin', 'Method', 'api/basic_json/rbegin/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::rend', 'Method', 'api/basic_json/rend/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::sax_parse', 'Function', 'api/basic_json/sax_parse/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::size', 'Method', 'api/basic_json/size/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::start_pos', 'Method', 'api/basic_json/start_pos/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::string_t', 'Type', 'api/basic_json/string_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::swap', 'Method', 'api/basic_json/swap/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::type', 'Method', 'api/basic_json/type/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::type_error', 'Class', 'api/basic_json/type_error/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::type_name', 'Method', 'api/basic_json/type_name/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::unflatten', 'Method', 'api/basic_json/unflatten/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::update', 'Method', 'api/basic_json/update/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_bjdata', 'Function', 'api/basic_json/to_bjdata/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_bson', 'Function', 'api/basic_json/to_bson/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_cbor', 'Function', 'api/basic_json/to_cbor/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_msgpack', 'Function', 'api/basic_json/to_msgpack/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_bon8', 'Function', 'api/basic_json/to_bon8/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_string', 'Method', 'api/basic_json/to_string/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_ubjson', 'Function', 'api/basic_json/to_ubjson/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::value', 'Method', 'api/basic_json/value/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::value_t', 'Enum', 'api/basic_json/value_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::with_t', 'Type', 'api/basic_json/with_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::~basic_json', 'Method', 'api/basic_json/~basic_json/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json', 'Class', 'api/json/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer', 'Class', 'api/json_pointer/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::back', 'Method', 'api/json_pointer/back/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::empty', 'Method', 'api/json_pointer/empty/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::front', 'Method', 'api/json_pointer/front/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::json_pointer', 'Constructor', 'api/json_pointer/json_pointer/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::operator==', 'Operator', 'api/json_pointer/operator_eq/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::operator!=', 'Operator', 'api/json_pointer/operator_ne/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::operator/', 'Operator', 'api/json_pointer/operator_slash/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::operator/=', 'Operator', 'api/json_pointer/operator_slasheq/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::operator string_t', 'Operator', 'api/json_pointer/operator_string_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::operator<=>', 'Operator', 'api/json_pointer/operator_spaceship/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::parent_pointer', 'Method', 'api/json_pointer/parent_pointer/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::pop_back', 'Method', 'api/json_pointer/pop_back/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::pop_front', 'Method', 'api/json_pointer/pop_front/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::push_back', 'Method', 'api/json_pointer/push_back/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::push_front', 'Method', 'api/json_pointer/push_front/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::string_t', 'Type', 'api/json_pointer/string_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::to_string', 'Method', 'api/json_pointer/to_string/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_sax', 'Class', 'api/json_sax/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_sax::binary', 'Method', 'api/json_sax/binary/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_sax::boolean', 'Method', 'api/json_sax/boolean/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_sax::end_array', 'Method', 'api/json_sax/end_array/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_sax::end_object', 'Method', 'api/json_sax/end_object/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_sax::key', 'Method', 'api/json_sax/key/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_sax::null', 'Method', 'api/json_sax/null/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_sax::number_float', 'Method', 'api/json_sax/number_float/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_sax::number_integer', 'Method', 'api/json_sax/number_integer/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_sax::number_unsigned', 'Method', 'api/json_sax/number_unsigned/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_sax::parse_error', 'Method', 'api/json_sax/parse_error/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_sax::start_array', 'Method', 'api/json_sax/start_array/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_sax::start_object', 'Method', 'api/json_sax/start_object/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_sax::string', 'Method', 'api/json_sax/string/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('operator""_json', 'Literal', 'api/operator_literal_json/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('operator""_json_pointer', 'Literal', 'api/operator_literal_json_pointer/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('operator<<', 'Operator', 'api/operator_ltlt/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('operator>>', 'Operator', 'api/operator_gtgt/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('ordered_json', 'Class', 'api/ordered_json/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('ordered_map', 'Class', 'api/ordered_map/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('std::formatter', 'Class', 'api/basic_json/std_formatter/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('std::hash', 'Class', 'api/basic_json/std_hash/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('std::swap', 'Function', 'api/basic_json/std_swap/index.html'); - --- Features -INSERT INTO searchIndex(name, type, path) VALUES ('Arbitrary Type Conversions', 'Guide', 'features/arbitrary_types/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Binary Formats', 'Guide', 'features/binary_formats/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Binary Formats: BJData', 'Guide', 'features/binary_formats/bjdata/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Binary Formats: BSON', 'Guide', 'features/binary_formats/bson/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Binary Formats: CBOR', 'Guide', 'features/binary_formats/cbor/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Binary Formats: MessagePack', 'Guide', 'features/binary_formats/messagepack/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Binary Formats: BON8', 'Guide', 'features/binary_formats/bon8/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Binary Formats: UBJSON', 'Guide', 'features/binary_formats/ubjson/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Binary Values', 'Guide', 'features/binary_values/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Comments', 'Guide', 'features/comments/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Element Access', 'Guide', 'features/element_access/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Element Access: Access with default value: value', 'Guide', 'features/element_access/default_value/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Element Access: Checked access: at', 'Guide', 'features/element_access/checked_access/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Element Access: Unchecked access: operator[]', 'Guide', 'features/element_access/unchecked_access/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Exceptions', 'Guide', 'home/exceptions/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Integration: Migration Guide', 'Guide', 'integration/migration_guide/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Integration: CMake', 'Guide', 'integration/cmake/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Integration: Header only', 'Guide', 'integration/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Integration: Package Managers', 'Guide', 'integration/package_managers/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Integration: Pkg-config', 'Guide', 'integration/pkg-config/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Iterators', 'Guide', 'features/iterators/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON Merge Patch', 'Guide', 'features/merge_patch/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON Patch and Diff', 'Guide', 'features/json_patch/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON Pointer', 'Guide', 'features/json_pointer/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('nlohmann Namespace', 'Guide', 'features/namespace/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Types', 'Guide', 'features/types/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Types: Number Handling', 'Guide', 'features/types/number_handling/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Object Order', 'Guide', 'features/object_order/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Performance', 'Guide', 'features/performance/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Parsing', 'Guide', 'features/parsing/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Parsing: JSON Lines', 'Guide', 'features/parsing/json_lines/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Parsing: Parser Callbacks', 'Guide', 'features/parsing/parser_callbacks/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Parsing: Parsing and Exceptions', 'Guide', 'features/parsing/parse_exceptions/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Parsing: SAX Interface', 'Guide', 'features/parsing/sax_interface/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Parsing: Untrusted Input', 'Guide', 'features/parsing/untrusted_input/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Runtime Assertions', 'Guide', 'features/assertions/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Specializing enum conversion', 'Guide', 'features/enum_conversion/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Supported Macros', 'Guide', 'features/macros/index.html'); - --- Macros -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_ASSERT', 'Macro', 'api/macros/json_assert/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_BRACE_INIT_COPY_SEMANTICS', 'Macro', 'api/macros/json_brace_init_copy_semantics/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_CATCH_USER', 'Macro', 'api/macros/json_throw_user/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DELETE_DEPRECATED_FUNCTIONS', 'Macro', 'api/macros/json_delete_deprecated_functions/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DIAGNOSTICS', 'Macro', 'api/macros/json_diagnostics/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DIAGNOSTIC_POSITIONS', 'Macro', 'api/macros/json_diagnostic_positions/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DISABLE_ENUM_SERIALIZATION', 'Macro', 'api/macros/json_disable_enum_serialization/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DISABLE_TUPLE_REFERENCE_CONVERSION', 'Macro', 'api/macros/json_disable_tuple_reference_conversion/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_11', 'Macro', 'api/macros/json_has_cpp_11/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_14', 'Macro', 'api/macros/json_has_cpp_11/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_17', 'Macro', 'api/macros/json_has_cpp_11/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_20', 'Macro', 'api/macros/json_has_cpp_11/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_23', 'Macro', 'api/macros/json_has_cpp_11/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_26', 'Macro', 'api/macros/json_has_cpp_11/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_EXPERIMENTAL_FILESYSTEM', 'Macro', 'api/macros/json_has_filesystem/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_FILESYSTEM', 'Macro', 'api/macros/json_has_filesystem/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_RANGES', 'Macro', 'api/macros/json_has_ranges/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_STATIC_RTTI', 'Macro', 'api/macros/json_has_static_rtti/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_STD_FORMAT', 'Macro', 'api/macros/json_has_std_format/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_THREE_WAY_COMPARISON', 'Macro', 'api/macros/json_has_three_way_comparison/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_NOEXCEPTION', 'Macro', 'api/macros/json_noexception/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_NO_AUTOMATIC_UDLS', 'Macro', 'api/macros/json_no_automatic_udls/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_NO_IO', 'Macro', 'api/macros/json_no_io/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_NO_THREAD_LOCAL', 'Macro', 'api/macros/json_no_thread_local/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_PRECISE_STREAM_POSITION', 'Macro', 'api/macros/json_precise_stream_position/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_SKIP_LIBRARY_VERSION_CHECK', 'Macro', 'api/macros/json_skip_library_version_check/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_SKIP_UNSUPPORTED_COMPILER_CHECK', 'Macro', 'api/macros/json_skip_unsupported_compiler_check/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_STRICT_BINARY_UTF8', 'Macro', 'api/macros/json_strict_binary_utf8/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_STRICT_NUL_HANDLING', 'Macro', 'api/macros/json_strict_nul_handling/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_THROW_USER', 'Macro', 'api/macros/json_throw_user/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_TRY_USER', 'Macro', 'api/macros/json_throw_user/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_GLOBAL_UDLS', 'Macro', 'api/macros/json_use_global_udls/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_IMPLICIT_CONVERSIONS', 'Macro', 'api/macros/json_use_implicit_conversions/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON', 'Macro', 'api/macros/json_use_legacy_discarded_value_comparison/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS', 'Macro', 'api/macros/json_use_objects_for_enum_keyed_maps/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_SIMDUTF', 'Macro', 'api/macros/json_use_simdutf/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Macros', 'Macro', 'api/macros/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE', 'Macro', 'api/macros/nlohmann_define_derived_type/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE', 'Macro', 'api/macros/nlohmann_define_derived_type/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT', 'Macro', 'api/macros/nlohmann_define_derived_type/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE', 'Macro', 'api/macros/nlohmann_define_derived_type/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE', 'Macro', 'api/macros/nlohmann_define_derived_type/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT', 'Macro', 'api/macros/nlohmann_define_derived_type/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_INTRUSIVE', 'Macro', 'api/macros/nlohmann_define_type_intrusive/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE', 'Macro', 'api/macros/nlohmann_define_type_intrusive/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT', 'Macro', 'api/macros/nlohmann_define_type_intrusive/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE', 'Macro', 'api/macros/nlohmann_define_type_non_intrusive/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE', 'Macro', 'api/macros/nlohmann_define_type_non_intrusive/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT', 'Macro', 'api/macros/nlohmann_define_type_non_intrusive/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_NAMESPACE', 'Macro', 'api/macros/nlohmann_json_namespace/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_NAMESPACE_BEGIN', 'Macro', 'api/macros/nlohmann_json_namespace_begin/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_NAMESPACE_END', 'Macro', 'api/macros/nlohmann_json_namespace_begin/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_NAMESPACE_NO_VERSION', 'Macro', 'api/macros/nlohmann_json_namespace_no_version/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_SERIALIZE_ENUM', 'Macro', 'api/macros/nlohmann_json_serialize_enum/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_SERIALIZE_ENUM_STRICT', 'Macro', 'api/macros/nlohmann_json_serialize_enum_strict/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_VERSION_MAJOR', 'Macro', 'api/macros/nlohmann_json_version_major/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_VERSION_MINOR', 'Macro', 'api/macros/nlohmann_json_version_major/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_VERSION_PATCH', 'Macro', 'api/macros/nlohmann_json_version_major/index.html'); diff --git a/docs/docset/generate_docset.py b/docs/docset/generate_docset.py new file mode 100755 index 000000000..e09e8d6d1 --- /dev/null +++ b/docs/docset/generate_docset.py @@ -0,0 +1,528 @@ +#!/usr/bin/env python3 + +"""Generate the Dash docset search index from the mkdocs sources.""" + +import argparse +import glob +import hashlib +import html +import os +import re +import shutil +import sqlite3 +import sys +import tarfile +import urllib.parse +import urllib.request + +import yaml + +HERE = os.path.dirname(os.path.abspath(__file__)) +MKDOCS_YML = os.path.join(HERE, '..', 'mkdocs', 'mkdocs.yml') +PAGES = os.path.join(HERE, '..', 'mkdocs', 'docs') + +# api pages whose (name, type) cannot be derived by the heuristics +OVERRIDES = { +} + +DOCSET = 'JSON_for_Modern_C++.docset' +TITLE_SUFFIX = ' - JSON for Modern C++' + +# CSS rules appended to the stylesheet: hide navigation items and fix spacing +# hide the navigation (the documentation browser has its own); Material's class selectors would win over element +# selectors, hence the classes and !important +CSS_PATCH = ( + '\n\n.md-header, .md-footer, .md-tabs, .md-sidebar--primary, .md-content__button { display: none !important; }' + '\n\n.md-sidebar--secondary, .md-main__inner { top: 0 !important; margin-top: 0 !important; }' +) + +USER_AGENT = ('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 ' + '(KHTML, like Gecko) Chrome/124.0 Safari/537.36') +CONTENT_TYPE_EXT = {'image/svg+xml': '.svg', 'image/png': '.png', 'image/jpeg': '.jpg', + 'image/gif': '.gif', 'image/webp': '.webp'} + +# remote loads that are allowed to remain (URL -> reason) +ALLOWED_REMOTE = { + # Material only loads this polyfill if the browser has no ResizeObserver + 'https://unpkg.com/resize-observer-polyfill': 'fallback for browsers without ResizeObserver', +} + +problems = [] + + +def problem(page, reason) -> None: + """Record a problem; all problems are reported at the end.""" + problems.append(f'generate_docset.py: {page}: {reason}') + + +class Loader(yaml.SafeLoader): + """YAML loader that tolerates the custom tags used in mkdocs.yml.""" + + +Loader.add_multi_constructor('', lambda loader, suffix, node: None) + + +def walk_nav(items, groups=()): + """Yield (group titles, nav title or None, md path) for all nav leaves.""" + for item in items: + if isinstance(item, str): + yield list(groups), None, item + elif isinstance(item, dict): + for title, value in item.items(): + if isinstance(value, list): + yield from walk_nav(value, groups + (str(title),)) + else: + yield list(groups), str(title), value + + +def page_path(md_path) -> str: + """Map a markdown path to the HTML path of the rendered page.""" + if md_path.endswith('/index.md'): + return md_path[:-len('index.md')] + 'index.html' + return md_path[:-len('.md')] + '/index.html' + + +def read_page(md_path) -> str: + """Read a page (resolving a snippet include), return '' if it does not exist.""" + try: + with open(os.path.join(PAGES, md_path), encoding='utf-8') as f: + text = f.read() + m = re.match(r'--8<-- "(.+)"\s*$', text) + if m: + with open(os.path.join(PAGES, m.group(1)), encoding='utf-8') as f: + text = f.read() + return text + except OSError: + return '' + + +def clean(text) -> str: + """Strip tags and entities from a heading and normalize whitespace.""" + text = text.replace('\\>', '\x00') # escaped '>' is not the end of a tag + text = html.unescape(re.sub(r']*>', '', text)).replace('\x00', '>') + return re.sub(r'\s+', ' ', text).strip() + + +def strip_fences(text) -> str: + """Remove fenced code blocks (their lines may start with '# ').""" + return re.sub(r'^(```|~~~).*?^\1[^\n]*$', '', text, flags=re.M | re.S) + + +def get_h1(text): + """Return the cleaned first H1 of a page or None.""" + body = strip_fences(text) + m = re.search(r'^# (.+)$', body, flags=re.M) + if m: + return clean(m.group(1)) + m = re.search(r'

(.*?)

', body, flags=re.S) + return clean(m.group(1)) if m else None + + +def split_top_level(text, seps=','): + """Split at separators that are not nested in <>, () or [].""" + parts, depth, current = [], 0, '' + for c in text: + if c in '<([': + depth += 1 + elif c in '>)]': + depth -= 1 + if c in seps and depth <= 0: + parts.append(current) + current = '' + else: + current += c + parts.append(current) + return [p.strip() for p in parts if p.strip()] + + +OPERATOR_SYMBOLS = ('<=>', '<<', '>>', '<=', '>=', '<', '>') + + +def api_names(h1) -> list: + """Derive the entry names from the H1 of an api page.""" + # hide the angle brackets of operators from the nesting detection + for i, sym in enumerate(OPERATOR_SYMBOLS): + h1 = h1.replace('operator' + sym, f'operator\x01{i}\x01') + names = [] + for part in split_top_level(h1): + name = part.replace('\\', '').replace('nlohmann::', '') + name = re.sub(r'\x01(\d)\x01', lambda m: OPERATOR_SYMBOLS[int(m.group(1))], name) + # drop qualifiers like "operator<<(basic_json)", but keep "operator()" + if not name.endswith('operator()'): + name = re.sub(r'(?<=\w|[<>=!+\-*/\[\]])\([^()]*\)$', '', name) + if name not in names: + names.append(name) + return names + + +def first_cpp_block(text): + """Return the first ```cpp block following the H1.""" + m = re.search(r'^# .*?^```cpp\n(.*?)^```', text, flags=re.M | re.S) + return m.group(1) if m else None + + +def api_type(name, decl): + """Determine the Dash entry type from name and declaration.""" + parts = name.split('::') + last = parts[-1] + if name.startswith('operator""'): + return 'Literal' + if last.startswith('operator'): + return 'Operator' + if decl is None: + return None + if re.search(r'\benum\b', decl): + return 'Enum' + if re.search(r'^\s*(template\s*<.*>\s*)?(class|struct)\s+\w+\s*(final\b|[:{;<]|$)', decl, flags=re.M): + return 'Class' + if re.search(r'\busing\s+\w+\s*=', decl) or 'typedef' in decl: + return 'Type' + if len(parts) > 1 and last == parts[-2]: + return 'Constructor' + if last.startswith('~'): + return 'Method' + if re.search(r'\bstatic\b', decl) or len(parts) == 1 or parts[0] == 'std': + return 'Function' + return 'Method' + + +def macro_names(h1) -> list: + """Split the H1 of a macro page into macro names.""" + return [n.strip() for n in re.split(r'[,/]', h1) if n.strip()] + + +def api_entries(md_path): + """Return the (name, type) pairs of an api page.""" + if md_path in OVERRIDES: + return OVERRIDES[md_path] + if md_path == 'api/macros/index.md': + return [('Macros', 'Macro')] + text = read_page(md_path) + h1 = get_h1(text) + if not h1: + problem(md_path, 'no H1 found') + return [] + if md_path.startswith('api/macros/'): + return [(n, 'Macro') for n in macro_names(h1)] + names = api_names(h1) + if not names: + problem(md_path, 'no names found') + return [] + decl = first_cpp_block(text) + result = [] + for name in names: + kind = api_type(name, decl) + if kind is None: + problem(md_path, f'no type determinable for {name}') + else: + result.append((name, kind)) + return result + + +def guide_entries(nav): + """Yield (name, type, md path) for all non-api nav pages.""" + for groups, title, md_path in walk_nav(nav): + if md_path.startswith('api/') or md_path == 'index.md': + continue + if not md_path.endswith('.md'): + continue + groups = groups[1:] # drop the top-level tab + if title is None: + title = get_h1(read_page(md_path)) + if not title: + problem(md_path, 'no title found') + continue + # "Parsing: Parsing Untrusted Input" -> "Parsing: Untrusted Input" + if groups and title.startswith(groups[-1] + ' ') and title[len(groups[-1]) + 1:][:1].isupper(): + title = title[len(groups[-1]) + 1:] + if md_path.endswith('/index.md') and groups and title == groups[-1]: + name = ': '.join(groups) + else: + name = ': '.join(groups + [title]) + yield name, 'Guide', md_path + + +def build_entries() -> list: + """Return the sorted list of (name, type, path) index entries.""" + nav = load_mkdocs_yml()['nav'] + + entries = set() + for name, kind, md_path in guide_entries(nav): + entries.add((name, kind, page_path(md_path))) + + api_root = os.path.join(PAGES, 'api') + on_disk = set() + for root, _, files in os.walk(api_root): + for file in files: + if file.endswith('.md'): + rel = os.path.relpath(os.path.join(root, file), PAGES) + on_disk.add(rel.replace(os.sep, '/')) + for _, _, md_path in walk_nav(nav): + if md_path.startswith('api/') and md_path not in on_disk: + problem(md_path, 'listed in nav but missing on disk') + for md_path in sorted(on_disk): + for name, kind in api_entries(md_path): + entries.add((name, kind, page_path(md_path))) + return sorted(entries) + + +def write_index(entries, out) -> None: + """Write the entries into a SQLite search index.""" + if os.path.exists(out): + os.remove(out) + con = sqlite3.connect(out) + con.execute('CREATE TABLE searchIndex(id INTEGER PRIMARY KEY, name TEXT, type TEXT, path TEXT)') + con.execute('CREATE UNIQUE INDEX anchor ON searchIndex (name, type, path)') + con.executemany('INSERT INTO searchIndex(name, type, path) VALUES (?, ?, ?)', entries) + con.commit() + con.close() + + +def html_files(root): + """Yield all HTML files below root.""" + for base, _, files in os.walk(root): + for file in files: + if file.endswith('.html'): + yield os.path.join(base, file) + + +def read(path) -> str: + with open(path, encoding='utf-8') as f: + return f.read() + + +def write(path, text) -> None: + with open(path, 'w', encoding='utf-8') as f: + f.write(text) + + +def remove_source_widget(docs) -> None: + """Drop data-md-component=source so Material does not query api.github.com for the stars and version.""" + pattern = re.compile(r'\sdata-md-component=(?:"source"|source\b)') + for path in html_files(docs): + text = read(path) + new = pattern.sub('', text) + if new != text: + write(path, new) + + +def patch_titles(docs, entries) -> None: + """Strip the site name from all titles; use the index names where available.""" + names = {} + for name, _, path in entries: + names.setdefault(path, []).append(name) + for file in html_files(docs): + rel = os.path.relpath(file, docs).replace(os.sep, '/') + text = read(file).replace(TITLE_SUFFIX, '') + if rel in names: + title = html.escape(', '.join(names[rel]), quote=False) + text = re.sub(r'.*?', lambda _: f'{title}', text, count=1, flags=re.S) + write(file, text) + + +IMG_RE = re.compile(r']*>', re.I) +ATTR_RE = r'''(?:{0})\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>]+))''' + + +def attr(tag, name): + """Return the value of an attribute in a tag or None.""" + m = re.search(r'(? bool: + return re.match(r'(https?:)?//', url.strip(), flags=re.I) is not None + + +def download(url, docs) -> str: + """Download url into assets/external and return the path relative to docs.""" + u = urllib.parse.urlparse(url if not url.startswith('//') else 'https:' + url) + req = urllib.request.Request(u.geturl(), headers={'User-Agent': USER_AGENT}) + with urllib.request.urlopen(req, timeout=20) as r: + data = r.read() + ctype = r.headers.get_content_type() + path = urllib.parse.unquote(u.path).lstrip('/') + ext = os.path.splitext(path)[1] + if u.query or not ext or path.endswith('/'): + digest = hashlib.sha1(url.encode()).hexdigest()[:12] + path = os.path.join(os.path.dirname(path), digest + CONTENT_TYPE_EXT.get(ctype, ext or '.bin')) + rel = os.path.normpath(os.path.join('assets', 'external', u.hostname, path)) + out = os.path.join(docs, rel) + os.makedirs(os.path.dirname(out), exist_ok=True) + with open(out, 'wb') as f: + f.write(data) + return rel.replace(os.sep, '/') + + +def localize_images(docs) -> None: + """Download remote images and rewrite their src; drop them on failure.""" + cache = {} + for file in html_files(docs): + text = read(file) + + def repl(m): + tag = m.group(0) + src = attr(tag, 'src') + if src is None or not is_remote(src): + return tag + if src not in cache: + try: + cache[src] = download(src, docs) + except Exception as e: # noqa: BLE001 + print(f'generate_docset.py: warning: cannot download {src}: {e}', file=sys.stderr) + cache[src] = None + if cache[src] is None: + return attr(tag, 'alt') or '' + local = os.path.relpath(os.path.join(docs, cache[src]), os.path.dirname(file)) + local = local.replace(os.sep, '/') + return re.sub(ATTR_RE.format('src'), lambda _: f'src="{local}"', tag, count=1, flags=re.I) + + new = IMG_RE.sub(repl, text) + if new != text: + write(file, new) + + +def load_mkdocs_yml() -> dict: + """Load mkdocs.yml, ignoring tags like !ENV and !!python/name.""" + with open(MKDOCS_YML, encoding='utf-8') as f: + return yaml.load(f, Loader=Loader) + + +def localize_site_urls(docs, site_url) -> None: + """Load assets that the theme's JavaScript references by absolute site URL from the docset. + + The privacy plugin rewrites the mermaid loader to "assets/external/unpkg.com/mermaid@11/...", so the + docset would fetch mermaid from the live site. __md_scope is the site root that Material defines in every page. + """ + pattern = re.compile(r'"' + re.escape(site_url) + r'(assets/[^"]*)"') + for base, _, files in os.walk(docs): + for file in (f for f in files if f.endswith('.js')): + path = os.path.join(base, file) + text = read(path) + new = pattern.sub(r'new URL("\1",__md_scope).href', text) + if new != text: + write(path, new) + + +def remote_loads(docs, site_url) -> list: + """Return 'file: url' strings for resources that would be loaded remotely.""" + found = [] + cdn = re.compile(r'https://(?:unpkg\.com|cdn\.jsdelivr\.net|cdnjs\.cloudflare\.com|' + r'fonts\.googleapis\.com|fonts\.gstatic\.com)/[^\s"\'`)\\]*') + css_url = re.compile(r'url\(\s*["\']?((?:https?:)?//[^)"\']+)', re.I) + css_import = re.compile(r'@import\s+(?:url\(\s*)?["\']?((?:https?:)?//[^)"\'; ]+)', re.I) + for base, _, files in os.walk(docs): + for file in files: + path = os.path.join(base, file) + rel = os.path.relpath(path, docs) + urls = [] + if file.endswith('.html'): + text = read(path) + for m in re.finditer(r'<[a-zA-Z][^>]*>', text): + tag = m.group(0) + for name in ('src', 'poster'): + v = attr(tag, name) + if v and is_remote(v): + urls.append(v) + v = attr(tag, 'srcset') + if v: + urls += [c.split()[0] for c in v.split(',') if c.strip() and is_remote(c.strip())] + if re.match(r' int: + entries = build_entries() + if problems: + print('\n'.join(problems), file=sys.stderr) + return 1 + docset = os.path.join(out_dir, DOCSET) + docs = os.path.join(docset, 'Contents', 'Resources', 'Documents') + if os.path.exists(docset): + shutil.rmtree(docset) + shutil.copytree(site, docs) + for icon in ('icon.png', 'icon@2x.png'): + shutil.copy(os.path.join(HERE, icon), docset) + shutil.copy(os.path.join(HERE, 'Info.plist'), os.path.join(docset, 'Contents')) + write_index(entries, os.path.join(docset, 'Contents', 'Resources', 'docSet.dsidx')) + + # patch CSS to hide navigation items and fix spacing + css = glob.glob(os.path.join(docs, 'assets', 'stylesheets', 'main.*.min.css')) + if len(css) != 1: + print(f'generate_docset.py: expected exactly one main.*.min.css, found {len(css)}', file=sys.stderr) + return 1 + with open(css[0], 'a', encoding='utf-8') as f: + f.write(CSS_PATCH) + + patch_titles(docs, entries) + remove_source_widget(docs) + for sitemap in glob.glob(os.path.join(docs, 'sitemap.*')): + os.remove(sitemap) + + # make the docset self-contained + site_url = load_mkdocs_yml()['site_url'] + localize_images(docs) + localize_site_urls(docs, site_url) + remote = remote_loads(docs, site_url) + if remote: + print('generate_docset.py: remote resources remain in the docset:', file=sys.stderr) + print('\n'.join(' ' + r for r in remote), file=sys.stderr) + return 1 + return 0 + + +def make_tgz(out_dir) -> int: + docset = os.path.join(out_dir, DOCSET) + if not os.path.isdir(docset): + print(f'generate_docset.py: {docset} does not exist', file=sys.stderr) + return 1 + with tarfile.open(os.path.join(out_dir, 'JSON_for_Modern_C++.tgz'), 'w:gz') as tar: + tar.add(docset, arcname=DOCSET, filter=lambda i: None if os.path.basename(i.name) == '.DS_Store' else i) + return 0 + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + sub = parser.add_subparsers(dest='command', required=True) + sub.add_parser('list', help='print the index entries as TSV') + p = sub.add_parser('index', help='write the SQLite search index') + p.add_argument('out') + p = sub.add_parser('docset', help='build the docset from a built mkdocs site') + p.add_argument('site_dir') + p.add_argument('out_dir') + p = sub.add_parser('tgz', help='pack the docset into a tarball') + p.add_argument('out_dir') + args = parser.parse_args() + + if args.command == 'docset': + return make_docset(args.site_dir, args.out_dir) + if args.command == 'tgz': + return make_tgz(args.out_dir) + + entries = build_entries() + if problems: + print('\n'.join(problems), file=sys.stderr) + return 1 + if args.command == 'list': + for entry in entries: + print('\t'.join(entry)) + elif args.command == 'index': + write_index(entries, args.out) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/docs/mkdocs/Makefile b/docs/mkdocs/Makefile index 55ee0d509..c1a14e617 100644 --- a/docs/mkdocs/Makefile +++ b/docs/mkdocs/Makefile @@ -17,6 +17,7 @@ build: style_check style_check: @cd docs ; ../venv/bin/python3 ../scripts/check_structure.py + @venv/bin/python3 ../docset/generate_docset.py list > /dev/null # check that all Mermaid diagrams parse (needs Node.js) # This target is used in the CI (ci_test_documentation_mermaid). diff --git a/docs/mkdocs/scripts/check_structure.py b/docs/mkdocs/scripts/check_structure.py index dd76dbab0..c4ecddba3 100755 --- a/docs/mkdocs/scripts/check_structure.py +++ b/docs/mkdocs/scripts/check_structure.py @@ -288,36 +288,6 @@ def check_header_links() -> None: f'link to "{match.group(0)}" does not point to a documentation page') -def check_docset() -> None: - """Every API page and every macro has an entry in the docset index; no entry points to a missing page.""" - entry_re = re.compile(r"VALUES \('((?:[^']|'')*)', '(\w+)', '([^']*)'\);") - names_by_path = {} - with open("../../docset/docSet.sql", encoding="utf-8") as sql: - for name, _, path in entry_re.findall(sql.read()): - names_by_path.setdefault(path, set()).add(name.replace("''", "'")) - - def to_path(page): - if os.path.basename(page) == "index.md": - return page[:-len("index.md")] + "index.html" - return page[:-len(".md")] + "/index.html" - - pages = sorted(glob.glob("**/*.md", recursive=True)) - for path in sorted(set(names_by_path) - {to_path(p) for p in pages}): - report("docset/stale_entry", "../../docset/docSet.sql", f'entry "{path}" has no documentation page') - for page in (p for p in pages if p.startswith("api/")): - names = names_by_path.get(to_path(page)) - if not names: - report("docset/missing_entry", page, "page has no entry in docs/docset/docSet.sql") - elif page.startswith("api/macros/") and os.path.basename(page) != "index.md": - with open(page, encoding="utf-8") as content: - text = content.read() - match = re.search(r"^# (.+)$", text, re.MULTILINE) or re.search(r"

(.*?)

", text, re.DOTALL) - title = re.sub(r"<[^>]+>|\s+", " ", match.group(1)) - for macro in filter(None, (x.strip() for x in re.split(r"[,/]", title))): - if macro not in names: - report("docset/missing_macro", page, f'macro "{macro}" has no entry in docs/docset/docSet.sql') - - if __name__ == "__main__": print(120 * "-") check_structure() @@ -327,7 +297,6 @@ if __name__ == "__main__": check_heading_levels() check_image_alt_text() check_header_links() - check_docset() print(120 * "-") if warnings > 0: From f8b47ff6f6de8b80bea03d685d646eb93b66b98d Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sat, 10 Oct 2026 16:21:53 +0200 Subject: [PATCH 2/9] Check the URL scheme before downloading in generate_docset.py (#5803) Codacy flagged two Bandit findings in the docset generator added in #5799: B310 (urlopen with an unchecked scheme) and B506 (yaml.load). download() now rejects anything but http(s) URLs before opening them, and the yaml.load call is marked, since its Loader derives from yaml.SafeLoader. The SHA-1 used to name downloaded files is marked as not used for security. Signed-off-by: Niels Lohmann --- docs/docset/generate_docset.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/docset/generate_docset.py b/docs/docset/generate_docset.py index e09e8d6d1..6f6a6e423 100755 --- a/docs/docset/generate_docset.py +++ b/docs/docset/generate_docset.py @@ -337,14 +337,17 @@ def is_remote(url) -> bool: def download(url, docs) -> str: """Download url into assets/external and return the path relative to docs.""" u = urllib.parse.urlparse(url if not url.startswith('//') else 'https:' + url) + if u.scheme.lower() not in ('http', 'https'): + raise ValueError(f'not an http(s) URL: {url}') req = urllib.request.Request(u.geturl(), headers={'User-Agent': USER_AGENT}) - with urllib.request.urlopen(req, timeout=20) as r: + # (the scheme is checked above) + with urllib.request.urlopen(req, timeout=20) as r: # nosec B310 data = r.read() ctype = r.headers.get_content_type() path = urllib.parse.unquote(u.path).lstrip('/') ext = os.path.splitext(path)[1] if u.query or not ext or path.endswith('/'): - digest = hashlib.sha1(url.encode()).hexdigest()[:12] + digest = hashlib.sha1(url.encode(), usedforsecurity=False).hexdigest()[:12] path = os.path.join(os.path.dirname(path), digest + CONTENT_TYPE_EXT.get(ctype, ext or '.bin')) rel = os.path.normpath(os.path.join('assets', 'external', u.hostname, path)) out = os.path.join(docs, rel) @@ -385,7 +388,8 @@ def localize_images(docs) -> None: def load_mkdocs_yml() -> dict: """Load mkdocs.yml, ignoring tags like !ENV and !!python/name.""" with open(MKDOCS_YML, encoding='utf-8') as f: - return yaml.load(f, Loader=Loader) + # (Loader is a yaml.SafeLoader) + return yaml.load(f, Loader=Loader) # nosec B506 def localize_site_urls(docs, site_url) -> None: From 1649d9eda459691f2a88bb08ae02c9b8a9ef5ca9 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 11 Oct 2026 08:21:13 +0200 Subject: [PATCH 3/9] Use and extend the detail helpers to remove duplicated code (#5783) * Use and extend the detail helpers to remove duplicated code Library: - binary_reader: format bytes with hex_byte() instead of snprintf - add throw_type_must_be() for the 20 copies of type_error.302 - binary_reader: add last_byte_error()/unexpected_byte() for the 32 "parse error at the last read byte" sites (replaces bon8_error) - json_sax: add check_container_size() for out_of_range.408 and diagnostic_positions::set_container_start/_end() - json_pointer: add throw_no_parent() (405) and throw_unresolved() (404) Tests: - unit-class_parser uses the shared utils::SaxCountdown - move SaxEventLogger (and its ExitAfter* variants) from unit-class_parser and unit-deserialization into the new tests/src/test_sax.hpp Signed-off-by: Niels Lohmann * Factor out more repeated error paths and test boilerplate Library: - add throw_cannot_use_with() for the 34 copies of type_error.304-312 "cannot use X with Y" - iter_impl: add throw_cannot_get_value() (invalid_iterator.214) - parser: add syntax_error() for the 12 parse_error.101 sites - ordered_map: share the four at() bodies via at_impl() - json_sax_dom_callback_parser: add pop_container() for end_object() and end_array() - binary_reader: build the two UBJSON/BJData length-type messages with concat() and last_byte_error() Tests: - move same_value(), the NDEBUG guard, and step 0 (parse without exceptions) of the seven fuzzer drivers into tests/src/fuzzer_common.hpp Signed-off-by: Niels Lohmann * Name the exception ids and address review comments Add detail::exception_id, a scoped enum with one named enumerator per documented exception id, and use it for every id in the library. The create() functions get an overload for it; the int overloads stay for user code. Following the review of #5783: add binary_reader::invalid_byte() and length_type_error(), basic_json::throw_subscript_wrong_type(), move the fuzzer includes and the using-declaration into fuzzer_common.hpp, and rename test_sax.hpp to sax_event_loggers.hpp. Signed-off-by: Niels Lohmann * Silence -Wweak-vtables for the SAX event loggers The loggers moved from anonymous namespaces in the test files into sax_event_loggers.hpp, so clang now warns that their vtables are emitted in every translation unit. Signed-off-by: Niels Lohmann * Silence MSVC 2015 C4100 in parser::syntax_error for static SAX::parse_error Signed-off-by: Niels Lohmann * Inline invalid_byte() into its call sites The wrapper only fixed the message string of unexpected_byte(), which is the same kind of per-argument helper that was declined for throw_type_must_be(). Call unexpected_byte("invalid byte", ...) directly. Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- .../nlohmann/detail/conversions/from_json.hpp | 34 +- .../nlohmann/detail/conversions/to_json.hpp | 4 +- include/nlohmann/detail/exceptions.hpp | 144 +++ .../nlohmann/detail/input/binary_reader.hpp | 219 ++-- .../nlohmann/detail/input/input_adapters.hpp | 8 +- include/nlohmann/detail/input/json_sax.hpp | 159 +-- include/nlohmann/detail/input/parser.hpp | 67 +- .../nlohmann/detail/iterators/iter_impl.hpp | 30 +- include/nlohmann/detail/json_pointer.hpp | 59 +- include/nlohmann/detail/macro_scope.hpp | 4 +- .../nlohmann/detail/output/binary_writer.hpp | 24 +- include/nlohmann/detail/output/serializer.hpp | 4 +- include/nlohmann/json.hpp | 151 +-- include/nlohmann/ordered_map.hpp | 41 +- single_include/nlohmann/json.hpp | 948 ++++++++++-------- tests/src/fuzzer-parse_bjdata.cpp | 35 +- tests/src/fuzzer-parse_bon8.cpp | 35 +- tests/src/fuzzer-parse_bson.cpp | 35 +- tests/src/fuzzer-parse_cbor.cpp | 35 +- tests/src/fuzzer-parse_json.cpp | 35 +- tests/src/fuzzer-parse_msgpack.cpp | 35 +- tests/src/fuzzer-parse_ubjson.cpp | 35 +- tests/src/fuzzer_common.hpp | 51 + tests/src/sax_event_loggers.hpp | 185 ++++ tests/src/unit-class_parser.cpp | 189 +--- tests/src/unit-deserialization.cpp | 154 +-- 26 files changed, 1329 insertions(+), 1391 deletions(-) create mode 100644 tests/src/fuzzer_common.hpp create mode 100644 tests/src/sax_event_loggers.hpp diff --git a/include/nlohmann/detail/conversions/from_json.hpp b/include/nlohmann/detail/conversions/from_json.hpp index 884ff014a..0332474b2 100644 --- a/include/nlohmann/detail/conversions/from_json.hpp +++ b/include/nlohmann/detail/conversions/from_json.hpp @@ -48,7 +48,7 @@ inline void from_json(const BasicJsonType& j, typename std::nullptr_t& n) { if (JSON_HEDLEY_UNLIKELY(!j.is_null())) { - JSON_THROW(type_error::create(302, concat("type must be null, but is ", j.type_name()), &j)); + throw_type_must_be("null", j); } n = nullptr; } @@ -102,7 +102,7 @@ void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) case value_t::binary: case value_t::discarded: default: - JSON_THROW(type_error::create(302, concat("type must be number, but is ", j.type_name()), &j)); + throw_type_must_be("number", j); } } @@ -111,7 +111,7 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& { if (JSON_HEDLEY_UNLIKELY(!j.is_boolean())) { - JSON_THROW(type_error::create(302, concat("type must be boolean, but is ", j.type_name()), &j)); + throw_type_must_be("boolean", j); } b = *j.template get_ptr(); } @@ -121,7 +121,7 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { - JSON_THROW(type_error::create(302, concat("type must be string, but is ", j.type_name()), &j)); + throw_type_must_be("string", j); } s = *j.template get_ptr(); } @@ -137,7 +137,7 @@ inline void from_json(const BasicJsonType& j, StringType& s) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { - JSON_THROW(type_error::create(302, concat("type must be string, but is ", j.type_name()), &j)); + throw_type_must_be("string", j); } s = *j.template get_ptr(); @@ -183,7 +183,7 @@ inline void from_json(const BasicJsonType& j, std::forward_list& l { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } l.clear(); std::transform(j.rbegin(), j.rend(), @@ -200,7 +200,7 @@ inline void from_json(const BasicJsonType& j, std::valarray& l) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } l.resize(j.size()); std::transform(j.begin(), j.end(), std::begin(l), @@ -305,7 +305,7 @@ void()) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } from_json_array_impl(j, arr, priority_tag<3> {}); @@ -324,7 +324,7 @@ auto from_json(const BasicJsonType& j, identity_tag> tag) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } return from_json_inplace_array_impl(j, tag, make_index_sequence {}); @@ -335,7 +335,7 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& { if (JSON_HEDLEY_UNLIKELY(!j.is_binary())) { - JSON_THROW(type_error::create(302, concat("type must be binary, but is ", j.type_name()), &j)); + throw_type_must_be("binary", j); } bin = *j.template get_ptr(); @@ -356,7 +356,7 @@ inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin) } else { - JSON_THROW(type_error::create(302, concat("type must be binary or array, but is ", j.type_name()), &j)); + throw_type_must_be("binary or array", j); } } @@ -377,7 +377,7 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) { if (JSON_HEDLEY_UNLIKELY(!j.is_object())) { - JSON_THROW(type_error::create(302, concat("type must be object, but is ", j.type_name()), &j)); + throw_type_must_be("object", j); } ConstructibleObjectType ret; @@ -432,7 +432,7 @@ inline void from_json(const BasicJsonType& j, ArithmeticType& val) case value_t::binary: case value_t::discarded: default: - JSON_THROW(type_error::create(302, concat("type must be number, but is ", j.type_name()), &j)); + throw_type_must_be("number", j); } } @@ -504,7 +504,7 @@ auto from_json(const BasicJsonType& j, TupleRelated&& t) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } return from_json_tuple_impl(j, std::forward(t), priority_tag<3> {}); @@ -517,14 +517,14 @@ void from_json_pair_array_to_map(const BasicJsonType& j, MapType& m) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } m.clear(); for (const auto& p : j) { if (JSON_HEDLEY_UNLIKELY(!p.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", p.type_name()), &p)); + throw_type_must_be("array", p); } m.emplace(p.at(0).template get(), p.at(1).template get()); } @@ -591,7 +591,7 @@ inline void from_json(const BasicJsonType& j, std_fs::path& p) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { - JSON_THROW(type_error::create(302, concat("type must be string, but is ", j.type_name()), &j)); + throw_type_must_be("string", j); } const auto& s = *j.template get_ptr(); // Checking for C++20 standard or later can be insufficient in case the diff --git a/include/nlohmann/detail/conversions/to_json.hpp b/include/nlohmann/detail/conversions/to_json.hpp index 8edaee2fe..d0d9610f4 100644 --- a/include/nlohmann/detail/conversions/to_json.hpp +++ b/include/nlohmann/detail/conversions/to_json.hpp @@ -414,13 +414,13 @@ inline void to_json(BasicJsonType& j, const EnumKeyedMap& map) BasicJsonType key = p.first; if (JSON_HEDLEY_UNLIKELY(!key.is_string())) { - JSON_THROW(type_error::create(302, concat("type must be string, but is ", key.type_name()), &key)); + throw_type_must_be("string", key); } auto& key_string = *key.template get_ptr(); if (JSON_HEDLEY_UNLIKELY(!obj.emplace(key_string, BasicJsonType(p.second)).second)) { - JSON_THROW(type_error::create(318, concat("duplicate object key '", key_string, "'"), &key)); + JSON_THROW(type_error::create(exception_id::enum_key_duplicate, concat("duplicate object key '", key_string, "'"), &key)); } } external_constructor::construct(j, std::move(obj)); diff --git a/include/nlohmann/detail/exceptions.hpp b/include/nlohmann/detail/exceptions.hpp index 155f4a4e5..f2c6651c4 100644 --- a/include/nlohmann/detail/exceptions.hpp +++ b/include/nlohmann/detail/exceptions.hpp @@ -45,6 +45,84 @@ namespace detail // exceptions // //////////////// +/*! +@brief the ids of the exceptions thrown by the library +@note The values are part of the public API: they are the `id` member of the + exceptions and appear in their `what()` messages. +@sa https://json.nlohmann.me/home/exceptions/ +*/ +enum class exception_id : int +{ + // parse_error + syntax_error = 101, ///< unexpected token or invalid literal while parsing JSON + patch_not_an_array = 104, ///< a JSON Patch document is not an array of objects + patch_invalid_operation = 105, ///< a JSON Patch operation is malformed + pointer_index_leading_zero = 106, ///< a JSON Pointer array index has a leading zero + pointer_missing_slash = 107, ///< a JSON Pointer does not start with '/' + pointer_invalid_escape = 108, ///< a JSON Pointer contains an escape other than ~0 and ~1 + pointer_index_not_a_number = 109, ///< a JSON Pointer array index is not a number + unexpected_end_of_input = 110, ///< a binary input ends early, or has bytes left at its end + unexpected_byte = 112, ///< a binary input contains an unexpected byte or invalid length + invalid_string_or_size = 113, ///< a binary input contains an invalid string or size specification + bson_unsupported_type = 114, ///< a BSON record type is not supported + invalid_high_precision_number = 115, ///< a UBJSON/BJData high-precision number cannot be parsed + // invalid_iterator + iterators_incompatible = 201, ///< the iterators of a range belong to different values + iterator_from_other_value = 202, ///< an iterator does not belong to the value it is used with + iterator_range_from_other_value = 203, ///< an iterator range does not belong to the value it is used with + iterator_range_out_of_range = 204, ///< an iterator range of a primitive value is not [begin, end) + iterator_out_of_range = 205, ///< an iterator of a primitive value is not begin() + iterator_range_of_null = 206, ///< an iterator range belongs to a null value + iterator_key_not_object = 207, ///< key() is called on an iterator of a non-object + iterator_subscript_on_object = 208, ///< operator[] is called on an iterator of an object + iterator_arithmetic_on_object = 209, ///< an offset operator is used on an iterator of an object + insert_range_incompatible = 210, ///< the iterators of an inserted range belong to different values + insert_range_into_itself = 211, ///< an inserted range belongs to the value it is inserted into + iterators_compare_different_values = 212, ///< iterators of different values are compared + iterator_order_on_object = 213, ///< iterators of an object are compared by order + iterator_value_unavailable = 214, ///< an iterator does not refer to a value + // type_error + object_from_non_pairs = 301, ///< an object is created from an initializer list that is not a list of pairs + type_mismatch = 302, ///< a value has the wrong type for a conversion + incompatible_reference_type = 303, ///< get_ref() is called with a reference type that does not match the value + at_wrong_type = 304, ///< at() is called on a value of the wrong type + subscript_wrong_type = 305, ///< operator[] is called on a value of the wrong type + value_wrong_type = 306, ///< value() is called on a value of the wrong type + erase_wrong_type = 307, ///< erase() is called on a value of the wrong type + push_back_wrong_type = 308, ///< push_back() or operator+= is called on a value of the wrong type + insert_wrong_type = 309, ///< insert() is called on a value of the wrong type + swap_wrong_type = 310, ///< swap() is called on a value of the wrong type + emplace_wrong_type = 311, ///< emplace() or emplace_back() is called on a value of the wrong type + update_wrong_type = 312, ///< update() is called on a value of the wrong type + unflatten_invalid_value = 313, ///< unflatten() finds conflicting paths + unflatten_not_object = 314, ///< unflatten() is called on a non-object + unflatten_value_not_primitive = 315, ///< unflatten() is called on an object with non-primitive values + invalid_utf8 = 316, ///< dump() finds a string that is not valid UTF-8 + type_not_serializable = 317, ///< a value cannot be serialized to the requested binary format + enum_key_duplicate = 318, ///< an enum-keyed map has two keys that serialize to the same string + discarded_value_used = 321, ///< a discarded value is used + // out_of_range + array_index_out_of_range = 401, ///< an array index is out of range + pointer_past_the_end_index = 402, ///< a JSON Pointer uses the array index '-' + key_not_found = 403, ///< an object key is not found + pointer_unresolved = 404, ///< a JSON Pointer reference token cannot be resolved + patch_on_root = 405, ///< the root of a value has no parent, e.g. for a JSON Patch 'remove' or 'add' at the root + number_overflow = 406, ///< a number cannot be stored without overflowing to NaN or INF + integer_too_large = 407, ///< an integer cannot be represented in the binary format + container_too_large = 408, ///< the size of a container in a binary input exceeds the maximal capacity + bson_key_with_null = 409, ///< a BSON key contains U+0000 + value_out_of_range = 410, ///< an enum value is undefined, or a JSON Pointer array index exceeds size_type + patch_add_parent_not_container = 411, ///< the parent of a JSON Patch 'add' target is not a container + length_too_large = 412, ///< a length does not fit into the length field of a binary format + patch_remove_parent_not_container = 413, ///< the parent of a JSON Patch 'remove' target is not a container + patch_move_into_child = 414, ///< a JSON Patch 'move' moves a value into one of its children + subtype_out_of_range = 415, ///< a binary subtype does not fit into one byte + // other_error + internal_error = 500, ///< unreachable code was reached + patch_test_failed = 501, ///< a JSON Patch 'test' operation failed + size_marker_required = 502 ///< UBJSON/BJData output with type markers requires size markers +}; + /// @brief general exception of the @ref basic_json class /// @sa https://json.nlohmann.me/api/basic_json/exception/ class exception : public std::exception @@ -195,6 +273,18 @@ class parse_error : public exception return {id_, byte_, w.c_str()}; } + template::value, int> = 0> + static parse_error create(exception_id id_, const position_t& pos, const std::string& what_arg, BasicJsonContext context) + { + return create(static_cast(id_), pos, what_arg, context); + } + + template::value, int> = 0> + static parse_error create(exception_id id_, std::size_t byte_, const std::string& what_arg, BasicJsonContext context) + { + return create(static_cast(id_), byte_, what_arg, context); + } + /*! @brief byte index of the parse error @@ -229,6 +319,12 @@ class invalid_iterator : public exception return {id_, w.c_str()}; } + template::value, int> = 0> + static invalid_iterator create(exception_id id_, const std::string& what_arg, BasicJsonContext context) + { + return create(static_cast(id_), what_arg, context); + } + private: JSON_HEDLEY_NON_NULL(3) invalid_iterator(int id_, const char* what_arg) @@ -247,6 +343,12 @@ class type_error : public exception return {id_, w.c_str()}; } + template::value, int> = 0> + static type_error create(exception_id id_, const std::string& what_arg, BasicJsonContext context) + { + return create(static_cast(id_), what_arg, context); + } + private: JSON_HEDLEY_NON_NULL(3) type_error(int id_, const char* what_arg) : exception(id_, what_arg) {} @@ -264,6 +366,12 @@ class out_of_range : public exception return {id_, w.c_str()}; } + template::value, int> = 0> + static out_of_range create(exception_id id_, const std::string& what_arg, BasicJsonContext context) + { + return create(static_cast(id_), what_arg, context); + } + private: JSON_HEDLEY_NON_NULL(3) out_of_range(int id_, const char* what_arg) : exception(id_, what_arg) {} @@ -281,6 +389,12 @@ class other_error : public exception return {id_, w.c_str()}; } + template::value, int> = 0> + static other_error create(exception_id id_, const std::string& what_arg, BasicJsonContext context) + { + return create(static_cast(id_), what_arg, context); + } + private: JSON_HEDLEY_NON_NULL(3) other_error(int id_, const char* what_arg) : exception(id_, what_arg) {} @@ -307,6 +421,36 @@ void templated_json_throw(ExceptionType exception) (void)exception; } +/*! +@brief throws because @a j does not have the type a conversion expects +@param[in] expected the expected type(s), e.g. "array" or "binary or array" +@param[in] j the value with the wrong type +@throw type_error.302 always +*/ +template +JSON_HEDLEY_NO_RETURN inline void throw_type_must_be(const char* expected, const BasicJsonType& j) +{ + static_cast(expected); // unused when JSON_NOEXCEPTION is defined + static_cast(j); + JSON_THROW(type_error::create(exception_id::type_mismatch, concat("type must be ", expected, ", but is ", j.type_name()), &j)); +} + +/*! +@brief throws because an operation is not supported for the type of @a j +@param[in] id_ the id of the type_error exception (at_wrong_type..update_wrong_type) +@param[in] operation the operation, e.g. "erase()" +@param[in] j the value the operation was called on +@throw type_error always +*/ +template +JSON_HEDLEY_NO_RETURN inline void throw_cannot_use_with(const exception_id id_, const char* operation, const BasicJsonType& j) +{ + static_cast(id_); // unused when JSON_NOEXCEPTION is defined + static_cast(operation); + static_cast(j); + JSON_THROW(type_error::create(id_, concat("cannot use ", operation, " with ", j.type_name()), &j)); +} + } // namespace detail NLOHMANN_JSON_NAMESPACE_END diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index 3dfff13b9..4fdb31609 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -12,7 +12,6 @@ #include // ldexp #include // size_t #include // uint8_t, uint16_t, uint32_t, uint64_t, uintmax_t -#include // snprintf #include // memcpy #include // back_inserter #include // numeric_limits @@ -196,8 +195,7 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY(current != char_traits::eof())) { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(110, chars_read, - exception_message(concat("expected end of input; last byte: 0x", get_token_string()), "value"), nullptr)); + return last_byte_error(exception_id::unexpected_end_of_input, concat("expected end of input; last byte: 0x", get_token_string()), "value"); } } @@ -319,8 +317,7 @@ class binary_reader { if (JSON_HEDLEY_UNLIKELY(document_size < 0 || static_cast(document_size) != chars_read - document_start)) { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(112, chars_read, - exception_message(concat("document size ", std::to_string(document_size), " does not match the number of bytes read (", std::to_string(chars_read - document_start), ")"), "document"), nullptr)); + return last_byte_error(exception_id::unexpected_byte, concat("document size ", std::to_string(document_size), " does not match the number of bytes read (", std::to_string(chars_read - document_start), ")"), "document"); } return true; } @@ -512,9 +509,7 @@ class binary_reader { if (JSON_HEDLEY_UNLIKELY(len < 1)) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("string length must be at least 1, is ", std::to_string(len)), "string"), nullptr)); + return last_byte_error(exception_id::unexpected_byte, concat("string length must be at least 1, is ", std::to_string(len)), "string"); } if (JSON_HEDLEY_UNLIKELY(!get_string(len - static_cast(1), result))) @@ -524,10 +519,7 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY(get() != 0x00)) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message("BSON string is not null-terminated", - "string"), nullptr)); + return last_byte_error(exception_id::unexpected_byte, "BSON string is not null-terminated", "string"); } return check_string_utf8(result, "string"); @@ -547,9 +539,7 @@ class binary_reader { if (JSON_HEDLEY_UNLIKELY(len < 0)) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("byte array length cannot be negative, is ", std::to_string(len)), "binary"), nullptr)); + return last_byte_error(exception_id::unexpected_byte, concat("byte array length cannot be negative, is ", std::to_string(len)), "binary"); } // All BSON binary values have a subtype @@ -639,11 +629,9 @@ class binary_reader default: // anything else is not supported (yet) { - std::array cr{{}}; - static_cast((std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast(element_type))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) - const std::string cr_str{cr.data()}; + const std::string cr_str = hex_byte(static_cast(element_type)); return sax->parse_error(element_type_parse_position, cr_str, - parse_error::create(114, element_type_parse_position, concat("Unsupported BSON record type 0x", cr_str), nullptr)); + parse_error::create(exception_id::bson_unsupported_type, element_type_parse_position, concat("Unsupported BSON record type 0x", cr_str), nullptr)); } } } @@ -967,9 +955,7 @@ class binary_reader { if (tag_handler == cbor_tag_handler_t::error) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("invalid byte: 0x", last_token), "value"), nullptr)); + return unexpected_byte("invalid byte", "value"); } // ignore and store: the tag value is already in the head, so @@ -989,9 +975,7 @@ class binary_reader { case cbor_tag_handler_t::error: { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("invalid byte: 0x", last_token), "value"), nullptr)); + return unexpected_byte("invalid byte", "value"); } case cbor_tag_handler_t::ignore: @@ -1065,9 +1049,7 @@ class binary_reader default: // anything else (0xFF is handled inside the other types) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("invalid byte: 0x", last_token), "value"), nullptr)); + return unexpected_byte("invalid byte", "value"); } } } @@ -1080,10 +1062,7 @@ class binary_reader */ bool cbor_indefinite_string_error(const char* type_name, const char* context) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("indefinite-length ", type_name, - " is not allowed inside indefinite-length ", type_name, "; last byte: 0x", last_token), context), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, concat("indefinite-length ", type_name, " is not allowed inside indefinite-length ", type_name, "; last byte: 0x", get_token_string()), context); } /*! @@ -1160,9 +1139,7 @@ class binary_reader default: { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("expected length specification (0x60-0x7B)", inside_indefinite ? "" : " or indefinite string type (0x7F)", "; last byte: 0x", last_token), "string"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, concat("expected length specification (0x60-0x7B)", inside_indefinite ? "" : " or indefinite string type (0x7F)", "; last byte: 0x", get_token_string()), "string"); } } } @@ -1292,9 +1269,7 @@ class binary_reader break; } - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("only string keys are supported, but found ", found, "; last byte: 0x", last_token), "object key"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, concat("only string keys are supported, but found ", found, "; last byte: 0x", get_token_string()), "object key"); } /*! @@ -1375,9 +1350,7 @@ class binary_reader default: { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("expected length specification (0x40-0x5B)", inside_indefinite ? "" : " or indefinite binary array type (0x5F)", "; last byte: 0x", last_token), "binary"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, concat("expected length specification (0x40-0x5B)", inside_indefinite ? "" : " or indefinite binary array type (0x5F)", "; last byte: 0x", get_token_string()), "binary"); } } } @@ -1523,7 +1496,7 @@ class binary_reader { if (JSON_HEDLEY_UNLIKELY(!value_in_range_of(len) || len == detail::unknown_size())) { - return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large, exception_message(concat("excessive ", context, " size"), "size"), nullptr)); } result = conditional_static_cast(len); @@ -2011,9 +1984,7 @@ class binary_reader 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(concat("invalid byte: 0x", last_token), "value"), nullptr)); + return unexpected_byte("invalid byte", "value"); } } } @@ -2094,9 +2065,7 @@ class binary_reader default: { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0x", last_token), "string"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, concat("expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0x", get_token_string()), "string"); } } } @@ -2188,9 +2157,7 @@ class binary_reader break; } - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("only string keys are supported, but found ", found, "; last byte: 0x", last_token), "object key"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, concat("only string keys are supported, but found ", found, "; last byte: 0x", get_token_string()), "object key"); } /*! @@ -2499,8 +2466,7 @@ class binary_reader { if (JSON_HEDLEY_UNLIKELY(len < 0)) { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, - exception_message("string length must not be negative", "string"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, "string length must not be negative", "string"); } return true; } @@ -2600,18 +2566,7 @@ class binary_reader 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); 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(message, "string"), nullptr)); + return length_type_error("", "string"); } /*! @@ -2696,12 +2651,11 @@ class binary_reader } if (JSON_HEDLEY_UNLIKELY(number < 0)) { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, - exception_message("count in an optimized container must be positive", "size"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, "count in an optimized container must be positive", "size"); } if (JSON_HEDLEY_UNLIKELY(!value_in_range_of(number))) { - return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large, exception_message("integer value overflow", "size"), nullptr)); } result = static_cast(number); // NOLINT(bugprone-signed-char-misuse,cert-str34-c): number is not a char @@ -2799,7 +2753,7 @@ class binary_reader } if (!value_in_range_of(number)) { - return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large, exception_message("integer value overflow", "size"), nullptr)); } result = detail::conditional_static_cast(number); @@ -2814,7 +2768,7 @@ class binary_reader } if (is_ndarray) // ndarray dimensional vector can only contain integers and cannot embed another array { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, exception_message("ndarray dimensional vector is not allowed", "size"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, "ndarray dimensional vector is not allowed", "size"); } std::vector dim; if (JSON_HEDLEY_UNLIKELY(!get_ubjson_ndarray_size(dim))) @@ -2850,9 +2804,7 @@ class binary_reader const char* type_name = bjd_type_name(ndarray_dtype); if (JSON_HEDLEY_UNLIKELY(type_name == nullptr)) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message("invalid byte: 0x" + last_token, "type"), nullptr)); + return unexpected_byte("invalid byte", "type"); } string_t type_key = "_ArrayType_"; @@ -2880,7 +2832,7 @@ class binary_reader // or SIZE_MAX. if (JSON_HEDLEY_UNLIKELY(result > (std::numeric_limits::max)() / i)) { - return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, exception_message("excessive ndarray size caused overflow", "size"), nullptr)); + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large, exception_message("excessive ndarray size caused overflow", "size"), nullptr)); } result *= i; // the pre-check above already rules out result becoming 0 @@ -2889,7 +2841,7 @@ class binary_reader // unknown-size container (see get_ubjson_size_type()) if (result == npos) { - return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, exception_message("excessive ndarray size caused overflow", "size"), nullptr)); + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large, exception_message("excessive ndarray size caused overflow", "size"), nullptr)); } if (JSON_HEDLEY_UNLIKELY(!emit_unsigned(i))) { @@ -2906,18 +2858,7 @@ class binary_reader 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(message, "size"), nullptr)); + return length_type_error(" after '#'", "size"); } /*! @@ -2950,9 +2891,7 @@ class binary_reader if (input_format == input_format_t::bjdata && JSON_HEDLEY_UNLIKELY(is_bjd_excluded_optimized_type(result.second))) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("marker 0x", last_token, " is not a permitted optimized array type"), "type"), nullptr)); + return last_byte_error(exception_id::unexpected_byte, concat("marker 0x", get_token_string(), " is not a permitted optimized array type"), "type"); } if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("type"))) @@ -2967,9 +2906,7 @@ class binary_reader { return false; } - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("expected '#' after type information; last byte: 0x", last_token), "size"), nullptr)); + return last_byte_error(exception_id::unexpected_byte, concat("expected '#' after type information; last byte: 0x", get_token_string()), "size"); } const bool is_error = get_ubjson_size_value(result.first, is_ndarray, 0, result.second); @@ -2988,8 +2925,7 @@ class binary_reader const bool is_error = get_ubjson_size_value(result.first, is_ndarray); if (input_format == input_format_t::bjdata && is_ndarray && !inside_ndarray) { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(112, chars_read, - exception_message("ndarray requires both type and size", "size"), nullptr)); + return last_byte_error(exception_id::unexpected_byte, "ndarray requires both type and size", "size"); } return is_error; } @@ -3121,9 +3057,7 @@ class binary_reader } if (JSON_HEDLEY_UNLIKELY(current > 127)) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("byte after 'C' must be in range 0x00..0x7F; last byte: 0x", last_token), "char"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, concat("byte after 'C' must be in range 0x00..0x7F; last byte: 0x", get_token_string()), "char"); } string_t s(1, static_cast(current)); return sax->string(s); @@ -3144,8 +3078,7 @@ class binary_reader default: // anything else break; } - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message("invalid byte: 0x" + last_token, "value"), nullptr)); + return unexpected_byte("invalid byte", "value"); } /*! @@ -3208,7 +3141,7 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY((size_and_type.second == 'Z' || size_and_type.second == 'T' || size_and_type.second == 'F') && size_and_type.first > max_valueless_container_size)) { - return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large, exception_message("excessive array size", "size"), nullptr)); } @@ -3244,9 +3177,7 @@ class binary_reader // do not accept ND-array size in objects in BJData if (input_format == input_format_t::bjdata && size_and_type.first != npos && (size_and_type.second & (1 << 8)) != 0) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message("BJData object does not support ND-array size in optimized format", "object"), nullptr)); + return last_byte_error(exception_id::unexpected_byte, "BJData object does not support ND-array size in optimized format", "object"); } if (size_and_type.first != npos) @@ -3283,7 +3214,7 @@ class binary_reader // the lexer would stop at a NUL and accept the digits before it if (JSON_HEDLEY_UNLIKELY(current == '\0')) { - return sax->parse_error(chars_read, "00", parse_error::create(115, chars_read, + return sax->parse_error(chars_read, "00", parse_error::create(exception_id::invalid_high_precision_number, chars_read, exception_message("invalid number text; last byte: 0x00", "high-precision number"), nullptr)); } number_vector.push_back(static_cast(current)); @@ -3300,7 +3231,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, + return sax->parse_error(chars_read, number_string, parse_error::create(exception_id::invalid_high_precision_number, chars_read, exception_message(concat("invalid number text: ", number_lexer.get_token_string()), "high-precision number"), nullptr)); } @@ -3318,7 +3249,7 @@ class binary_reader return sax->parse_error( chars_read, number_string, - out_of_range::create(406, concat("number overflow parsing '", number_string, '\''), nullptr)); + out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", number_string, '\''), nullptr)); } // number_string is a std::string, while the SAX interface takes a // string_t; convert explicitly, as the two are only implicitly @@ -3340,7 +3271,7 @@ class binary_reader case token_type::end_of_input: case token_type::literal_or_value: default: - return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read, + return sax->parse_error(chars_read, number_string, parse_error::create(exception_id::invalid_high_precision_number, chars_read, exception_message(concat("invalid number text: ", number_lexer.get_token_string()), "high-precision number"), nullptr)); } } @@ -3399,20 +3330,6 @@ class binary_reader return 0x80 <= c && c <= 0xBF; } - /*! - @brief report a parse error at the last read byte - - @param[in] detail a detailed error message - @param[in] context further context information - @return false - */ - bool bon8_error(const std::string& detail, const char* context) - { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat(detail, ": 0x", last_token), context), nullptr)); - } - /*! @brief read a BON8 value and everything nested inside it @@ -3628,7 +3545,7 @@ class binary_reader } // 0xFE: end of container where a value is expected - return bon8_error("invalid byte", "value"); + return unexpected_byte("invalid byte", "value"); } /*! @@ -3755,7 +3672,7 @@ class binary_reader current = byte; } - return bon8_error("expected a string; last byte", "key"); + return unexpected_byte("expected a string; last byte", "key"); } /*! @@ -3878,7 +3795,7 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY(!valid_second)) { - return bon8_error("invalid UTF-8 byte", "string"); + return unexpected_byte("invalid UTF-8 byte", "string"); } result.push_back(static_cast(byte)); @@ -3892,7 +3809,7 @@ class binary_reader } if (JSON_HEDLEY_UNLIKELY(!is_bon8_continuation(current))) { - return bon8_error("invalid UTF-8 byte", "string"); + return unexpected_byte("invalid UTF-8 byte", "string"); } result.push_back(static_cast(current)); } @@ -3937,7 +3854,7 @@ class binary_reader { // in case of failure, advance position by 1 to report the failing location ++chars_read; - sax->parse_error(chars_read, "", parse_error::create(110, chars_read, exception_message("unexpected end of input", context), nullptr)); + sax->parse_error(chars_read, "", parse_error::create(exception_id::unexpected_end_of_input, chars_read, exception_message("unexpected end of input", context), nullptr)); return false; } return true; @@ -4091,7 +4008,7 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY(std::isfinite(number) && !std::isfinite(result))) { return sax->parse_error(chars_read, get_token_string(), - out_of_range::create(406, exception_message("number overflow", "value"), nullptr)); + out_of_range::create(exception_id::number_overflow, exception_message("number overflow", "value"), nullptr)); } return sax->number_float(result, ""); } @@ -4211,9 +4128,7 @@ class binary_reader if (error_handler == error_handler_t::strict) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message("invalid string: ill-formed UTF-8 byte", context), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, "invalid string: ill-formed UTF-8 byte", context); } result = sanitize_utf8(result, error_handler); @@ -4309,19 +4224,53 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY(current == char_traits::eof())) { return sax->parse_error(chars_read, "", - parse_error::create(110, chars_read, exception_message("unexpected end of input", context), nullptr)); + parse_error::create(exception_id::unexpected_end_of_input, chars_read, exception_message("unexpected end of input", context), nullptr)); } return true; } + /*! + @brief reports a parse error at the last read byte + @param[in] id_ the id of the parse_error exception + @param[in] detail a detailed error message + @param[in] context further context information + @return the result of the SAX parser's parse_error() + */ + bool last_byte_error(const exception_id id_, const std::string& detail, const std::string& context) const + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(id_, chars_read, exception_message(detail, context), nullptr)); + } + + /*! + @brief reports the last read byte as unexpected (parse_error.112) + @param[in] detail what is wrong with the byte, e.g. "invalid byte" + @param[in] context further context information + @return the result of the SAX parser's parse_error() + */ + bool unexpected_byte(const char* detail, const std::string& context) const + { + return last_byte_error(exception_id::unexpected_byte, concat(detail, ": 0x", get_token_string()), context); + } + + /*! + @brief reports that the last read byte is not a UBJSON/BJData length type (parse_error.113) + @param[in] position where the length type was expected, e.g. " after '#'" + @param[in] context further context information + @return the result of the SAX parser's parse_error() + */ + bool length_type_error(const char* position, const char* context) const + { + const char* types = input_format == input_format_t::bjdata ? "U, i, u, I, m, l, M, L" : "U, i, I, l, L"; + return last_byte_error(exception_id::invalid_string_or_size, + concat("expected length type specification (", types, ")", position, "; last byte: 0x", get_token_string()), context); + } + /*! @return a string representation of the last read byte */ std::string get_token_string() const { - std::array cr{{}}; - static_cast((std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast(current))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) - return std::string{cr.data()}; + return hex_byte(static_cast(current)); } /*! diff --git a/include/nlohmann/detail/input/input_adapters.hpp b/include/nlohmann/detail/input/input_adapters.hpp index 9578e275a..847890f7a 100644 --- a/include/nlohmann/detail/input/input_adapters.hpp +++ b/include/nlohmann/detail/input/input_adapters.hpp @@ -581,7 +581,7 @@ class wide_string_input_adapter template JSON_HEDLEY_NO_RETURN std::size_t get_elements(T* /*dest*/, std::size_t /*count*/ = 1) { - JSON_THROW(parse_error::create(112, 1, "wide string type cannot be interpreted as binary data", nullptr)); + JSON_THROW(parse_error::create(exception_id::unexpected_byte, 1, "wide string type cannot be interpreted as binary data", nullptr)); } private: @@ -793,7 +793,7 @@ inline file_input_adapter input_adapter(std::FILE* file) { if (file == nullptr) { - JSON_THROW(parse_error::create(101, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); + JSON_THROW(parse_error::create(exception_id::syntax_error, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); } return file_input_adapter(file); } @@ -802,7 +802,7 @@ inline input_stream_adapter input_adapter(std::istream& stream) { if (stream.rdbuf() == nullptr) { - JSON_THROW(parse_error::create(101, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); + JSON_THROW(parse_error::create(exception_id::syntax_error, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); } return input_stream_adapter(stream); } @@ -827,7 +827,7 @@ contiguous_bytes_input_adapter input_adapter(CharT b) { if (b == nullptr) { - JSON_THROW(parse_error::create(101, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); + JSON_THROW(parse_error::create(exception_id::syntax_error, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); } auto length = std::strlen(reinterpret_cast(b)); const auto* ptr = reinterpret_cast(b); diff --git a/include/nlohmann/detail/input/json_sax.hpp b/include/nlohmann/detail/input/json_sax.hpp index 146944261..816359a60 100644 --- a/include/nlohmann/detail/input/json_sax.hpp +++ b/include/nlohmann/detail/input/json_sax.hpp @@ -176,6 +176,27 @@ template inline void reserve_array(ArrayType& /*arr*/, std::size_t /*len*/, priority_tag<0> /*unused*/) {} +/*! +@brief reports an object or array whose announced size exceeds max_size() + +Shared by json_sax_dom_parser and json_sax_dom_callback_parser. + +@param[in] sax the SAX parser to report the error to +@param[in] len the number of elements announced by the input, or unknown_size() +@param[in] kind "object" or "array" +@param[in] ref the object or array that was just created +@return whether parsing should continue (false after reporting out_of_range.408) +*/ +template +bool check_container_size(SAX& sax, std::size_t len, const char* kind, BasicJsonType* ref) +{ + if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref->max_size())) + { + return sax.parse_error(0, "", out_of_range::create(exception_id::container_too_large, concat("excessive ", kind, " size: ", std::to_string(len)), ref)); + } + return true; +} + #if JSON_DIAGNOSTIC_POSITIONS /*! @brief set the diagnostic positions of a value the DOM SAX parsers just stored @@ -185,6 +206,35 @@ befriends this struct, as the position members are private. */ struct diagnostic_positions { + /*! + @param[in,out] v the object or array whose opening brace or bracket was just read + @param[in] lexer the lexer that read it, or nullptr to leave @a v alone + */ + template + static void set_container_start(BasicJsonType& v, LexerType* lexer) + { + if (lexer) + { + // Lexer has read the first character of the container, so + // subtract 1 from the position to get the correct start position. + v.start_position = lexer->get_position() - 1; + } + } + + /*! + @param[in,out] v the object or array whose closing brace or bracket was just read + @param[in] lexer the lexer that read it, or nullptr to leave @a v alone + */ + template + static void set_container_end(BasicJsonType& v, LexerType* lexer) + { + if (lexer) + { + // Lexer's position is past the closing brace or bracket, so set that as the end position. + v.end_position = lexer->get_position(); + } + } + /*! @param[in,out] v the value that was just parsed @param[in] lexer the lexer that read it, or nullptr to leave @a v alone @@ -349,22 +399,11 @@ class json_sax_dom_parser ref_stack.push_back(handle_value(BasicJsonType::value_t::object)); #if JSON_DIAGNOSTIC_POSITIONS - // Manually set the start position of the object here. // Ensure this is after the call to handle_value to ensure correct start position. - if (m_lexer_ref) - { - // Lexer has read the first character of the object, so - // subtract 1 from the position to get the correct start position. - ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; - } + diagnostic_positions::set_container_start(*ref_stack.back(), m_lexer_ref); #endif - if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) - { - return parse_error(0, "", out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back())); - } - - return true; + return check_container_size(*this, len, "object", ref_stack.back()); } bool key(string_t& val) @@ -383,11 +422,7 @@ class json_sax_dom_parser JSON_ASSERT(ref_stack.back()->is_object()); #if JSON_DIAGNOSTIC_POSITIONS - if (m_lexer_ref) - { - // Lexer's position is past the closing brace, so set that as the end position. - ref_stack.back()->end_position = m_lexer_ref->get_position(); - } + diagnostic_positions::set_container_end(*ref_stack.back(), m_lexer_ref); #endif ref_stack.back()->set_parents(); @@ -400,17 +435,13 @@ class json_sax_dom_parser ref_stack.push_back(handle_value(BasicJsonType::value_t::array)); #if JSON_DIAGNOSTIC_POSITIONS - // Manually set the start position of the array here. // Ensure this is after the call to handle_value to ensure correct start position. - if (m_lexer_ref) - { - ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; - } + diagnostic_positions::set_container_start(*ref_stack.back(), m_lexer_ref); #endif - if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) + if (JSON_HEDLEY_UNLIKELY(!check_container_size(*this, len, "array", ref_stack.back()))) { - return parse_error(0, "", out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back())); + return false; } if (len != detail::unknown_size()) @@ -427,11 +458,7 @@ class json_sax_dom_parser JSON_ASSERT(ref_stack.back()->is_array()); #if JSON_DIAGNOSTIC_POSITIONS - if (m_lexer_ref) - { - // Lexer's position is past the closing bracket, so set that as the end position. - ref_stack.back()->end_position = m_lexer_ref->get_position(); - } + diagnostic_positions::set_container_end(*ref_stack.back(), m_lexer_ref); #endif ref_stack.back()->set_parents(); @@ -611,21 +638,11 @@ class json_sax_dom_callback_parser { #if JSON_DIAGNOSTIC_POSITIONS - // Manually set the start position of the object here. // Ensure this is after the call to handle_value to ensure correct start position. - if (m_lexer_ref) - { - // Lexer has read the first character of the object, so - // subtract 1 from the position to get the correct start position. - ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; - } + diagnostic_positions::set_container_start(*ref_stack.back(), m_lexer_ref); #endif - // check object limit - if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) - { - return parse_error(0, "", out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back())); - } + return check_container_size(*this, len, "object", ref_stack.back()); } return true; } @@ -695,11 +712,7 @@ class json_sax_dom_callback_parser { #if JSON_DIAGNOSTIC_POSITIONS - if (m_lexer_ref) - { - // Lexer's position is past the closing brace, so set that as the end position. - ref_stack.back()->end_position = m_lexer_ref->get_position(); - } + diagnostic_positions::set_container_end(*ref_stack.back(), m_lexer_ref); #endif ref_stack.back()->set_parents(); @@ -710,13 +723,7 @@ class json_sax_dom_callback_parser } } - JSON_ASSERT(!ref_stack.empty()); - JSON_ASSERT(!keep_stack.empty()); - JSON_ASSERT(!container_key_stack.empty()); - ref_stack.pop_back(); - keep_stack.pop_back(); - const string_t object_key = std::move(container_key_stack.back()); - container_key_stack.pop_back(); + const string_t object_key = pop_container(); if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured()) { @@ -742,20 +749,13 @@ class json_sax_dom_callback_parser { #if JSON_DIAGNOSTIC_POSITIONS - // Manually set the start position of the array here. // Ensure this is after the call to handle_value to ensure correct start position. - if (m_lexer_ref) - { - // Lexer has read the first character of the array, so - // subtract 1 from the position to get the correct start position. - ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; - } + diagnostic_positions::set_container_start(*ref_stack.back(), m_lexer_ref); #endif - // check array limit - if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) + if (JSON_HEDLEY_UNLIKELY(!check_container_size(*this, len, "array", ref_stack.back()))) { - return parse_error(0, "", out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back())); + return false; } if (len != detail::unknown_size()) @@ -779,11 +779,7 @@ class json_sax_dom_callback_parser { #if JSON_DIAGNOSTIC_POSITIONS - if (m_lexer_ref) - { - // Lexer's position is past the closing bracket, so set that as the end position. - ref_stack.back()->end_position = m_lexer_ref->get_position(); - } + diagnostic_positions::set_container_end(*ref_stack.back(), m_lexer_ref); #endif ref_stack.back()->set_parents(); @@ -809,13 +805,7 @@ class json_sax_dom_callback_parser } } - JSON_ASSERT(!ref_stack.empty()); - JSON_ASSERT(!keep_stack.empty()); - JSON_ASSERT(!container_key_stack.empty()); - ref_stack.pop_back(); - keep_stack.pop_back(); - const string_t object_key = std::move(container_key_stack.back()); - container_key_stack.pop_back(); + const string_t object_key = pop_container(); // remove discarded value if (!ref_stack.empty() && ref_stack.back()) @@ -902,6 +892,23 @@ class json_sax_dom_callback_parser return string_t{}; } + /*! + @brief leave the object or array that end_object()/end_array() just closed + @return the key the container is stored under in its parent (see + container_key_stack) + */ + string_t pop_container() + { + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(!keep_stack.empty()); + JSON_ASSERT(!container_key_stack.empty()); + ref_stack.pop_back(); + keep_stack.pop_back(); + string_t object_key = std::move(container_key_stack.back()); + container_key_stack.pop_back(); + return object_key; + } + /*! @brief remove the discarded value the callback rejected from its parent, unless it is a duplicate key's slot with a stashed previous value, in diff --git a/include/nlohmann/detail/input/parser.hpp b/include/nlohmann/detail/input/parser.hpp index aa2b30fd7..3a53ff6b3 100644 --- a/include/nlohmann/detail/input/parser.hpp +++ b/include/nlohmann/detail/input/parser.hpp @@ -156,9 +156,7 @@ class parser // strict mode: next byte must be EOF if (get_token() != token_type::end_of_input) { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), nullptr)); + return syntax_error(*sax, exception_message(token_type::end_of_input, "value")); } } else @@ -197,10 +195,7 @@ class parser // in strict mode, input must be completely read if (get_token() != token_type::end_of_input) { - sdp.parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), - exception_message(token_type::end_of_input, "value"), nullptr)); + syntax_error(sdp, exception_message(token_type::end_of_input, "value")); } } else @@ -250,9 +245,7 @@ class parser // parse key if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string)) { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), nullptr)); + return syntax_error(*sax, exception_message(token_type::value_string, "object key")); } if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) { @@ -262,9 +255,7 @@ class parser // parse separator (:) if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator))) { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), nullptr)); + return syntax_error(*sax, exception_message(token_type::name_separator, "object separator")); } // remember we are now inside an object @@ -307,7 +298,7 @@ class parser { return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), - out_of_range::create(406, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr)); + out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr)); } if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string()))) @@ -375,23 +366,16 @@ class parser case token_type::parse_error: { // using "uninitialized" to avoid an "expected" message - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::uninitialized, "value"), nullptr)); + return syntax_error(*sax, exception_message(token_type::uninitialized, "value")); } case token_type::end_of_input: { if (JSON_HEDLEY_UNLIKELY(m_lexer.get_position().chars_read_total == 1)) { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), - "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); + return syntax_error(*sax, "attempting to parse an empty input; check that your input string or stream contains the expected JSON"); } - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), nullptr)); + return syntax_error(*sax, exception_message(token_type::literal_or_value, "value")); } case token_type::uninitialized: case token_type::end_array: @@ -401,9 +385,7 @@ class parser case token_type::literal_or_value: default: // the last token was unexpected { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), nullptr)); + return syntax_error(*sax, exception_message(token_type::literal_or_value, "value")); } } } @@ -454,9 +436,7 @@ class parser continue; } - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_array, "array"), nullptr)); + return syntax_error(*sax, exception_message(token_type::end_array, "array")); } // states.back() is false -> object @@ -473,9 +453,7 @@ class parser // parse key if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string)) { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), nullptr)); + return syntax_error(*sax, exception_message(token_type::value_string, "object key")); } if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) @@ -486,9 +464,7 @@ class parser // parse separator (:) if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator))) { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), nullptr)); + return syntax_error(*sax, exception_message(token_type::name_separator, "object separator")); } // parse values @@ -516,9 +492,7 @@ class parser continue; } - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_object, "object"), nullptr)); + return syntax_error(*sax, exception_message(token_type::end_object, "object")); } } @@ -535,6 +509,21 @@ class parser return (last_token = m_lexer.scan_expecting(expected_type)) == expected_type; } + /*! + @brief reports a syntax error at the current token (parse_error.101) + @param[in] sax the SAX parser to report the error to + @param[in] message the error message + @return the result of the SAX parser's parse_error() + */ + template + bool syntax_error(SAX& sax, const std::string& message) + { + // MSVC 2015 reports C4100 (unreferenced parameter) when SAX::parse_error is static + static_cast(sax); + return sax.parse_error(m_lexer.get_position(), m_lexer.get_token_string(), + parse_error::create(exception_id::syntax_error, m_lexer.get_position(), message, nullptr)); + } + std::string exception_message(const token_type expected, const std::string& context) { std::string error_msg = "syntax error "; diff --git a/include/nlohmann/detail/iterators/iter_impl.hpp b/include/nlohmann/detail/iterators/iter_impl.hpp index 84fdd07f8..52b459ad2 100644 --- a/include/nlohmann/detail/iterators/iter_impl.hpp +++ b/include/nlohmann/detail/iterators/iter_impl.hpp @@ -279,6 +279,12 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci } } + /// @throw invalid_iterator.214 always + JSON_HEDLEY_NO_RETURN void throw_cannot_get_value() const + { + JSON_THROW(invalid_iterator::create(exception_id::iterator_value_unavailable, "cannot get value", m_object)); + } + public: /*! @brief return a reference to the value pointed to by the iterator @@ -303,7 +309,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci } case value_t::null: - JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); + throw_cannot_get_value(); case value_t::string: case value_t::boolean: @@ -319,7 +325,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci return *m_object; } - JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); + throw_cannot_get_value(); } } } @@ -361,7 +367,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci return m_object; } - JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); + throw_cannot_get_value(); } } } @@ -478,7 +484,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci // if objects are not the same, the comparison is undefined if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) { - JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers", m_object)); + JSON_THROW(invalid_iterator::create(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object)); } // value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493 @@ -527,7 +533,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci // if objects are not the same, the comparison is undefined if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) { - JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers", m_object)); + JSON_THROW(invalid_iterator::create(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object)); } // value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493 @@ -540,7 +546,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci switch (m_object->m_data.m_type) { case value_t::object: - JSON_THROW(invalid_iterator::create(213, "cannot compare order of object iterators", m_object)); + JSON_THROW(invalid_iterator::create(exception_id::iterator_order_on_object, "cannot compare order of object iterators", m_object)); case value_t::array: return (m_it.array_iterator < other.m_it.array_iterator); @@ -596,7 +602,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci switch (m_object->m_data.m_type) { case value_t::object: - JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators", m_object)); + JSON_THROW(invalid_iterator::create(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object)); case value_t::array: { @@ -675,7 +681,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci switch (m_object->m_data.m_type) { case value_t::object: - JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators", m_object)); + JSON_THROW(invalid_iterator::create(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object)); case value_t::array: return m_it.array_iterator - other.m_it.array_iterator; @@ -704,13 +710,13 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci switch (m_object->m_data.m_type) { case value_t::object: - JSON_THROW(invalid_iterator::create(208, "cannot use operator[] for object iterators", m_object)); + JSON_THROW(invalid_iterator::create(exception_id::iterator_subscript_on_object, "cannot use operator[] for object iterators", m_object)); case value_t::array: return *std::next(m_it.array_iterator, n); case value_t::null: - JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); + throw_cannot_get_value(); case value_t::string: case value_t::boolean: @@ -726,7 +732,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci return *m_object; } - JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); + throw_cannot_get_value(); } } } @@ -744,7 +750,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci return m_it.object_iterator->first; } - JSON_THROW(invalid_iterator::create(207, "cannot use key() for non-object iterators", m_object)); + JSON_THROW(invalid_iterator::create(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators", m_object)); } /*! diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index 2ec4e3b58..cff8420be 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -166,7 +166,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(empty())) { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + throw_no_parent(); } reference_tokens.erase(reference_tokens.begin()); @@ -178,7 +178,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(empty())) { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + throw_no_parent(); } return reference_tokens.front(); @@ -204,7 +204,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(empty())) { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + throw_no_parent(); } reference_tokens.pop_back(); @@ -216,7 +216,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(empty())) { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + throw_no_parent(); } return reference_tokens.back(); @@ -244,6 +244,21 @@ class json_pointer } private: + /// @throw out_of_range.405 always + JSON_HEDLEY_NO_RETURN static void throw_no_parent() + { + JSON_THROW(detail::out_of_range::create(detail::exception_id::patch_on_root, "JSON pointer has no parent", nullptr)); + } + + /// @throw out_of_range.404 always + template + JSON_HEDLEY_NO_RETURN static void throw_unresolved(const string_t& reference_token, BasicJsonContext context) + { + static_cast(reference_token); // unused when JSON_NOEXCEPTION is defined + static_cast(context); + JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_unresolved, detail::concat("unresolved reference token '", reference_token, "'"), context)); + } + /*! @brief result of @ref parse_array_index @@ -329,13 +344,13 @@ class json_pointer // the branches differ in their messages, not after JSON_THROW's expansion // NOLINTNEXTLINE(bugprone-branch-clone) case array_index_status::leading_zero: - JSON_THROW(detail::parse_error::create(106, 0, detail::concat("array index '", s, "' must not begin with '0'"), nullptr)); + JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_leading_zero, 0, detail::concat("array index '", s, "' must not begin with '0'"), nullptr)); case array_index_status::not_a_number: - JSON_THROW(detail::parse_error::create(109, 0, detail::concat("array index '", s, "' is not a number"), nullptr)); + JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_not_a_number, 0, detail::concat("array index '", s, "' is not a number"), nullptr)); case array_index_status::unresolved: - JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", s, "'"), nullptr)); + throw_unresolved(s, nullptr); case array_index_status::exceeds_size_type: - JSON_THROW(detail::out_of_range::create(410, detail::concat("array index ", s, " exceeds size_type"), nullptr)); + JSON_THROW(detail::out_of_range::create(detail::exception_id::value_out_of_range, detail::concat("array index ", s, " exceeds size_type"), nullptr)); case array_index_status::ok: default: break; @@ -349,7 +364,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(empty())) { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + throw_no_parent(); } json_pointer result = *this; @@ -486,7 +501,7 @@ class json_pointer case detail::value_t::binary: case detail::value_t::discarded: default: - JSON_THROW(detail::type_error::create(313, "invalid value to unflatten", &j)); + JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_invalid_value, "invalid value to unflatten", &j)); } id = tree.find_child(id, reference_token); @@ -569,7 +584,7 @@ class json_pointer case detail::value_t::binary: case detail::value_t::discarded: default: - JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr)); + throw_unresolved(reference_token, ptr); } } @@ -601,7 +616,7 @@ class json_pointer if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) { // "-" always fails the range check - JSON_THROW(detail::out_of_range::create(402, detail::concat( + JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat( "array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr)); } @@ -610,7 +625,7 @@ class json_pointer // Bounds check before access to avoid exception with JSON_NOEXCEPTION if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size())) { - JSON_THROW(detail::out_of_range::create(401, detail::concat( + JSON_THROW(detail::out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat( "array index ", std::to_string(idx), " is out of range"), ptr)); } ptr = &ptr->operator[](idx); @@ -626,7 +641,7 @@ class json_pointer case detail::value_t::binary: case detail::value_t::discarded: default: - JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr)); + throw_unresolved(reference_token, ptr); } } @@ -670,7 +685,7 @@ class json_pointer if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) { // "-" cannot be used for const access - JSON_THROW(detail::out_of_range::create(402, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr)); + JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr)); } // use unchecked array access; the const operator[] @@ -688,7 +703,7 @@ class json_pointer case detail::value_t::binary: case detail::value_t::discarded: default: - JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr)); + throw_unresolved(reference_token, ptr); } } @@ -743,9 +758,9 @@ class json_pointer // the branches differ in their messages, not after JSON_THROW's expansion // NOLINTNEXTLINE(bugprone-branch-clone) case array_index_status::leading_zero: - JSON_THROW(detail::parse_error::create(106, 0, detail::concat("array index '", reference_token, "' must not begin with '0'"), nullptr)); + JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_leading_zero, 0, detail::concat("array index '", reference_token, "' must not begin with '0'"), nullptr)); case array_index_status::not_a_number: - JSON_THROW(detail::parse_error::create(109, 0, detail::concat("array index '", reference_token, "' is not a number"), nullptr)); + JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_not_a_number, 0, detail::concat("array index '", reference_token, "' is not a number"), nullptr)); case array_index_status::unresolved: case array_index_status::exceeds_size_type: return nullptr; @@ -872,7 +887,7 @@ class json_pointer // check if a nonempty reference string begins with slash if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/')) { - JSON_THROW(detail::parse_error::create(107, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", reference_string, "'"), nullptr)); + JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_missing_slash, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", reference_string, "'"), nullptr)); } // extract the reference tokens: @@ -908,7 +923,7 @@ class json_pointer (reference_token[pos + 1] != '0' && reference_token[pos + 1] != '1'))) { - JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'", nullptr)); + JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr)); } } @@ -1072,7 +1087,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(!value.is_object())) { - JSON_THROW(detail::type_error::create(314, "only objects can be unflattened", &value)); + JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_not_object, "only objects can be unflattened", &value)); } BasicJsonType result; @@ -1096,7 +1111,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive())) { - JSON_THROW(detail::type_error::create(315, "values in object must be primitive", &element.second)); + JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_value_not_primitive, "values in object must be primitive", &element.second)); } // Assign the value to the reference pointed to by JSON pointer. Note diff --git a/include/nlohmann/detail/macro_scope.hpp b/include/nlohmann/detail/macro_scope.hpp index 1a847e1a9..f5f6473b9 100644 --- a/include/nlohmann/detail/macro_scope.hpp +++ b/include/nlohmann/detail/macro_scope.hpp @@ -312,7 +312,7 @@ return ej_pair.first == e; \ }); \ if (it != std::end(m)) j = it->second; \ - else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(410,"enum value out of range for " #ENUM_TYPE, nullptr)); \ + else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range,"enum value out of range for " #ENUM_TYPE, nullptr)); \ } \ template \ inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ @@ -327,7 +327,7 @@ return ej_pair.second == j; \ }); \ if (it != std::end(m)) e = it->first; \ - else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(410, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", j.dump(-1, ' ', false, nlohmann::detail::error_handler_t::replace)), &j)); \ + else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", j.dump(-1, ' ', false, nlohmann::detail::error_handler_t::replace)), &j)); \ } // Ugly macros to avoid uglier copy-paste when specializing basic_json. They diff --git a/include/nlohmann/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp index 212d2658f..cbe771ee8 100644 --- a/include/nlohmann/detail/output/binary_writer.hpp +++ b/include/nlohmann/detail/output/binary_writer.hpp @@ -157,7 +157,7 @@ class binary_writer case value_t::discarded: default: { - JSON_THROW(type_error::create(317, concat("to serialize to BSON, top-level type must be object, but is ", j.type_name()), &j)); + JSON_THROW(type_error::create(exception_id::type_not_serializable, concat("to serialize to BSON, top-level type must be object, but is ", j.type_name()), &j)); } } } @@ -348,7 +348,7 @@ class binary_writer { if (JSON_HEDLEY_UNLIKELY(!value_in_range_of(length))) { - JSON_THROW(out_of_range::create(412, concat("MessagePack length ", std::to_string(length), " exceeds maximum of ", std::to_string((std::numeric_limits::max)())), &j)); + JSON_THROW(out_of_range::create(exception_id::length_too_large, concat("MessagePack length ", std::to_string(length), " exceeds maximum of ", std::to_string((std::numeric_limits::max)())), &j)); } static_cast(j); @@ -575,7 +575,7 @@ class binary_writer { if (JSON_HEDLEY_UNLIKELY(j.m_data.m_value.binary->subtype() > (std::numeric_limits::max)())) { - JSON_THROW(out_of_range::create(415, concat("subtype ", std::to_string(j.m_data.m_value.binary->subtype()), " is too large for the MessagePack ext type (max 255)"), &j)); + JSON_THROW(out_of_range::create(exception_id::subtype_out_of_range, concat("subtype ", std::to_string(j.m_data.m_value.binary->subtype()), " is too large for the MessagePack ext type (max 255)"), &j)); } write_number(static_cast(j.m_data.m_value.binary->subtype())); @@ -730,7 +730,7 @@ class binary_writer { if (!use_count) { - JSON_THROW(other_error::create(502, "use_type requires use_size = true", &j)); + JSON_THROW(other_error::create(exception_id::size_marker_required, "use_type requires use_size = true", &j)); } oa.write_character(to_char_type('$')); oa.write_character(bjdata_draft3 ? 'B' : 'U'); @@ -831,7 +831,7 @@ class binary_writer { static_cast(j); // unused when JSON_NOEXCEPTION is defined static_cast(format_name); - JSON_THROW(type_error::create(321, concat("cannot serialize discarded value to ", format_name), &j)); + JSON_THROW(type_error::create(exception_id::discarded_value_used, concat("cannot serialize discarded value to ", format_name), &j)); } void write_msgpack_array_prefix(const std::size_t N, const BasicJsonType& j) @@ -1091,7 +1091,7 @@ class binary_writer { if (!use_count) { - JSON_THROW(other_error::create(502, "use_type requires use_size = true", &j)); + JSON_THROW(other_error::create(exception_id::size_marker_required, "use_type requires use_size = true", &j)); } const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata); const bool same_prefix = std::all_of(j.begin() + 1, j.end(), @@ -1141,7 +1141,7 @@ class binary_writer { if (!use_count) { - JSON_THROW(other_error::create(502, "use_type requires use_size = true", &j)); + JSON_THROW(other_error::create(exception_id::size_marker_required, "use_type requires use_size = true", &j)); } const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata); const bool same_prefix = std::all_of(j.begin(), j.end(), @@ -1358,7 +1358,7 @@ class binary_writer const auto it = name.find(static_cast(0)); if (JSON_HEDLEY_UNLIKELY(it != BasicJsonType::string_t::npos)) { - JSON_THROW(out_of_range::create(409, concat("BSON key cannot contain code point U+0000 (at byte ", std::to_string(it), ")"), &j)); + JSON_THROW(out_of_range::create(exception_id::bson_key_with_null, concat("BSON key cannot contain code point U+0000 (at byte ", std::to_string(it), ")"), &j)); } string_t storage; @@ -1376,7 +1376,7 @@ class binary_writer { if (JSON_HEDLEY_UNLIKELY(!value_in_range_of(size))) { - JSON_THROW(out_of_range::create(412, concat("BSON length ", std::to_string(size), " exceeds maximum of ", std::to_string((std::numeric_limits::max)())), nullptr)); + JSON_THROW(out_of_range::create(exception_id::length_too_large, concat("BSON length ", std::to_string(size), " exceeds maximum of ", std::to_string((std::numeric_limits::max)())), nullptr)); } return static_cast(size); @@ -1562,7 +1562,7 @@ class binary_writer if (value.has_subtype() && JSON_HEDLEY_UNLIKELY(value.subtype() > (std::numeric_limits::max)())) { - JSON_THROW(out_of_range::create(415, concat("subtype ", std::to_string(value.subtype()), " is too large for the BSON binary subtype (max 255)"), &j)); + JSON_THROW(out_of_range::create(exception_id::subtype_out_of_range, concat("subtype ", std::to_string(value.subtype()), " is too large for the BSON binary subtype (max 255)"), &j)); } return sizeof(std::int32_t) + value.size() + 1ul; @@ -2591,7 +2591,7 @@ class binary_writer { if (j.m_data.m_value.number_unsigned > static_cast((std::numeric_limits::max)())) { - JSON_THROW(out_of_range::create(407, concat("integer number ", std::to_string(j.m_data.m_value.number_unsigned), " cannot be represented by BON8 as it does not fit int64"), &j)); + JSON_THROW(out_of_range::create(exception_id::integer_too_large, concat("integer number ", std::to_string(j.m_data.m_value.number_unsigned), " cannot be represented by BON8 as it does not fit int64"), &j)); } write_bon8_integer(static_cast(j.m_data.m_value.number_unsigned)); string_open = false; @@ -2744,7 +2744,7 @@ class binary_writer const std::size_t valid = valid_utf8_prefix(data, s.size()); if (JSON_HEDLEY_UNLIKELY(valid != s.size())) { - JSON_THROW(type_error::create(316, concat("invalid UTF-8 byte at index ", std::to_string(valid), ": 0x", detail::hex_byte(data[valid])), &context)); + JSON_THROW(type_error::create(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(valid), ": 0x", detail::hex_byte(data[valid])), &context)); } } diff --git a/include/nlohmann/detail/output/serializer.hpp b/include/nlohmann/detail/output/serializer.hpp index 8306440ef..64c4fcf03 100644 --- a/include/nlohmann/detail/output/serializer.hpp +++ b/include/nlohmann/detail/output/serializer.hpp @@ -888,7 +888,7 @@ class serializer { case error_handler_t::strict: { - JSON_THROW(type_error::create(316, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", detail::hex_byte(byte)), nullptr)); + JSON_THROW(type_error::create(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", detail::hex_byte(byte)), nullptr)); } case error_handler_t::ignore: @@ -1021,7 +1021,7 @@ class serializer { case error_handler_t::strict: { - JSON_THROW(type_error::create(316, concat("incomplete UTF-8 string; last byte: 0x", detail::hex_byte(static_cast(s[s.size() - 1]))), nullptr)); + JSON_THROW(type_error::create(exception_id::invalid_utf8, concat("incomplete UTF-8 string; last byte: 0x", detail::hex_byte(static_cast(s[s.size() - 1]))), nullptr)); } case error_handler_t::ignore: diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 9c89ab0bc..b841e9fa3 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -639,7 +639,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec object = nullptr; // silence warning, see #821 if (JSON_HEDLEY_UNLIKELY(t == value_t::null)) { - JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.12.0", nullptr)); // LCOV_EXCL_LINE + JSON_THROW(other_error::create(detail::exception_id::internal_error, "961c151d2e87f2686a955a9be24d316f1362bf21 3.12.0", nullptr)); // LCOV_EXCL_LINE } break; } @@ -2287,7 +2287,7 @@ public: // if an object is wanted but impossible, throw an exception if (JSON_HEDLEY_UNLIKELY(manual_type == value_t::object && !is_an_object)) { - JSON_THROW(type_error::create(301, "cannot create object from initializer list", nullptr)); + JSON_THROW(type_error::create(detail::exception_id::object_from_non_pairs, "cannot create object from initializer list", nullptr)); } } @@ -2407,7 +2407,7 @@ public: // make sure the iterator fits the current value if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) { - JSON_THROW(invalid_iterator::create(201, "iterators are not compatible", nullptr)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterators_incompatible, "iterators are not compatible", nullptr)); } // copy type from the first iterator @@ -2426,7 +2426,7 @@ public: if (JSON_HEDLEY_UNLIKELY(!first.m_it.primitive_iterator.is_begin() || !last.m_it.primitive_iterator.is_end())) { - JSON_THROW(invalid_iterator::create(204, "iterators out of range", first.m_object)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_range_out_of_range, "iterators out of range", first.m_object)); } break; } @@ -2494,7 +2494,7 @@ public: case value_t::null: case value_t::discarded: default: - JSON_THROW(invalid_iterator::create(206, detail::concat("cannot construct with iterators from ", first.m_object->type_name()), first.m_object)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_range_of_null, detail::concat("cannot construct with iterators from ", first.m_object->type_name()), first.m_object)); } set_parents(); @@ -2882,7 +2882,7 @@ public: return *ptr; } - JSON_THROW(type_error::create(303, detail::concat("incompatible ReferenceType for get_ref, actual type is ", obj.type_name()), &obj)); + JSON_THROW(type_error::create(detail::exception_id::incompatible_reference_type, detail::concat("incompatible ReferenceType for get_ref, actual type is ", obj.type_name()), &obj)); } public: @@ -3266,7 +3266,7 @@ public: { if (!is_binary()) { - JSON_THROW(type_error::create(302, detail::concat("type must be binary, but is ", type_name()), this)); + detail::throw_type_must_be("binary", *this); } return *get_ptr(); @@ -3278,7 +3278,7 @@ public: { if (!is_binary()) { - JSON_THROW(type_error::create(302, detail::concat("type must be binary, but is ", type_name()), this)); + detail::throw_type_must_be("binary", *this); } return *get_ptr(); @@ -3320,7 +3320,7 @@ public: // at only works for objects if (JSON_HEDLEY_UNLIKELY(!j.is_object())) { - JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", j.type_name()), &j)); + detail::throw_cannot_use_with(detail::exception_id::at_wrong_type, "at()", j); } auto it = object_lookup(j, std::forward(key)); @@ -3330,7 +3330,7 @@ public: // std::map or ordered_map) never moves from its argument, so key is still // valid here regardless of whether KeyType was deduced as an rvalue reference // NOLINTNEXTLINE(bugprone-use-after-move,hicpp-invalid-access-moved) - JSON_THROW(out_of_range::create(403, detail::concat("key '", string_t(key), "' not found"), &j)); + JSON_THROW(out_of_range::create(detail::exception_id::key_not_found, detail::concat("key '", string_t(key), "' not found"), &j)); } return it->second; } @@ -3345,12 +3345,12 @@ public: // at only works for arrays if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", j.type_name()), &j)); + detail::throw_cannot_use_with(detail::exception_id::at_wrong_type, "at()", j); } if (JSON_HEDLEY_UNLIKELY(idx >= j.m_data.m_value.array->size())) { - JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), &j)); + JSON_THROW(out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat("array index ", std::to_string(idx), " is out of range"), &j)); } return (*j.m_data.m_value.array)[idx]; @@ -3383,6 +3383,15 @@ public: m_data.m_type = value_t::object; } + /// @brief throws because operator[] is not supported for the type of this value + /// @param[in] argument the kind of the operator's argument, "numeric" or "string" + /// @throw type_error.305 always + JSON_HEDLEY_NO_RETURN void throw_subscript_wrong_type(const char* argument) const + { + detail::throw_cannot_use_with(detail::exception_id::subscript_wrong_type, + detail::concat("operator[] with a ", argument, " argument").c_str(), *this); + } + public: //////////////////// // element access // @@ -3487,7 +3496,7 @@ public: return m_data.m_value.array->operator[](idx); } - JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a numeric argument with ", type_name()), this)); + throw_subscript_wrong_type("numeric"); } /// @brief access specified array element @@ -3501,7 +3510,7 @@ public: return m_data.m_value.array->operator[](idx); } - JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a numeric argument with ", type_name()), this)); + throw_subscript_wrong_type("numeric"); } /// @brief access specified object element @@ -3521,7 +3530,7 @@ public: return set_parent(result.first->second); } - JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this)); + throw_subscript_wrong_type("string"); } /// @brief access specified object element @@ -3536,7 +3545,7 @@ public: return it->second; } - JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this)); + throw_subscript_wrong_type("string"); } // these two functions resolve a (const) char * ambiguity affecting Clang and MSVC @@ -3572,7 +3581,7 @@ public: return set_parent(result.first->second); } - JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this)); + throw_subscript_wrong_type("string"); } /// @brief access specified object element @@ -3589,7 +3598,7 @@ public: return it->second; } - JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this)); + throw_subscript_wrong_type("string"); } private: @@ -3613,7 +3622,7 @@ public: // value only works for objects if (JSON_HEDLEY_UNLIKELY(!is_object())) { - JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::value_wrong_type, "value()", *this); } const auto it = find(std::forward(key)); @@ -3628,7 +3637,7 @@ public: // value only works for arrays and objects if (JSON_HEDLEY_UNLIKELY(!is_structured())) { - JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::value_wrong_type, "value()", *this); } return ptr.get_checked_or_null(this); @@ -3795,7 +3804,7 @@ public: // make sure the iterator fits the current value if (JSON_HEDLEY_UNLIKELY(this != pos.m_object)) { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); } IteratorType result = end(); @@ -3811,7 +3820,7 @@ public: { if (JSON_HEDLEY_UNLIKELY(!pos.m_it.primitive_iterator.is_begin())) { - JSON_THROW(invalid_iterator::create(205, "iterator out of range", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_out_of_range, "iterator out of range", this)); } m_data.m_value.destroy(m_data.m_type); @@ -3837,7 +3846,7 @@ public: case value_t::null: case value_t::discarded: default: - JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::erase_wrong_type, "erase()", *this); } return result; @@ -3853,7 +3862,7 @@ public: // make sure the iterator fits the current value if (JSON_HEDLEY_UNLIKELY(this != first.m_object || this != last.m_object)) { - JSON_THROW(invalid_iterator::create(203, "iterators do not fit current value", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_range_from_other_value, "iterators do not fit current value", this)); } IteratorType result = end(); @@ -3870,7 +3879,7 @@ public: if (JSON_HEDLEY_UNLIKELY(!first.m_it.primitive_iterator.is_begin() || !last.m_it.primitive_iterator.is_end())) { - JSON_THROW(invalid_iterator::create(204, "iterators out of range", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_range_out_of_range, "iterators out of range", this)); } m_data.m_value.destroy(m_data.m_type); @@ -3898,7 +3907,7 @@ public: case value_t::null: case value_t::discarded: default: - JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::erase_wrong_type, "erase()", *this); } return result; @@ -3912,7 +3921,7 @@ public: // this erase only works for objects if (JSON_HEDLEY_UNLIKELY(!is_object())) { - JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::erase_wrong_type, "erase()", *this); } const auto erased = m_data.m_value.object->erase(std::forward(key)); @@ -3927,7 +3936,7 @@ public: // this erase only works for objects if (JSON_HEDLEY_UNLIKELY(!is_object())) { - JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::erase_wrong_type, "erase()", *this); } const auto it = object_lookup(*this, std::forward(key)); @@ -3969,14 +3978,14 @@ public: { if (JSON_HEDLEY_UNLIKELY(idx >= size())) { - JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), this)); + JSON_THROW(out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat("array index ", std::to_string(idx), " is out of range"), this)); } m_data.m_value.array->erase(m_data.m_value.array->begin() + static_cast(idx)); } else { - JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::erase_wrong_type, "erase()", *this); } } @@ -4465,7 +4474,7 @@ public: // push_back only works for null objects or arrays if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) { - JSON_THROW(type_error::create(308, detail::concat("cannot use push_back() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::push_back_wrong_type, "push_back()", *this); } // transform a null object into an array @@ -4496,7 +4505,7 @@ public: // push_back only works for null objects or arrays if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) { - JSON_THROW(type_error::create(308, detail::concat("cannot use push_back() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::push_back_wrong_type, "push_back()", *this); } // transform a null object into an array @@ -4526,7 +4535,7 @@ public: // push_back only works for null objects or objects if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object()))) { - JSON_THROW(type_error::create(308, detail::concat("cannot use push_back() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::push_back_wrong_type, "push_back()", *this); } // transform a null object into an object @@ -4580,7 +4589,7 @@ public: // emplace_back only works for null objects or arrays if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) { - JSON_THROW(type_error::create(311, detail::concat("cannot use emplace_back() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::emplace_wrong_type, "emplace_back()", *this); } // transform a null object into an array @@ -4603,7 +4612,7 @@ public: // emplace only works for null objects or arrays if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object()))) { - JSON_THROW(type_error::create(311, detail::concat("cannot use emplace() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::emplace_wrong_type, "emplace()", *this); } // transform a null object into an object @@ -4655,14 +4664,14 @@ public: // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); } // insert to array and return iterator return insert_iterator(pos, val); } - JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); } /// @brief inserts element into array @@ -4675,7 +4684,7 @@ public: // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); } // moving into a local first keeps this safe even if val aliases @@ -4684,7 +4693,7 @@ public: return insert_iterator(pos, std::move(tmp)); } - JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); } /// @brief inserts copies of element into array @@ -4697,14 +4706,14 @@ public: // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); } // insert to array and return iterator return insert_iterator(pos, cnt, val); } - JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); } /// @brief inserts range of elements into array @@ -4714,30 +4723,30 @@ public: // insert only works for arrays if (JSON_HEDLEY_UNLIKELY(!is_array())) { - JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); } // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); } // check if range iterators belong to the same JSON object if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) { - JSON_THROW(invalid_iterator::create(210, "iterators do not fit", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::insert_range_incompatible, "iterators do not fit", this)); } if (JSON_HEDLEY_UNLIKELY(first.m_object == this)) { - JSON_THROW(invalid_iterator::create(211, "passed iterators may not belong to container", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::insert_range_into_itself, "passed iterators may not belong to container", this)); } // passed iterators must belong to arrays if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_array())) { - JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to arrays", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterators first and last must point to arrays", this)); } // insert to array and return iterator @@ -4751,13 +4760,13 @@ public: // insert only works for arrays if (JSON_HEDLEY_UNLIKELY(!is_array())) { - JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); } // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); } // copy the values first: ilist may refer to elements of this array @@ -4779,19 +4788,19 @@ public: // insert only works for objects if (JSON_HEDLEY_UNLIKELY(!is_object())) { - JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); } // check if range iterators belong to the same JSON object if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) { - JSON_THROW(invalid_iterator::create(210, "iterators do not fit", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::insert_range_incompatible, "iterators do not fit", this)); } // passed iterators must belong to objects if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object())) { - JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterators first and last must point to objects", this)); } m_data.m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator); @@ -4808,7 +4817,7 @@ public: // j, not the copy made below) if (JSON_HEDLEY_UNLIKELY(!j.is_object())) { - JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", j.type_name()), &j)); + detail::throw_cannot_use_with(detail::exception_id::update_wrong_type, "update()", j); } // copy first: j may be *this or one of its descendants, and is @@ -4826,13 +4835,13 @@ public: // check if range iterators belong to the same JSON object if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) { - JSON_THROW(invalid_iterator::create(210, "iterators do not fit", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::insert_range_incompatible, "iterators do not fit", this)); } // passed iterators must belong to objects if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object())) { - JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", first.m_object->type_name()), first.m_object)); + detail::throw_cannot_use_with(detail::exception_id::update_wrong_type, "update()", *first.m_object); } // copy first: the range may belong to *this or one of its @@ -4868,7 +4877,7 @@ public: if (JSON_HEDLEY_UNLIKELY(!is_object())) { - JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::update_wrong_type, "update()", *this); } } @@ -5031,7 +5040,7 @@ public: } else { - JSON_THROW(type_error::create(310, detail::concat("cannot use swap(array_t&) with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(array_t&)", *this); } } @@ -5048,7 +5057,7 @@ public: } else { - JSON_THROW(type_error::create(310, detail::concat("cannot use swap(object_t&) with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(object_t&)", *this); } } @@ -5064,7 +5073,7 @@ public: } else { - JSON_THROW(type_error::create(310, detail::concat("cannot use swap(string_t&) with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(string_t&)", *this); } } @@ -5080,7 +5089,7 @@ public: } else { - JSON_THROW(type_error::create(310, detail::concat("cannot use swap(binary_t&) with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(binary_t&)", *this); } } @@ -5096,7 +5105,7 @@ public: } else { - JSON_THROW(type_error::create(310, detail::concat("cannot use swap(binary_t::container_type&) with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(binary_t::container_type&)", *this); } } @@ -6558,7 +6567,7 @@ public: if (JSON_HEDLEY_UNLIKELY(idx > parent.size())) { // avoid undefined behavior - JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), &parent)); + JSON_THROW(out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat("array index ", std::to_string(idx), " is out of range"), &parent)); } // default case: insert add offset @@ -6577,7 +6586,7 @@ public: case value_t::binary: case value_t::discarded: default: - JSON_THROW(out_of_range::create(411, detail::concat("cannot add value: the JSON Patch 'add' target's parent is of type ", parent.type_name(), ", but must be an object or array"), &parent)); + JSON_THROW(out_of_range::create(detail::exception_id::patch_add_parent_not_container, detail::concat("cannot add value: the JSON Patch 'add' target's parent is of type ", parent.type_name(), ", but must be an object or array"), &parent)); } }; @@ -6600,7 +6609,7 @@ public: } else { - JSON_THROW(out_of_range::create(403, detail::concat("key '", last_path, "' not found"), this)); + JSON_THROW(out_of_range::create(detail::exception_id::key_not_found, detail::concat("key '", last_path, "' not found"), this)); } } else if (parent.is_array()) @@ -6612,7 +6621,7 @@ public: { // the parent of a "remove" target must be an object or array // (see #5396) - JSON_THROW(out_of_range::create(413, detail::concat("cannot remove value: the JSON Patch 'remove' target's parent is of type ", parent.type_name(), ", but must be an object or array"), &parent)); + JSON_THROW(out_of_range::create(detail::exception_id::patch_remove_parent_not_container, detail::concat("cannot remove value: the JSON Patch 'remove' target's parent is of type ", parent.type_name(), ", but must be an object or array"), &parent)); } }; @@ -6643,7 +6652,7 @@ public: // type check: top level value must be an array if (JSON_HEDLEY_UNLIKELY(!json_patch.is_array())) { - JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects", &json_patch)); + JSON_THROW(parse_error::create(detail::exception_id::patch_not_an_array, 0, "JSON patch must be an array of objects", &json_patch)); } // iterate and apply the operations @@ -6664,14 +6673,14 @@ public: if (JSON_HEDLEY_UNLIKELY(it == val.m_data.m_value.object->end())) { // NOLINTNEXTLINE(performance-inefficient-string-concatenation) - JSON_THROW(parse_error::create(105, 0, detail::concat(error_msg, " must have member '", member, "'"), &val)); + JSON_THROW(parse_error::create(detail::exception_id::patch_invalid_operation, 0, detail::concat(error_msg, " must have member '", member, "'"), &val)); } // check if the result is of type string if (JSON_HEDLEY_UNLIKELY(string_type && !it->second.is_string())) { // NOLINTNEXTLINE(performance-inefficient-string-concatenation) - JSON_THROW(parse_error::create(105, 0, detail::concat(error_msg, " must have string member '", member, "'"), &val)); + JSON_THROW(parse_error::create(detail::exception_id::patch_invalid_operation, 0, detail::concat(error_msg, " must have string member '", member, "'"), &val)); } // no error: return value @@ -6681,7 +6690,7 @@ public: // type check: every element of the array must be an object if (JSON_HEDLEY_UNLIKELY(!val.is_object())) { - JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects", &val)); + JSON_THROW(parse_error::create(detail::exception_id::patch_not_an_array, 0, "JSON patch must be an array of objects", &val)); } // collect mandatory members @@ -6717,7 +6726,7 @@ public: if (JSON_HEDLEY_UNLIKELY(is_proper_prefix(from_ptr, ptr))) { - JSON_THROW(out_of_range::create(414, detail::concat("cannot move value: 'from' path '", from_path, "' is a proper prefix of 'path' '", path, "'"), &result)); + JSON_THROW(out_of_range::create(detail::exception_id::patch_move_into_child, detail::concat("cannot move value: 'from' path '", from_path, "' is a proper prefix of 'path' '", path, "'"), &result)); } // the "from" location must exist - use at() @@ -6764,7 +6773,7 @@ public: // throw an exception if the test fails if (JSON_HEDLEY_UNLIKELY(!success)) { - JSON_THROW(other_error::create(501, detail::concat("unsuccessful: ", val.dump()), &val)); + JSON_THROW(other_error::create(detail::exception_id::patch_test_failed, detail::concat("unsuccessful: ", val.dump()), &val)); } break; @@ -6775,7 +6784,7 @@ public: { // op must be "add", "remove", "replace", "move", "copy", or // "test" - JSON_THROW(parse_error::create(105, 0, detail::concat("operation value '", op, "' is invalid"), &val)); + JSON_THROW(parse_error::create(detail::exception_id::patch_invalid_operation, 0, detail::concat("operation value '", op, "' is invalid"), &val)); } } } diff --git a/include/nlohmann/ordered_map.hpp b/include/nlohmann/ordered_map.hpp index 74539d376..5f0be7d28 100644 --- a/include/nlohmann/ordered_map.hpp +++ b/include/nlohmann/ordered_map.hpp @@ -90,6 +90,19 @@ private: return self.end(); } + /// @brief shared implementation of the const and non-const at() overloads + /// @throw std::out_of_range if @a key is not found + template + static auto at_impl(Self& self, const KeyType& key) -> decltype((self.begin()->second)) + { + const auto it = find_impl(self, key); + if (it == self.end()) + { + JSON_THROW(std::out_of_range("key not found")); + } + return it->second; + } + /// @brief remove the entry @a it points to, preserving order /// @note keys are not movable, so the tail is destroyed and re-constructed in place void erase_at(iterator it) @@ -156,46 +169,26 @@ public: T& at(const key_type& key) { - const auto it = find_impl(*this, key); - if (it == this->end()) - { - JSON_THROW(std::out_of_range("key not found")); - } - return it->second; + return at_impl(*this, key); } template::value, int> = 0> T & at(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward) { - const auto it = find_impl(*this, key); - if (it == this->end()) - { - JSON_THROW(std::out_of_range("key not found")); - } - return it->second; + return at_impl(*this, key); } const T& at(const key_type& key) const { - const auto it = find_impl(*this, key); - if (it == this->end()) - { - JSON_THROW(std::out_of_range("key not found")); - } - return it->second; + return at_impl(*this, key); } template::value, int> = 0> const T & at(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward) { - const auto it = find_impl(*this, key); - if (it == this->end()) - { - JSON_THROW(std::out_of_range("key not found")); - } - return it->second; + return at_impl(*this, key); } size_type erase(const key_type& key) diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 09f67bdc3..a8a9bab3e 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -2649,7 +2649,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP return ej_pair.first == e; \ }); \ if (it != std::end(m)) j = it->second; \ - else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(410,"enum value out of range for " #ENUM_TYPE, nullptr)); \ + else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range,"enum value out of range for " #ENUM_TYPE, nullptr)); \ } \ template \ inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ @@ -2664,7 +2664,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP return ej_pair.second == j; \ }); \ if (it != std::end(m)) e = it->first; \ - else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(410, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", j.dump(-1, ' ', false, nlohmann::detail::error_handler_t::replace)), &j)); \ + else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", j.dump(-1, ' ', false, nlohmann::detail::error_handler_t::replace)), &j)); \ } // Ugly macros to avoid uglier copy-paste when specializing basic_json. They @@ -5237,6 +5237,84 @@ namespace detail // exceptions // //////////////// +/*! +@brief the ids of the exceptions thrown by the library +@note The values are part of the public API: they are the `id` member of the + exceptions and appear in their `what()` messages. +@sa https://json.nlohmann.me/home/exceptions/ +*/ +enum class exception_id : int +{ + // parse_error + syntax_error = 101, ///< unexpected token or invalid literal while parsing JSON + patch_not_an_array = 104, ///< a JSON Patch document is not an array of objects + patch_invalid_operation = 105, ///< a JSON Patch operation is malformed + pointer_index_leading_zero = 106, ///< a JSON Pointer array index has a leading zero + pointer_missing_slash = 107, ///< a JSON Pointer does not start with '/' + pointer_invalid_escape = 108, ///< a JSON Pointer contains an escape other than ~0 and ~1 + pointer_index_not_a_number = 109, ///< a JSON Pointer array index is not a number + unexpected_end_of_input = 110, ///< a binary input ends early, or has bytes left at its end + unexpected_byte = 112, ///< a binary input contains an unexpected byte or invalid length + invalid_string_or_size = 113, ///< a binary input contains an invalid string or size specification + bson_unsupported_type = 114, ///< a BSON record type is not supported + invalid_high_precision_number = 115, ///< a UBJSON/BJData high-precision number cannot be parsed + // invalid_iterator + iterators_incompatible = 201, ///< the iterators of a range belong to different values + iterator_from_other_value = 202, ///< an iterator does not belong to the value it is used with + iterator_range_from_other_value = 203, ///< an iterator range does not belong to the value it is used with + iterator_range_out_of_range = 204, ///< an iterator range of a primitive value is not [begin, end) + iterator_out_of_range = 205, ///< an iterator of a primitive value is not begin() + iterator_range_of_null = 206, ///< an iterator range belongs to a null value + iterator_key_not_object = 207, ///< key() is called on an iterator of a non-object + iterator_subscript_on_object = 208, ///< operator[] is called on an iterator of an object + iterator_arithmetic_on_object = 209, ///< an offset operator is used on an iterator of an object + insert_range_incompatible = 210, ///< the iterators of an inserted range belong to different values + insert_range_into_itself = 211, ///< an inserted range belongs to the value it is inserted into + iterators_compare_different_values = 212, ///< iterators of different values are compared + iterator_order_on_object = 213, ///< iterators of an object are compared by order + iterator_value_unavailable = 214, ///< an iterator does not refer to a value + // type_error + object_from_non_pairs = 301, ///< an object is created from an initializer list that is not a list of pairs + type_mismatch = 302, ///< a value has the wrong type for a conversion + incompatible_reference_type = 303, ///< get_ref() is called with a reference type that does not match the value + at_wrong_type = 304, ///< at() is called on a value of the wrong type + subscript_wrong_type = 305, ///< operator[] is called on a value of the wrong type + value_wrong_type = 306, ///< value() is called on a value of the wrong type + erase_wrong_type = 307, ///< erase() is called on a value of the wrong type + push_back_wrong_type = 308, ///< push_back() or operator+= is called on a value of the wrong type + insert_wrong_type = 309, ///< insert() is called on a value of the wrong type + swap_wrong_type = 310, ///< swap() is called on a value of the wrong type + emplace_wrong_type = 311, ///< emplace() or emplace_back() is called on a value of the wrong type + update_wrong_type = 312, ///< update() is called on a value of the wrong type + unflatten_invalid_value = 313, ///< unflatten() finds conflicting paths + unflatten_not_object = 314, ///< unflatten() is called on a non-object + unflatten_value_not_primitive = 315, ///< unflatten() is called on an object with non-primitive values + invalid_utf8 = 316, ///< dump() finds a string that is not valid UTF-8 + type_not_serializable = 317, ///< a value cannot be serialized to the requested binary format + enum_key_duplicate = 318, ///< an enum-keyed map has two keys that serialize to the same string + discarded_value_used = 321, ///< a discarded value is used + // out_of_range + array_index_out_of_range = 401, ///< an array index is out of range + pointer_past_the_end_index = 402, ///< a JSON Pointer uses the array index '-' + key_not_found = 403, ///< an object key is not found + pointer_unresolved = 404, ///< a JSON Pointer reference token cannot be resolved + patch_on_root = 405, ///< the root of a value has no parent, e.g. for a JSON Patch 'remove' or 'add' at the root + number_overflow = 406, ///< a number cannot be stored without overflowing to NaN or INF + integer_too_large = 407, ///< an integer cannot be represented in the binary format + container_too_large = 408, ///< the size of a container in a binary input exceeds the maximal capacity + bson_key_with_null = 409, ///< a BSON key contains U+0000 + value_out_of_range = 410, ///< an enum value is undefined, or a JSON Pointer array index exceeds size_type + patch_add_parent_not_container = 411, ///< the parent of a JSON Patch 'add' target is not a container + length_too_large = 412, ///< a length does not fit into the length field of a binary format + patch_remove_parent_not_container = 413, ///< the parent of a JSON Patch 'remove' target is not a container + patch_move_into_child = 414, ///< a JSON Patch 'move' moves a value into one of its children + subtype_out_of_range = 415, ///< a binary subtype does not fit into one byte + // other_error + internal_error = 500, ///< unreachable code was reached + patch_test_failed = 501, ///< a JSON Patch 'test' operation failed + size_marker_required = 502 ///< UBJSON/BJData output with type markers requires size markers +}; + /// @brief general exception of the @ref basic_json class /// @sa https://json.nlohmann.me/api/basic_json/exception/ class exception : public std::exception @@ -5387,6 +5465,18 @@ class parse_error : public exception return {id_, byte_, w.c_str()}; } + template::value, int> = 0> + static parse_error create(exception_id id_, const position_t& pos, const std::string& what_arg, BasicJsonContext context) + { + return create(static_cast(id_), pos, what_arg, context); + } + + template::value, int> = 0> + static parse_error create(exception_id id_, std::size_t byte_, const std::string& what_arg, BasicJsonContext context) + { + return create(static_cast(id_), byte_, what_arg, context); + } + /*! @brief byte index of the parse error @@ -5421,6 +5511,12 @@ class invalid_iterator : public exception return {id_, w.c_str()}; } + template::value, int> = 0> + static invalid_iterator create(exception_id id_, const std::string& what_arg, BasicJsonContext context) + { + return create(static_cast(id_), what_arg, context); + } + private: JSON_HEDLEY_NON_NULL(3) invalid_iterator(int id_, const char* what_arg) @@ -5439,6 +5535,12 @@ class type_error : public exception return {id_, w.c_str()}; } + template::value, int> = 0> + static type_error create(exception_id id_, const std::string& what_arg, BasicJsonContext context) + { + return create(static_cast(id_), what_arg, context); + } + private: JSON_HEDLEY_NON_NULL(3) type_error(int id_, const char* what_arg) : exception(id_, what_arg) {} @@ -5456,6 +5558,12 @@ class out_of_range : public exception return {id_, w.c_str()}; } + template::value, int> = 0> + static out_of_range create(exception_id id_, const std::string& what_arg, BasicJsonContext context) + { + return create(static_cast(id_), what_arg, context); + } + private: JSON_HEDLEY_NON_NULL(3) out_of_range(int id_, const char* what_arg) : exception(id_, what_arg) {} @@ -5473,6 +5581,12 @@ class other_error : public exception return {id_, w.c_str()}; } + template::value, int> = 0> + static other_error create(exception_id id_, const std::string& what_arg, BasicJsonContext context) + { + return create(static_cast(id_), what_arg, context); + } + private: JSON_HEDLEY_NON_NULL(3) other_error(int id_, const char* what_arg) : exception(id_, what_arg) {} @@ -5499,6 +5613,36 @@ void templated_json_throw(ExceptionType exception) (void)exception; } +/*! +@brief throws because @a j does not have the type a conversion expects +@param[in] expected the expected type(s), e.g. "array" or "binary or array" +@param[in] j the value with the wrong type +@throw type_error.302 always +*/ +template +JSON_HEDLEY_NO_RETURN inline void throw_type_must_be(const char* expected, const BasicJsonType& j) +{ + static_cast(expected); // unused when JSON_NOEXCEPTION is defined + static_cast(j); + JSON_THROW(type_error::create(exception_id::type_mismatch, concat("type must be ", expected, ", but is ", j.type_name()), &j)); +} + +/*! +@brief throws because an operation is not supported for the type of @a j +@param[in] id_ the id of the type_error exception (at_wrong_type..update_wrong_type) +@param[in] operation the operation, e.g. "erase()" +@param[in] j the value the operation was called on +@throw type_error always +*/ +template +JSON_HEDLEY_NO_RETURN inline void throw_cannot_use_with(const exception_id id_, const char* operation, const BasicJsonType& j) +{ + static_cast(id_); // unused when JSON_NOEXCEPTION is defined + static_cast(operation); + static_cast(j); + JSON_THROW(type_error::create(id_, concat("cannot use ", operation, " with ", j.type_name()), &j)); +} + } // namespace detail NLOHMANN_JSON_NAMESPACE_END @@ -5591,7 +5735,7 @@ inline void from_json(const BasicJsonType& j, typename std::nullptr_t& n) { if (JSON_HEDLEY_UNLIKELY(!j.is_null())) { - JSON_THROW(type_error::create(302, concat("type must be null, but is ", j.type_name()), &j)); + throw_type_must_be("null", j); } n = nullptr; } @@ -5645,7 +5789,7 @@ void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) case value_t::binary: case value_t::discarded: default: - JSON_THROW(type_error::create(302, concat("type must be number, but is ", j.type_name()), &j)); + throw_type_must_be("number", j); } } @@ -5654,7 +5798,7 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& { if (JSON_HEDLEY_UNLIKELY(!j.is_boolean())) { - JSON_THROW(type_error::create(302, concat("type must be boolean, but is ", j.type_name()), &j)); + throw_type_must_be("boolean", j); } b = *j.template get_ptr(); } @@ -5664,7 +5808,7 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { - JSON_THROW(type_error::create(302, concat("type must be string, but is ", j.type_name()), &j)); + throw_type_must_be("string", j); } s = *j.template get_ptr(); } @@ -5680,7 +5824,7 @@ inline void from_json(const BasicJsonType& j, StringType& s) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { - JSON_THROW(type_error::create(302, concat("type must be string, but is ", j.type_name()), &j)); + throw_type_must_be("string", j); } s = *j.template get_ptr(); @@ -5726,7 +5870,7 @@ inline void from_json(const BasicJsonType& j, std::forward_list& l { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } l.clear(); std::transform(j.rbegin(), j.rend(), @@ -5743,7 +5887,7 @@ inline void from_json(const BasicJsonType& j, std::valarray& l) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } l.resize(j.size()); std::transform(j.begin(), j.end(), std::begin(l), @@ -5848,7 +5992,7 @@ void()) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } from_json_array_impl(j, arr, priority_tag<3> {}); @@ -5867,7 +6011,7 @@ auto from_json(const BasicJsonType& j, identity_tag> tag) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } return from_json_inplace_array_impl(j, tag, make_index_sequence {}); @@ -5878,7 +6022,7 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& { if (JSON_HEDLEY_UNLIKELY(!j.is_binary())) { - JSON_THROW(type_error::create(302, concat("type must be binary, but is ", j.type_name()), &j)); + throw_type_must_be("binary", j); } bin = *j.template get_ptr(); @@ -5899,7 +6043,7 @@ inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin) } else { - JSON_THROW(type_error::create(302, concat("type must be binary or array, but is ", j.type_name()), &j)); + throw_type_must_be("binary or array", j); } } @@ -5920,7 +6064,7 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) { if (JSON_HEDLEY_UNLIKELY(!j.is_object())) { - JSON_THROW(type_error::create(302, concat("type must be object, but is ", j.type_name()), &j)); + throw_type_must_be("object", j); } ConstructibleObjectType ret; @@ -5975,7 +6119,7 @@ inline void from_json(const BasicJsonType& j, ArithmeticType& val) case value_t::binary: case value_t::discarded: default: - JSON_THROW(type_error::create(302, concat("type must be number, but is ", j.type_name()), &j)); + throw_type_must_be("number", j); } } @@ -6047,7 +6191,7 @@ auto from_json(const BasicJsonType& j, TupleRelated&& t) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } return from_json_tuple_impl(j, std::forward(t), priority_tag<3> {}); @@ -6060,14 +6204,14 @@ void from_json_pair_array_to_map(const BasicJsonType& j, MapType& m) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } m.clear(); for (const auto& p : j) { if (JSON_HEDLEY_UNLIKELY(!p.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", p.type_name()), &p)); + throw_type_must_be("array", p); } m.emplace(p.at(0).template get(), p.at(1).template get()); } @@ -6134,7 +6278,7 @@ inline void from_json(const BasicJsonType& j, std_fs::path& p) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { - JSON_THROW(type_error::create(302, concat("type must be string, but is ", j.type_name()), &j)); + throw_type_must_be("string", j); } const auto& s = *j.template get_ptr(); // Checking for C++20 standard or later can be insufficient in case the @@ -7208,13 +7352,13 @@ inline void to_json(BasicJsonType& j, const EnumKeyedMap& map) BasicJsonType key = p.first; if (JSON_HEDLEY_UNLIKELY(!key.is_string())) { - JSON_THROW(type_error::create(302, concat("type must be string, but is ", key.type_name()), &key)); + throw_type_must_be("string", key); } auto& key_string = *key.template get_ptr(); if (JSON_HEDLEY_UNLIKELY(!obj.emplace(key_string, BasicJsonType(p.second)).second)) { - JSON_THROW(type_error::create(318, concat("duplicate object key '", key_string, "'"), &key)); + JSON_THROW(type_error::create(exception_id::enum_key_duplicate, concat("duplicate object key '", key_string, "'"), &key)); } } external_constructor::construct(j, std::move(obj)); @@ -7792,7 +7936,6 @@ NLOHMANN_JSON_NAMESPACE_END #include // ldexp #include // size_t #include // uint8_t, uint16_t, uint32_t, uint64_t, uintmax_t -#include // snprintf #include // memcpy #include // back_inserter #include // numeric_limits @@ -8394,7 +8537,7 @@ class wide_string_input_adapter template JSON_HEDLEY_NO_RETURN std::size_t get_elements(T* /*dest*/, std::size_t /*count*/ = 1) { - JSON_THROW(parse_error::create(112, 1, "wide string type cannot be interpreted as binary data", nullptr)); + JSON_THROW(parse_error::create(exception_id::unexpected_byte, 1, "wide string type cannot be interpreted as binary data", nullptr)); } private: @@ -8606,7 +8749,7 @@ inline file_input_adapter input_adapter(std::FILE* file) { if (file == nullptr) { - JSON_THROW(parse_error::create(101, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); + JSON_THROW(parse_error::create(exception_id::syntax_error, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); } return file_input_adapter(file); } @@ -8615,7 +8758,7 @@ inline input_stream_adapter input_adapter(std::istream& stream) { if (stream.rdbuf() == nullptr) { - JSON_THROW(parse_error::create(101, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); + JSON_THROW(parse_error::create(exception_id::syntax_error, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); } return input_stream_adapter(stream); } @@ -8640,7 +8783,7 @@ contiguous_bytes_input_adapter input_adapter(CharT b) { if (b == nullptr) { - JSON_THROW(parse_error::create(101, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); + JSON_THROW(parse_error::create(exception_id::syntax_error, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); } auto length = std::strlen(reinterpret_cast(b)); const auto* ptr = reinterpret_cast(b); @@ -12603,6 +12746,27 @@ template inline void reserve_array(ArrayType& /*arr*/, std::size_t /*len*/, priority_tag<0> /*unused*/) {} +/*! +@brief reports an object or array whose announced size exceeds max_size() + +Shared by json_sax_dom_parser and json_sax_dom_callback_parser. + +@param[in] sax the SAX parser to report the error to +@param[in] len the number of elements announced by the input, or unknown_size() +@param[in] kind "object" or "array" +@param[in] ref the object or array that was just created +@return whether parsing should continue (false after reporting out_of_range.408) +*/ +template +bool check_container_size(SAX& sax, std::size_t len, const char* kind, BasicJsonType* ref) +{ + if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref->max_size())) + { + return sax.parse_error(0, "", out_of_range::create(exception_id::container_too_large, concat("excessive ", kind, " size: ", std::to_string(len)), ref)); + } + return true; +} + #if JSON_DIAGNOSTIC_POSITIONS /*! @brief set the diagnostic positions of a value the DOM SAX parsers just stored @@ -12612,6 +12776,35 @@ befriends this struct, as the position members are private. */ struct diagnostic_positions { + /*! + @param[in,out] v the object or array whose opening brace or bracket was just read + @param[in] lexer the lexer that read it, or nullptr to leave @a v alone + */ + template + static void set_container_start(BasicJsonType& v, LexerType* lexer) + { + if (lexer) + { + // Lexer has read the first character of the container, so + // subtract 1 from the position to get the correct start position. + v.start_position = lexer->get_position() - 1; + } + } + + /*! + @param[in,out] v the object or array whose closing brace or bracket was just read + @param[in] lexer the lexer that read it, or nullptr to leave @a v alone + */ + template + static void set_container_end(BasicJsonType& v, LexerType* lexer) + { + if (lexer) + { + // Lexer's position is past the closing brace or bracket, so set that as the end position. + v.end_position = lexer->get_position(); + } + } + /*! @param[in,out] v the value that was just parsed @param[in] lexer the lexer that read it, or nullptr to leave @a v alone @@ -12776,22 +12969,11 @@ class json_sax_dom_parser ref_stack.push_back(handle_value(BasicJsonType::value_t::object)); #if JSON_DIAGNOSTIC_POSITIONS - // Manually set the start position of the object here. // Ensure this is after the call to handle_value to ensure correct start position. - if (m_lexer_ref) - { - // Lexer has read the first character of the object, so - // subtract 1 from the position to get the correct start position. - ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; - } + diagnostic_positions::set_container_start(*ref_stack.back(), m_lexer_ref); #endif - if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) - { - return parse_error(0, "", out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back())); - } - - return true; + return check_container_size(*this, len, "object", ref_stack.back()); } bool key(string_t& val) @@ -12810,11 +12992,7 @@ class json_sax_dom_parser JSON_ASSERT(ref_stack.back()->is_object()); #if JSON_DIAGNOSTIC_POSITIONS - if (m_lexer_ref) - { - // Lexer's position is past the closing brace, so set that as the end position. - ref_stack.back()->end_position = m_lexer_ref->get_position(); - } + diagnostic_positions::set_container_end(*ref_stack.back(), m_lexer_ref); #endif ref_stack.back()->set_parents(); @@ -12827,17 +13005,13 @@ class json_sax_dom_parser ref_stack.push_back(handle_value(BasicJsonType::value_t::array)); #if JSON_DIAGNOSTIC_POSITIONS - // Manually set the start position of the array here. // Ensure this is after the call to handle_value to ensure correct start position. - if (m_lexer_ref) - { - ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; - } + diagnostic_positions::set_container_start(*ref_stack.back(), m_lexer_ref); #endif - if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) + if (JSON_HEDLEY_UNLIKELY(!check_container_size(*this, len, "array", ref_stack.back()))) { - return parse_error(0, "", out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back())); + return false; } if (len != detail::unknown_size()) @@ -12854,11 +13028,7 @@ class json_sax_dom_parser JSON_ASSERT(ref_stack.back()->is_array()); #if JSON_DIAGNOSTIC_POSITIONS - if (m_lexer_ref) - { - // Lexer's position is past the closing bracket, so set that as the end position. - ref_stack.back()->end_position = m_lexer_ref->get_position(); - } + diagnostic_positions::set_container_end(*ref_stack.back(), m_lexer_ref); #endif ref_stack.back()->set_parents(); @@ -13038,21 +13208,11 @@ class json_sax_dom_callback_parser { #if JSON_DIAGNOSTIC_POSITIONS - // Manually set the start position of the object here. // Ensure this is after the call to handle_value to ensure correct start position. - if (m_lexer_ref) - { - // Lexer has read the first character of the object, so - // subtract 1 from the position to get the correct start position. - ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; - } + diagnostic_positions::set_container_start(*ref_stack.back(), m_lexer_ref); #endif - // check object limit - if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) - { - return parse_error(0, "", out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back())); - } + return check_container_size(*this, len, "object", ref_stack.back()); } return true; } @@ -13122,11 +13282,7 @@ class json_sax_dom_callback_parser { #if JSON_DIAGNOSTIC_POSITIONS - if (m_lexer_ref) - { - // Lexer's position is past the closing brace, so set that as the end position. - ref_stack.back()->end_position = m_lexer_ref->get_position(); - } + diagnostic_positions::set_container_end(*ref_stack.back(), m_lexer_ref); #endif ref_stack.back()->set_parents(); @@ -13137,13 +13293,7 @@ class json_sax_dom_callback_parser } } - JSON_ASSERT(!ref_stack.empty()); - JSON_ASSERT(!keep_stack.empty()); - JSON_ASSERT(!container_key_stack.empty()); - ref_stack.pop_back(); - keep_stack.pop_back(); - const string_t object_key = std::move(container_key_stack.back()); - container_key_stack.pop_back(); + const string_t object_key = pop_container(); if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured()) { @@ -13169,20 +13319,13 @@ class json_sax_dom_callback_parser { #if JSON_DIAGNOSTIC_POSITIONS - // Manually set the start position of the array here. // Ensure this is after the call to handle_value to ensure correct start position. - if (m_lexer_ref) - { - // Lexer has read the first character of the array, so - // subtract 1 from the position to get the correct start position. - ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; - } + diagnostic_positions::set_container_start(*ref_stack.back(), m_lexer_ref); #endif - // check array limit - if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) + if (JSON_HEDLEY_UNLIKELY(!check_container_size(*this, len, "array", ref_stack.back()))) { - return parse_error(0, "", out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back())); + return false; } if (len != detail::unknown_size()) @@ -13206,11 +13349,7 @@ class json_sax_dom_callback_parser { #if JSON_DIAGNOSTIC_POSITIONS - if (m_lexer_ref) - { - // Lexer's position is past the closing bracket, so set that as the end position. - ref_stack.back()->end_position = m_lexer_ref->get_position(); - } + diagnostic_positions::set_container_end(*ref_stack.back(), m_lexer_ref); #endif ref_stack.back()->set_parents(); @@ -13236,13 +13375,7 @@ class json_sax_dom_callback_parser } } - JSON_ASSERT(!ref_stack.empty()); - JSON_ASSERT(!keep_stack.empty()); - JSON_ASSERT(!container_key_stack.empty()); - ref_stack.pop_back(); - keep_stack.pop_back(); - const string_t object_key = std::move(container_key_stack.back()); - container_key_stack.pop_back(); + const string_t object_key = pop_container(); // remove discarded value if (!ref_stack.empty() && ref_stack.back()) @@ -13329,6 +13462,23 @@ class json_sax_dom_callback_parser return string_t{}; } + /*! + @brief leave the object or array that end_object()/end_array() just closed + @return the key the container is stored under in its parent (see + container_key_stack) + */ + string_t pop_container() + { + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(!keep_stack.empty()); + JSON_ASSERT(!container_key_stack.empty()); + ref_stack.pop_back(); + keep_stack.pop_back(); + string_t object_key = std::move(container_key_stack.back()); + container_key_stack.pop_back(); + return object_key; + } + /*! @brief remove the discarded value the callback rejected from its parent, unless it is a duplicate key's slot with a stashed previous value, in @@ -13904,8 +14054,7 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY(current != char_traits::eof())) { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(110, chars_read, - exception_message(concat("expected end of input; last byte: 0x", get_token_string()), "value"), nullptr)); + return last_byte_error(exception_id::unexpected_end_of_input, concat("expected end of input; last byte: 0x", get_token_string()), "value"); } } @@ -14027,8 +14176,7 @@ class binary_reader { if (JSON_HEDLEY_UNLIKELY(document_size < 0 || static_cast(document_size) != chars_read - document_start)) { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(112, chars_read, - exception_message(concat("document size ", std::to_string(document_size), " does not match the number of bytes read (", std::to_string(chars_read - document_start), ")"), "document"), nullptr)); + return last_byte_error(exception_id::unexpected_byte, concat("document size ", std::to_string(document_size), " does not match the number of bytes read (", std::to_string(chars_read - document_start), ")"), "document"); } return true; } @@ -14220,9 +14368,7 @@ class binary_reader { if (JSON_HEDLEY_UNLIKELY(len < 1)) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("string length must be at least 1, is ", std::to_string(len)), "string"), nullptr)); + return last_byte_error(exception_id::unexpected_byte, concat("string length must be at least 1, is ", std::to_string(len)), "string"); } if (JSON_HEDLEY_UNLIKELY(!get_string(len - static_cast(1), result))) @@ -14232,10 +14378,7 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY(get() != 0x00)) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message("BSON string is not null-terminated", - "string"), nullptr)); + return last_byte_error(exception_id::unexpected_byte, "BSON string is not null-terminated", "string"); } return check_string_utf8(result, "string"); @@ -14255,9 +14398,7 @@ class binary_reader { if (JSON_HEDLEY_UNLIKELY(len < 0)) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("byte array length cannot be negative, is ", std::to_string(len)), "binary"), nullptr)); + return last_byte_error(exception_id::unexpected_byte, concat("byte array length cannot be negative, is ", std::to_string(len)), "binary"); } // All BSON binary values have a subtype @@ -14347,11 +14488,9 @@ class binary_reader default: // anything else is not supported (yet) { - std::array cr{{}}; - static_cast((std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast(element_type))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) - const std::string cr_str{cr.data()}; + const std::string cr_str = hex_byte(static_cast(element_type)); return sax->parse_error(element_type_parse_position, cr_str, - parse_error::create(114, element_type_parse_position, concat("Unsupported BSON record type 0x", cr_str), nullptr)); + parse_error::create(exception_id::bson_unsupported_type, element_type_parse_position, concat("Unsupported BSON record type 0x", cr_str), nullptr)); } } } @@ -14675,9 +14814,7 @@ class binary_reader { if (tag_handler == cbor_tag_handler_t::error) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("invalid byte: 0x", last_token), "value"), nullptr)); + return unexpected_byte("invalid byte", "value"); } // ignore and store: the tag value is already in the head, so @@ -14697,9 +14834,7 @@ class binary_reader { case cbor_tag_handler_t::error: { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("invalid byte: 0x", last_token), "value"), nullptr)); + return unexpected_byte("invalid byte", "value"); } case cbor_tag_handler_t::ignore: @@ -14773,9 +14908,7 @@ class binary_reader default: // anything else (0xFF is handled inside the other types) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("invalid byte: 0x", last_token), "value"), nullptr)); + return unexpected_byte("invalid byte", "value"); } } } @@ -14788,10 +14921,7 @@ class binary_reader */ bool cbor_indefinite_string_error(const char* type_name, const char* context) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("indefinite-length ", type_name, - " is not allowed inside indefinite-length ", type_name, "; last byte: 0x", last_token), context), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, concat("indefinite-length ", type_name, " is not allowed inside indefinite-length ", type_name, "; last byte: 0x", get_token_string()), context); } /*! @@ -14868,9 +14998,7 @@ class binary_reader default: { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("expected length specification (0x60-0x7B)", inside_indefinite ? "" : " or indefinite string type (0x7F)", "; last byte: 0x", last_token), "string"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, concat("expected length specification (0x60-0x7B)", inside_indefinite ? "" : " or indefinite string type (0x7F)", "; last byte: 0x", get_token_string()), "string"); } } } @@ -15000,9 +15128,7 @@ class binary_reader break; } - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("only string keys are supported, but found ", found, "; last byte: 0x", last_token), "object key"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, concat("only string keys are supported, but found ", found, "; last byte: 0x", get_token_string()), "object key"); } /*! @@ -15083,9 +15209,7 @@ class binary_reader default: { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("expected length specification (0x40-0x5B)", inside_indefinite ? "" : " or indefinite binary array type (0x5F)", "; last byte: 0x", last_token), "binary"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, concat("expected length specification (0x40-0x5B)", inside_indefinite ? "" : " or indefinite binary array type (0x5F)", "; last byte: 0x", get_token_string()), "binary"); } } } @@ -15231,7 +15355,7 @@ class binary_reader { if (JSON_HEDLEY_UNLIKELY(!value_in_range_of(len) || len == detail::unknown_size())) { - return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large, exception_message(concat("excessive ", context, " size"), "size"), nullptr)); } result = conditional_static_cast(len); @@ -15719,9 +15843,7 @@ class binary_reader 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(concat("invalid byte: 0x", last_token), "value"), nullptr)); + return unexpected_byte("invalid byte", "value"); } } } @@ -15802,9 +15924,7 @@ class binary_reader default: { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0x", last_token), "string"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, concat("expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0x", get_token_string()), "string"); } } } @@ -15896,9 +16016,7 @@ class binary_reader break; } - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("only string keys are supported, but found ", found, "; last byte: 0x", last_token), "object key"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, concat("only string keys are supported, but found ", found, "; last byte: 0x", get_token_string()), "object key"); } /*! @@ -16207,8 +16325,7 @@ class binary_reader { if (JSON_HEDLEY_UNLIKELY(len < 0)) { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, - exception_message("string length must not be negative", "string"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, "string length must not be negative", "string"); } return true; } @@ -16308,18 +16425,7 @@ class binary_reader 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); 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(message, "string"), nullptr)); + return length_type_error("", "string"); } /*! @@ -16404,12 +16510,11 @@ class binary_reader } if (JSON_HEDLEY_UNLIKELY(number < 0)) { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, - exception_message("count in an optimized container must be positive", "size"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, "count in an optimized container must be positive", "size"); } if (JSON_HEDLEY_UNLIKELY(!value_in_range_of(number))) { - return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large, exception_message("integer value overflow", "size"), nullptr)); } result = static_cast(number); // NOLINT(bugprone-signed-char-misuse,cert-str34-c): number is not a char @@ -16507,7 +16612,7 @@ class binary_reader } if (!value_in_range_of(number)) { - return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large, exception_message("integer value overflow", "size"), nullptr)); } result = detail::conditional_static_cast(number); @@ -16522,7 +16627,7 @@ class binary_reader } if (is_ndarray) // ndarray dimensional vector can only contain integers and cannot embed another array { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, exception_message("ndarray dimensional vector is not allowed", "size"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, "ndarray dimensional vector is not allowed", "size"); } std::vector dim; if (JSON_HEDLEY_UNLIKELY(!get_ubjson_ndarray_size(dim))) @@ -16558,9 +16663,7 @@ class binary_reader const char* type_name = bjd_type_name(ndarray_dtype); if (JSON_HEDLEY_UNLIKELY(type_name == nullptr)) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message("invalid byte: 0x" + last_token, "type"), nullptr)); + return unexpected_byte("invalid byte", "type"); } string_t type_key = "_ArrayType_"; @@ -16588,7 +16691,7 @@ class binary_reader // or SIZE_MAX. if (JSON_HEDLEY_UNLIKELY(result > (std::numeric_limits::max)() / i)) { - return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, exception_message("excessive ndarray size caused overflow", "size"), nullptr)); + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large, exception_message("excessive ndarray size caused overflow", "size"), nullptr)); } result *= i; // the pre-check above already rules out result becoming 0 @@ -16597,7 +16700,7 @@ class binary_reader // unknown-size container (see get_ubjson_size_type()) if (result == npos) { - return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, exception_message("excessive ndarray size caused overflow", "size"), nullptr)); + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large, exception_message("excessive ndarray size caused overflow", "size"), nullptr)); } if (JSON_HEDLEY_UNLIKELY(!emit_unsigned(i))) { @@ -16614,18 +16717,7 @@ class binary_reader 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(message, "size"), nullptr)); + return length_type_error(" after '#'", "size"); } /*! @@ -16658,9 +16750,7 @@ class binary_reader if (input_format == input_format_t::bjdata && JSON_HEDLEY_UNLIKELY(is_bjd_excluded_optimized_type(result.second))) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("marker 0x", last_token, " is not a permitted optimized array type"), "type"), nullptr)); + return last_byte_error(exception_id::unexpected_byte, concat("marker 0x", get_token_string(), " is not a permitted optimized array type"), "type"); } if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("type"))) @@ -16675,9 +16765,7 @@ class binary_reader { return false; } - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("expected '#' after type information; last byte: 0x", last_token), "size"), nullptr)); + return last_byte_error(exception_id::unexpected_byte, concat("expected '#' after type information; last byte: 0x", get_token_string()), "size"); } const bool is_error = get_ubjson_size_value(result.first, is_ndarray, 0, result.second); @@ -16696,8 +16784,7 @@ class binary_reader const bool is_error = get_ubjson_size_value(result.first, is_ndarray); if (input_format == input_format_t::bjdata && is_ndarray && !inside_ndarray) { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(112, chars_read, - exception_message("ndarray requires both type and size", "size"), nullptr)); + return last_byte_error(exception_id::unexpected_byte, "ndarray requires both type and size", "size"); } return is_error; } @@ -16829,9 +16916,7 @@ class binary_reader } if (JSON_HEDLEY_UNLIKELY(current > 127)) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("byte after 'C' must be in range 0x00..0x7F; last byte: 0x", last_token), "char"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, concat("byte after 'C' must be in range 0x00..0x7F; last byte: 0x", get_token_string()), "char"); } string_t s(1, static_cast(current)); return sax->string(s); @@ -16852,8 +16937,7 @@ class binary_reader default: // anything else break; } - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message("invalid byte: 0x" + last_token, "value"), nullptr)); + return unexpected_byte("invalid byte", "value"); } /*! @@ -16916,7 +17000,7 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY((size_and_type.second == 'Z' || size_and_type.second == 'T' || size_and_type.second == 'F') && size_and_type.first > max_valueless_container_size)) { - return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large, exception_message("excessive array size", "size"), nullptr)); } @@ -16952,9 +17036,7 @@ class binary_reader // do not accept ND-array size in objects in BJData if (input_format == input_format_t::bjdata && size_and_type.first != npos && (size_and_type.second & (1 << 8)) != 0) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message("BJData object does not support ND-array size in optimized format", "object"), nullptr)); + return last_byte_error(exception_id::unexpected_byte, "BJData object does not support ND-array size in optimized format", "object"); } if (size_and_type.first != npos) @@ -16991,7 +17073,7 @@ class binary_reader // the lexer would stop at a NUL and accept the digits before it if (JSON_HEDLEY_UNLIKELY(current == '\0')) { - return sax->parse_error(chars_read, "00", parse_error::create(115, chars_read, + return sax->parse_error(chars_read, "00", parse_error::create(exception_id::invalid_high_precision_number, chars_read, exception_message("invalid number text; last byte: 0x00", "high-precision number"), nullptr)); } number_vector.push_back(static_cast(current)); @@ -17008,7 +17090,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, + return sax->parse_error(chars_read, number_string, parse_error::create(exception_id::invalid_high_precision_number, chars_read, exception_message(concat("invalid number text: ", number_lexer.get_token_string()), "high-precision number"), nullptr)); } @@ -17026,7 +17108,7 @@ class binary_reader return sax->parse_error( chars_read, number_string, - out_of_range::create(406, concat("number overflow parsing '", number_string, '\''), nullptr)); + out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", number_string, '\''), nullptr)); } // number_string is a std::string, while the SAX interface takes a // string_t; convert explicitly, as the two are only implicitly @@ -17048,7 +17130,7 @@ class binary_reader case token_type::end_of_input: case token_type::literal_or_value: default: - return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read, + return sax->parse_error(chars_read, number_string, parse_error::create(exception_id::invalid_high_precision_number, chars_read, exception_message(concat("invalid number text: ", number_lexer.get_token_string()), "high-precision number"), nullptr)); } } @@ -17107,20 +17189,6 @@ class binary_reader return 0x80 <= c && c <= 0xBF; } - /*! - @brief report a parse error at the last read byte - - @param[in] detail a detailed error message - @param[in] context further context information - @return false - */ - bool bon8_error(const std::string& detail, const char* context) - { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat(detail, ": 0x", last_token), context), nullptr)); - } - /*! @brief read a BON8 value and everything nested inside it @@ -17336,7 +17404,7 @@ class binary_reader } // 0xFE: end of container where a value is expected - return bon8_error("invalid byte", "value"); + return unexpected_byte("invalid byte", "value"); } /*! @@ -17463,7 +17531,7 @@ class binary_reader current = byte; } - return bon8_error("expected a string; last byte", "key"); + return unexpected_byte("expected a string; last byte", "key"); } /*! @@ -17586,7 +17654,7 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY(!valid_second)) { - return bon8_error("invalid UTF-8 byte", "string"); + return unexpected_byte("invalid UTF-8 byte", "string"); } result.push_back(static_cast(byte)); @@ -17600,7 +17668,7 @@ class binary_reader } if (JSON_HEDLEY_UNLIKELY(!is_bon8_continuation(current))) { - return bon8_error("invalid UTF-8 byte", "string"); + return unexpected_byte("invalid UTF-8 byte", "string"); } result.push_back(static_cast(current)); } @@ -17645,7 +17713,7 @@ class binary_reader { // in case of failure, advance position by 1 to report the failing location ++chars_read; - sax->parse_error(chars_read, "", parse_error::create(110, chars_read, exception_message("unexpected end of input", context), nullptr)); + sax->parse_error(chars_read, "", parse_error::create(exception_id::unexpected_end_of_input, chars_read, exception_message("unexpected end of input", context), nullptr)); return false; } return true; @@ -17799,7 +17867,7 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY(std::isfinite(number) && !std::isfinite(result))) { return sax->parse_error(chars_read, get_token_string(), - out_of_range::create(406, exception_message("number overflow", "value"), nullptr)); + out_of_range::create(exception_id::number_overflow, exception_message("number overflow", "value"), nullptr)); } return sax->number_float(result, ""); } @@ -17919,9 +17987,7 @@ class binary_reader if (error_handler == error_handler_t::strict) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message("invalid string: ill-formed UTF-8 byte", context), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, "invalid string: ill-formed UTF-8 byte", context); } result = sanitize_utf8(result, error_handler); @@ -18017,19 +18083,53 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY(current == char_traits::eof())) { return sax->parse_error(chars_read, "", - parse_error::create(110, chars_read, exception_message("unexpected end of input", context), nullptr)); + parse_error::create(exception_id::unexpected_end_of_input, chars_read, exception_message("unexpected end of input", context), nullptr)); } return true; } + /*! + @brief reports a parse error at the last read byte + @param[in] id_ the id of the parse_error exception + @param[in] detail a detailed error message + @param[in] context further context information + @return the result of the SAX parser's parse_error() + */ + bool last_byte_error(const exception_id id_, const std::string& detail, const std::string& context) const + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(id_, chars_read, exception_message(detail, context), nullptr)); + } + + /*! + @brief reports the last read byte as unexpected (parse_error.112) + @param[in] detail what is wrong with the byte, e.g. "invalid byte" + @param[in] context further context information + @return the result of the SAX parser's parse_error() + */ + bool unexpected_byte(const char* detail, const std::string& context) const + { + return last_byte_error(exception_id::unexpected_byte, concat(detail, ": 0x", get_token_string()), context); + } + + /*! + @brief reports that the last read byte is not a UBJSON/BJData length type (parse_error.113) + @param[in] position where the length type was expected, e.g. " after '#'" + @param[in] context further context information + @return the result of the SAX parser's parse_error() + */ + bool length_type_error(const char* position, const char* context) const + { + const char* types = input_format == input_format_t::bjdata ? "U, i, u, I, m, l, M, L" : "U, i, I, l, L"; + return last_byte_error(exception_id::invalid_string_or_size, + concat("expected length type specification (", types, ")", position, "; last byte: 0x", get_token_string()), context); + } + /*! @return a string representation of the last read byte */ std::string get_token_string() const { - std::array cr{{}}; - static_cast((std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast(current))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) - return std::string{cr.data()}; + return hex_byte(static_cast(current)); } /*! @@ -18346,9 +18446,7 @@ class parser // strict mode: next byte must be EOF if (get_token() != token_type::end_of_input) { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), nullptr)); + return syntax_error(*sax, exception_message(token_type::end_of_input, "value")); } } else @@ -18387,10 +18485,7 @@ class parser // in strict mode, input must be completely read if (get_token() != token_type::end_of_input) { - sdp.parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), - exception_message(token_type::end_of_input, "value"), nullptr)); + syntax_error(sdp, exception_message(token_type::end_of_input, "value")); } } else @@ -18440,9 +18535,7 @@ class parser // parse key if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string)) { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), nullptr)); + return syntax_error(*sax, exception_message(token_type::value_string, "object key")); } if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) { @@ -18452,9 +18545,7 @@ class parser // parse separator (:) if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator))) { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), nullptr)); + return syntax_error(*sax, exception_message(token_type::name_separator, "object separator")); } // remember we are now inside an object @@ -18497,7 +18588,7 @@ class parser { return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), - out_of_range::create(406, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr)); + out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr)); } if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string()))) @@ -18565,23 +18656,16 @@ class parser case token_type::parse_error: { // using "uninitialized" to avoid an "expected" message - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::uninitialized, "value"), nullptr)); + return syntax_error(*sax, exception_message(token_type::uninitialized, "value")); } case token_type::end_of_input: { if (JSON_HEDLEY_UNLIKELY(m_lexer.get_position().chars_read_total == 1)) { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), - "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); + return syntax_error(*sax, "attempting to parse an empty input; check that your input string or stream contains the expected JSON"); } - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), nullptr)); + return syntax_error(*sax, exception_message(token_type::literal_or_value, "value")); } case token_type::uninitialized: case token_type::end_array: @@ -18591,9 +18675,7 @@ class parser case token_type::literal_or_value: default: // the last token was unexpected { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), nullptr)); + return syntax_error(*sax, exception_message(token_type::literal_or_value, "value")); } } } @@ -18644,9 +18726,7 @@ class parser continue; } - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_array, "array"), nullptr)); + return syntax_error(*sax, exception_message(token_type::end_array, "array")); } // states.back() is false -> object @@ -18663,9 +18743,7 @@ class parser // parse key if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string)) { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), nullptr)); + return syntax_error(*sax, exception_message(token_type::value_string, "object key")); } if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) @@ -18676,9 +18754,7 @@ class parser // parse separator (:) if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator))) { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), nullptr)); + return syntax_error(*sax, exception_message(token_type::name_separator, "object separator")); } // parse values @@ -18706,9 +18782,7 @@ class parser continue; } - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_object, "object"), nullptr)); + return syntax_error(*sax, exception_message(token_type::end_object, "object")); } } @@ -18725,6 +18799,21 @@ class parser return (last_token = m_lexer.scan_expecting(expected_type)) == expected_type; } + /*! + @brief reports a syntax error at the current token (parse_error.101) + @param[in] sax the SAX parser to report the error to + @param[in] message the error message + @return the result of the SAX parser's parse_error() + */ + template + bool syntax_error(SAX& sax, const std::string& message) + { + // MSVC 2015 reports C4100 (unreferenced parameter) when SAX::parse_error is static + static_cast(sax); + return sax.parse_error(m_lexer.get_position(), m_lexer.get_token_string(), + parse_error::create(exception_id::syntax_error, m_lexer.get_position(), message, nullptr)); + } + std::string exception_message(const token_type expected, const std::string& context) { std::string error_msg = "syntax error "; @@ -19231,6 +19320,12 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci } } + /// @throw invalid_iterator.214 always + JSON_HEDLEY_NO_RETURN void throw_cannot_get_value() const + { + JSON_THROW(invalid_iterator::create(exception_id::iterator_value_unavailable, "cannot get value", m_object)); + } + public: /*! @brief return a reference to the value pointed to by the iterator @@ -19255,7 +19350,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci } case value_t::null: - JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); + throw_cannot_get_value(); case value_t::string: case value_t::boolean: @@ -19271,7 +19366,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci return *m_object; } - JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); + throw_cannot_get_value(); } } } @@ -19313,7 +19408,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci return m_object; } - JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); + throw_cannot_get_value(); } } } @@ -19430,7 +19525,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci // if objects are not the same, the comparison is undefined if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) { - JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers", m_object)); + JSON_THROW(invalid_iterator::create(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object)); } // value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493 @@ -19479,7 +19574,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci // if objects are not the same, the comparison is undefined if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) { - JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers", m_object)); + JSON_THROW(invalid_iterator::create(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object)); } // value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493 @@ -19492,7 +19587,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci switch (m_object->m_data.m_type) { case value_t::object: - JSON_THROW(invalid_iterator::create(213, "cannot compare order of object iterators", m_object)); + JSON_THROW(invalid_iterator::create(exception_id::iterator_order_on_object, "cannot compare order of object iterators", m_object)); case value_t::array: return (m_it.array_iterator < other.m_it.array_iterator); @@ -19548,7 +19643,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci switch (m_object->m_data.m_type) { case value_t::object: - JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators", m_object)); + JSON_THROW(invalid_iterator::create(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object)); case value_t::array: { @@ -19627,7 +19722,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci switch (m_object->m_data.m_type) { case value_t::object: - JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators", m_object)); + JSON_THROW(invalid_iterator::create(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object)); case value_t::array: return m_it.array_iterator - other.m_it.array_iterator; @@ -19656,13 +19751,13 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci switch (m_object->m_data.m_type) { case value_t::object: - JSON_THROW(invalid_iterator::create(208, "cannot use operator[] for object iterators", m_object)); + JSON_THROW(invalid_iterator::create(exception_id::iterator_subscript_on_object, "cannot use operator[] for object iterators", m_object)); case value_t::array: return *std::next(m_it.array_iterator, n); case value_t::null: - JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); + throw_cannot_get_value(); case value_t::string: case value_t::boolean: @@ -19678,7 +19773,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci return *m_object; } - JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); + throw_cannot_get_value(); } } } @@ -19696,7 +19791,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci return m_it.object_iterator->first; } - JSON_THROW(invalid_iterator::create(207, "cannot use key() for non-object iterators", m_object)); + JSON_THROW(invalid_iterator::create(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators", m_object)); } /*! @@ -20084,7 +20179,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(empty())) { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + throw_no_parent(); } reference_tokens.erase(reference_tokens.begin()); @@ -20096,7 +20191,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(empty())) { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + throw_no_parent(); } return reference_tokens.front(); @@ -20122,7 +20217,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(empty())) { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + throw_no_parent(); } reference_tokens.pop_back(); @@ -20134,7 +20229,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(empty())) { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + throw_no_parent(); } return reference_tokens.back(); @@ -20162,6 +20257,21 @@ class json_pointer } private: + /// @throw out_of_range.405 always + JSON_HEDLEY_NO_RETURN static void throw_no_parent() + { + JSON_THROW(detail::out_of_range::create(detail::exception_id::patch_on_root, "JSON pointer has no parent", nullptr)); + } + + /// @throw out_of_range.404 always + template + JSON_HEDLEY_NO_RETURN static void throw_unresolved(const string_t& reference_token, BasicJsonContext context) + { + static_cast(reference_token); // unused when JSON_NOEXCEPTION is defined + static_cast(context); + JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_unresolved, detail::concat("unresolved reference token '", reference_token, "'"), context)); + } + /*! @brief result of @ref parse_array_index @@ -20247,13 +20357,13 @@ class json_pointer // the branches differ in their messages, not after JSON_THROW's expansion // NOLINTNEXTLINE(bugprone-branch-clone) case array_index_status::leading_zero: - JSON_THROW(detail::parse_error::create(106, 0, detail::concat("array index '", s, "' must not begin with '0'"), nullptr)); + JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_leading_zero, 0, detail::concat("array index '", s, "' must not begin with '0'"), nullptr)); case array_index_status::not_a_number: - JSON_THROW(detail::parse_error::create(109, 0, detail::concat("array index '", s, "' is not a number"), nullptr)); + JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_not_a_number, 0, detail::concat("array index '", s, "' is not a number"), nullptr)); case array_index_status::unresolved: - JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", s, "'"), nullptr)); + throw_unresolved(s, nullptr); case array_index_status::exceeds_size_type: - JSON_THROW(detail::out_of_range::create(410, detail::concat("array index ", s, " exceeds size_type"), nullptr)); + JSON_THROW(detail::out_of_range::create(detail::exception_id::value_out_of_range, detail::concat("array index ", s, " exceeds size_type"), nullptr)); case array_index_status::ok: default: break; @@ -20267,7 +20377,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(empty())) { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + throw_no_parent(); } json_pointer result = *this; @@ -20404,7 +20514,7 @@ class json_pointer case detail::value_t::binary: case detail::value_t::discarded: default: - JSON_THROW(detail::type_error::create(313, "invalid value to unflatten", &j)); + JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_invalid_value, "invalid value to unflatten", &j)); } id = tree.find_child(id, reference_token); @@ -20487,7 +20597,7 @@ class json_pointer case detail::value_t::binary: case detail::value_t::discarded: default: - JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr)); + throw_unresolved(reference_token, ptr); } } @@ -20519,7 +20629,7 @@ class json_pointer if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) { // "-" always fails the range check - JSON_THROW(detail::out_of_range::create(402, detail::concat( + JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat( "array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr)); } @@ -20528,7 +20638,7 @@ class json_pointer // Bounds check before access to avoid exception with JSON_NOEXCEPTION if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size())) { - JSON_THROW(detail::out_of_range::create(401, detail::concat( + JSON_THROW(detail::out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat( "array index ", std::to_string(idx), " is out of range"), ptr)); } ptr = &ptr->operator[](idx); @@ -20544,7 +20654,7 @@ class json_pointer case detail::value_t::binary: case detail::value_t::discarded: default: - JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr)); + throw_unresolved(reference_token, ptr); } } @@ -20588,7 +20698,7 @@ class json_pointer if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) { // "-" cannot be used for const access - JSON_THROW(detail::out_of_range::create(402, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr)); + JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr)); } // use unchecked array access; the const operator[] @@ -20606,7 +20716,7 @@ class json_pointer case detail::value_t::binary: case detail::value_t::discarded: default: - JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr)); + throw_unresolved(reference_token, ptr); } } @@ -20661,9 +20771,9 @@ class json_pointer // the branches differ in their messages, not after JSON_THROW's expansion // NOLINTNEXTLINE(bugprone-branch-clone) case array_index_status::leading_zero: - JSON_THROW(detail::parse_error::create(106, 0, detail::concat("array index '", reference_token, "' must not begin with '0'"), nullptr)); + JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_leading_zero, 0, detail::concat("array index '", reference_token, "' must not begin with '0'"), nullptr)); case array_index_status::not_a_number: - JSON_THROW(detail::parse_error::create(109, 0, detail::concat("array index '", reference_token, "' is not a number"), nullptr)); + JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_not_a_number, 0, detail::concat("array index '", reference_token, "' is not a number"), nullptr)); case array_index_status::unresolved: case array_index_status::exceeds_size_type: return nullptr; @@ -20790,7 +20900,7 @@ class json_pointer // check if a nonempty reference string begins with slash if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/')) { - JSON_THROW(detail::parse_error::create(107, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", reference_string, "'"), nullptr)); + JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_missing_slash, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", reference_string, "'"), nullptr)); } // extract the reference tokens: @@ -20826,7 +20936,7 @@ class json_pointer (reference_token[pos + 1] != '0' && reference_token[pos + 1] != '1'))) { - JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'", nullptr)); + JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr)); } } @@ -20990,7 +21100,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(!value.is_object())) { - JSON_THROW(detail::type_error::create(314, "only objects can be unflattened", &value)); + JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_not_object, "only objects can be unflattened", &value)); } BasicJsonType result; @@ -21014,7 +21124,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive())) { - JSON_THROW(detail::type_error::create(315, "values in object must be primitive", &element.second)); + JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_value_not_primitive, "values in object must be primitive", &element.second)); } // Assign the value to the reference pointed to by JSON pointer. Note @@ -21774,7 +21884,7 @@ class binary_writer case value_t::discarded: default: { - JSON_THROW(type_error::create(317, concat("to serialize to BSON, top-level type must be object, but is ", j.type_name()), &j)); + JSON_THROW(type_error::create(exception_id::type_not_serializable, concat("to serialize to BSON, top-level type must be object, but is ", j.type_name()), &j)); } } } @@ -21965,7 +22075,7 @@ class binary_writer { if (JSON_HEDLEY_UNLIKELY(!value_in_range_of(length))) { - JSON_THROW(out_of_range::create(412, concat("MessagePack length ", std::to_string(length), " exceeds maximum of ", std::to_string((std::numeric_limits::max)())), &j)); + JSON_THROW(out_of_range::create(exception_id::length_too_large, concat("MessagePack length ", std::to_string(length), " exceeds maximum of ", std::to_string((std::numeric_limits::max)())), &j)); } static_cast(j); @@ -22192,7 +22302,7 @@ class binary_writer { if (JSON_HEDLEY_UNLIKELY(j.m_data.m_value.binary->subtype() > (std::numeric_limits::max)())) { - JSON_THROW(out_of_range::create(415, concat("subtype ", std::to_string(j.m_data.m_value.binary->subtype()), " is too large for the MessagePack ext type (max 255)"), &j)); + JSON_THROW(out_of_range::create(exception_id::subtype_out_of_range, concat("subtype ", std::to_string(j.m_data.m_value.binary->subtype()), " is too large for the MessagePack ext type (max 255)"), &j)); } write_number(static_cast(j.m_data.m_value.binary->subtype())); @@ -22347,7 +22457,7 @@ class binary_writer { if (!use_count) { - JSON_THROW(other_error::create(502, "use_type requires use_size = true", &j)); + JSON_THROW(other_error::create(exception_id::size_marker_required, "use_type requires use_size = true", &j)); } oa.write_character(to_char_type('$')); oa.write_character(bjdata_draft3 ? 'B' : 'U'); @@ -22448,7 +22558,7 @@ class binary_writer { static_cast(j); // unused when JSON_NOEXCEPTION is defined static_cast(format_name); - JSON_THROW(type_error::create(321, concat("cannot serialize discarded value to ", format_name), &j)); + JSON_THROW(type_error::create(exception_id::discarded_value_used, concat("cannot serialize discarded value to ", format_name), &j)); } void write_msgpack_array_prefix(const std::size_t N, const BasicJsonType& j) @@ -22708,7 +22818,7 @@ class binary_writer { if (!use_count) { - JSON_THROW(other_error::create(502, "use_type requires use_size = true", &j)); + JSON_THROW(other_error::create(exception_id::size_marker_required, "use_type requires use_size = true", &j)); } const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata); const bool same_prefix = std::all_of(j.begin() + 1, j.end(), @@ -22758,7 +22868,7 @@ class binary_writer { if (!use_count) { - JSON_THROW(other_error::create(502, "use_type requires use_size = true", &j)); + JSON_THROW(other_error::create(exception_id::size_marker_required, "use_type requires use_size = true", &j)); } const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata); const bool same_prefix = std::all_of(j.begin(), j.end(), @@ -22975,7 +23085,7 @@ class binary_writer const auto it = name.find(static_cast(0)); if (JSON_HEDLEY_UNLIKELY(it != BasicJsonType::string_t::npos)) { - JSON_THROW(out_of_range::create(409, concat("BSON key cannot contain code point U+0000 (at byte ", std::to_string(it), ")"), &j)); + JSON_THROW(out_of_range::create(exception_id::bson_key_with_null, concat("BSON key cannot contain code point U+0000 (at byte ", std::to_string(it), ")"), &j)); } string_t storage; @@ -22993,7 +23103,7 @@ class binary_writer { if (JSON_HEDLEY_UNLIKELY(!value_in_range_of(size))) { - JSON_THROW(out_of_range::create(412, concat("BSON length ", std::to_string(size), " exceeds maximum of ", std::to_string((std::numeric_limits::max)())), nullptr)); + JSON_THROW(out_of_range::create(exception_id::length_too_large, concat("BSON length ", std::to_string(size), " exceeds maximum of ", std::to_string((std::numeric_limits::max)())), nullptr)); } return static_cast(size); @@ -23179,7 +23289,7 @@ class binary_writer if (value.has_subtype() && JSON_HEDLEY_UNLIKELY(value.subtype() > (std::numeric_limits::max)())) { - JSON_THROW(out_of_range::create(415, concat("subtype ", std::to_string(value.subtype()), " is too large for the BSON binary subtype (max 255)"), &j)); + JSON_THROW(out_of_range::create(exception_id::subtype_out_of_range, concat("subtype ", std::to_string(value.subtype()), " is too large for the BSON binary subtype (max 255)"), &j)); } return sizeof(std::int32_t) + value.size() + 1ul; @@ -24208,7 +24318,7 @@ class binary_writer { if (j.m_data.m_value.number_unsigned > static_cast((std::numeric_limits::max)())) { - JSON_THROW(out_of_range::create(407, concat("integer number ", std::to_string(j.m_data.m_value.number_unsigned), " cannot be represented by BON8 as it does not fit int64"), &j)); + JSON_THROW(out_of_range::create(exception_id::integer_too_large, concat("integer number ", std::to_string(j.m_data.m_value.number_unsigned), " cannot be represented by BON8 as it does not fit int64"), &j)); } write_bon8_integer(static_cast(j.m_data.m_value.number_unsigned)); string_open = false; @@ -24361,7 +24471,7 @@ class binary_writer const std::size_t valid = valid_utf8_prefix(data, s.size()); if (JSON_HEDLEY_UNLIKELY(valid != s.size())) { - JSON_THROW(type_error::create(316, concat("invalid UTF-8 byte at index ", std::to_string(valid), ": 0x", detail::hex_byte(data[valid])), &context)); + JSON_THROW(type_error::create(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(valid), ": 0x", detail::hex_byte(data[valid])), &context)); } } @@ -26785,7 +26895,7 @@ class serializer { case error_handler_t::strict: { - JSON_THROW(type_error::create(316, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", detail::hex_byte(byte)), nullptr)); + JSON_THROW(type_error::create(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", detail::hex_byte(byte)), nullptr)); } case error_handler_t::ignore: @@ -26918,7 +27028,7 @@ class serializer { case error_handler_t::strict: { - JSON_THROW(type_error::create(316, concat("incomplete UTF-8 string; last byte: 0x", detail::hex_byte(static_cast(s[s.size() - 1]))), nullptr)); + JSON_THROW(type_error::create(exception_id::invalid_utf8, concat("incomplete UTF-8 string; last byte: 0x", detail::hex_byte(static_cast(s[s.size() - 1]))), nullptr)); } case error_handler_t::ignore: @@ -27623,6 +27733,19 @@ private: return self.end(); } + /// @brief shared implementation of the const and non-const at() overloads + /// @throw std::out_of_range if @a key is not found + template + static auto at_impl(Self& self, const KeyType& key) -> decltype((self.begin()->second)) + { + const auto it = find_impl(self, key); + if (it == self.end()) + { + JSON_THROW(std::out_of_range("key not found")); + } + return it->second; + } + /// @brief remove the entry @a it points to, preserving order /// @note keys are not movable, so the tail is destroyed and re-constructed in place void erase_at(iterator it) @@ -27689,46 +27812,26 @@ public: T& at(const key_type& key) { - const auto it = find_impl(*this, key); - if (it == this->end()) - { - JSON_THROW(std::out_of_range("key not found")); - } - return it->second; + return at_impl(*this, key); } template::value, int> = 0> T & at(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward) { - const auto it = find_impl(*this, key); - if (it == this->end()) - { - JSON_THROW(std::out_of_range("key not found")); - } - return it->second; + return at_impl(*this, key); } const T& at(const key_type& key) const { - const auto it = find_impl(*this, key); - if (it == this->end()) - { - JSON_THROW(std::out_of_range("key not found")); - } - return it->second; + return at_impl(*this, key); } template::value, int> = 0> const T & at(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward) { - const auto it = find_impl(*this, key); - if (it == this->end()) - { - JSON_THROW(std::out_of_range("key not found")); - } - return it->second; + return at_impl(*this, key); } size_type erase(const key_type& key) @@ -28509,7 +28612,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec object = nullptr; // silence warning, see #821 if (JSON_HEDLEY_UNLIKELY(t == value_t::null)) { - JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.12.0", nullptr)); // LCOV_EXCL_LINE + JSON_THROW(other_error::create(detail::exception_id::internal_error, "961c151d2e87f2686a955a9be24d316f1362bf21 3.12.0", nullptr)); // LCOV_EXCL_LINE } break; } @@ -30157,7 +30260,7 @@ public: // if an object is wanted but impossible, throw an exception if (JSON_HEDLEY_UNLIKELY(manual_type == value_t::object && !is_an_object)) { - JSON_THROW(type_error::create(301, "cannot create object from initializer list", nullptr)); + JSON_THROW(type_error::create(detail::exception_id::object_from_non_pairs, "cannot create object from initializer list", nullptr)); } } @@ -30277,7 +30380,7 @@ public: // make sure the iterator fits the current value if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) { - JSON_THROW(invalid_iterator::create(201, "iterators are not compatible", nullptr)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterators_incompatible, "iterators are not compatible", nullptr)); } // copy type from the first iterator @@ -30296,7 +30399,7 @@ public: if (JSON_HEDLEY_UNLIKELY(!first.m_it.primitive_iterator.is_begin() || !last.m_it.primitive_iterator.is_end())) { - JSON_THROW(invalid_iterator::create(204, "iterators out of range", first.m_object)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_range_out_of_range, "iterators out of range", first.m_object)); } break; } @@ -30364,7 +30467,7 @@ public: case value_t::null: case value_t::discarded: default: - JSON_THROW(invalid_iterator::create(206, detail::concat("cannot construct with iterators from ", first.m_object->type_name()), first.m_object)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_range_of_null, detail::concat("cannot construct with iterators from ", first.m_object->type_name()), first.m_object)); } set_parents(); @@ -30752,7 +30855,7 @@ public: return *ptr; } - JSON_THROW(type_error::create(303, detail::concat("incompatible ReferenceType for get_ref, actual type is ", obj.type_name()), &obj)); + JSON_THROW(type_error::create(detail::exception_id::incompatible_reference_type, detail::concat("incompatible ReferenceType for get_ref, actual type is ", obj.type_name()), &obj)); } public: @@ -31136,7 +31239,7 @@ public: { if (!is_binary()) { - JSON_THROW(type_error::create(302, detail::concat("type must be binary, but is ", type_name()), this)); + detail::throw_type_must_be("binary", *this); } return *get_ptr(); @@ -31148,7 +31251,7 @@ public: { if (!is_binary()) { - JSON_THROW(type_error::create(302, detail::concat("type must be binary, but is ", type_name()), this)); + detail::throw_type_must_be("binary", *this); } return *get_ptr(); @@ -31190,7 +31293,7 @@ public: // at only works for objects if (JSON_HEDLEY_UNLIKELY(!j.is_object())) { - JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", j.type_name()), &j)); + detail::throw_cannot_use_with(detail::exception_id::at_wrong_type, "at()", j); } auto it = object_lookup(j, std::forward(key)); @@ -31200,7 +31303,7 @@ public: // std::map or ordered_map) never moves from its argument, so key is still // valid here regardless of whether KeyType was deduced as an rvalue reference // NOLINTNEXTLINE(bugprone-use-after-move,hicpp-invalid-access-moved) - JSON_THROW(out_of_range::create(403, detail::concat("key '", string_t(key), "' not found"), &j)); + JSON_THROW(out_of_range::create(detail::exception_id::key_not_found, detail::concat("key '", string_t(key), "' not found"), &j)); } return it->second; } @@ -31215,12 +31318,12 @@ public: // at only works for arrays if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", j.type_name()), &j)); + detail::throw_cannot_use_with(detail::exception_id::at_wrong_type, "at()", j); } if (JSON_HEDLEY_UNLIKELY(idx >= j.m_data.m_value.array->size())) { - JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), &j)); + JSON_THROW(out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat("array index ", std::to_string(idx), " is out of range"), &j)); } return (*j.m_data.m_value.array)[idx]; @@ -31253,6 +31356,15 @@ public: m_data.m_type = value_t::object; } + /// @brief throws because operator[] is not supported for the type of this value + /// @param[in] argument the kind of the operator's argument, "numeric" or "string" + /// @throw type_error.305 always + JSON_HEDLEY_NO_RETURN void throw_subscript_wrong_type(const char* argument) const + { + detail::throw_cannot_use_with(detail::exception_id::subscript_wrong_type, + detail::concat("operator[] with a ", argument, " argument").c_str(), *this); + } + public: //////////////////// // element access // @@ -31357,7 +31469,7 @@ public: return m_data.m_value.array->operator[](idx); } - JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a numeric argument with ", type_name()), this)); + throw_subscript_wrong_type("numeric"); } /// @brief access specified array element @@ -31371,7 +31483,7 @@ public: return m_data.m_value.array->operator[](idx); } - JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a numeric argument with ", type_name()), this)); + throw_subscript_wrong_type("numeric"); } /// @brief access specified object element @@ -31391,7 +31503,7 @@ public: return set_parent(result.first->second); } - JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this)); + throw_subscript_wrong_type("string"); } /// @brief access specified object element @@ -31406,7 +31518,7 @@ public: return it->second; } - JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this)); + throw_subscript_wrong_type("string"); } // these two functions resolve a (const) char * ambiguity affecting Clang and MSVC @@ -31442,7 +31554,7 @@ public: return set_parent(result.first->second); } - JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this)); + throw_subscript_wrong_type("string"); } /// @brief access specified object element @@ -31459,7 +31571,7 @@ public: return it->second; } - JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this)); + throw_subscript_wrong_type("string"); } private: @@ -31483,7 +31595,7 @@ public: // value only works for objects if (JSON_HEDLEY_UNLIKELY(!is_object())) { - JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::value_wrong_type, "value()", *this); } const auto it = find(std::forward(key)); @@ -31498,7 +31610,7 @@ public: // value only works for arrays and objects if (JSON_HEDLEY_UNLIKELY(!is_structured())) { - JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::value_wrong_type, "value()", *this); } return ptr.get_checked_or_null(this); @@ -31665,7 +31777,7 @@ public: // make sure the iterator fits the current value if (JSON_HEDLEY_UNLIKELY(this != pos.m_object)) { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); } IteratorType result = end(); @@ -31681,7 +31793,7 @@ public: { if (JSON_HEDLEY_UNLIKELY(!pos.m_it.primitive_iterator.is_begin())) { - JSON_THROW(invalid_iterator::create(205, "iterator out of range", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_out_of_range, "iterator out of range", this)); } m_data.m_value.destroy(m_data.m_type); @@ -31707,7 +31819,7 @@ public: case value_t::null: case value_t::discarded: default: - JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::erase_wrong_type, "erase()", *this); } return result; @@ -31723,7 +31835,7 @@ public: // make sure the iterator fits the current value if (JSON_HEDLEY_UNLIKELY(this != first.m_object || this != last.m_object)) { - JSON_THROW(invalid_iterator::create(203, "iterators do not fit current value", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_range_from_other_value, "iterators do not fit current value", this)); } IteratorType result = end(); @@ -31740,7 +31852,7 @@ public: if (JSON_HEDLEY_UNLIKELY(!first.m_it.primitive_iterator.is_begin() || !last.m_it.primitive_iterator.is_end())) { - JSON_THROW(invalid_iterator::create(204, "iterators out of range", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_range_out_of_range, "iterators out of range", this)); } m_data.m_value.destroy(m_data.m_type); @@ -31768,7 +31880,7 @@ public: case value_t::null: case value_t::discarded: default: - JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::erase_wrong_type, "erase()", *this); } return result; @@ -31782,7 +31894,7 @@ public: // this erase only works for objects if (JSON_HEDLEY_UNLIKELY(!is_object())) { - JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::erase_wrong_type, "erase()", *this); } const auto erased = m_data.m_value.object->erase(std::forward(key)); @@ -31797,7 +31909,7 @@ public: // this erase only works for objects if (JSON_HEDLEY_UNLIKELY(!is_object())) { - JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::erase_wrong_type, "erase()", *this); } const auto it = object_lookup(*this, std::forward(key)); @@ -31839,14 +31951,14 @@ public: { if (JSON_HEDLEY_UNLIKELY(idx >= size())) { - JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), this)); + JSON_THROW(out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat("array index ", std::to_string(idx), " is out of range"), this)); } m_data.m_value.array->erase(m_data.m_value.array->begin() + static_cast(idx)); } else { - JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::erase_wrong_type, "erase()", *this); } } @@ -32335,7 +32447,7 @@ public: // push_back only works for null objects or arrays if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) { - JSON_THROW(type_error::create(308, detail::concat("cannot use push_back() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::push_back_wrong_type, "push_back()", *this); } // transform a null object into an array @@ -32366,7 +32478,7 @@ public: // push_back only works for null objects or arrays if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) { - JSON_THROW(type_error::create(308, detail::concat("cannot use push_back() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::push_back_wrong_type, "push_back()", *this); } // transform a null object into an array @@ -32396,7 +32508,7 @@ public: // push_back only works for null objects or objects if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object()))) { - JSON_THROW(type_error::create(308, detail::concat("cannot use push_back() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::push_back_wrong_type, "push_back()", *this); } // transform a null object into an object @@ -32450,7 +32562,7 @@ public: // emplace_back only works for null objects or arrays if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) { - JSON_THROW(type_error::create(311, detail::concat("cannot use emplace_back() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::emplace_wrong_type, "emplace_back()", *this); } // transform a null object into an array @@ -32473,7 +32585,7 @@ public: // emplace only works for null objects or arrays if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object()))) { - JSON_THROW(type_error::create(311, detail::concat("cannot use emplace() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::emplace_wrong_type, "emplace()", *this); } // transform a null object into an object @@ -32525,14 +32637,14 @@ public: // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); } // insert to array and return iterator return insert_iterator(pos, val); } - JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); } /// @brief inserts element into array @@ -32545,7 +32657,7 @@ public: // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); } // moving into a local first keeps this safe even if val aliases @@ -32554,7 +32666,7 @@ public: return insert_iterator(pos, std::move(tmp)); } - JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); } /// @brief inserts copies of element into array @@ -32567,14 +32679,14 @@ public: // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); } // insert to array and return iterator return insert_iterator(pos, cnt, val); } - JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); } /// @brief inserts range of elements into array @@ -32584,30 +32696,30 @@ public: // insert only works for arrays if (JSON_HEDLEY_UNLIKELY(!is_array())) { - JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); } // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); } // check if range iterators belong to the same JSON object if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) { - JSON_THROW(invalid_iterator::create(210, "iterators do not fit", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::insert_range_incompatible, "iterators do not fit", this)); } if (JSON_HEDLEY_UNLIKELY(first.m_object == this)) { - JSON_THROW(invalid_iterator::create(211, "passed iterators may not belong to container", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::insert_range_into_itself, "passed iterators may not belong to container", this)); } // passed iterators must belong to arrays if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_array())) { - JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to arrays", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterators first and last must point to arrays", this)); } // insert to array and return iterator @@ -32621,13 +32733,13 @@ public: // insert only works for arrays if (JSON_HEDLEY_UNLIKELY(!is_array())) { - JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); } // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); } // copy the values first: ilist may refer to elements of this array @@ -32649,19 +32761,19 @@ public: // insert only works for objects if (JSON_HEDLEY_UNLIKELY(!is_object())) { - JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); } // check if range iterators belong to the same JSON object if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) { - JSON_THROW(invalid_iterator::create(210, "iterators do not fit", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::insert_range_incompatible, "iterators do not fit", this)); } // passed iterators must belong to objects if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object())) { - JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterators first and last must point to objects", this)); } m_data.m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator); @@ -32678,7 +32790,7 @@ public: // j, not the copy made below) if (JSON_HEDLEY_UNLIKELY(!j.is_object())) { - JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", j.type_name()), &j)); + detail::throw_cannot_use_with(detail::exception_id::update_wrong_type, "update()", j); } // copy first: j may be *this or one of its descendants, and is @@ -32696,13 +32808,13 @@ public: // check if range iterators belong to the same JSON object if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) { - JSON_THROW(invalid_iterator::create(210, "iterators do not fit", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::insert_range_incompatible, "iterators do not fit", this)); } // passed iterators must belong to objects if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object())) { - JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", first.m_object->type_name()), first.m_object)); + detail::throw_cannot_use_with(detail::exception_id::update_wrong_type, "update()", *first.m_object); } // copy first: the range may belong to *this or one of its @@ -32738,7 +32850,7 @@ public: if (JSON_HEDLEY_UNLIKELY(!is_object())) { - JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::update_wrong_type, "update()", *this); } } @@ -32901,7 +33013,7 @@ public: } else { - JSON_THROW(type_error::create(310, detail::concat("cannot use swap(array_t&) with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(array_t&)", *this); } } @@ -32918,7 +33030,7 @@ public: } else { - JSON_THROW(type_error::create(310, detail::concat("cannot use swap(object_t&) with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(object_t&)", *this); } } @@ -32934,7 +33046,7 @@ public: } else { - JSON_THROW(type_error::create(310, detail::concat("cannot use swap(string_t&) with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(string_t&)", *this); } } @@ -32950,7 +33062,7 @@ public: } else { - JSON_THROW(type_error::create(310, detail::concat("cannot use swap(binary_t&) with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(binary_t&)", *this); } } @@ -32966,7 +33078,7 @@ public: } else { - JSON_THROW(type_error::create(310, detail::concat("cannot use swap(binary_t::container_type&) with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(binary_t::container_type&)", *this); } } @@ -34428,7 +34540,7 @@ public: if (JSON_HEDLEY_UNLIKELY(idx > parent.size())) { // avoid undefined behavior - JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), &parent)); + JSON_THROW(out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat("array index ", std::to_string(idx), " is out of range"), &parent)); } // default case: insert add offset @@ -34447,7 +34559,7 @@ public: case value_t::binary: case value_t::discarded: default: - JSON_THROW(out_of_range::create(411, detail::concat("cannot add value: the JSON Patch 'add' target's parent is of type ", parent.type_name(), ", but must be an object or array"), &parent)); + JSON_THROW(out_of_range::create(detail::exception_id::patch_add_parent_not_container, detail::concat("cannot add value: the JSON Patch 'add' target's parent is of type ", parent.type_name(), ", but must be an object or array"), &parent)); } }; @@ -34470,7 +34582,7 @@ public: } else { - JSON_THROW(out_of_range::create(403, detail::concat("key '", last_path, "' not found"), this)); + JSON_THROW(out_of_range::create(detail::exception_id::key_not_found, detail::concat("key '", last_path, "' not found"), this)); } } else if (parent.is_array()) @@ -34482,7 +34594,7 @@ public: { // the parent of a "remove" target must be an object or array // (see #5396) - JSON_THROW(out_of_range::create(413, detail::concat("cannot remove value: the JSON Patch 'remove' target's parent is of type ", parent.type_name(), ", but must be an object or array"), &parent)); + JSON_THROW(out_of_range::create(detail::exception_id::patch_remove_parent_not_container, detail::concat("cannot remove value: the JSON Patch 'remove' target's parent is of type ", parent.type_name(), ", but must be an object or array"), &parent)); } }; @@ -34513,7 +34625,7 @@ public: // type check: top level value must be an array if (JSON_HEDLEY_UNLIKELY(!json_patch.is_array())) { - JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects", &json_patch)); + JSON_THROW(parse_error::create(detail::exception_id::patch_not_an_array, 0, "JSON patch must be an array of objects", &json_patch)); } // iterate and apply the operations @@ -34534,14 +34646,14 @@ public: if (JSON_HEDLEY_UNLIKELY(it == val.m_data.m_value.object->end())) { // NOLINTNEXTLINE(performance-inefficient-string-concatenation) - JSON_THROW(parse_error::create(105, 0, detail::concat(error_msg, " must have member '", member, "'"), &val)); + JSON_THROW(parse_error::create(detail::exception_id::patch_invalid_operation, 0, detail::concat(error_msg, " must have member '", member, "'"), &val)); } // check if the result is of type string if (JSON_HEDLEY_UNLIKELY(string_type && !it->second.is_string())) { // NOLINTNEXTLINE(performance-inefficient-string-concatenation) - JSON_THROW(parse_error::create(105, 0, detail::concat(error_msg, " must have string member '", member, "'"), &val)); + JSON_THROW(parse_error::create(detail::exception_id::patch_invalid_operation, 0, detail::concat(error_msg, " must have string member '", member, "'"), &val)); } // no error: return value @@ -34551,7 +34663,7 @@ public: // type check: every element of the array must be an object if (JSON_HEDLEY_UNLIKELY(!val.is_object())) { - JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects", &val)); + JSON_THROW(parse_error::create(detail::exception_id::patch_not_an_array, 0, "JSON patch must be an array of objects", &val)); } // collect mandatory members @@ -34587,7 +34699,7 @@ public: if (JSON_HEDLEY_UNLIKELY(is_proper_prefix(from_ptr, ptr))) { - JSON_THROW(out_of_range::create(414, detail::concat("cannot move value: 'from' path '", from_path, "' is a proper prefix of 'path' '", path, "'"), &result)); + JSON_THROW(out_of_range::create(detail::exception_id::patch_move_into_child, detail::concat("cannot move value: 'from' path '", from_path, "' is a proper prefix of 'path' '", path, "'"), &result)); } // the "from" location must exist - use at() @@ -34634,7 +34746,7 @@ public: // throw an exception if the test fails if (JSON_HEDLEY_UNLIKELY(!success)) { - JSON_THROW(other_error::create(501, detail::concat("unsuccessful: ", val.dump()), &val)); + JSON_THROW(other_error::create(detail::exception_id::patch_test_failed, detail::concat("unsuccessful: ", val.dump()), &val)); } break; @@ -34645,7 +34757,7 @@ public: { // op must be "add", "remove", "replace", "move", "copy", or // "test" - JSON_THROW(parse_error::create(105, 0, detail::concat("operation value '", op, "' is invalid"), &val)); + JSON_THROW(parse_error::create(detail::exception_id::patch_invalid_operation, 0, detail::concat("operation value '", op, "' is invalid"), &val)); } } } diff --git a/tests/src/fuzzer-parse_bjdata.cpp b/tests/src/fuzzer-parse_bjdata.cpp index 6302db104..92e1bed4c 100644 --- a/tests/src/fuzzer-parse_bjdata.cpp +++ b/tests/src/fuzzer-parse_bjdata.cpp @@ -51,22 +51,7 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer drivers. */ -#include -#include - -// the round-trip checks below are assertions; NDEBUG would compile them away -#ifdef NDEBUG - #error "the fuzzer drivers must be built without NDEBUG" -#endif - -using json = nlohmann::json; - -// compares dumps rather than values, because NaN != NaN; keep writes strings -// byte for byte, so ill-formed UTF-8 that a binary reader accepts cannot throw -static bool same_value(const json& lhs, const json& rhs) -{ - return lhs.dump(-1, ' ', false, json::error_handler_t::keep) == rhs.dump(-1, ' ', false, json::error_handler_t::keep); -} +#include "fuzzer_common.hpp" // value-stable comparison for the round-trip checks below; see the note // above on why this compares dump()s rather than the json values directly @@ -80,23 +65,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { std::vector const vec1(data, data + size); - // step 0: parse input without exceptions; a parse error must then be - // reported as a discarded value, never thrown - json j_noexcept; + // step 0: parse input without exceptions bool noexcept_threw = false; - try - { - j_noexcept = json::from_bjdata(vec1, true, false); - } - catch (const json::parse_error&) - { - assert(false); - } - catch (const json::exception&) - { - // type and out-of-range errors are not parse errors and still throw - noexcept_threw = true; - } + json const j_noexcept = parse_without_exceptions([&] { return json::from_bjdata(vec1, true, false); }, noexcept_threw); // whether step 1 succeeded; if not, the catch blocks below check that // step 0 failed, too bool parsed = false; diff --git a/tests/src/fuzzer-parse_bon8.cpp b/tests/src/fuzzer-parse_bon8.cpp index 8d9a119fa..792606d05 100644 --- a/tests/src/fuzzer-parse_bon8.cpp +++ b/tests/src/fuzzer-parse_bon8.cpp @@ -25,23 +25,8 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer drivers. */ -#include #include -#include - -// the round-trip checks below are assertions; NDEBUG would compile them away -#ifdef NDEBUG - #error "the fuzzer drivers must be built without NDEBUG" -#endif - -using json = nlohmann::json; - -// compares dumps rather than values, because NaN != NaN; keep writes strings -// byte for byte, so ill-formed UTF-8 that a binary reader accepts cannot throw -static bool same_value(const json& lhs, const json& rhs) -{ - return lhs.dump(-1, ' ', false, json::error_handler_t::keep) == rhs.dump(-1, ' ', false, json::error_handler_t::keep); -} +#include "fuzzer_common.hpp" namespace { @@ -72,23 +57,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) std::vector const vec1(data, data + size); - // step 0: parse input without exceptions; a parse error must then be - // reported as a discarded value, never thrown - json j_noexcept; + // step 0: parse input without exceptions bool noexcept_threw = false; - try - { - j_noexcept = json::from_bon8(vec1, true, false); - } - catch (const json::parse_error&) - { - assert(false); - } - catch (const json::exception&) - { - // type and out-of-range errors are not parse errors and still throw - noexcept_threw = true; - } + json const j_noexcept = parse_without_exceptions([&] { return json::from_bon8(vec1, true, false); }, noexcept_threw); // whether step 1 succeeded; if not, the catch blocks below check that // step 0 failed, too bool parsed = false; diff --git a/tests/src/fuzzer-parse_bson.cpp b/tests/src/fuzzer-parse_bson.cpp index 357bc7eb2..548c30f38 100644 --- a/tests/src/fuzzer-parse_bson.cpp +++ b/tests/src/fuzzer-parse_bson.cpp @@ -21,45 +21,16 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer drivers. */ -#include -#include - -// the round-trip checks below are assertions; NDEBUG would compile them away -#ifdef NDEBUG - #error "the fuzzer drivers must be built without NDEBUG" -#endif - -using json = nlohmann::json; - -// compares dumps rather than values, because NaN != NaN; keep writes strings -// byte for byte, so ill-formed UTF-8 that a binary reader accepts cannot throw -static bool same_value(const json& lhs, const json& rhs) -{ - return lhs.dump(-1, ' ', false, json::error_handler_t::keep) == rhs.dump(-1, ' ', false, json::error_handler_t::keep); -} +#include "fuzzer_common.hpp" // see http://llvm.org/docs/LibFuzzer.html extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { std::vector const vec1(data, data + size); - // step 0: parse input without exceptions; a parse error must then be - // reported as a discarded value, never thrown - json j_noexcept; + // step 0: parse input without exceptions bool noexcept_threw = false; - try - { - j_noexcept = json::from_bson(vec1, true, false); - } - catch (const json::parse_error&) - { - assert(false); - } - catch (const json::exception&) - { - // type and out-of-range errors are not parse errors and still throw - noexcept_threw = true; - } + json const j_noexcept = parse_without_exceptions([&] { return json::from_bson(vec1, true, false); }, noexcept_threw); // whether step 1 succeeded; if not, the catch blocks below check that // step 0 failed, too bool parsed = false; diff --git a/tests/src/fuzzer-parse_cbor.cpp b/tests/src/fuzzer-parse_cbor.cpp index 1574e8aee..6d995dc6e 100644 --- a/tests/src/fuzzer-parse_cbor.cpp +++ b/tests/src/fuzzer-parse_cbor.cpp @@ -21,45 +21,16 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer drivers. */ -#include -#include - -// the round-trip checks below are assertions; NDEBUG would compile them away -#ifdef NDEBUG - #error "the fuzzer drivers must be built without NDEBUG" -#endif - -using json = nlohmann::json; - -// compares dumps rather than values, because NaN != NaN; keep writes strings -// byte for byte, so ill-formed UTF-8 that a binary reader accepts cannot throw -static bool same_value(const json& lhs, const json& rhs) -{ - return lhs.dump(-1, ' ', false, json::error_handler_t::keep) == rhs.dump(-1, ' ', false, json::error_handler_t::keep); -} +#include "fuzzer_common.hpp" // see http://llvm.org/docs/LibFuzzer.html extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { std::vector const vec1(data, data + size); - // step 0: parse input without exceptions; a parse error must then be - // reported as a discarded value, never thrown - json j_noexcept; + // step 0: parse input without exceptions bool noexcept_threw = false; - try - { - j_noexcept = json::from_cbor(vec1, true, false); - } - catch (const json::parse_error&) - { - assert(false); - } - catch (const json::exception&) - { - // type and out-of-range errors are not parse errors and still throw - noexcept_threw = true; - } + json const j_noexcept = parse_without_exceptions([&] { return json::from_cbor(vec1, true, false); }, noexcept_threw); // whether step 1 succeeded; if not, the catch blocks below check that // step 0 failed, too bool parsed = false; diff --git a/tests/src/fuzzer-parse_json.cpp b/tests/src/fuzzer-parse_json.cpp index c0dd969f3..31a811a6c 100644 --- a/tests/src/fuzzer-parse_json.cpp +++ b/tests/src/fuzzer-parse_json.cpp @@ -22,43 +22,14 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer drivers. */ -#include -#include - -// the round-trip checks below are assertions; NDEBUG would compile them away -#ifdef NDEBUG - #error "the fuzzer drivers must be built without NDEBUG" -#endif - -using json = nlohmann::json; - -// compares dumps rather than values, because NaN != NaN; keep writes strings -// byte for byte, so ill-formed UTF-8 that a binary reader accepts cannot throw -static bool same_value(const json& lhs, const json& rhs) -{ - return lhs.dump(-1, ' ', false, json::error_handler_t::keep) == rhs.dump(-1, ' ', false, json::error_handler_t::keep); -} +#include "fuzzer_common.hpp" // see http://llvm.org/docs/LibFuzzer.html extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - // step 0: parse input without exceptions; a parse error must then be - // reported as a discarded value, never thrown - json j_noexcept; + // step 0: parse input without exceptions bool noexcept_threw = false; - try - { - j_noexcept = json::parse(data, data + size, nullptr, false); - } - catch (const json::parse_error&) - { - assert(false); - } - catch (const json::exception&) - { - // type and out-of-range errors are not parse errors and still throw - noexcept_threw = true; - } + json const j_noexcept = parse_without_exceptions([&] { return json::parse(data, data + size, nullptr, false); }, noexcept_threw); // whether step 1 succeeded; if not, the catch blocks below check that // step 0 failed, too bool parsed = false; diff --git a/tests/src/fuzzer-parse_msgpack.cpp b/tests/src/fuzzer-parse_msgpack.cpp index 9ab2aadd8..a5e2e346f 100644 --- a/tests/src/fuzzer-parse_msgpack.cpp +++ b/tests/src/fuzzer-parse_msgpack.cpp @@ -21,45 +21,16 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer drivers. */ -#include -#include - -// the round-trip checks below are assertions; NDEBUG would compile them away -#ifdef NDEBUG - #error "the fuzzer drivers must be built without NDEBUG" -#endif - -using json = nlohmann::json; - -// compares dumps rather than values, because NaN != NaN; keep writes strings -// byte for byte, so ill-formed UTF-8 that a binary reader accepts cannot throw -static bool same_value(const json& lhs, const json& rhs) -{ - return lhs.dump(-1, ' ', false, json::error_handler_t::keep) == rhs.dump(-1, ' ', false, json::error_handler_t::keep); -} +#include "fuzzer_common.hpp" // see http://llvm.org/docs/LibFuzzer.html extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { std::vector const vec1(data, data + size); - // step 0: parse input without exceptions; a parse error must then be - // reported as a discarded value, never thrown - json j_noexcept; + // step 0: parse input without exceptions bool noexcept_threw = false; - try - { - j_noexcept = json::from_msgpack(vec1, true, false); - } - catch (const json::parse_error&) - { - assert(false); - } - catch (const json::exception&) - { - // type and out-of-range errors are not parse errors and still throw - noexcept_threw = true; - } + json const j_noexcept = parse_without_exceptions([&] { return json::from_msgpack(vec1, true, false); }, noexcept_threw); // whether step 1 succeeded; if not, the catch blocks below check that // step 0 failed, too bool parsed = false; diff --git a/tests/src/fuzzer-parse_ubjson.cpp b/tests/src/fuzzer-parse_ubjson.cpp index 9750286ae..641526c90 100644 --- a/tests/src/fuzzer-parse_ubjson.cpp +++ b/tests/src/fuzzer-parse_ubjson.cpp @@ -30,45 +30,16 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer drivers. */ -#include -#include - -// the round-trip checks below are assertions; NDEBUG would compile them away -#ifdef NDEBUG - #error "the fuzzer drivers must be built without NDEBUG" -#endif - -using json = nlohmann::json; - -// compares dumps rather than values, because NaN != NaN; keep writes strings -// byte for byte, so ill-formed UTF-8 that a binary reader accepts cannot throw -static bool same_value(const json& lhs, const json& rhs) -{ - return lhs.dump(-1, ' ', false, json::error_handler_t::keep) == rhs.dump(-1, ' ', false, json::error_handler_t::keep); -} +#include "fuzzer_common.hpp" // see http://llvm.org/docs/LibFuzzer.html extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { std::vector const vec1(data, data + size); - // step 0: parse input without exceptions; a parse error must then be - // reported as a discarded value, never thrown - json j_noexcept; + // step 0: parse input without exceptions bool noexcept_threw = false; - try - { - j_noexcept = json::from_ubjson(vec1, true, false); - } - catch (const json::parse_error&) - { - assert(false); - } - catch (const json::exception&) - { - // type and out-of-range errors are not parse errors and still throw - noexcept_threw = true; - } + json const j_noexcept = parse_without_exceptions([&] { return json::from_ubjson(vec1, true, false); }, noexcept_threw); // whether step 1 succeeded; if not, the catch blocks below check that // step 0 failed, too bool parsed = false; diff --git a/tests/src/fuzzer_common.hpp b/tests/src/fuzzer_common.hpp new file mode 100644 index 000000000..4b6c9289c --- /dev/null +++ b/tests/src/fuzzer_common.hpp @@ -0,0 +1,51 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ (supporting code) +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +// code shared by the fuzzer drivers tests/src/fuzzer-parse_*.cpp + +#pragma once + +#include // assert +#include + +using nlohmann::json; // NOLINT(google-global-names-in-headers): shared by all fuzzer drivers + +// the round-trip checks of the drivers are assertions; NDEBUG would compile them away +#ifdef NDEBUG + #error "the fuzzer drivers must be built without NDEBUG" +#endif + +// compares dumps rather than values, because NaN != NaN; keep writes strings +// byte for byte, so ill-formed UTF-8 that a binary reader accepts cannot throw +inline bool same_value(const json& lhs, const json& rhs) +{ + return lhs.dump(-1, ' ', false, json::error_handler_t::keep) == rhs.dump(-1, ' ', false, json::error_handler_t::keep); +} + +// step 0 of each driver: parse the input without exceptions; a parse error +// must then be reported as a discarded value, never thrown. Type and +// out-of-range errors are not parse errors and still throw; then @a threw is +// set and null is returned. +template +json parse_without_exceptions(Parse parse, bool& threw) +{ + threw = false; + try + { + return parse(); + } + catch (const json::parse_error&) + { + assert(false); + } + catch (const json::exception&) + { + threw = true; + } + return {}; +} diff --git a/tests/src/sax_event_loggers.hpp b/tests/src/sax_event_loggers.hpp new file mode 100644 index 000000000..e39ad9e56 --- /dev/null +++ b/tests/src/sax_event_loggers.hpp @@ -0,0 +1,185 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ (supporting code) +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#pragma once + +#include // size_t +#include // uint8_t (via json::binary_t) +#include // numeric_limits +#include // string, to_string +#include // vector + +#include + +// the loggers are defined in this header only, so their vtables are emitted in +// every test that includes it +#if defined(__clang__) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wweak-vtables" +#endif + +namespace utils +{ +/// a SAX event consumer that records every event it receives as a +/// human-readable string, used by the deserialization tests to check the +/// exact sequence of SAX events a parse run produces +struct SaxEventLogger : public nlohmann::json_sax +{ + using json = nlohmann::json; + + bool null() override + { + events.emplace_back("null()"); + return true; + } + + bool boolean(bool val) override + { + events.emplace_back(val ? "boolean(true)" : "boolean(false)"); + return true; + } + + bool number_integer(json::number_integer_t val) override + { + events.push_back("number_integer(" + std::to_string(val) + ")"); + return true; + } + + bool number_unsigned(json::number_unsigned_t val) override + { + events.push_back("number_unsigned(" + std::to_string(val) + ")"); + return true; + } + + bool number_float(json::number_float_t /*val*/, const std::string& s) override + { + events.push_back("number_float(" + s + ")"); + return true; + } + + bool string(std::string& val) override + { + events.push_back("string(" + val + ")"); + return true; + } + + bool binary(json::binary_t& val) override + { + std::string binary_contents = "binary("; + std::string comma_space; + for (const auto b : val) + { + binary_contents.append(comma_space); + binary_contents.append(std::to_string(static_cast(b))); + comma_space = ", "; + } + binary_contents.append(")"); + events.push_back(binary_contents); + return true; + } + + bool start_object(std::size_t elements) override + { + if (elements == (std::numeric_limits::max)()) + { + events.emplace_back("start_object()"); + } + else + { + events.push_back("start_object(" + std::to_string(elements) + ")"); + } + return true; + } + + bool key(std::string& val) override + { + events.push_back("key(" + val + ")"); + return true; + } + + bool end_object() override + { + events.emplace_back("end_object()"); + return true; + } + + bool start_array(std::size_t elements) override + { + if (elements == (std::numeric_limits::max)()) + { + events.emplace_back("start_array()"); + } + else + { + events.push_back("start_array(" + std::to_string(elements) + ")"); + } + return true; + } + + bool end_array() override + { + events.emplace_back("end_array()"); + return true; + } + + bool parse_error(std::size_t position, const std::string& /*last_token*/, const json::exception& /*ex*/) override + { + errored = true; + events.push_back("parse_error(" + std::to_string(position) + ")"); + return false; + } + + std::vector events {}; // NOLINT(readability-redundant-member-init) + bool errored = false; +}; + +struct SaxEventLoggerExitAfterStartObject : public SaxEventLogger +{ + bool start_object(std::size_t elements) override + { + if (elements == (std::numeric_limits::max)()) + { + events.emplace_back("start_object()"); + } + else + { + events.push_back("start_object(" + std::to_string(elements) + ")"); + } + return false; + } +}; + +struct SaxEventLoggerExitAfterKey : public SaxEventLogger +{ + bool key(std::string& val) override + { + events.push_back("key(" + val + ")"); + return false; + } +}; + +struct SaxEventLoggerExitAfterStartArray : public SaxEventLogger +{ + bool start_array(std::size_t elements) override + { + if (elements == (std::numeric_limits::max)()) + { + events.emplace_back("start_array()"); + } + else + { + events.push_back("start_array(" + std::to_string(elements) + ")"); + } + return false; + } +}; +} // namespace utils + +#if defined(__clang__) + #pragma clang diagnostic pop +#endif diff --git a/tests/src/unit-class_parser.cpp b/tests/src/unit-class_parser.cpp index 978d94f92..3245d76b9 100644 --- a/tests/src/unit-class_parser.cpp +++ b/tests/src/unit-class_parser.cpp @@ -37,194 +37,15 @@ using nlohmann::json; #include #include +#include "sax_countdown.hpp" +#include "sax_event_loggers.hpp" #include "test_utils.hpp" +using utils::SaxCountdown; +using utils::SaxEventLogger; + namespace { -class SaxEventLogger -{ - public: - bool null() - { - events.emplace_back("null()"); - return true; - } - - bool boolean(bool val) - { - events.emplace_back(val ? "boolean(true)" : "boolean(false)"); - return true; - } - - bool number_integer(json::number_integer_t val) - { - events.push_back("number_integer(" + std::to_string(val) + ")"); - return true; - } - - bool number_unsigned(json::number_unsigned_t val) - { - events.push_back("number_unsigned(" + std::to_string(val) + ")"); - return true; - } - - bool number_float(json::number_float_t /*unused*/, const std::string& s) - { - events.push_back("number_float(" + s + ")"); - return true; - } - - bool string(std::string& val) - { - events.push_back("string(" + val + ")"); - return true; - } - - bool binary(json::binary_t& val) - { - std::string binary_contents = "binary("; - std::string comma_space; - for (auto b : val) - { - binary_contents.append(comma_space); - binary_contents.append(std::to_string(static_cast(b))); - comma_space = ", "; - } - binary_contents.append(")"); - events.push_back(binary_contents); - return true; - } - - bool start_object(std::size_t elements) - { - if (elements == (std::numeric_limits::max)()) - { - events.emplace_back("start_object()"); - } - else - { - events.push_back("start_object(" + std::to_string(elements) + ")"); - } - return true; - } - - bool key(std::string& val) - { - events.push_back("key(" + val + ")"); - return true; - } - - bool end_object() - { - events.emplace_back("end_object()"); - return true; - } - - bool start_array(std::size_t elements) - { - if (elements == (std::numeric_limits::max)()) - { - events.emplace_back("start_array()"); - } - else - { - events.push_back("start_array(" + std::to_string(elements) + ")"); - } - return true; - } - - bool end_array() - { - events.emplace_back("end_array()"); - return true; - } - - bool parse_error(std::size_t position, const std::string& /*unused*/, const json::exception& /*unused*/) - { - errored = true; - events.push_back("parse_error(" + std::to_string(position) + ")"); - return false; - } - - std::vector events {}; // NOLINT(readability-redundant-member-init) - bool errored = false; -}; - -class SaxCountdown : public nlohmann::json::json_sax_t -{ - public: - explicit SaxCountdown(const int count) : events_left(count) - {} - - bool null() override - { - return events_left-- > 0; - } - - bool boolean(bool /*val*/) override - { - return events_left-- > 0; - } - - bool number_integer(json::number_integer_t /*val*/) override - { - return events_left-- > 0; - } - - bool number_unsigned(json::number_unsigned_t /*val*/) override - { - return events_left-- > 0; - } - - bool number_float(json::number_float_t /*val*/, const std::string& /*s*/) override - { - return events_left-- > 0; - } - - bool string(std::string& /*val*/) override - { - return events_left-- > 0; - } - - bool binary(json::binary_t& /*val*/) override - { - return events_left-- > 0; - } - - bool start_object(std::size_t /*elements*/) override - { - return events_left-- > 0; - } - - bool key(std::string& /*val*/) override - { - return events_left-- > 0; - } - - bool end_object() override - { - return events_left-- > 0; - } - - bool start_array(std::size_t /*elements*/) override - { - return events_left-- > 0; - } - - bool end_array() override - { - return events_left-- > 0; - } - - bool parse_error(std::size_t /*position*/, const std::string& /*last_token*/, const json::exception& /*ex*/) override - { - return false; - } - - private: - int events_left = 0; -}; - json parser_helper(const std::string& s); bool accept_helper(const std::string& s); void comments_helper(const std::string& s); diff --git a/tests/src/unit-deserialization.cpp b/tests/src/unit-deserialization.cpp index ca9b63112..6222def34 100644 --- a/tests/src/unit-deserialization.cpp +++ b/tests/src/unit-deserialization.cpp @@ -36,155 +36,15 @@ using nlohmann::json; #include #include +#include "sax_event_loggers.hpp" + +using utils::SaxEventLogger; +using utils::SaxEventLoggerExitAfterKey; +using utils::SaxEventLoggerExitAfterStartArray; +using utils::SaxEventLoggerExitAfterStartObject; + namespace { -struct SaxEventLogger : public nlohmann::json_sax -{ - bool null() override - { - events.emplace_back("null()"); - return true; - } - - bool boolean(bool val) override - { - events.emplace_back(val ? "boolean(true)" : "boolean(false)"); - return true; - } - - bool number_integer(json::number_integer_t val) override - { - events.push_back("number_integer(" + std::to_string(val) + ")"); - return true; - } - - bool number_unsigned(json::number_unsigned_t val) override - { - events.push_back("number_unsigned(" + std::to_string(val) + ")"); - return true; - } - - bool number_float(json::number_float_t /*val*/, const std::string& s) override - { - events.push_back("number_float(" + s + ")"); - return true; - } - - bool string(std::string& val) override - { - events.push_back("string(" + val + ")"); - return true; - } - - bool binary(json::binary_t& val) override - { - std::string binary_contents = "binary("; - std::string comma_space; - for (auto b : val) - { - binary_contents.append(comma_space); - binary_contents.append(std::to_string(static_cast(b))); - comma_space = ", "; - } - binary_contents.append(")"); - events.push_back(binary_contents); - return true; - } - - bool start_object(std::size_t elements) override - { - if (elements == (std::numeric_limits::max)()) - { - events.emplace_back("start_object()"); - } - else - { - events.push_back("start_object(" + std::to_string(elements) + ")"); - } - return true; - } - - bool key(std::string& val) override - { - events.push_back("key(" + val + ")"); - return true; - } - - bool end_object() override - { - events.emplace_back("end_object()"); - return true; - } - - bool start_array(std::size_t elements) override - { - if (elements == (std::numeric_limits::max)()) - { - events.emplace_back("start_array()"); - } - else - { - events.push_back("start_array(" + std::to_string(elements) + ")"); - } - return true; - } - - bool end_array() override - { - events.emplace_back("end_array()"); - return true; - } - - bool parse_error(std::size_t position, const std::string& /*last_token*/, const json::exception& /*ex*/) override - { - events.push_back("parse_error(" + std::to_string(position) + ")"); - return false; - } - - std::vector events {}; // NOLINT(readability-redundant-member-init) -}; - -struct SaxEventLoggerExitAfterStartObject : public SaxEventLogger -{ - bool start_object(std::size_t elements) override - { - if (elements == (std::numeric_limits::max)()) - { - events.emplace_back("start_object()"); - } - else - { - events.push_back("start_object(" + std::to_string(elements) + ")"); - } - return false; - } -}; - -struct SaxEventLoggerExitAfterKey : public SaxEventLogger -{ - bool key(std::string& val) override - { - events.push_back("key(" + val + ")"); - return false; - } -}; - -struct SaxEventLoggerExitAfterStartArray : public SaxEventLogger -{ - bool start_array(std::size_t elements) override - { - if (elements == (std::numeric_limits::max)()) - { - events.emplace_back("start_array()"); - } - else - { - events.push_back("start_array(" + std::to_string(elements) + ")"); - } - return false; - } -}; - template class proxy_iterator { From 2cca04ae6f1a63ed65843a466b95d70f218885f5 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 11 Oct 2026 08:21:41 +0200 Subject: [PATCH 4/9] Report one-character non-numeric array indices like longer ones (#5800) * Report one-character non-numeric array indices like longer ones A JSON pointer reference token that is not a number but has only one character (e.g. "/a/x") was reported as out_of_range.404 ("unresolved reference token"), because the "is not a number" check only ran for tokens longer than one character; "/a/xy" got parse_error.109. Both now throw parse_error.109. "-" and the empty token are still reported as out_of_range.404. As a consequence, value(json_pointer, default) on an array now throws for "/x" as it already did for "/xy". Also document why ordered_map::erase's destroy/placement-new loop on pair is kept despite [basic.life]/8 before C++20. Signed-off-by: Niels Lohmann * Document the one-character array index change Add 3.13.0 version-history entries to at, operator[], value, patch, patch_inplace, and unflatten, and describe in exceptions.md which array indices throw parse_error.109 and which out_of_range.404. Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/api/basic_json/at.md | 4 +++- docs/mkdocs/docs/api/basic_json/operator[].md | 4 +++- docs/mkdocs/docs/api/basic_json/patch.md | 3 +++ .../docs/api/basic_json/patch_inplace.md | 3 +++ docs/mkdocs/docs/api/basic_json/unflatten.md | 8 +++++-- docs/mkdocs/docs/api/basic_json/value.md | 4 +++- docs/mkdocs/docs/home/exceptions.md | 12 +++++++++-- include/nlohmann/detail/json_pointer.hpp | 8 ++++--- include/nlohmann/ordered_map.hpp | 8 +++++++ single_include/nlohmann/json.hpp | 16 +++++++++++--- tests/src/unit-element_access2.cpp | 2 ++ tests/src/unit-json_patch.cpp | 15 +++++++++++++ tests/src/unit-json_pointer.cpp | 21 +++++++++++++++++++ 13 files changed, 95 insertions(+), 13 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json/at.md b/docs/mkdocs/docs/api/basic_json/at.md index 18f108964..ad851c77e 100644 --- a/docs/mkdocs/docs/api/basic_json/at.md +++ b/docs/mkdocs/docs/api/basic_json/at.md @@ -241,4 +241,6 @@ Strong exception safety: if an exception occurs, the original value stays intact 3. Added in version 3.11.0. Fixed in version 3.13.0 to consistently accept `std::string_view`-convertible keys, as already supported by [`operator[]`](operator[].md), [`value`](value.md), [`find`](find.md), and other lookup functions. -4. Added in version 2.0.0. +4. Added in version 2.0.0. Throws [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) instead of +[`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) for a one-character array index that is not +a digit (e.g., `/x`) in version 3.13.0, as it already did for longer ones. diff --git a/docs/mkdocs/docs/api/basic_json/operator[].md b/docs/mkdocs/docs/api/basic_json/operator[].md index 74996d629..f31d90072 100644 --- a/docs/mkdocs/docs/api/basic_json/operator[].md +++ b/docs/mkdocs/docs/api/basic_json/operator[].md @@ -286,4 +286,6 @@ Strong exception safety: if an exception occurs, the original value stays intact 3. Added in version 3.11.0. Fixed in version 3.13.0 to consistently accept `std::string_view`-convertible keys, as already supported by [`at`](at.md), [`value`](value.md), [`find`](find.md), and other lookup functions. 4. Added in version 2.0.0. A missing array index in the const version is guarded by a runtime assertion since - version 3.13.0. + version 3.13.0. Throws [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) instead of +[`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) for a one-character array index that is not +a digit (e.g., `/x`) in version 3.13.0, as it already did for longer ones. diff --git a/docs/mkdocs/docs/api/basic_json/patch.md b/docs/mkdocs/docs/api/basic_json/patch.md index 8c2d7f231..951ee46e6 100644 --- a/docs/mkdocs/docs/api/basic_json/patch.md +++ b/docs/mkdocs/docs/api/basic_json/patch.md @@ -112,3 +112,6 @@ is thrown. In any case, the original value is not changed: the patch is applied location has a non-object/non-array parent in version 3.13.0. - Added [`out_of_range.414`](../../home/exceptions.md#jsonexceptionout_of_range414) and rejected a "move" operation whose "from" location is a proper prefix of its "path" location instead of silently producing a corrupted result in version 3.13.0. +- Throws [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) instead of + [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) for a one-character array index that is + not a digit (e.g., `/x`) in version 3.13.0, as it already did for longer ones. diff --git a/docs/mkdocs/docs/api/basic_json/patch_inplace.md b/docs/mkdocs/docs/api/basic_json/patch_inplace.md index 2d46a2cc8..dff8b095b 100644 --- a/docs/mkdocs/docs/api/basic_json/patch_inplace.md +++ b/docs/mkdocs/docs/api/basic_json/patch_inplace.md @@ -110,3 +110,6 @@ function throws an exception. location has a non-object/non-array parent in version 3.13.0. - Added [`out_of_range.414`](../../home/exceptions.md#jsonexceptionout_of_range414) and rejected a "move" operation whose "from" location is a proper prefix of its "path" location instead of silently producing a corrupted result in version 3.13.0. +- Throws [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) instead of + [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) for a one-character array index that is + not a digit (e.g., `/x`) in version 3.13.0, as it already did for longer ones. diff --git a/docs/mkdocs/docs/api/basic_json/unflatten.md b/docs/mkdocs/docs/api/basic_json/unflatten.md index a8f0a60b0..661ffa3e8 100644 --- a/docs/mkdocs/docs/api/basic_json/unflatten.md +++ b/docs/mkdocs/docs/api/basic_json/unflatten.md @@ -36,8 +36,9 @@ The function can throw the following exceptions: - Throws [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) if an array index in a key is not a number; example: `"array index 'one' is not a number"` - Throws [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) if a level becomes an array - (because one of its keys is `0`) and another key at that level cannot be an array index; example: - `"unresolved reference token 'x'"` + (because one of its keys is `0`) and another key at that level begins with a digit but is not a valid array index + (such as `1a`), or is `-`; example: + `"unresolved reference token '-'"` ## Complexity @@ -80,3 +81,6 @@ Apart from these two cases, for a JSON value `j`, the following is always true: - Added in version 2.0.0. - Made the array/object decision independent of the object's iteration order in version 3.13.0. +- Throws [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) instead of + [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) for a one-character array index that is + not a digit (e.g., `/x`) in version 3.13.0, as it already did for longer ones. diff --git a/docs/mkdocs/docs/api/basic_json/value.md b/docs/mkdocs/docs/api/basic_json/value.md index 80f5691dc..f8a1c5528 100644 --- a/docs/mkdocs/docs/api/basic_json/value.md +++ b/docs/mkdocs/docs/api/basic_json/value.md @@ -227,4 +227,6 @@ changes to any JSON value. [`operator[]`](operator[].md), [`at`](at.md), [`find`](find.md), and other lookup functions. 3. Added in version 2.0.2. Extended to work with arrays in version 3.13.0, including fixing an issue where resolving `ptr` through an array unexpectedly threw `out_of_range` instead of returning the resolved element (or - `default_value`, as documented). + `default_value`, as documented). Throws [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) + instead of returning `default_value` for a one-character array index that is not a digit (e.g., `/x`) in version + 3.13.0, as it already did for longer ones. diff --git a/docs/mkdocs/docs/home/exceptions.md b/docs/mkdocs/docs/home/exceptions.md index d6fdce37a..32312334a 100644 --- a/docs/mkdocs/docs/home/exceptions.md +++ b/docs/mkdocs/docs/home/exceptions.md @@ -278,7 +278,9 @@ In a JSON Pointer, only `~0` and `~1` are valid escape sequences. ### json.exception.parse_error.109 -A JSON Pointer array index must be a number. +A JSON Pointer array index must be a number. This exception is thrown for an array index that does not begin with a +digit, except for `-` and the empty reference token, which throw [`out_of_range.404`](#jsonexceptionout_of_range404) +where they cannot be resolved. !!! failure "Example messages" @@ -289,6 +291,11 @@ A JSON Pointer array index must be a number. [json.exception.parse_error.109] parse error: array index '+1' is not a number ``` +!!! note + + Before version 3.13.0, a one-character array index that is not a digit (e.g., `x`) threw + [`out_of_range.404`](#jsonexceptionout_of_range404) instead. + ### json.exception.parse_error.110 When parsing a [binary format](../features/binary_formats/index.md), the byte vector ends before the complete value has @@ -871,7 +878,8 @@ The provided key was not found in the JSON object. ### json.exception.out_of_range.404 -A reference token in a JSON Pointer could not be resolved. +A reference token in a JSON Pointer could not be resolved, for instance an array index that begins with a digit but +contains other characters (e.g., `1a`), or `-` where it cannot be used. !!! failure "Example message" diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index cff8420be..1b439f9bf 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -270,7 +270,7 @@ class json_pointer { ok, ///< @a s is a valid, representable array index leading_zero, ///< @a s begins with '0' but has more than one character - not_a_number, ///< @a s does not begin with a digit + not_a_number, ///< @a s is neither empty nor "-" and does not begin with a digit unresolved, ///< @a s could not be converted to an integer exceeds_size_type ///< @a s converts to an integer that exceeds size_type }; @@ -297,8 +297,10 @@ class json_pointer return array_index_status::leading_zero; } - // error condition (cf. RFC 6901, Sect. 4) - if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && !(s[0] >= '1' && s[0] <= '9'))) + // error condition (cf. RFC 6901, Sect. 4); this also covers single- + // character tokens, so "/x" and "/xy" fail alike; "-" and the empty + // token are left to the conversion below and are reported as unresolved + if (JSON_HEDLEY_UNLIKELY(!s.empty() && s != "-" && !(s[0] >= '0' && s[0] <= '9'))) { return array_index_status::not_a_number; } diff --git a/include/nlohmann/ordered_map.hpp b/include/nlohmann/ordered_map.hpp index 5f0be7d28..fdcf4a5c0 100644 --- a/include/nlohmann/ordered_map.hpp +++ b/include/nlohmann/ordered_map.hpp @@ -240,6 +240,14 @@ public: // ^ ^ // first last + // Note on conformance: before C++20, [basic.life]/8 did not allow an + // object of a type with a const member (like value_type's const Key) + // to transparently replace the destroyed one, so strictly, accessing + // it through the vector's existing pointers would have required + // std::launder (which does not exist before C++17). C++20 dropped that + // condition (P1971R0, NB comment US 041). Compilers have always treated + // this pattern as intended, so it is kept deliberately. + // Since we cannot move const Keys, we re-construct them in place. // We start at first and re-construct (viz. copy) the elements from // the back of the vector. Example for the first iteration: diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index a8a9bab3e..c4f7ab348 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -20283,7 +20283,7 @@ class json_pointer { ok, ///< @a s is a valid, representable array index leading_zero, ///< @a s begins with '0' but has more than one character - not_a_number, ///< @a s does not begin with a digit + not_a_number, ///< @a s is neither empty nor "-" and does not begin with a digit unresolved, ///< @a s could not be converted to an integer exceeds_size_type ///< @a s converts to an integer that exceeds size_type }; @@ -20310,8 +20310,10 @@ class json_pointer return array_index_status::leading_zero; } - // error condition (cf. RFC 6901, Sect. 4) - if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && !(s[0] >= '1' && s[0] <= '9'))) + // error condition (cf. RFC 6901, Sect. 4); this also covers single- + // character tokens, so "/x" and "/xy" fail alike; "-" and the empty + // token are left to the conversion below and are reported as unresolved + if (JSON_HEDLEY_UNLIKELY(!s.empty() && s != "-" && !(s[0] >= '0' && s[0] <= '9'))) { return array_index_status::not_a_number; } @@ -27883,6 +27885,14 @@ public: // ^ ^ // first last + // Note on conformance: before C++20, [basic.life]/8 did not allow an + // object of a type with a const member (like value_type's const Key) + // to transparently replace the destroyed one, so strictly, accessing + // it through the vector's existing pointers would have required + // std::launder (which does not exist before C++17). C++20 dropped that + // condition (P1971R0, NB comment US 041). Compilers have always treated + // this pattern as intended, so it is kept deliberately. + // Since we cannot move const Keys, we re-construct them in place. // We start at first and re-construct (viz. copy) the elements from // the back of the vector. Example for the first iteration: diff --git a/tests/src/unit-element_access2.cpp b/tests/src/unit-element_access2.cpp index 1b59d8add..7765c0601 100644 --- a/tests/src/unit-element_access2.cpp +++ b/tests/src/unit-element_access2.cpp @@ -535,6 +535,8 @@ TEST_CASE_TEMPLATE("element access 2", Json, nlohmann::json, nlohmann::ordered_j // Test malformed index (non-numeric) throws parse_error CHECK_THROWS_WITH_AS(j_array.value("/foo"_json_pointer, 1), "[json.exception.parse_error.109] parse error: array index 'foo' is not a number", typename Json::parse_error&); CHECK_THROWS_WITH_AS(j_array_const.value("/foo"_json_pointer, 1), "[json.exception.parse_error.109] parse error: array index 'foo' is not a number", typename Json::parse_error&); + CHECK_THROWS_WITH_AS(j_array.value("/x"_json_pointer, 1), "[json.exception.parse_error.109] parse error: array index 'x' is not a number", typename Json::parse_error&); + CHECK_THROWS_WITH_AS(j_array_const.value("/x"_json_pointer, 1), "[json.exception.parse_error.109] parse error: array index 'x' is not a number", typename Json::parse_error&); // Test leading-zero index throws parse_error CHECK_THROWS_WITH_AS(j_array.value("/01"_json_pointer, 1), "[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'", typename Json::parse_error&); diff --git a/tests/src/unit-json_patch.cpp b/tests/src/unit-json_patch.cpp index 55775c0b6..9e83edbcf 100644 --- a/tests/src/unit-json_patch.cpp +++ b/tests/src/unit-json_patch.cpp @@ -1713,6 +1713,21 @@ TEST_CASE("JSON patch - move where 'from' is a proper prefix of 'path' (regressi CHECK(doc.patch(patch) == R"({"b": 1})"_json); } + SECTION("array index tokens that are not a number") + { + json const doc = R"({"a": [1, 2]})"_json; + + // "-" cannot be removed and is reported as unresolved + json const patch_dash = {{{"op", "remove"}, {"path", "/a/-"}}}; + CHECK_THROWS_WITH_AS(doc.patch(patch_dash), "[json.exception.out_of_range.404] unresolved reference token '-'", json::out_of_range&); + + // a single character is reported like a longer token + json const patch_x = {{{"op", "remove"}, {"path", "/a/x"}}}; + CHECK_THROWS_WITH_AS(doc.patch(patch_x), "[json.exception.parse_error.109] parse error: array index 'x' is not a number", json::parse_error&); + json const patch_xy = {{{"op", "remove"}, {"path", "/a/xy"}}}; + CHECK_THROWS_WITH_AS(doc.patch(patch_xy), "[json.exception.parse_error.109] parse error: array index 'xy' is not a number", json::parse_error&); + } + SECTION("the array-append token '-' is an ordinary child token") { // "-" (append-to-array) addresses a location *inside* the array, diff --git a/tests/src/unit-json_pointer.cpp b/tests/src/unit-json_pointer.cpp index 1faf511f4..658a7fa8d 100644 --- a/tests/src/unit-json_pointer.cpp +++ b/tests/src/unit-json_pointer.cpp @@ -421,6 +421,27 @@ TEST_CASE("JSON pointers") CHECK_THROWS_WITH_AS(json({{"/list/0", 1}, {"/list/1", 2}, {"/list/three", 3}}).unflatten(), "[json.exception.parse_error.109] parse error: array index 'three' is not a number", json::parse_error&); + // a single-character token that is not a digit is reported like a + // longer one (parse_error.109), not as unresolved (out_of_range.404) + CHECK_THROWS_WITH_AS(j["/x"_json_pointer] = 1, + "[json.exception.parse_error.109] parse error: array index 'x' is not a number", json::parse_error&); + CHECK_THROWS_WITH_AS(j_const["/x"_json_pointer] == 1, + "[json.exception.parse_error.109] parse error: array index 'x' is not a number", json::parse_error&); + CHECK_THROWS_WITH_AS(j.at("/x"_json_pointer) = 1, + "[json.exception.parse_error.109] parse error: array index 'x' is not a number", json::parse_error&); + CHECK_THROWS_WITH_AS(j_const.at("/x"_json_pointer) == 1, + "[json.exception.parse_error.109] parse error: array index 'x' is not a number", json::parse_error&); + CHECK_THROWS_WITH_AS(j.at("/+"_json_pointer), + "[json.exception.parse_error.109] parse error: array index '+' is not a number", json::parse_error&); + CHECK(!j.contains("/x"_json_pointer)); + CHECK(!j_const.contains("/x"_json_pointer)); + CHECK_THROWS_WITH_AS(json({{"/list/0", 1}, {"/list/x", 2}}).unflatten(), + "[json.exception.parse_error.109] parse error: array index 'x' is not a number", json::parse_error&); + + // "-" is a valid reference token, so it is still reported as unresolved + CHECK_THROWS_WITH_AS(json({{"/list/0", 1}, {"/list/-", 2}}).unflatten(), + "[json.exception.out_of_range.404] unresolved reference token '-'", json::out_of_range&); + // assign to "-" j["/-"_json_pointer] = 99; CHECK(j == json({1, 13, 3, 33, nullptr, 55, 99})); From 6197341a21fe5045451a8cf3a0e49038428b1d35 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 11 Oct 2026 08:21:51 +0200 Subject: [PATCH 5/9] Clean up non-code files (#5801) * Clean up README, contribution guide, and repository metadata - REUSE.toml: fix the Hedley path and SPDX id (CC0-1.0), mark the docset icons as the public-domain JSON logo - CITATION.cff: v3.12.0 was released on 2025-04-11 - FILES.md: list all workflows, describe the Meson option defaults - README: ctest -LE, table of contents, typos, stale Android/MinGW advice, moved links, merge duplicate Thanks entries, list the analysis tools used in CI - CONTRIBUTING: iterative parser, json_literals.hpp is generated, links - .gitignore: backups and release outputs; .gitattributes: mark generated files - labeler: label other build systems and .github documentation - MODULE.bazel: add the module version Signed-off-by: Niels Lohmann * Clean up CI, CMake, Makefile, and Bazel files - CMakeLists.txt: avoid VERSION_GREATER_EQUAL, which CMake < 3.7 lacks - ci.cmake: test JSON_DisableTupleReferenceConversion in ci_cmake_flags, remove unused variables and unreachable per-compiler targets, look up Clang tools consistently, forward CMAKE_CXX_FLAGS to ci_module_cpp20, format json_literals.hpp and remove backups in ci_test_amalgamation - BUILD.bazel: add json_literals.hpp to the single-header target - workflows: format json_literals.hpp before copying it, drop the obsolete natvis --version plumbing, name natvis and macro_builder in failure messages, drop the duplicate amalgamation job, install Valgrind only where needed, republish docs on version bumps, fix stale names - Makefile: complete .PHONY and help, check-amalgamation always restores the checked-in files, natvis uses its own venv, macro_builder_check installs astyle, clean removes the fuzzer binaries - remove tools/amalgamate/config_json_view.json (json_view.hpp is not on develop yet) Signed-off-by: Niels Lohmann * Fix inaccuracies in the documentation - version history: json_base_class_t (3.11.3), JSON_HAS_CPP_11 (3.10.0), JSON_HAS_RANGES exclusions, define-type macros, ABI tags - releases: 3.12.0 raised the minimum CMake version - from_*: the (ptr, len) overloads are deleted, not removed, in 4.0.0 - contains/count/find: document the deleted integral overloads - add JSON_HAS_RANGE_VIEW_CONVERSION and list the JSON_HAS_* macros in the macro overview - mention BON8 and error_handler where binary formats are listed - broken links, outdated URLs, warning count, Hunter v0.26.12 - copy_markdown_source hook: expand snippets in the Markdown copies Signed-off-by: Niels Lohmann * Pass /EHsc to the Windows C++20 module build ci_module_cpp20 now forwards CMAKE_CXX_FLAGS to the module build. The Windows workflow sets CMAKE_CXX_FLAGS, which replaces CMake's MSVC defaults including /EHsc, so failed with C4530 under /WX. Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- .gitattributes | 3 + .github/CODEOWNERS | 2 +- .github/CONTRIBUTING.md | 24 +- .github/ISSUE_TEMPLATE/bug.yaml | 4 +- .github/labeler.yml | 11 + .github/workflows/check_amalgamation.yml | 20 +- .../workflows/comment_check_amalgamation.yml | 2 +- .github/workflows/publish_documentation.yml | 1 + .github/workflows/ubuntu.yml | 5 +- .github/workflows/windows.yml | 6 +- .gitignore | 15 +- BUILD.bazel | 1 + CITATION.cff | 2 +- CMakeLists.txt | 2 +- FILES.md | 11 +- MODULE.bazel | 1 + Makefile | 63 +- README.md | 870 +++++++++--------- REUSE.toml | 10 +- cmake/ci.cmake | 31 +- cmake/scripts/gen_bazel_build_file.cmake | 1 + docs/README.md | 4 +- docs/mkdocs/docs/api/basic_json/contains.md | 9 + docs/mkdocs/docs/api/basic_json/count.md | 7 + .../docs/api/basic_json/error_handler_t.md | 4 +- docs/mkdocs/docs/api/basic_json/find.md | 9 + .../mkdocs/docs/api/basic_json/from_bjdata.md | 3 +- docs/mkdocs/docs/api/basic_json/from_bon8.md | 3 +- docs/mkdocs/docs/api/basic_json/from_bson.md | 3 +- docs/mkdocs/docs/api/basic_json/from_cbor.md | 3 +- .../docs/api/basic_json/from_msgpack.md | 3 +- .../mkdocs/docs/api/basic_json/from_ubjson.md | 3 +- .../docs/api/basic_json/json_base_class_t.md | 2 +- docs/mkdocs/docs/api/macros/index.md | 1 + .../mkdocs/docs/api/macros/json_has_cpp_11.md | 2 +- .../macros/json_has_range_view_conversion.md | 43 + .../mkdocs/docs/api/macros/json_has_ranges.md | 5 +- .../api/macros/json_strict_binary_utf8.md | 5 +- .../macros/nlohmann_define_derived_type.md | 2 + .../macros/nlohmann_define_type_intrusive.md | 3 + .../nlohmann_define_type_non_intrusive.md | 3 + .../api/macros/nlohmann_json_namespace.md | 4 +- .../macros/nlohmann_json_namespace_begin.md | 2 +- docs/mkdocs/docs/community/assurance_case.md | 2 +- .../docs/community/quality_assurance.md | 4 +- docs/mkdocs/docs/features/conversions.md | 3 +- docs/mkdocs/docs/features/macros.md | 39 + docs/mkdocs/docs/features/namespace.md | 6 + docs/mkdocs/docs/home/architecture.md | 2 +- docs/mkdocs/docs/home/design_goals.md | 4 +- docs/mkdocs/docs/home/exceptions.md | 4 +- docs/mkdocs/docs/home/releases.md | 2 +- .../docs/integration/hunter/CMakeLists.txt | 4 +- docs/mkdocs/hooks/copy_markdown_source.py | 31 +- docs/mkdocs/includes/glossary.md | 2 +- docs/mkdocs/mkdocs.yml | 1 + tools/amalgamate/config_json_view.json | 9 - tools/generate_natvis/README.md | 3 +- 58 files changed, 751 insertions(+), 573 deletions(-) create mode 100644 .gitattributes create mode 100644 docs/mkdocs/docs/api/macros/json_has_range_view_conversion.md delete mode 100644 tools/amalgamate/config_json_view.json diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..314c895f7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +# generated files are collapsed in diffs and excluded from language statistics +single_include/** linguist-generated +nlohmann_json.natvis linguist-generated diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e1e1040fc..a23b24c31 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,5 +1,5 @@ # JSON for Modern C++ was originally written by Niels Lohmann. -# Since 2013, over 250 contributors have helped to improve the library. +# Since 2013, over 400 contributors have helped to improve the library. # This CODEOWNERS file is only to make sure that @nlohmann is requested # for a code review in case of a pull request. diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 1e42fe769..52c8f3385 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -98,7 +98,7 @@ $ ctest --test-dir build -j 10 The test suite should report: ``` -100% tests passed, 0 tests failed out of 98 +100% tests passed, 0 tests failed out of ``` #### Add tests @@ -157,8 +157,9 @@ and each page's title (H1) and declaration by ### Amalgamate the source code The single-header files -[`single_include/nlohmann/json.hpp`](https://github.com/nlohmann/json/blob/develop/single_include/nlohmann/json.hpp) and -[`single_include/nlohmann/json_fwd.hpp`](https://github.com/nlohmann/json/blob/develop/single_include/nlohmann/json_fwd.hpp) +[`single_include/nlohmann/json.hpp`](https://github.com/nlohmann/json/blob/develop/single_include/nlohmann/json.hpp), +[`single_include/nlohmann/json_fwd.hpp`](https://github.com/nlohmann/json/blob/develop/single_include/nlohmann/json_fwd.hpp), and +[`single_include/nlohmann/json_literals.hpp`](https://github.com/nlohmann/json/blob/develop/single_include/nlohmann/json_literals.hpp) are **generated** from the source files in the [`include/nlohmann` directory](https://github.com/nlohmann/json/tree/develop/include/nlohmann). **Do not** edit the files directly; instead, modify the include/nlohmann sources and regenerate the files by executing: @@ -181,7 +182,7 @@ The amalgamation check in CI fails if any of these generated files is out of dat ## Recommended documentation -- The library’s [README file](https://github.com/nlohmann/json/blob/master/README.md) is an excellent starting point to +- The library’s [README file](https://github.com/nlohmann/json/blob/develop/README.md) is an excellent starting point to understand its functionality. - The [documentation page](https://json.nlohmann.me) is the reference documentation of the library. - [RFC 8259](https://datatracker.ietf.org/doc/html/rfc8259) is the reference for the JavaScript Object Notation (JSON) @@ -220,9 +221,9 @@ feature macros then allows users to test their code against the library in the n ### Break C++11 language conformance This library is designed to work with C++11 and later. This means that any -[supported C++11 compiler](https://github.com/nlohmann/json/blob/master/README.md#supported-compilers) should compile +[supported C++11 compiler](https://github.com/nlohmann/json/blob/develop/README.md#supported-compilers) should compile the library without problems. Some compilers like GCC 4.7 (and earlier), Clang 3.3 (and earlier), or Microsoft Visual -Studio 13.0 and earlier are known not to work due to missing or incomplete C++11 support. +C++ 2013 and earlier are known not to work due to missing or incomplete C++11 support. Please do not add features that do not work with the mentioned supported compilers. Please guard features from C++14 and later against the respective [`JSON_HAS_CPP_14`](https://json.nlohmann.me/api/macros/json_has_cpp_11/) macros. @@ -238,12 +239,11 @@ extension. The following areas really need contribution and are always welcomed: - Extending the **continuous integration** toward more exotic compilers such as Android NDK, Intel's Compiler, or the - bleeding-edge versions Clang. -- Improving the efficiency of the **JSON parser**. The current parser is implemented as a naive recursive descent parser - with hand-coded string handling. More sophisticated approaches like LALR parsers would be really appreciated. That - said, parser generators like Bison or ANTLR do not play nice with single-header files -- I really would like to keep - the parser inside the `json.hpp` header, and I am not aware of approaches similar to [`re2c`](http://re2c.org) for - parsing. + bleeding-edge versions of Clang. +- Improving the efficiency of the **JSON parser**. The current parser is a hand-written iterative parser that keeps an + explicit stack instead of recursing, with hand-coded string handling. Speeding up the lexer or the value construction + would be appreciated. Parser generators like Bison or ANTLR do not play nice with single-header files -- I really + would like to keep the parser inside the `json.hpp` header. - Extending and updating existing **benchmarks** to include (the most recent version of) this library. Though efficiency is not everything, speed and memory consumption are very important characteristics for C++ developers, so having proper comparisons would be interesting. diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml index ed426675d..d578e592f 100644 --- a/.github/ISSUE_TEMPLATE/bug.yaml +++ b/.github/ISSUE_TEMPLATE/bug.yaml @@ -52,7 +52,7 @@ body: If possible, provide a small and self-contained example that triggers the bug. Please understand that we cannot analyze and debug large code bases. Please do not paste screenshots here. - render: Shell + render: cpp - type: textarea id: output attributes: @@ -79,7 +79,7 @@ body: description: > Which version of the library did you use? If it is a released version, please enter the version number (e.g., 3.12.0). Otherwise, please enter - the commit hash. If you got the library from another source as the + the commit hash. If you got the library from another source than the GitHub repository (e.g., via a package manager), please also state this. validations: diff --git a/.github/labeler.yml b/.github/labeler.yml index 5708f84f5..d1ab6f6c2 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -21,6 +21,17 @@ labels: files: - "cmake/.*" +# other build systems +- label: "CMake" + files: + - "(meson\\.build|meson_options\\.txt|BUILD\\.bazel|MODULE\\.bazel|Package\\.swift)" + +- label: "documentation" + files: + - ".github/ISSUE_TEMPLATE/.*" + - ".github/config\\.yml" + - ".github/(CONTRIBUTING|SECURITY|CODE_OF_CONDUCT|PULL_REQUEST_TEMPLATE)\\.md" + - label: "CI" files: - ".github/workflows/.*" diff --git a/.github/workflows/check_amalgamation.yml b/.github/workflows/check_amalgamation.yml index a497b4ce4..eb964c979 100644 --- a/.github/workflows/check_amalgamation.yml +++ b/.github/workflows/check_amalgamation.yml @@ -83,8 +83,7 @@ jobs: # byte for byte), so there is nothing to gain from checking out a # separate copy, and doing so would make this step fail on a PR # that adds support for a new dispatch table until that PR itself - # merges to develop, the same way generate_natvis.py's --version - # requirement briefly did. + # merges to develop. TMPDIR=$(mktemp -d ./macro_builder_check.XXXXXX) c++ -std=c++11 tools/macro_builder/main.cpp -o "$TMPDIR/macro_builder" "$TMPDIR/macro_builder" > "$TMPDIR/paste.hpp" @@ -117,7 +116,6 @@ jobs: python3 $TOOL_DIR/amalgamate.py -c $TOOL_DIR/config_json.json -s . python3 $TOOL_DIR/amalgamate.py -c $TOOL_DIR/config_json_fwd.json -s . - cp include/nlohmann/json_literals.hpp $INCLUDE_DIR/json_literals.hpp # the header list of the Bazel "json" target must match the files in include/ cmake -P cmake/scripts/gen_bazel_build_file.cmake @@ -138,17 +136,13 @@ jobs: ${{ github.workspace }}/venv/bin/astyle --project=tools/astyle/.astylerc --suffix=none --quiet \ $(find $SOURCE_DIRS -type f \( -name '*.hpp' -o -name '*.cpp' -o -name '*.cu' \) -not -path 'tests/thirdparty/*' -not -path 'tests/abi/include/nlohmann/*' | sort) + # json_literals.hpp is copied verbatim from the (now formatted) include/ + # directory, like `make amalgamate` does + cp include/nlohmann/json_literals.hpp $INCLUDE_DIR/json_literals.hpp + - name: Regenerate nlohmann_json.natvis run: | cd $MAIN_DIR - # Pass --version explicitly so this step also works with the tool - # copy from develop before this repository's own generate_natvis.py - # learns to derive the version itself: the older script requires - # --version, and the newer one accepts it as an explicit override. - ABI_MACROS=include/nlohmann/detail/abi_macros.hpp - VERSION_MAJOR=$(grep -m1 'define NLOHMANN_JSON_VERSION_MAJOR' $ABI_MACROS | grep -o '[0-9]\+') - VERSION_MINOR=$(grep -m1 'define NLOHMANN_JSON_VERSION_MINOR' $ABI_MACROS | grep -o '[0-9]\+') - VERSION_PATCH=$(grep -m1 'define NLOHMANN_JSON_VERSION_PATCH' $ABI_MACROS | grep -o '[0-9]\+') # The template is what this check verifies the committed natvis # file against, so take it from this PR rather than from the # develop checkout: generate_natvis.py loads the template from its @@ -156,7 +150,7 @@ jobs: # template could never pass. Like the macro_builder step above, # this only renders PR content in a job without credentials. cp tools/generate_natvis/nlohmann_json.natvis.j2 $NATVIS_TOOL_DIR/ - python3 $NATVIS_TOOL_DIR/generate_natvis.py --version "$VERSION_MAJOR.$VERSION_MINOR.$VERSION_PATCH" $MAIN_DIR + python3 $NATVIS_TOOL_DIR/generate_natvis.py $MAIN_DIR - name: Build patch and check for differences id: diff @@ -165,7 +159,7 @@ jobs: mkdir -p ${{ github.workspace }}/patch git diff --patch --no-color > ${{ github.workspace }}/patch/amalgamation.patch if [ -s ${{ github.workspace }}/patch/amalgamation.patch ]; then - echo "The source code has not been amalgamated/formatted correctly or BUILD.bazel is out of date. Diff:" + echo "The source code has not been amalgamated/formatted correctly, or BUILD.bazel, nlohmann_json.natvis, or the macro_builder tables in macro_scope.hpp are out of date ('make amalgamate BUILD.bazel natvis' fixes all but the tables, see tools/macro_builder/README.md). Diff:" cat ${{ github.workspace }}/patch/amalgamation.patch echo "has_diff=true" >> "$GITHUB_OUTPUT" else diff --git a/.github/workflows/comment_check_amalgamation.yml b/.github/workflows/comment_check_amalgamation.yml index d5bd23fc8..32bca8486 100644 --- a/.github/workflows/comment_check_amalgamation.yml +++ b/.github/workflows/comment_check_amalgamation.yml @@ -96,7 +96,7 @@ jobs: issue_number: issue_number, owner: context.repo.owner, repo: context.repo.repo, - body: '## 🔴 Amalgamation check failed! 🔴\nThe source code has not been amalgamated and/or formatted correctly, or `BUILD.bazel` is out of date.' + body: '## 🔴 Amalgamation check failed! 🔴\nThe source code has not been amalgamated and/or formatted correctly, or `BUILD.bazel`, `nlohmann_json.natvis`, or the macro_builder tables in `macro_scope.hpp` are out of date. `make amalgamate BUILD.bazel natvis` fixes all but the tables (see `tools/macro_builder/README.md`).' + (hasPatch ? '\n\n📎 A ready-to-apply patch is attached to the [failed workflow run](' + runUrl + ') as the `amalgamation-patch` artifact.' + ' Download it, then apply it locally from the repository root with:' + '\n\n```shell\ngit apply amalgamation.patch\n```\n\n' diff --git a/.github/workflows/publish_documentation.yml b/.github/workflows/publish_documentation.yml index 5e39574bc..3256c819b 100644 --- a/.github/workflows/publish_documentation.yml +++ b/.github/workflows/publish_documentation.yml @@ -15,6 +15,7 @@ on: - .github/SECURITY.md - cmake/clang_flags.cmake - cmake/gcc_flags.cmake + - include/nlohmann/detail/abi_macros.hpp - tests/fmt_formatter/project/main.cpp - tools/astyle/.astylerc workflow_dispatch: diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 3c94bc1e4..edce983a5 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -109,7 +109,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - target: [ci_test_amalgamation, ci_test_single_header, ci_cppcheck, ci_cpplint, ci_reproducible_tests, ci_non_git_tests, ci_offline_testdata, ci_reuse_compliance, ci_test_valgrind] + target: [ci_test_single_header, ci_cppcheck, ci_cpplint, ci_reproducible_tests, ci_non_git_tests, ci_offline_testdata, ci_reuse_compliance, ci_test_valgrind] steps: - name: Harden Runner uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1 @@ -117,6 +117,7 @@ jobs: egress-policy: audit - name: Install Valgrind + if: matrix.target == 'ci_test_valgrind' run: sudo apt-get update ; sudo apt-get install -y valgrind - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -279,7 +280,7 @@ jobs: persist-credentials: false - name: Get latest CMake and ninja uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2 - - name: Set env FORCE_STDCPPFS_FLAG for clang 7 / 8 / 9 / 10 + - name: Disable filesystem support (std::filesystem is unusable) for clang 7 / 8 / 9 / 10 run: echo "JSON_FORCED_GLOBAL_COMPILE_OPTIONS=-DJSON_HAS_FILESYSTEM=0;-DJSON_HAS_EXPERIMENTAL_FILESYSTEM=0" >> "$GITHUB_ENV" if: ${{ matrix.compiler == '7' || matrix.compiler == '8' || matrix.compiler == '9' || matrix.compiler == '10' }} - name: Run CMake diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index c5220e66c..0ac496124 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -187,7 +187,7 @@ jobs: - name: Test run: cd build ; ctest -j 10 -C Debug --output-on-failure - clang-cl-12: + clang-cl: runs-on: windows-2022 strategy: matrix: @@ -217,8 +217,10 @@ jobs: uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2 with: cmakeVersion: 4.3.4 + # CMAKE_CXX_FLAGS replaces CMake's MSVC defaults (which include /EHsc), and + # ci_module_cpp20 forwards it to the module build, so list /EHsc explicitly - name: Run CMake (Debug) - run: cmake -S . -B build -G "Visual Studio 17 2022" -DJSON_CI=ON -DCMAKE_CXX_FLAGS="/permissive- /std:c++latest /utf-8 /W4 /WX" + run: cmake -S . -B build -G "Visual Studio 17 2022" -DJSON_CI=ON -DCMAKE_CXX_FLAGS="/permissive- /std:c++latest /utf-8 /EHsc /W4 /WX" - name: Build run: cmake --build build --config Debug --target ci_module_cpp20 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 474b62347..e26648c61 100644 --- a/.gitignore +++ b/.gitignore @@ -27,7 +27,6 @@ /docs/docset/JSON_for_Modern_C++.tgz /docs/docset/docSet.dsidx /docs/mkdocs/.cache/ -/docs/mkdocs/docs/__pycache__/ /docs/mkdocs/hooks/__pycache__/ /docs/mkdocs/scripts/mermaid/node_modules/ /docs/mkdocs/site/ @@ -47,3 +46,17 @@ nlohmann_json.spdx # Bazel-related MODULE.bazel.lock + +# backups left by `make check-amalgamation` or the ci_test_amalgamation target if they are interrupted +/BUILD.bazel~ +/nlohmann_json.natvis~ +/single_include/nlohmann/*.hpp~ +/venv_astyle/ + +# release outputs of `make release` +/include.zip +/include.zip.asc +/json.tar.xz +/json.tar.xz.asc +/release_files/ +/single_include/nlohmann/*.asc diff --git a/BUILD.bazel b/BUILD.bazel index 268c3e959..08d242eef 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -81,6 +81,7 @@ cc_library( hdrs = [ "single_include/nlohmann/json.hpp", "single_include/nlohmann/json_fwd.hpp", + "single_include/nlohmann/json_literals.hpp", ], includes = ["single_include"], visibility = ["//visibility:public"], diff --git a/CITATION.cff b/CITATION.cff index eb5118999..9aad845a4 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -8,7 +8,7 @@ authors: website: https://nlohmann.me title: "JSON for Modern C++" version: 3.12.0 -date-released: 2025-04-07 +date-released: 2025-04-11 license: MIT repository-code: "https://github.com/nlohmann/json" url: https://json.nlohmann.me diff --git a/CMakeLists.txt b/CMakeLists.txt index 19f5dca30..1369b973e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,7 +30,7 @@ endif () option(NLOHMANN_JSON_BUILD_MODULES "Build C++ modules support" OFF) if(NLOHMANN_JSON_BUILD_MODULES) - if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.28) + if(NOT CMAKE_VERSION VERSION_LESS 3.28) message(STATUS "Building nlohmann.json C++ module") add_subdirectory(src/modules) else() diff --git a/FILES.md b/FILES.md index fbb57815a..63cb3e806 100644 --- a/FILES.md +++ b/FILES.md @@ -14,7 +14,7 @@ This file describes the source for supporting files; that is, files that are not The [GitHub Actions](https://docs.github.com/en/actions) workflows that build, test, and analyze the library. Each file in this folder defines one workflow: - `ubuntu.yml`, `macos.yml`, `windows.yml` — build and run the test suite on Linux, macOS, and Windows. -- `check_amalgamation.yml` — verify that the single-header amalgamation in `single_include` is up to date on pull requests. +- `check_amalgamation.yml` — verify on pull requests and pushes to `develop` that the generated files (the single-header amalgamation in `single_include`, `BUILD.bazel`, and `nlohmann_json.natvis`) are up to date. - `comment_check_amalgamation.yml` — comment on a pull request when the amalgamation check failed. - `cifuzz.yml` — run short fuzzing sessions via [OSS-Fuzz CIFuzz](https://google.github.io/oss-fuzz/getting-started/continuous-integration/) on pull requests. - `codeql-analysis.yml` — run [CodeQL](https://codeql.github.com) code scanning. @@ -24,7 +24,9 @@ The [GitHub Actions](https://docs.github.com/en/actions) workflows that build, t - `dependency-review.yml` — scan dependency changes in pull requests for known vulnerabilities. - `labeler.yml` — the "Pull Request Labeler" workflow (see `.github/labeler.yml`). - `stale.yml` — comment on and close stale issues and pull requests. -- `publish_documentation.yml` — build and publish the documentation on every merge to the `develop` branch. +- `publish_documentation.yml` — build and publish the documentation on pushes to the `develop` branch that touch the documentation sources (or the files it embeds). +- `check_docs_links.yml` — check the links of the documentation weekly, as external links can break without any change in this repository. +- `cancel_closed_pr_runs.yml` — cancel unfinished workflow runs of a pull request once it is closed or merged. Further documentation: @@ -266,8 +268,9 @@ meson setup builddir ninja -C builddir ``` -`meson_options.txt` defines the options, which mirror the CMake options that change the library's target (for example, -`-DDiagnostics=true`). Meson requires this file next to `meson.build`, so it is also part of `include.zip`. `make check_build_options` +`meson_options.txt` defines the options, which mirror the names of the CMake options that change the library's target (for example, +`-DDiagnostics=true`); their defaults may differ (for example, `MultipleHeaders` defaults to `false` in Meson, while +`JSON_MultipleHeaders` is `ON` in CMake). Meson requires this file next to `meson.build`, so it is also part of `include.zip`. `make check_build_options` ([`tools/check_build_options`](tools/check_build_options/README.md)) checks in CI that both files and the pkg-config files stay in sync with the CMake options. diff --git a/MODULE.bazel b/MODULE.bazel index 51754e7a3..6b06b8e7f 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -1,5 +1,6 @@ module( name = "nlohmann_json", + version = "3.12.0", compatibility_level = 1, ) diff --git a/Makefile b/Makefile index 093fbb7b5..3bd8da5ff 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: pretty clean ChangeLog.md release update_hedley update_hedley_undef BUILD.bazel natvis macro_builder_check check_build_options +.PHONY: all amalgamate check-amalgamation check_build_options pretty install_astyle clean ChangeLog.md release update_hedley update_hedley_undef BUILD.bazel natvis macro_builder_check run_benchmarks pvs_studio serve_header reuse spdx ########################################################################## # configuration @@ -41,12 +41,18 @@ all: @echo "BUILD.bazel - regenerate the Bazel BUILD file from the include/nlohmann sources" @echo "ChangeLog.md - generate ChangeLog file" @echo "check-amalgamation - check whether sources have been amalgamated and BUILD.bazel is up to date" + @echo "check_build_options - check that the Meson build and the pkg-config files offer the options of the CMake target" @echo "clean - remove built files" - @echo "fuzzing - see tests/fuzzing.md for how to build and run the fuzzers" + @echo "(fuzzers) - see tests/fuzzing.md, or run 'make -C tests fuzzers' to build the AFL fuzzers" @echo "macro_builder_check - check that macro_scope.hpp matches tools/macro_builder's output" @echo "natvis - regenerate nlohmann_json.natvis from the current ABI tags and version" @echo "pretty - beautify code with Artistic Style" + @echo "pvs_studio - run the PVS-Studio static analyzer and open the report" + @echo "release - create the release files (include.zip, json.tar.xz, single headers, signatures, hashes)" + @echo "reuse - annotate the source files with REUSE license headers and lint them" @echo "run_benchmarks - build and run benchmarks" + @echo "serve_header - serve the single header, re-amalgamated on demand (see tools/serve_header)" + @echo "spdx - generate the SPDX software bill of materials nlohmann_json.spdx" @echo "update_hedley - download Hedley and regenerate hedley.hpp / hedley_undef.hpp" @echo "update_hedley_undef - rebuild hedley_undef.hpp from the JSON_HEDLEY_* #define names in hedley.hpp" @@ -108,13 +114,18 @@ $(AMALGAMATED_FWD_FILE): $(SRCS) $(AMALGAMATED_LITERALS_FILE): include/nlohmann/json_literals.hpp cp include/nlohmann/json_literals.hpp $(AMALGAMATED_LITERALS_FILE) +NATVIS_VENV=tools/generate_natvis/venv + # regenerate nlohmann_json.natvis from the ABI tags and version in include/nlohmann/detail/abi_macros.hpp +# (the dependencies are installed into a virtual environment first) natvis: - python3 tools/generate_natvis/generate_natvis.py . + @test -d $(NATVIS_VENV) || python3 -mvenv $(NATVIS_VENV) + @$(NATVIS_VENV)/bin/python3 -c 'import jinja2' 2>/dev/null || $(NATVIS_VENV)/bin/pip3 install --quiet -r tools/generate_natvis/requirements.txt + $(NATVIS_VENV)/bin/python3 tools/generate_natvis/generate_natvis.py . # regenerate the two tools/macro_builder blocks of $(MACRO_SCOPE_HPP) (see its README.md) and diff against the # checked-in header; phony, because it never writes $(MACRO_SCOPE_HPP) itself -macro_builder_check: +macro_builder_check: install_astyle @set -e; \ TMPDIR=$$(mktemp -d ./macro_builder_check.XXXXXX); \ trap 'rm -rf "$$TMPDIR"' EXIT; \ @@ -131,27 +142,27 @@ macro_builder_check: check_build_options: python3 tools/check_build_options/check_build_options.py . -# check if file single_include/nlohmann/json.hpp has been amalgamated from the nlohmann sources +# check if the single headers have been amalgamated from the nlohmann sources and if BUILD.bazel and +# nlohmann_json.natvis are up to date; all checks are run, and the checked-in files are always restored check-amalgamation: - @mv $(AMALGAMATED_FILE) $(AMALGAMATED_FILE)~ - @mv $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_FWD_FILE)~ - @mv $(AMALGAMATED_LITERALS_FILE) $(AMALGAMATED_LITERALS_FILE)~ - @$(MAKE) amalgamate - @diff $(AMALGAMATED_FILE) $(AMALGAMATED_FILE)~ || (echo "===================================================================\n Amalgamation required! Please read the contribution guidelines\n in file .github/CONTRIBUTING.md.\n===================================================================" ; mv $(AMALGAMATED_FILE)~ $(AMALGAMATED_FILE) ; false) - @diff $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_FWD_FILE)~ || (echo "===================================================================\n Amalgamation required! Please read the contribution guidelines\n in file .github/CONTRIBUTING.md.\n===================================================================" ; mv $(AMALGAMATED_FWD_FILE)~ $(AMALGAMATED_FWD_FILE) ; false) - @diff $(AMALGAMATED_LITERALS_FILE) $(AMALGAMATED_LITERALS_FILE)~ || (echo "===================================================================\n Amalgamation required! Please read the contribution guidelines\n in file .github/CONTRIBUTING.md.\n===================================================================" ; mv $(AMALGAMATED_LITERALS_FILE)~ $(AMALGAMATED_LITERALS_FILE) ; false) - @mv $(AMALGAMATED_FILE)~ $(AMALGAMATED_FILE) - @mv $(AMALGAMATED_FWD_FILE)~ $(AMALGAMATED_FWD_FILE) - @mv $(AMALGAMATED_LITERALS_FILE)~ $(AMALGAMATED_LITERALS_FILE) - @mv BUILD.bazel BUILD.bazel~ - @$(MAKE) BUILD.bazel - @diff BUILD.bazel BUILD.bazel~ || (echo "===================================================================\n BUILD.bazel is out of date! Please run 'make BUILD.bazel'.\n===================================================================" ; mv BUILD.bazel~ BUILD.bazel ; false) - @mv BUILD.bazel~ BUILD.bazel - @mv nlohmann_json.natvis nlohmann_json.natvis~ - @$(MAKE) natvis - @diff nlohmann_json.natvis nlohmann_json.natvis~ || (echo "===================================================================\n nlohmann_json.natvis is out of date! Please run 'make natvis'.\n===================================================================" ; mv nlohmann_json.natvis~ nlohmann_json.natvis ; false) - @mv nlohmann_json.natvis~ nlohmann_json.natvis - @$(MAKE) macro_builder_check + @status=0; \ + banner() { printf '===================================================================\n %s\n===================================================================\n' "$$1"; }; \ + for FILE in $(AMALGAMATED_FILE) $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_LITERALS_FILE) BUILD.bazel nlohmann_json.natvis; do \ + mv "$$FILE" "$$FILE~" || exit 1; \ + done; \ + $(MAKE) amalgamate || status=1; \ + $(MAKE) BUILD.bazel || status=1; \ + $(MAKE) natvis || status=1; \ + for FILE in $(AMALGAMATED_FILE) $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_LITERALS_FILE); do \ + diff "$$FILE" "$$FILE~" || { banner "$$FILE is out of date! Amalgamation required! Please read the contribution guidelines in file .github/CONTRIBUTING.md."; status=1; }; \ + done; \ + diff BUILD.bazel BUILD.bazel~ || { banner "BUILD.bazel is out of date! Please run 'make BUILD.bazel'."; status=1; }; \ + diff nlohmann_json.natvis nlohmann_json.natvis~ || { banner "nlohmann_json.natvis is out of date! Please run 'make natvis'."; status=1; }; \ + for FILE in $(AMALGAMATED_FILE) $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_LITERALS_FILE) BUILD.bazel nlohmann_json.natvis; do \ + mv "$$FILE~" "$$FILE"; \ + done; \ + $(MAKE) macro_builder_check || status=1; \ + exit $$status # generate the Bazel BUILD file; phony, because a removed header would not trigger a rebuild BUILD.bazel: @@ -212,8 +223,8 @@ release: include.zip json.tar.xz # clean up clean: - rm -fr fuzz fuzz-testing *.dSYM tests/*.dSYM - rm -fr cmake-build-benchmarks fuzz-testing cmake-build-pvs-studio release_files + rm -fr *.dSYM tests/*.dSYM tests/parse_*_fuzzer + rm -fr cmake-build-benchmarks cmake-build-pvs-studio release_files $(MAKE) clean -Cdocs diff --git a/README.md b/README.md index c6c5613d5..e4b8932fc 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ - [Pkg-config](#pkg-config) - [License](#license) - [Contact](#contact) +- [Security](#security) - [Thanks](#thanks) - [Used third-party tools](#used-third-party-tools) - [Notes](#notes) @@ -112,9 +113,9 @@ There is also a [**docset**](https://github.com/Kapeli/Dash-User-Contributions/t ## Quick reference -- **Constructors** [basic_json](https://json.nlohmann.me/api/basic_json/basic_json), [array](https://json.nlohmann.me/api/basic_json/array), [binary](https://json.nlohmann.me/api/basic_json/binary), [object](https://json.nlohmann.me/api/basic_json/object) +- **Constructors**: [basic_json](https://json.nlohmann.me/api/basic_json/basic_json), [array](https://json.nlohmann.me/api/basic_json/array), [binary](https://json.nlohmann.me/api/basic_json/binary), [object](https://json.nlohmann.me/api/basic_json/object) - **Object inspection**: [type](https://json.nlohmann.me/api/basic_json/type), [operator value_t](https://json.nlohmann.me/api/basic_json/operator_value_t), [type_name](https://json.nlohmann.me/api/basic_json/type_name), [is_primitive](https://json.nlohmann.me/api/basic_json/is_primitive), [is_structured](https://json.nlohmann.me/api/basic_json/is_structured), [is_null](https://json.nlohmann.me/api/basic_json/is_null), [is_boolean](https://json.nlohmann.me/api/basic_json/is_boolean), [is_number](https://json.nlohmann.me/api/basic_json/is_number), [is_number_integer](https://json.nlohmann.me/api/basic_json/is_number_integer), [is_number_unsigned](https://json.nlohmann.me/api/basic_json/is_number_unsigned), [is_number_float](https://json.nlohmann.me/api/basic_json/is_number_float), [is_object](https://json.nlohmann.me/api/basic_json/is_object), [is_array](https://json.nlohmann.me/api/basic_json/is_array), [is_string](https://json.nlohmann.me/api/basic_json/is_string), [is_binary](https://json.nlohmann.me/api/basic_json/is_binary), [is_discarded](https://json.nlohmann.me/api/basic_json/is_discarded) -- **Value access**; [get](https://json.nlohmann.me/api/basic_json/get), [get_to](https://json.nlohmann.me/api/basic_json/get_to), [get_ptr](https://json.nlohmann.me/api/basic_json/get_ptr), [get_ref](https://json.nlohmann.me/api/basic_json/get_ref), [operator ValueType](https://json.nlohmann.me/api/basic_json/operator_ValueType), [get_binary](https://json.nlohmann.me/api/basic_json/get_binary) +- **Value access**: [get](https://json.nlohmann.me/api/basic_json/get), [get_to](https://json.nlohmann.me/api/basic_json/get_to), [get_ptr](https://json.nlohmann.me/api/basic_json/get_ptr), [get_ref](https://json.nlohmann.me/api/basic_json/get_ref), [operator ValueType](https://json.nlohmann.me/api/basic_json/operator_ValueType), [get_binary](https://json.nlohmann.me/api/basic_json/get_binary) - **Element access**: [at](https://json.nlohmann.me/api/basic_json/at), [operator[]](https://json.nlohmann.me/api/basic_json/operator[]), [value](https://json.nlohmann.me/api/basic_json/value), [front](https://json.nlohmann.me/api/basic_json/front), [back](https://json.nlohmann.me/api/basic_json/back) - **Lookup**: [find](https://json.nlohmann.me/api/basic_json/find), [count](https://json.nlohmann.me/api/basic_json/count), [contains](https://json.nlohmann.me/api/basic_json/contains) - **Iterators**: [begin](https://json.nlohmann.me/api/basic_json/begin), [cbegin](https://json.nlohmann.me/api/basic_json/cbegin), [end](https://json.nlohmann.me/api/basic_json/end), [cend](https://json.nlohmann.me/api/basic_json/cend), [rbegin](https://json.nlohmann.me/api/basic_json/rbegin), [rend](https://json.nlohmann.me/api/basic_json/rend), [crbegin](https://json.nlohmann.me/api/basic_json/crbegin), [crend](https://json.nlohmann.me/api/basic_json/crend), [items](https://json.nlohmann.me/api/basic_json/items) @@ -1184,7 +1185,7 @@ binary[1]; // 0xFE binary.set_subtype(0x10); // serialize to MessagePack -auto cbor = json::to_msgpack(j); // 0xD5 (fixext2), 0x10, 0xCA, 0xFE +auto msgpack = json::to_msgpack(j); // 0xD5 (fixext2), 0x10, 0xCA, 0xFE ``` ## Customers @@ -1220,17 +1221,7 @@ I would be happy to learn about other compilers/versions. Please note: - GCC 4.8 has a bug [57824](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=57824): multiline raw strings cannot be the arguments to macros. Don't use multiline raw strings directly in macros with this compiler. -- Android defaults to using very old compilers and C++ libraries. To fix this, add the following to your `Application.mk`. This will switch to the LLVM C++ library, the Clang compiler, and enable C++11 and other features disabled by default. - - ```makefile - APP_STL := c++_shared - NDK_TOOLCHAIN_VERSION := clang3.6 - APP_CPPFLAGS += -frtti -fexceptions - ``` - - The code compiles successfully with [Android NDK](https://developer.android.com/ndk/index.html?hl=ml), Revision 9 - 11 (and possibly later) and [CrystaX's Android NDK](https://www.crystax.net/en/android/ndk) version 10. - -- For GCC running on MinGW or Android SDK, the error `'to_string' is not a member of 'std'` (or similarly, for `strtod` or `strtof`) may occur. Note this is not an issue with the code, but rather with the compiler itself. On Android, see above to build with a newer environment. For MinGW, please refer to [this site](https://tehsausage.com/mingw-to-string) and [this discussion](https://github.com/nlohmann/json/issues/136) for information on how to fix this bug. For Android NDK using `APP_STL := gnustl_static`, please refer to [this discussion](https://github.com/nlohmann/json/issues/219). +- For GCC running on MinGW or Android, the error `'to_string' is not a member of 'std'` (or similarly, for `strtod` or `strtof`) may occur. Note this is not an issue with the code, but rather with the compiler or C++ standard library itself: use a recent toolchain (for Android, a current [NDK](https://developer.android.com/ndk) with the LLVM C++ library `APP_STL := c++_shared`, and for MinGW, a distribution with a complete C++11 standard library). See [this discussion](https://github.com/nlohmann/json/issues/136) for MinGW and [this discussion](https://github.com/nlohmann/json/issues/219) for the old Android NDK using `APP_STL := gnustl_static`. - Unsupported versions of GCC and Clang are rejected by `#error` directives. This can be switched off by defining `JSON_SKIP_UNSUPPORTED_COMPILER_CHECK`. Note that you can expect no support in this case. @@ -1437,436 +1428,416 @@ I deeply appreciate the help of the following people. 12. [kepkin](https://github.com/kepkin) patiently pushed forward the support for Microsoft Visual Studio. 13. [gregmarr](https://github.com/gregmarr) simplified the implementation of reverse iterators and helped with numerous hints and improvements. In particular, he pushed forward the implementation of user-defined types. 14. [Caio Luppi](https://github.com/caiovlp) fixed a bug in the Unicode handling. -15. [dariomt](https://github.com/dariomt) fixed some typos in the examples. +15. [dariomt](https://github.com/dariomt) fixed some typos in the examples and fixed a subtlety in MSVC type support and implemented the `get_ref()` function to get a reference to stored values. 16. [Daniel Frey](https://github.com/d-frey) cleaned up some pointers and implemented exception-safe memory allocation. 17. [Colin Hirsch](https://github.com/ColinH) took care of a small namespace issue. 18. [Huu Nguyen](https://github.com/whoshuu) corrected a variable name in the documentation. 19. [Silverweed](https://github.com/silverweed) overloaded `parse()` to accept an rvalue reference. -20. [dariomt](https://github.com/dariomt) fixed a subtlety in MSVC type support and implemented the `get_ref()` function to get a reference to stored values. -21. [ZahlGraf](https://github.com/ZahlGraf) added a workaround that allows compilation using Android NDK. -22. [whackashoe](https://github.com/whackashoe) replaced a function that was marked as unsafe by Visual Studio. -23. [406345](https://github.com/406345) fixed two small warnings. -24. [Glen Fernandes](https://github.com/glenfe) noted a potential portability problem in the `has_mapped_type` function. -25. [Corbin Hughes](https://github.com/nibroc) fixed some typos in the contribution guidelines. -26. [twelsby](https://github.com/twelsby) fixed the array subscript operator, an issue that failed the MSVC build, and floating-point parsing/dumping. He further added support for unsigned integer numbers and implemented better roundtrip support for parsed numbers. -27. [Volker Diels-Grabsch](https://github.com/vog) fixed a link in the README file. -28. [msm-](https://github.com/msm-) added support for American Fuzzy Lop. -29. [Annihil](https://github.com/Annihil) fixed an example in the README file. -30. [Themercee](https://github.com/Themercee) noted a wrong URL in the README file. -31. [Lv Zheng](https://github.com/lv-zheng) fixed a namespace issue with `int64_t` and `uint64_t`. -32. [abc100m](https://github.com/abc100m) analyzed the issues with GCC 4.8 and proposed a [partial solution](https://github.com/nlohmann/json/pull/212). -33. [zewt](https://github.com/zewt) added useful notes to the README file about Android. -34. [Róbert Márki](https://github.com/robertmrk) added a fix to use move iterators and improved the integration via CMake. -35. [Chris Kitching](https://github.com/ChrisKitching) cleaned up the CMake files. -36. [Tom Needham](https://github.com/06needhamt) fixed a subtle bug with MSVC 2015 which was also proposed by [Michael K.](https://github.com/Epidal). -37. [Mário Feroldi](https://github.com/thelostt) fixed a small typo. -38. [duncanwerner](https://github.com/duncanwerner) found a really embarrassing performance regression in the 2.0.0 release. -39. [Damien](https://github.com/dtoma) fixed one of the last conversion warnings. -40. [Thomas Braun](https://github.com/t-b) fixed a warning in a test case and adjusted MSVC calls in the CI. -41. [Théo DELRIEU](https://github.com/theodelrieu) patiently and constructively oversaw the long way toward [iterator-range parsing](https://github.com/nlohmann/json/issues/290). He also implemented the magic behind the serialization/deserialization of user-defined types and split the single header file into smaller chunks. -42. [Stefan](https://github.com/5tefan) fixed a minor issue in the documentation. -43. [Vasil Dimov](https://github.com/vasild) fixed the documentation regarding conversions from `std::multiset`. -44. [ChristophJud](https://github.com/ChristophJud) overworked the CMake files to ease project inclusion. -45. [Vladimir Petrigo](https://github.com/vpetrigo) made a SFINAE hack more readable and added Visual Studio 17 to the build matrix. -46. [Denis Andrejew](https://github.com/seeekr) fixed a grammar issue in the README file. -47. [Pierre-Antoine Lacaze](https://github.com/palacaze) found a subtle bug in the `dump()` function. -48. [TurpentineDistillery](https://github.com/TurpentineDistillery) pointed to [`std::locale::classic()`](https://en.cppreference.com/w/cpp/locale/locale/classic) to avoid too much locale joggling, found some nice performance improvements in the parser, improved the benchmarking code, and realized locale-independent number parsing and printing. -49. [cgzones](https://github.com/cgzones) had an idea how to fix the Coverity scan. -50. [Jared Grubb](https://github.com/jaredgrubb) silenced a nasty documentation warning. -51. [Yixin Zhang](https://github.com/qwename) fixed an integer overflow check. -52. [Bosswestfalen](https://github.com/Bosswestfalen) merged two iterator classes into a smaller one. -53. [Daniel599](https://github.com/Daniel599) helped to get Travis to execute the tests with Clang's sanitizers. -54. [Jonathan Lee](https://github.com/vjon) fixed an example in the README file. -55. [gnzlbg](https://github.com/gnzlbg) supported the implementation of user-defined types. -56. [Alexej Harm](https://github.com/qis) helped to get the user-defined types working with Visual Studio. -57. [Jared Grubb](https://github.com/jaredgrubb) supported the implementation of user-defined types. -58. [EnricoBilla](https://github.com/EnricoBilla) noted a typo in an example. -59. [Martin Hořeňovský](https://github.com/horenmar) found a way for a 2x speedup for the compilation time of the test suite. -60. [ukhegg](https://github.com/ukhegg) proposed an improvement for the examples section. -61. [rswanson-ihi](https://github.com/rswanson-ihi) noted a typo in the README. -62. [Mihai Stan](https://github.com/stanmihai4) fixed a bug in the comparison with `nullptr`s. -63. [Tushar Maheshwari](https://github.com/tusharpm) added [cotire](https://github.com/sakra/cotire) support to speed up the compilation. -64. [TedLyngmo](https://github.com/TedLyngmo) noted a typo in the README, removed unnecessary bit arithmetic, and fixed some `-Weffc++` warnings. -65. [Krzysztof Woś](https://github.com/krzysztofwos) made exceptions more visible. -66. [ftillier](https://github.com/ftillier) fixed a compiler warning. -67. [tinloaf](https://github.com/tinloaf) made sure all pushed warnings are properly popped. -68. [Fytch](https://github.com/Fytch) found a bug in the documentation. -69. [Jay Sistar](https://github.com/Type1J) implemented a Meson build description. -70. [Henry Lee](https://github.com/HenryRLee) fixed a warning in ICC and improved the iterator implementation. -71. [Vincent Thiery](https://github.com/vthiery) maintains a package for the Conan package manager. -72. [Steffen](https://github.com/koemeet) fixed a potential issue with MSVC and `std::min`. -73. [Mike Tzou](https://github.com/Chocobo1) fixed some typos. -74. [amrcode](https://github.com/amrcode) noted misleading documentation about comparison of floats. -75. [Oleg Endo](https://github.com/olegendo) reduced the memory consumption by replacing `` with ``. -76. [dan-42](https://github.com/dan-42) cleaned up the CMake files to simplify including/reusing of the library. -77. [Nikita Ofitserov](https://github.com/himikof) allowed for moving values from initializer lists. -78. [Greg Hurrell](https://github.com/wincent) fixed a typo. -79. [Dmitry Kukovinets](https://github.com/DmitryKuk) fixed a typo. -80. [kbthomp1](https://github.com/kbthomp1) fixed an issue related to the Intel OSX compiler. -81. [Markus Werle](https://github.com/daixtrose) fixed a typo. -82. [WebProdPP](https://github.com/WebProdPP) fixed a subtle error in a precondition check. -83. [Alex](https://github.com/leha-bot) noted an error in a code sample. -84. [Tom de Geus](https://github.com/tdegeus) reported some warnings with ICC and helped to fix them. -85. [Perry Kundert](https://github.com/pjkundert) simplified reading from input streams. -86. [Sonu Lohani](https://github.com/sonulohani) fixed a small compilation error. -87. [Jamie Seward](https://github.com/jseward) fixed all MSVC warnings. -88. [Nate Vargas](https://github.com/eld00d) added a Doxygen tag file. -89. [pvleuven](https://github.com/pvleuven) helped to fix a warning in ICC. -90. [Pavel](https://github.com/crea7or) helped to fix some warnings in MSVC. -91. [Jamie Seward](https://github.com/jseward) avoided unnecessary string copies in `find()` and `count()`. -92. [Mitja](https://github.com/Itja) fixed some typos. -93. [Jorrit Wronski](https://github.com/jowr) updated the Hunter package links. -94. [Matthias Möller](https://github.com/TinyTinni) added a `.natvis` for the MSVC debug view. -95. [bogemic](https://github.com/bogemic) fixed some C++17 deprecation warnings. -96. [Eren Okka](https://github.com/erengy) fixed some MSVC warnings. -97. [abolz](https://github.com/abolz) integrated the Grisu2 algorithm for proper floating-point formatting, allowing more roundtrip checks to succeed. -98. [Vadim Evard](https://github.com/Pipeliner) fixed a Markdown issue in the README. -99. [zerodefect](https://github.com/zerodefect) fixed a compiler warning. -100. [Kert](https://github.com/kaidokert) allowed to template the string type in the serialization and added the possibility to override the exceptional behavior. -101. [mark-99](https://github.com/mark-99) helped fix an ICC error. -102. [Patrik Huber](https://github.com/patrikhuber) fixed links in the README file. -103. [johnfb](https://github.com/johnfb) found a bug in the implementation of CBOR's indefinite length strings. -104. [Paul Fultz II](https://github.com/pfultz2) added a note on the cget package manager. -105. [Wilson Lin](https://github.com/wla80) made the integration section of the README more concise. -106. [RalfBielig](https://github.com/ralfbielig) detected and fixed a memory leak in the parser callback. -107. [agrianius](https://github.com/agrianius) allowed dumping JSON to an alternative string type. -108. [Kevin Tonon](https://github.com/ktonon) overworked the C++11 compiler checks in CMake. -109. [Axel Huebl](https://github.com/ax3l) simplified a CMake check and added support for the [Spack package manager](https://spack.io). -110. [Carlos O'Ryan](https://github.com/coryan) fixed a typo. -111. [James Upjohn](https://github.com/jupjohn) fixed a version number in the compilers section. -112. [Chuck Atkins](https://github.com/chuckatkins) adjusted the CMake files to the CMake packaging guidelines and provided documentation for the CMake integration. -113. [Jan Schöppach](https://github.com/dns13) fixed a typo. -114. [martin-mfg](https://github.com/martin-mfg) fixed a typo. -115. [Matthias Möller](https://github.com/TinyTinni) removed the dependency from `std::stringstream`. -116. [agrianius](https://github.com/agrianius) added code to use alternative string implementations. -117. [Daniel599](https://github.com/Daniel599) allowed to use more algorithms with the `items()` function. -118. [Julius Rakow](https://github.com/juliusrakow) fixed the Meson include directory and fixed the links to [cppreference.com](https://cppreference.com). -119. [Sonu Lohani](https://github.com/sonulohani) fixed the compilation with MSVC 2015 in debug mode. -120. [grembo](https://github.com/grembo) fixed the test suite and re-enabled several test cases. -121. [Hyeon Kim](https://github.com/simnalamburt) introduced the macro `JSON_INTERNAL_CATCH` to control the exception handling inside the library. -122. [thyu](https://github.com/thyu) fixed a compiler warning. -123. [David Guthrie](https://github.com/LEgregius) fixed a subtle compilation error with Clang 3.4.2. -124. [Dennis Fischer](https://github.com/dennisfischer) allowed to call `find_package` without installing the library. -125. [Hyeon Kim](https://github.com/simnalamburt) fixed an issue with a double macro definition. -126. [Ben Berman](https://github.com/rivertam) made some error messages more understandable. -127. [zakalibit](https://github.com/zakalibit) fixed a compilation problem with the Intel C++ compiler. -128. [mandreyel](https://github.com/mandreyel) fixed a compilation problem. -129. [Kostiantyn Ponomarenko](https://github.com/koponomarenko) added version and license information to the Meson build file. -130. [Henry Schreiner](https://github.com/henryiii) added support for GCC 4.8. -131. [knilch](https://github.com/knilch0r) made sure the test suite does not stall when run in the wrong directory. -132. [Antonio Borondo](https://github.com/antonioborondo) fixed an MSVC 2017 warning. -133. [Dan Gendreau](https://github.com/dgendreau) implemented the `NLOHMANN_JSON_SERIALIZE_ENUM` macro to quickly define an enum/JSON mapping. -134. [efp](https://github.com/efp) added line and column information to parse errors. -135. [julian-becker](https://github.com/julian-becker) added BSON support. -136. [Pratik Chowdhury](https://github.com/pratikpc) added support for structured bindings. -137. [David Avedissian](https://github.com/davedissian) added support for Clang 5.0.1 (PS4 version). -138. [Jonathan Dumaresq](https://github.com/dumarjo) implemented an input adapter to read from `FILE*`. -139. [kjpus](https://github.com/kjpus) fixed a link in the documentation. -140. [Manvendra Singh](https://github.com/manu-chroma) fixed a typo in the documentation. -141. [ziggurat29](https://github.com/ziggurat29) fixed an MSVC warning. -142. [Sylvain Corlay](https://github.com/SylvainCorlay) added code to avoid an issue with MSVC. -143. [mefyl](https://github.com/mefyl) fixed a bug when JSON was parsed from an input stream. -144. [Millian Poquet](https://github.com/mpoquet) allowed to install the library via Meson. -145. [Michael Behrns-Miller](https://github.com/moodboom) found an issue with a missing namespace. -146. [Nasztanovics Ferenc](https://github.com/naszta) fixed a compilation issue with libc 2.12. -147. [Andreas Schwab](https://github.com/andreas-schwab) fixed the endian conversion. -148. [Mark-Dunning](https://github.com/Mark-Dunning) fixed a warning in MSVC. -149. [Gareth Sylvester-Bradley](https://github.com/garethsb-sony) added `operator/` for JSON Pointers. -150. [John-Mark](https://github.com/johnmarkwayve) noted a missing header. -151. [Vitaly Zaitsev](https://github.com/xvitaly) fixed compilation with GCC 9.0. -152. [Laurent Stacul](https://github.com/stac47) fixed compilation with GCC 9.0. -153. [Ivor Wanders](https://github.com/iwanders) helped to reduce the CMake requirement to version 3.1. -154. [njlr](https://github.com/njlr) updated the Buckaroo instructions. -155. [Lion](https://github.com/lieff) fixed a compilation issue with GCC 7 on CentOS. -156. [Isaac Nickaein](https://github.com/nickaein) improved the integer serialization performance and implemented the `contains()` function. -157. [past-due](https://github.com/past-due) suppressed an unfixable warning. -158. [Elvis Oric](https://github.com/elvisoric) improved Meson support. -159. [Matěj Plch](https://github.com/Afforix) fixed an example in the README. -160. [Mark Beckwith](https://github.com/wythe) fixed a typo. -161. [scinart](https://github.com/scinart) fixed a bug in the serializer. -162. [Patrick Boettcher](https://github.com/pboettch) implemented `push_back()` and `pop_back()` for JSON Pointers. -163. [Bruno Oliveira](https://github.com/nicoddemus) added support for Conda. -164. [Michele Caini](https://github.com/skypjack) fixed links in the README. -165. [Hani](https://github.com/hnkb) documented how to install the library with NuGet. -166. [Mark Beckwith](https://github.com/wythe) fixed a typo. -167. [yann-morin-1998](https://github.com/yann-morin-1998) helped to reduce the CMake requirement to version 3.1. -168. [Konstantin Podsvirov](https://github.com/podsvirov) maintains a package for the MSYS2 software distro. -169. [remyabel](https://github.com/remyabel2) added GNUInstallDirs to the CMake files. -170. [Taylor Howard](https://github.com/taylorhoward92) fixed a unit test. -171. [Gabe Ron](https://github.com/Macr0Nerd) implemented the `to_string` method. -172. [Watal M. Iwasaki](https://github.com/heavywatal) fixed a Clang warning. -173. [Viktor Kirilov](https://github.com/onqtam) switched the unit tests from [Catch](https://github.com/philsquared/Catch) to [doctest](https://github.com/onqtam/doctest) -174. [Juncheng E](https://github.com/ejcjason) fixed a typo. -175. [tete17](https://github.com/tete17) fixed a bug in the `contains` function. -176. [Xav83](https://github.com/Xav83) fixed some cppcheck warnings. -177. [0xflotus](https://github.com/0xflotus) fixed some typos. -178. [Christian Deneke](https://github.com/chris0x44) added a const version of `json_pointer::back`. -179. [Julien Hamaide](https://github.com/crazyjul) made the `items()` function work with custom string types. -180. [Evan Nemerson](https://github.com/nemequ) updated fixed a bug in Hedley and updated this library accordingly. -181. [Florian Pigorsch](https://github.com/flopp) fixed a lot of typos. -182. [Camille Bégué](https://github.com/cbegue) fixed an issue in the conversion from `std::pair` and `std::tuple` to `json`. -183. [Anthony VH](https://github.com/AnthonyVH) fixed a compile error in an enum deserialization. -184. [Yuriy Vountesmery](https://github.com/ua-code-dragon) noted a subtle bug in a preprocessor check. -185. [Chen](https://github.com/dota17) fixed numerous issues in the library. -186. [Antony Kellermann](https://github.com/aokellermann) added a CI step for GCC 10.1. -187. [Alex](https://github.com/gistrec) fixed an MSVC warning. -188. [Rainer](https://github.com/rvjr) proposed an improvement in the floating-point serialization in CBOR. -189. [Francois Chabot](https://github.com/FrancoisChabot) made performance improvements in the input adapters. -190. [Arthur Sonzogni](https://github.com/ArthurSonzogni) documented how the library can be included via `FetchContent`. -191. [Rimas Misevičius](https://github.com/rmisev) fixed an error message. -192. [Alexander Myasnikov](https://github.com/alexandermyasnikov) fixed some examples and a link in the README. -193. [Hubert Chathi](https://github.com/uhoreg) made CMake's version config file architecture-independent. -194. [OmnipotentEntity](https://github.com/OmnipotentEntity) implemented the binary values for CBOR, MessagePack, BSON, and UBJSON. -195. [ArtemSarmini](https://github.com/ArtemSarmini) fixed a compilation issue with GCC 10 and fixed a leak. -196. [Evgenii Sopov](https://github.com/sea5kg) integrated the library to the wsjcpp package manager. -197. [Sergey Linev](https://github.com/linev) fixed a compiler warning. -198. [Miguel Magalhães](https://github.com/magamig) fixed the year in the copyright. -199. [Gareth Sylvester-Bradley](https://github.com/garethsb-sony) fixed a compilation issue with MSVC. -200. [Alexander “weej” Jones](https://github.com/alex-weej) fixed an example in the README. -201. [Antoine Cœur](https://github.com/Coeur) fixed some typos in the documentation. -202. [jothepro](https://github.com/jothepro) updated links to the Hunter package. -203. [Dave Lee](https://github.com/kastiglione) fixed a link in the README. -204. [Joël Lamotte](https://github.com/Klaim) added instruction for using Build2's package manager. -205. [Paul Jurczak](https://github.com/pauljurczak) fixed an example in the README. -206. [Sonu Lohani](https://github.com/sonulohani) fixed a warning. -207. [Carlos Gomes Martinho](https://github.com/gocarlos) updated the Conan package source. -208. [Konstantin Podsvirov](https://github.com/podsvirov) fixed the MSYS2 package documentation. -209. [Tridacnid](https://github.com/Tridacnid) improved the CMake tests. -210. [Michael](https://github.com/MBalszun) fixed MSVC warnings. -211. [Quentin Barbarat](https://github.com/quentin-dev) fixed an example in the documentation. -212. [XyFreak](https://github.com/XyFreak) fixed a compiler warning. -213. [TotalCaesar659](https://github.com/TotalCaesar659) fixed links in the README. -214. [Tanuj Garg](https://github.com/tanuj208) improved the fuzzer coverage for UBSAN input. -215. [AODQ](https://github.com/AODQ) fixed a compiler warning. -216. [jwittbrodt](https://github.com/jwittbrodt) made `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE` inline. -217. [pfeatherstone](https://github.com/pfeatherstone) improved the upper bound of arguments of the `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE`/`NLOHMANN_DEFINE_TYPE_INTRUSIVE` macros. -218. [Jan Procházka](https://github.com/jprochazk) fixed a bug in the CBOR parser for binary and string values. -219. [T0b1-iOS](https://github.com/T0b1-iOS) fixed a bug in the new hash implementation. -220. [Matthew Bauer](https://github.com/matthewbauer) adjusted the CBOR writer to create tags for binary subtypes. -221. [gatopeich](https://github.com/gatopeich) implemented an ordered map container for `nlohmann::ordered_json`. -222. [Érico Nogueira Rolim](https://github.com/ericonr) added support for pkg-config. -223. [KonanM](https://github.com/KonanM) proposed an implementation for the `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE`/`NLOHMANN_DEFINE_TYPE_INTRUSIVE` macros. -224. [Guillaume Racicot](https://github.com/gracicot) implemented `string_view` support and allowed C++20 support. -225. [Alex Reinking](https://github.com/alexreinking) improved CMake support for `FetchContent`. -226. [Hannes Domani](https://github.com/ssbssa) provided a GDB pretty printer. -227. Lars Wirzenius reviewed the README file. -228. [Jun Jie](https://github.com/ongjunjie) fixed a compiler path in the CMake scripts. -229. [Ronak Buch](https://github.com/rbuch) fixed typos in the documentation. -230. [Alexander Karzhenkov](https://github.com/karzhenkov) fixed a move constructor and the Travis builds. -231. [Leonardo Lima](https://github.com/leozz37) added CPM.Cmake support. -232. [Joseph Blackman](https://github.com/jbzdarkid) fixed a warning. -233. [Yaroslav](https://github.com/YarikTH) updated doctest and implemented unit tests. -234. [Martin Stump](https://github.com/globberwops) fixed a bug in the CMake files. -235. [Jaakko Moisio](https://github.com/jasujm) fixed a bug in the input adapters. -236. [bl-ue](https://github.com/bl-ue) fixed some Markdown issues in the README file. -237. [William A. Wieselquist](https://github.com/wawiesel) fixed an example from the README. -238. [abbaswasim](https://github.com/abbaswasim) fixed an example from the README. -239. [Remy Jette](https://github.com/remyjette) fixed a warning. -240. [Fraser](https://github.com/frasermarlow) fixed the documentation. -241. [Ben Beasley](https://github.com/musicinmybrain) updated doctest. -242. [Doron Behar](https://github.com/doronbehar) fixed pkg-config.pc. -243. [raduteo](https://github.com/raduteo) fixed a warning. -244. [David Pfahler](https://github.com/theShmoo) added the possibility to compile the library without I/O support. -245. [Morten Fyhn Amundsen](https://github.com/mortenfyhn) fixed a typo. -246. [jpl-mac](https://github.com/jpl-mac) allowed treating the library as a system header in CMake. -247. [Jason Dsouza](https://github.com/jasmcaus) fixed the indentation of the CMake file. -248. [offa](https://github.com/offa) added a link to Conan Center to the documentation. -249. [TotalCaesar659](https://github.com/TotalCaesar659) updated the links in the documentation to use HTTPS. -250. [Rafail Giavrimis](https://github.com/grafail) fixed the Google Benchmark default branch. -251. [Louis Dionne](https://github.com/ldionne) fixed a conversion operator. -252. [justanotheranonymoususer](https://github.com/justanotheranonymoususer) made the examples in the README more consistent. -253. [Finkman](https://github.com/Finkman) suppressed some `-Wfloat-equal` warnings. -254. [Ferry Huberts](https://github.com/fhuberts) fixed `-Wswitch-enum` warnings. -255. [Arseniy Terekhin](https://github.com/senyai) made the GDB pretty-printer robust against unset variable names. -256. [Amir Masoud Abdol](https://github.com/amirmasoudabdol) updated the Homebrew command as nlohmann/json is now in homebrew-core. -257. [Hallot](https://github.com/Hallot) fixed some `-Wextra-semi-stmt warnings`. -258. [Giovanni Cerretani](https://github.com/gcerretani) fixed `-Wunused` warnings on `JSON_DIAGNOSTICS`. -259. [Bogdan Popescu](https://github.com/Kapeli) hosts the [docset](https://github.com/Kapeli/Dash-User-Contributions/tree/master/docsets/JSON_for_Modern_C%2B%2B) for offline documentation viewers. -260. [Carl Smedstad](https://github.com/carlsmedstad) fixed an assertion error when using `JSON_DIAGNOSTICS`. -261. [miikka75](https://github.com/miikka75) provided an important fix to compile C++17 code with Clang 9. -262. [Maarten Becker](https://github.com/kernie) fixed a warning for shadowed variables. -263. [Cristi Vîjdea](https://github.com/axnsan12) fixed typos in the `operator[]` documentation. -264. [Alex Beregszaszi](https://github.com/axic) fixed spelling mistakes in comments. -265. [Dirk Stolle](https://github.com/striezel) fixed typos in documentation. -266. [Daniel Albuschat](https://github.com/daniel-kun) corrected the parameter name in the `parse` documentation. -267. [Prince Mendiratta](https://github.com/Prince-Mendiratta) fixed a link to the FAQ. -268. [Florian Albrechtskirchinger](https://github.com/falbrechtskirchinger) implemented `std::string_view` support for object keys and made dozens of other improvements. -269. [Qianqian Fang](https://github.com/fangq) implemented the Binary JData (BJData) format. -270. [pketelsen](https://github.com/pketelsen) added macros `NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT` and `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT`. -271. [DarkZeros](https://github.com/DarkZeros) adjusted to code to not clash with Arduino defines. -272. [flagarde](https://github.com/flagarde) fixed the output of `meta()` for MSVC. -273. [Giovanni Cerretani](https://github.com/gcerretani) fixed a check for `std::filesystem`. -274. [Dimitris Apostolou](https://github.com/rex4539) fixed a typo. -275. [Ferry Huberts](https://github.com/fhuberts) fixed a typo. -276. [Michael Nosthoff](https://github.com/heinemml) fixed a typo. -277. [JungHoon Lee](https://github.com/jhnlee) fixed a typo. -278. [Faruk D.](https://github.com/fdiblen) fixed the CITATION.CFF file. -279. [Andrea Cocito](https://github.com/puffetto) added a clarification on macro usage to the documentation. -280. [Krzysiek Karbowiak](https://github.com/kkarbowiak) refactored the tests to use `CHECK_THROWS_WITH_AS`. -281. [Chaoqi Zhang](https://github.com/prncoprs) fixed a typo. -282. [ivanovmp](https://github.com/ivanovmp) fixed a whitespace error. -283. [KsaNL](https://github.com/KsaNL) fixed a build error when including ``. -284. [Andrea Pappacoda](https://github.com/Tachi107) moved `.pc` and `.cmake` files to `share` directory. -285. [Wolf Vollprecht](https://github.com/wolfv) added the `patch_inplace` function. -286. [Jake Zimmerman](https://github.com/jez) highlighted common usage patterns in the README file. -287. [NN](https://github.com/NN---) added the Visual Studio output directory to `.gitignore`. -288. [Romain Reignier](https://github.com/romainreignier) improved the performance of the vector output adapter. -289. [Mike](https://github.com/Mike-Leo-Smith) fixed the `std::iterator_traits`. -290. [Richard Hozák](https://github.com/richardhozak) added macro `JSON_NO_ENUM` to disable default enum conversions. -291. [vakokako](https://github.com/vakokako) fixed tests when compiling with C++20. -292. [Alexander “weej” Jones](https://github.com/alexweej) fixed an example in the README. -293. [Eli Schwartz](https://github.com/eli-schwartz) added more files to the `include.zip` archive. -294. [Kevin Lu](https://github.com/kevinlul) fixed a compilation issue when typedefs with certain names were present. -295. [Trevor Hickey](https://github.com/luxe) improved the description of an example. -296. [Jef LeCompte](https://github.com/jef) updated the year in the README file. -297. [Alexandre Hamez](https://github.com/ahamez) fixed a warning. -298. [Maninderpal Badhan](https://github.com/mbadhan) fixed a typo. -299. [kevin--](https://github.com/kevin--) added a note to an example in the README file. -300. [I](https://github.com/wx257osn2) fixed a typo. -301. [Gregorio Litenstein](https://github.com/Lord-Kamina) fixed the Clang detection. -302. [Andreas Smas](https://github.com/andoma) added a Doozer badge. -303. [WanCW](https://github.com/wancw) fixed the string conversion with Clang. -304. [zhaohuaxishi](https://github.com/zhaohuaxishi) fixed a Doxygen error. -305. [emvivre](https://github.com/emvivre) removed an invalid parameter from CMake. -306. [Tobias Hermann](https://github.com/Dobiasd) fixed a link in the README file. -307. [Michael](https://github.com/traits) fixed a warning. -308. [Ryan Mulder](https://github.com/ryanjmulder) added `ensure_ascii` to the `dump` function. -309. [Muri Nicanor](https://github.com/murinicanor) fixed the `sed` discovery in the Makefile. -310. [David Avedissian](https://github.com/dgavedissian) implemented SFINAE-friendly `iterator_traits`. -311. [AQNOUCH Mohammed](https://github.com/aqnouch) fixed a typo in the README. -312. [Gareth Sylvester-Bradley](https://github.com/garethsb) added `operator/=` and `operator/` to construct JSON pointers. -313. [Michael Macnair](https://github.com/mykter) added support for afl-fuzz testing. -314. [Berkus Decker](https://github.com/berkus) fixed a typo in the README. -315. [Illia Polishchuk](https://github.com/ilqvya) improved the CMake testing. -316. [Ikko Ashimine](https://github.com/eltociear) fixed a typo. -317. [Raphael Grimm](https://github.com/barcode) added the possibility to define a custom base class. -318. [tocic](https://github.com/tocic) fixed typos in the documentation. -319. [Vertexwahn](https://github.com/Vertexwahn) added Bazel build support. -320. [Dirk Stolle](https://github.com/striezel) fixed typos in the documentation. -321. [DavidKorczynski](https://github.com/DavidKorczynski) added a CIFuzz CI GitHub action. -322. [Finkman](https://github.com/Finkman) fixed the debug pretty-printer. -323. [Florian Segginger](https://github.com/floriansegginger) bumped the years in the README. -324. [haadfida](https://github.com/haadfida) cleaned up the badges of used services. -325. [Arsen Arsenović](https://github.com/ArsenArsen) fixed a build error. -326. [theevilone45](https://github.com/theevilone45) fixed a typo in a CMake file. -327. [Sergei Trofimovich](https://github.com/trofi) fixed the custom allocator support. -328. [Joyce](https://github.com/joycebrum) fixed some security issues in the GitHub workflows. -329. [Nicolas Jakob](https://github.com/njakob) add vcpkg version badge. -330. [Tomerkm](https://github.com/Tomerkm) added tests. -331. [No.](https://github.com/tusooa) fixed the use of `get<>` calls. -332. [taro](https://github.com/tarolling) fixed a typo in the `CODEOWNERS` file. -333. [Ikko Eltociear Ashimine](https://github.com/eltociear) fixed a typo. -334. [Felix Yan](https://github.com/felixonmars) fixed a typo in the README. -335. [HO-COOH](https://github.com/HO-COOH) fixed a parenthesis in the documentation. -336. [Ivor Wanders](https://github.com/iwanders) fixed the examples to catch exception by `const&`. -337. [miny1233](https://github.com/miny1233) fixed a parenthesis in the documentation. -338. [tomalakgeretkal](https://github.com/tomalakgeretkal) fixed a compilation error. -339. [alferov](https://github.com/ALF-ONE) fixed a compilation error. -340. [Craig Scott](https://github.com/craigscott-crascit) fixed a deprecation warning in CMake. -341. [Vyacheslav Zhdanovskiy](https://github.com/ZeronSix) added macros for serialization-only types. -342. [Mathieu Westphal](https://github.com/mwestphal) fixed typos. -343. [scribam](https://github.com/scribam) fixed the MinGW workflow. -344. [Aleksei Sapitskii](https://github.com/aleksproger) added support for Apple's Swift Package Manager. -345. [Benjamin Buch](https://github.com/bebuch) fixed the installation path in CMake. -346. [Colby Haskell](https://github.com/colbychaskell) clarified the parse error message in case a file cannot be opened. -347. [Juan Carlos Arevalo Baeza](https://github.com/TheJCAB) fixed the enum conversion. -348. [alferov](https://github.com/ALF-ONE) fixed a version in the documentation. -349. [ss](https://github.com/serge-s) fixed the amalgamation call. -350. [AniketDhemare](https://github.com/AniketDhemare) fixed a version in the documentation. -351. [Philip Müller](https://github.com/philip-paul-mueller) fixed an example. -352. [Leila Shcheglova](https://github.com/LeilaShcheglova) fixed a warning in a test. -353. [Alex Prabhat Bara](https://github.com/alexprabhat99) fixed a function name in the documentation. -354. [laterlaugh](https://github.com/laterlaugh) fixed some typos. -355. [Yuanhao Jia](https://github.com/MrJia1997) fixed the GDB pretty printer. -356. [Fallen_Breath](https://github.com/Fallen-Breath) fixed an example for JSON Pointer. -357. [Nikhil Idiculla](https://github.com/tsnl) fixed some typos. -358. [Griffin Myers](https://github.com/gmyers18) updated the Natvis file. -359. [thetimr](https://github.com/thetimr) fixed a typo in the documentation. -360. [Balazs Erseki](https://github.com/zerocukor287) fixed a URL in the contribution guidelines. -361. [Niccolò Iardella](https://github.com/rotolof) added `NLOHMANN_DEFINE_DERIVED_TYPE_*` macros. -362. [Borislav Stanimirov](https://github.com/iboB) allowed overriding the CMake target name. -363. [Captain Crutches](https://github.com/captaincrutches) made `iterator_proxy_value` a `std::forward_iterator`. -364. [Fredrik Sandhei](https://github.com/fsandhei) added type conversion support for `std::optional`. -365. [jh96](https://github.com/jordan-hoang) added exceptions when `nullptr` is passed to `parse`. -366. [Stuart Gorman](https://github.com/StuartGorman) fixed number parsing when `EINTR` set in `errno`. -367. [Dylan Baker](https://github.com/dcbaker) generated a pkg-config file that follows the pkg-config conventions. -368. [Tianyi Chen](https://github.com/TianyiChen) optimized the binary `get_number` implementation. -369. [peng-wang-cn](https://github.com/peng-wang-cn) added type conversion support for multidimensional arrays. -370. [Einars Netlis-Galejs](https://github.com/EinarsNG) added `ONLY_SERIALIZE` for `NLOHMANN_DEFINE_DERIVED_TYPE_*` macros. -371. [Marcel](https://github.com/mering) removed `alwayslink=True` Bazel flag. -372. [Harinath Nampally](https://github.com/hnampally) added diagnostic positions to exceptions. -373. [Nissim Armand Ben Danan](https://github.com/NissimBendanan) fixed `NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT` with an empty JSON instance. -374. [Michael Valladolid](https://github.com/codenut) added support for BSON uint64 serialization/deserialization. -375. [Nikhil](https://github.com/nikhilreddydev) updated the documentation. -376. [Nebojša Cvetković](https://github.com/nebkat) added support for BJDATA optimized binary array type. -377. [Sushrut Shringarputale](https://github.com/sushshring) added support for diagnostic positions. -378. [kimci86](https://github.com/kimci86) templated to `NLOHMANN_DEFINE_TYPE` macros to also support `ordered_json`. -379. [Richard Topchii](https://github.com/richardtop) added support for VisionOS in the Swift Package Manager. -380. [Robert Chisholm](https://github.com/Robadob) fixed a typo. -381. [zjyhjqs](https://github.com/zjyhjqs) added CPack support. -382. [bitFiedler](https://github.com/bitFiedler) made GDB pretty printer work with Python 3.8. -383. [Gianfranco Costamagna](https://github.com/LocutusOfBorg) fixed a compiler warning. -384. [risa2000](https://github.com/risa2000) made `std::filesystem::path` conversion to/from UTF-8 encoded string explicit. -385. [AM](https://github.com/maqnouch) fixed typos in the README. -386. [dmenendez-gruposantander](https://github.com/dmenendez-gruposantander) fixed typos in the comments of the examples. -387. [Mihai Stan](https://github.com/mstan-xx) fixed comparisons against the literal `0`. -388. [Matt Gumbel](https://github.com/intelmatt) fixed some `-Weffc++` warnings. -389. [vimpunk](https://github.com/vimpunk) moved a lambda out of an unevaluated context to support older compilers. -390. [Chris Harris](https://github.com/cjh1) fixed the compilation with GCC 4.8. -391. [Palmer Dabbelt](https://github.com/palmer-dabbelt) generated and installed a pkg-config file. -392. [Gus Pozuelo](https://github.com/ap-viavi) made `ordered_map` compatible with GCC 5.5, Clang 3.6, and Xcode 9. -393. [AK](https://github.com/Lioncky) fixed an MSVC build error caused by the `min`/`max` macros from `windows.h`. -394. [Sergiu Deitsch](https://github.com/sergiud) provided a fallback for missing `char8_t` support. -395. [Xiaochuan Ye](https://github.com/XueSongTap) fixed `from_msgpack` for `std::byte` input by specializing `std::char_traits`. -396. [Ville Vesilehto](https://github.com/thevilledev) fixed an overflow in the BJData size calculation and rejected overflowing negative integers in CBOR. -397. [NmPassTHFan](https://github.com/nmpassthf) replaced the deprecated `std::is_trivial` for C++26. -398. [Chris Ever](https://github.com/chirsz-ever) added the `ignore_trailing_commas` parser option. -399. [Kuan-Fu Wu](https://github.com/kfwu1999) fixed the example code for `json_pointer` initialization. -400. [David Kilzer](https://github.com/ddkilzer) added a missing header to the input adapters. -401. [Miko](https://github.com/mikomikotaishi) added proper C++20 module support, simplified the module API, and fixed missing exports. -402. [hitgirl](https://github.com/hitgil) fixed the CMake configuration when cross-compiling. -403. [Devon Thomas](https://github.com/ThomaDevOSU) mentioned the Artistic Style formatting in the contribution guidelines. -404. [Erik Hu](https://github.com/Erikhu1) made Coveralls upload errors non-fatal in the CI. -405. [co63oc](https://github.com/co63oc) fixed typos. -406. [DmitriBogdanov](https://github.com/DmitriBogdanov) fixed broken package manager links in the documentation. -407. [Bander](https://github.com/banderzhm) improved the MSVC compatibility of the C++ modules. -408. [Andy Choi](https://github.com/ccpong) removed an unnecessary `template` keyword before `get` in the README and the documentation. -409. [SamareshSingh](https://github.com/ssam18) fixed single-element brace initialization to copy/move instead of wrapping in an array, fixed the `WITH_DEFAULT` macros for `ordered_map`, and handled moved events in `serve_header.py`. -410. [Aditya](https://github.com/Lumowhisp) improved the documentation of the documentation generation. -411. [cheese1](https://github.com/cheese1) clarified the README. -412. [KhloodElhossiny](https://github.com/khloodelhossiny) enabled `std::string_view` keys in `operator[]`. -413. [Charles Cabergs](https://github.com/cacharle) fixed a `-Wtautological-constant-out-of-range-compare` warning. -414. [EALePain](https://github.com/EALePain) made the `std::tuple` conversion work with reference types such as `std::tie`. -415. [koala_oishi](https://github.com/chibi-dogs) fixed grammatical wording in the README. -416. [riccardoori11](https://github.com/riccardoori11) fixed a typo in the documentation. -417. [Swastik Bose](https://github.com/VasuBhakt) fixed the parent pointers after `update()` with `JSON_DIAGNOSTICS` and fixed the Doxygen autolinking of requirements. -418. [trdesilva](https://github.com/trdesilva) added `front`, `pop_front`, and `push_front` to `json_pointer`. -419. [Akhilesh Arora](https://github.com/akhilesharora) fixed an incomplete-type error with `ordered_json`. -420. [Hariom Phulre](https://github.com/hariomphulre) fixed the C++20 modules compilation with GCC. -421. [Kirill Lokotkov](https://github.com/RUSLoker) fixed printing `long double` values. -422. [George Sedov](https://github.com/radistmorse) added the `NLOHMANN_DEFINE_TYPE_*_WITH_NAMES` macros. -423. [Caillin Nugent](https://github.com/nugentcaillin) added the `NLOHMANN_JSON_SERIALIZE_ENUM_STRICT` macro. -424. [Cosmin D.](https://github.com/drcosmin) fixed `std::filesystem::path` conversions and added an MSVC workaround for `std::unique_ptr`. -425. [Paul Dreik](https://github.com/pauldreik) fixed a test relying on implementation-specific behavior. -426. [Daniel Falk](https://github.com/daniel-falk) added missing copyright notices to the SBOM. -427. [Federico Sfriso](https://github.com/federicosfriso05-dotcom) added support for constructing JSON values from C++20 range views. -428. [Luke Banicevic](https://github.com/banaboi) fixed corrupt BSON output for lengths exceeding `INT32_MAX`, cleaned up the BSON writer, and improved the documentation. -429. [Patrick Armstrong](https://github.com/Patrick10199) updated the CBOR references and the half-precision float assertions. -430. [Yash Bavadiya](https://github.com/xevrion) added checks to all BSON reads. -431. [hum4nBeing](https://github.com/hum4nBeing) fixed the overflow handling of high-precision numbers in UBJSON. -432. [tomatotomata](https://github.com/tomatotomata) added checks for reading CBOR tagged subtypes. -433. [YingqiDuan](https://github.com/YingqiDuan) documented the BSON interoperability. -434. [KBS](https://github.com/youdie006) documented the standards compliance and the strictness of `parse()` and `operator>>`. -435. [Petr Bělohlávek](https://github.com/petrbel) added Clang 21 and 22 to the CI. -436. [Dmitry Rantovov](https://github.com/darkdi) fixed the placement of a CBOR documentation block. -437. [ljcjclljc](https://github.com/ljcjclljc) fixed the comparison of large unsigned integers with signed integers. -438. [Sahil Kamate](https://github.com/sahilkamate03) fixed the handling of CBOR tags 0-5 and 21-23. -439. [Krishnanand G](https://github.com/Krishnanand-G) made the UBJSON writer reject `use_type` without `use_size`. -440. [whn](https://github.com/Whning0513) documented the lenient BSON input handling and corrected the complexity of `to_bson`. -441. [elix3r](https://github.com/22elix3r) fixed `update()` with `merge_objects` when merging a primitive into an object. -442. [Avionic Harshit](https://github.com/avionicharshit-byte) made `diff()` linear when an array shrinks. -443. [Qatadaha Bin Matloob](https://github.com/qatcod) fixed comparisons between integers and floats and fixed unparsable BJData output. -444. [Wu Shuwen](https://github.com/dajiaohuang) removed an unused include. +20. [ZahlGraf](https://github.com/ZahlGraf) added a workaround that allows compilation using Android NDK. +21. [whackashoe](https://github.com/whackashoe) replaced a function that was marked as unsafe by Visual Studio. +22. [406345](https://github.com/406345) fixed two small warnings. +23. [Glen Fernandes](https://github.com/glenfe) noted a potential portability problem in the `has_mapped_type` function. +24. [Corbin Hughes](https://github.com/nibroc) fixed some typos in the contribution guidelines. +25. [twelsby](https://github.com/twelsby) fixed the array subscript operator, an issue that failed the MSVC build, and floating-point parsing/dumping. He further added support for unsigned integer numbers and implemented better roundtrip support for parsed numbers. +26. [Volker Diels-Grabsch](https://github.com/vog) fixed a link in the README file. +27. [msm-](https://github.com/msm-) added support for American Fuzzy Lop. +28. [Annihil](https://github.com/Annihil) fixed an example in the README file. +29. [Themercee](https://github.com/Themercee) noted a wrong URL in the README file. +30. [Lv Zheng](https://github.com/lv-zheng) fixed a namespace issue with `int64_t` and `uint64_t`. +31. [abc100m](https://github.com/abc100m) analyzed the issues with GCC 4.8 and proposed a [partial solution](https://github.com/nlohmann/json/pull/212). +32. [zewt](https://github.com/zewt) added useful notes to the README file about Android. +33. [Róbert Márki](https://github.com/robertmrk) added a fix to use move iterators and improved the integration via CMake. +34. [Chris Kitching](https://github.com/ChrisKitching) cleaned up the CMake files. +35. [Tom Needham](https://github.com/06needhamt) fixed a subtle bug with MSVC 2015 which was also proposed by [Michael K.](https://github.com/Epidal). +36. [Mário Feroldi](https://github.com/thelostt) fixed a small typo. +37. [duncanwerner](https://github.com/duncanwerner) found a really embarrassing performance regression in the 2.0.0 release. +38. [Damien](https://github.com/dtoma) fixed one of the last conversion warnings. +39. [Thomas Braun](https://github.com/t-b) fixed a warning in a test case and adjusted MSVC calls in the CI. +40. [Théo DELRIEU](https://github.com/theodelrieu) patiently and constructively oversaw the long way toward [iterator-range parsing](https://github.com/nlohmann/json/issues/290). He also implemented the magic behind the serialization/deserialization of user-defined types and split the single header file into smaller chunks. +41. [Stefan](https://github.com/5tefan) fixed a minor issue in the documentation. +42. [Vasil Dimov](https://github.com/vasild) fixed the documentation regarding conversions from `std::multiset`. +43. [ChristophJud](https://github.com/ChristophJud) overworked the CMake files to ease project inclusion. +44. [Vladimir Petrigo](https://github.com/vpetrigo) made a SFINAE hack more readable and added Visual Studio 17 to the build matrix. +45. [Denis Andrejew](https://github.com/seeekr) fixed a grammar issue in the README file. +46. [Pierre-Antoine Lacaze](https://github.com/palacaze) found a subtle bug in the `dump()` function. +47. [TurpentineDistillery](https://github.com/TurpentineDistillery) pointed to [`std::locale::classic()`](https://en.cppreference.com/w/cpp/locale/locale/classic) to avoid too much locale joggling, found some nice performance improvements in the parser, improved the benchmarking code, and realized locale-independent number parsing and printing. +48. [cgzones](https://github.com/cgzones) had an idea how to fix the Coverity scan. +49. [Jared Grubb](https://github.com/jaredgrubb) silenced a nasty documentation warning and supported the implementation of user-defined types. +50. [Yixin Zhang](https://github.com/qwename) fixed an integer overflow check. +51. [Bosswestfalen](https://github.com/Bosswestfalen) merged two iterator classes into a smaller one. +52. [Daniel599](https://github.com/Daniel599) helped to get Travis to execute the tests with Clang's sanitizers and allowed to use more algorithms with the `items()` function. +53. [Jonathan Lee](https://github.com/vjon) fixed an example in the README file. +54. [gnzlbg](https://github.com/gnzlbg) supported the implementation of user-defined types. +55. [Alexej Harm](https://github.com/qis) helped to get the user-defined types working with Visual Studio. +56. [EnricoBilla](https://github.com/EnricoBilla) noted a typo in an example. +57. [Martin Hořeňovský](https://github.com/horenmar) found a way for a 2x speedup for the compilation time of the test suite. +58. [ukhegg](https://github.com/ukhegg) proposed an improvement for the examples section. +59. [rswanson-ihi](https://github.com/rswanson-ihi) noted a typo in the README. +60. [Mihai Stan](https://github.com/stanmihai4) fixed a bug in the comparison with `nullptr`s. +61. [Tushar Maheshwari](https://github.com/tusharpm) added [cotire](https://github.com/sakra/cotire) support to speed up the compilation. +62. [TedLyngmo](https://github.com/TedLyngmo) noted a typo in the README, removed unnecessary bit arithmetic, and fixed some `-Weffc++` warnings. +63. [Krzysztof Woś](https://github.com/krzysztofwos) made exceptions more visible. +64. [ftillier](https://github.com/ftillier) fixed a compiler warning. +65. [tinloaf](https://github.com/tinloaf) made sure all pushed warnings are properly popped. +66. [Fytch](https://github.com/Fytch) found a bug in the documentation. +67. [Jay Sistar](https://github.com/Type1J) implemented a Meson build description. +68. [Henry Lee](https://github.com/HenryRLee) fixed a warning in ICC and improved the iterator implementation. +69. [Vincent Thiery](https://github.com/vthiery) maintains a package for the Conan package manager. +70. [Steffen](https://github.com/koemeet) fixed a potential issue with MSVC and `std::min`. +71. [Mike Tzou](https://github.com/Chocobo1) fixed some typos. +72. [amrcode](https://github.com/amrcode) noted misleading documentation about comparison of floats. +73. [Oleg Endo](https://github.com/olegendo) reduced the memory consumption by replacing `` with ``. +74. [dan-42](https://github.com/dan-42) cleaned up the CMake files to simplify including/reusing of the library. +75. [Nikita Ofitserov](https://github.com/himikof) allowed for moving values from initializer lists. +76. [Greg Hurrell](https://github.com/wincent) fixed a typo. +77. [Dmitry Kukovinets](https://github.com/DmitryKuk) fixed a typo. +78. [kbthomp1](https://github.com/kbthomp1) fixed an issue related to the Intel OSX compiler. +79. [Markus Werle](https://github.com/daixtrose) fixed a typo. +80. [WebProdPP](https://github.com/WebProdPP) fixed a subtle error in a precondition check. +81. [Alex](https://github.com/leha-bot) noted an error in a code sample. +82. [Tom de Geus](https://github.com/tdegeus) reported some warnings with ICC and helped to fix them. +83. [Perry Kundert](https://github.com/pjkundert) simplified reading from input streams. +84. [Sonu Lohani](https://github.com/sonulohani) fixed a small compilation error, fixed the compilation with MSVC 2015 in debug mode and fixed a warning. +85. [Jamie Seward](https://github.com/jseward) fixed all MSVC warnings and avoided unnecessary string copies in `find()` and `count()`. +86. [Nate Vargas](https://github.com/eld00d) added a Doxygen tag file. +87. [pvleuven](https://github.com/pvleuven) helped to fix a warning in ICC. +88. [Pavel](https://github.com/crea7or) helped to fix some warnings in MSVC. +89. [Mitja](https://github.com/Itja) fixed some typos. +90. [Jorrit Wronski](https://github.com/jowr) updated the Hunter package links. +91. [Matthias Möller](https://github.com/TinyTinni) added a `.natvis` for the MSVC debug view and removed the dependency from `std::stringstream`. +92. [bogemic](https://github.com/bogemic) fixed some C++17 deprecation warnings. +93. [Eren Okka](https://github.com/erengy) fixed some MSVC warnings. +94. [abolz](https://github.com/abolz) integrated the Grisu2 algorithm for proper floating-point formatting, allowing more roundtrip checks to succeed. +95. [Vadim Evard](https://github.com/Pipeliner) fixed a Markdown issue in the README. +96. [zerodefect](https://github.com/zerodefect) fixed a compiler warning. +97. [Kert](https://github.com/kaidokert) allowed to template the string type in the serialization and added the possibility to override the exceptional behavior. +98. [mark-99](https://github.com/mark-99) helped fix an ICC error. +99. [Patrik Huber](https://github.com/patrikhuber) fixed links in the README file. +100. [johnfb](https://github.com/johnfb) found a bug in the implementation of CBOR's indefinite length strings. +101. [Paul Fultz II](https://github.com/pfultz2) added a note on the cget package manager. +102. [Wilson Lin](https://github.com/wla80) made the integration section of the README more concise. +103. [RalfBielig](https://github.com/ralfbielig) detected and fixed a memory leak in the parser callback. +104. [agrianius](https://github.com/agrianius) allowed dumping JSON to an alternative string type and added code to use alternative string implementations. +105. [Kevin Tonon](https://github.com/ktonon) overworked the C++11 compiler checks in CMake. +106. [Axel Huebl](https://github.com/ax3l) simplified a CMake check and added support for the [Spack package manager](https://spack.io). +107. [Carlos O'Ryan](https://github.com/coryan) fixed a typo. +108. [James Upjohn](https://github.com/jupjohn) fixed a version number in the compilers section. +109. [Chuck Atkins](https://github.com/chuckatkins) adjusted the CMake files to the CMake packaging guidelines and provided documentation for the CMake integration. +110. [Jan Schöppach](https://github.com/dns13) fixed a typo. +111. [martin-mfg](https://github.com/martin-mfg) fixed a typo. +112. [Julius Rakow](https://github.com/juliusrakow) fixed the Meson include directory and fixed the links to [cppreference.com](https://cppreference.com). +113. [grembo](https://github.com/grembo) fixed the test suite and re-enabled several test cases. +114. [Hyeon Kim](https://github.com/simnalamburt) introduced the macro `JSON_INTERNAL_CATCH` to control the exception handling inside the library and fixed an issue with a double macro definition. +115. [thyu](https://github.com/thyu) fixed a compiler warning. +116. [David Guthrie](https://github.com/LEgregius) fixed a subtle compilation error with Clang 3.4.2. +117. [Dennis Fischer](https://github.com/dennisfischer) allowed to call `find_package` without installing the library. +118. [Ben Berman](https://github.com/rivertam) made some error messages more understandable. +119. [zakalibit](https://github.com/zakalibit) fixed a compilation problem with the Intel C++ compiler. +120. [mandreyel](https://github.com/mandreyel) fixed a compilation problem. +121. [Kostiantyn Ponomarenko](https://github.com/koponomarenko) added version and license information to the Meson build file. +122. [Henry Schreiner](https://github.com/henryiii) added support for GCC 4.8. +123. [knilch](https://github.com/knilch0r) made sure the test suite does not stall when run in the wrong directory. +124. [Antonio Borondo](https://github.com/antonioborondo) fixed an MSVC 2017 warning. +125. [Dan Gendreau](https://github.com/dgendreau) implemented the `NLOHMANN_JSON_SERIALIZE_ENUM` macro to quickly define an enum/JSON mapping. +126. [efp](https://github.com/efp) added line and column information to parse errors. +127. [julian-becker](https://github.com/julian-becker) added BSON support. +128. [Pratik Chowdhury](https://github.com/pratikpc) added support for structured bindings. +129. [David Avedissian](https://github.com/davedissian) added support for Clang 5.0.1 (PS4 version). +130. [Jonathan Dumaresq](https://github.com/dumarjo) implemented an input adapter to read from `FILE*`. +131. [kjpus](https://github.com/kjpus) fixed a link in the documentation. +132. [Manvendra Singh](https://github.com/manu-chroma) fixed a typo in the documentation. +133. [ziggurat29](https://github.com/ziggurat29) fixed an MSVC warning. +134. [Sylvain Corlay](https://github.com/SylvainCorlay) added code to avoid an issue with MSVC. +135. [mefyl](https://github.com/mefyl) fixed a bug when JSON was parsed from an input stream. +136. [Millian Poquet](https://github.com/mpoquet) allowed to install the library via Meson. +137. [Michael Behrns-Miller](https://github.com/moodboom) found an issue with a missing namespace. +138. [Nasztanovics Ferenc](https://github.com/naszta) fixed a compilation issue with libc 2.12. +139. [Andreas Schwab](https://github.com/andreas-schwab) fixed the endian conversion. +140. [Mark-Dunning](https://github.com/Mark-Dunning) fixed a warning in MSVC. +141. [Gareth Sylvester-Bradley](https://github.com/garethsb-sony) added `operator/` for JSON Pointers and fixed a compilation issue with MSVC. +142. [John-Mark](https://github.com/johnmarkwayve) noted a missing header. +143. [Vitaly Zaitsev](https://github.com/xvitaly) fixed compilation with GCC 9.0. +144. [Laurent Stacul](https://github.com/stac47) fixed compilation with GCC 9.0. +145. [Ivor Wanders](https://github.com/iwanders) helped to reduce the CMake requirement to version 3.1 and fixed the examples to catch exception by `const&`. +146. [njlr](https://github.com/njlr) updated the Buckaroo instructions. +147. [Lion](https://github.com/lieff) fixed a compilation issue with GCC 7 on CentOS. +148. [Isaac Nickaein](https://github.com/nickaein) improved the integer serialization performance and implemented the `contains()` function. +149. [past-due](https://github.com/past-due) suppressed an unfixable warning. +150. [Elvis Oric](https://github.com/elvisoric) improved Meson support. +151. [Matěj Plch](https://github.com/Afforix) fixed an example in the README. +152. [Mark Beckwith](https://github.com/wythe) fixed two typos. +153. [scinart](https://github.com/scinart) fixed a bug in the serializer. +154. [Patrick Boettcher](https://github.com/pboettch) implemented `push_back()` and `pop_back()` for JSON Pointers. +155. [Bruno Oliveira](https://github.com/nicoddemus) added support for Conda. +156. [Michele Caini](https://github.com/skypjack) fixed links in the README. +157. [Hani](https://github.com/hnkb) documented how to install the library with NuGet. +158. [yann-morin-1998](https://github.com/yann-morin-1998) helped to reduce the CMake requirement to version 3.1. +159. [Konstantin Podsvirov](https://github.com/podsvirov) maintains a package for the MSYS2 software distro and fixed the MSYS2 package documentation. +160. [remyabel](https://github.com/remyabel2) added GNUInstallDirs to the CMake files. +161. [Taylor Howard](https://github.com/taylorhoward92) fixed a unit test. +162. [Gabe Ron](https://github.com/Macr0Nerd) implemented the `to_string` method. +163. [Watal M. Iwasaki](https://github.com/heavywatal) fixed a Clang warning. +164. [Viktor Kirilov](https://github.com/onqtam) switched the unit tests from [Catch](https://github.com/philsquared/Catch) to [doctest](https://github.com/doctest/doctest) +165. [Juncheng E](https://github.com/ejcjason) fixed a typo. +166. [tete17](https://github.com/tete17) fixed a bug in the `contains` function. +167. [Xav83](https://github.com/Xav83) fixed some cppcheck warnings. +168. [0xflotus](https://github.com/0xflotus) fixed some typos. +169. [Christian Deneke](https://github.com/chris0x44) added a const version of `json_pointer::back`. +170. [Julien Hamaide](https://github.com/crazyjul) made the `items()` function work with custom string types. +171. [Evan Nemerson](https://github.com/nemequ) updated fixed a bug in Hedley and updated this library accordingly. +172. [Florian Pigorsch](https://github.com/flopp) fixed a lot of typos. +173. [Camille Bégué](https://github.com/cbegue) fixed an issue in the conversion from `std::pair` and `std::tuple` to `json`. +174. [Anthony VH](https://github.com/AnthonyVH) fixed a compile error in an enum deserialization. +175. [Yuriy Vountesmery](https://github.com/ua-code-dragon) noted a subtle bug in a preprocessor check. +176. [Chen](https://github.com/dota17) fixed numerous issues in the library. +177. [Antony Kellermann](https://github.com/aokellermann) added a CI step for GCC 10.1. +178. [Alex](https://github.com/gistrec) fixed an MSVC warning. +179. [Rainer](https://github.com/rvjr) proposed an improvement in the floating-point serialization in CBOR. +180. [Francois Chabot](https://github.com/FrancoisChabot) made performance improvements in the input adapters. +181. [Arthur Sonzogni](https://github.com/ArthurSonzogni) documented how the library can be included via `FetchContent`. +182. [Rimas Misevičius](https://github.com/rmisev) fixed an error message. +183. [Alexander Myasnikov](https://github.com/alexandermyasnikov) fixed some examples and a link in the README. +184. [Hubert Chathi](https://github.com/uhoreg) made CMake's version config file architecture-independent. +185. [OmnipotentEntity](https://github.com/OmnipotentEntity) implemented the binary values for CBOR, MessagePack, BSON, and UBJSON. +186. [ArtemSarmini](https://github.com/ArtemSarmini) fixed a compilation issue with GCC 10 and fixed a leak. +187. [Evgenii Sopov](https://github.com/sea5kg) integrated the library to the wsjcpp package manager. +188. [Sergey Linev](https://github.com/linev) fixed a compiler warning. +189. [Miguel Magalhães](https://github.com/magamig) fixed the year in the copyright. +190. [Alexander “weej” Jones](https://github.com/alex-weej) fixed an example in the README. +191. [Antoine Cœur](https://github.com/Coeur) fixed some typos in the documentation. +192. [jothepro](https://github.com/jothepro) updated links to the Hunter package. +193. [Dave Lee](https://github.com/kastiglione) fixed a link in the README. +194. [Joël Lamotte](https://github.com/Klaim) added instruction for using Build2's package manager. +195. [Paul Jurczak](https://github.com/pauljurczak) fixed an example in the README. +196. [Carlos Gomes Martinho](https://github.com/gocarlos) updated the Conan package source. +197. [Tridacnid](https://github.com/Tridacnid) improved the CMake tests. +198. [Michael](https://github.com/MBalszun) fixed MSVC warnings. +199. [Quentin Barbarat](https://github.com/quentin-dev) fixed an example in the documentation. +200. [XyFreak](https://github.com/XyFreak) fixed a compiler warning. +201. [TotalCaesar659](https://github.com/TotalCaesar659) fixed links in the README and updated the links in the documentation to use HTTPS. +202. [Tanuj Garg](https://github.com/tanuj208) improved the fuzzer coverage for UBSAN input. +203. [AODQ](https://github.com/AODQ) fixed a compiler warning. +204. [jwittbrodt](https://github.com/jwittbrodt) made `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE` inline. +205. [pfeatherstone](https://github.com/pfeatherstone) improved the upper bound of arguments of the `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE`/`NLOHMANN_DEFINE_TYPE_INTRUSIVE` macros. +206. [Jan Procházka](https://github.com/jprochazk) fixed a bug in the CBOR parser for binary and string values. +207. [T0b1-iOS](https://github.com/T0b1-iOS) fixed a bug in the new hash implementation. +208. [Matthew Bauer](https://github.com/matthewbauer) adjusted the CBOR writer to create tags for binary subtypes. +209. [gatopeich](https://github.com/gatopeich) implemented an ordered map container for `nlohmann::ordered_json`. +210. [Érico Nogueira Rolim](https://github.com/ericonr) added support for pkg-config. +211. [KonanM](https://github.com/KonanM) proposed an implementation for the `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE`/`NLOHMANN_DEFINE_TYPE_INTRUSIVE` macros. +212. [Guillaume Racicot](https://github.com/gracicot) implemented `string_view` support and allowed C++20 support. +213. [Alex Reinking](https://github.com/alexreinking) improved CMake support for `FetchContent`. +214. [Hannes Domani](https://github.com/ssbssa) provided a GDB pretty printer. +215. Lars Wirzenius reviewed the README file. +216. [Jun Jie](https://github.com/ongjunjie) fixed a compiler path in the CMake scripts. +217. [Ronak Buch](https://github.com/rbuch) fixed typos in the documentation. +218. [Alexander Karzhenkov](https://github.com/karzhenkov) fixed a move constructor and the Travis builds. +219. [Leonardo Lima](https://github.com/leozz37) added CPM.Cmake support. +220. [Joseph Blackman](https://github.com/jbzdarkid) fixed a warning. +221. [Yaroslav](https://github.com/YarikTH) updated doctest and implemented unit tests. +222. [Martin Stump](https://github.com/globberwops) fixed a bug in the CMake files. +223. [Jaakko Moisio](https://github.com/jasujm) fixed a bug in the input adapters. +224. [bl-ue](https://github.com/bl-ue) fixed some Markdown issues in the README file. +225. [William A. Wieselquist](https://github.com/wawiesel) fixed an example from the README. +226. [abbaswasim](https://github.com/abbaswasim) fixed an example from the README. +227. [Remy Jette](https://github.com/remyjette) fixed a warning. +228. [Fraser](https://github.com/frasermarlow) fixed the documentation. +229. [Ben Beasley](https://github.com/musicinmybrain) updated doctest. +230. [Doron Behar](https://github.com/doronbehar) fixed pkg-config.pc. +231. [raduteo](https://github.com/raduteo) fixed a warning. +232. [David Pfahler](https://github.com/theShmoo) added the possibility to compile the library without I/O support. +233. [Morten Fyhn Amundsen](https://github.com/mortenfyhn) fixed a typo. +234. [jpl-mac](https://github.com/jpl-mac) allowed treating the library as a system header in CMake. +235. [Jason Dsouza](https://github.com/jasmcaus) fixed the indentation of the CMake file. +236. [offa](https://github.com/offa) added a link to Conan Center to the documentation. +237. [Rafail Giavrimis](https://github.com/grafail) fixed the Google Benchmark default branch. +238. [Louis Dionne](https://github.com/ldionne) fixed a conversion operator. +239. [justanotheranonymoususer](https://github.com/justanotheranonymoususer) made the examples in the README more consistent. +240. [Finkman](https://github.com/Finkman) suppressed some `-Wfloat-equal` warnings and fixed the debug pretty-printer. +241. [Ferry Huberts](https://github.com/fhuberts) fixed `-Wswitch-enum` warnings and fixed a typo. +242. [Arseniy Terekhin](https://github.com/senyai) made the GDB pretty-printer robust against unset variable names. +243. [Amir Masoud Abdol](https://github.com/amirmasoudabdol) updated the Homebrew command as nlohmann/json is now in homebrew-core. +244. [Hallot](https://github.com/Hallot) fixed some `-Wextra-semi-stmt warnings`. +245. [Giovanni Cerretani](https://github.com/gcerretani) fixed `-Wunused` warnings on `JSON_DIAGNOSTICS` and fixed a check for `std::filesystem`. +246. [Bogdan Popescu](https://github.com/Kapeli) hosts the [docset](https://github.com/Kapeli/Dash-User-Contributions/tree/master/docsets/JSON_for_Modern_C%2B%2B) for offline documentation viewers. +247. [Carl Smedstad](https://github.com/carlsmedstad) fixed an assertion error when using `JSON_DIAGNOSTICS`. +248. [miikka75](https://github.com/miikka75) provided an important fix to compile C++17 code with Clang 9. +249. [Maarten Becker](https://github.com/kernie) fixed a warning for shadowed variables. +250. [Cristi Vîjdea](https://github.com/axnsan12) fixed typos in the `operator[]` documentation. +251. [Alex Beregszaszi](https://github.com/axic) fixed spelling mistakes in comments. +252. [Dirk Stolle](https://github.com/striezel) fixed typos in the documentation. +253. [Daniel Albuschat](https://github.com/daniel-kun) corrected the parameter name in the `parse` documentation. +254. [Prince Mendiratta](https://github.com/Prince-Mendiratta) fixed a link to the FAQ. +255. [Florian Albrechtskirchinger](https://github.com/falbrechtskirchinger) implemented `std::string_view` support for object keys and made dozens of other improvements. +256. [Qianqian Fang](https://github.com/fangq) implemented the Binary JData (BJData) format. +257. [pketelsen](https://github.com/pketelsen) added macros `NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT` and `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT`. +258. [DarkZeros](https://github.com/DarkZeros) adjusted to code to not clash with Arduino defines. +259. [flagarde](https://github.com/flagarde) fixed the output of `meta()` for MSVC. +260. [Dimitris Apostolou](https://github.com/rex4539) fixed a typo. +261. [Michael Nosthoff](https://github.com/heinemml) fixed a typo. +262. [JungHoon Lee](https://github.com/jhnlee) fixed a typo. +263. [Faruk D.](https://github.com/fdiblen) fixed the CITATION.CFF file. +264. [Andrea Cocito](https://github.com/puffetto) added a clarification on macro usage to the documentation. +265. [Krzysiek Karbowiak](https://github.com/kkarbowiak) refactored the tests to use `CHECK_THROWS_WITH_AS`. +266. [Chaoqi Zhang](https://github.com/prncoprs) fixed a typo. +267. [ivanovmp](https://github.com/ivanovmp) fixed a whitespace error. +268. [KsaNL](https://github.com/KsaNL) fixed a build error when including ``. +269. [Andrea Pappacoda](https://github.com/Tachi107) moved `.pc` and `.cmake` files to `share` directory. +270. [Wolf Vollprecht](https://github.com/wolfv) added the `patch_inplace` function. +271. [Jake Zimmerman](https://github.com/jez) highlighted common usage patterns in the README file. +272. [NN](https://github.com/NN---) added the Visual Studio output directory to `.gitignore`. +273. [Romain Reignier](https://github.com/romainreignier) improved the performance of the vector output adapter. +274. [Mike](https://github.com/Mike-Leo-Smith) fixed the `std::iterator_traits`. +275. [Richard Hozák](https://github.com/richardhozak) added macro `JSON_NO_ENUM` to disable default enum conversions. +276. [vakokako](https://github.com/vakokako) fixed tests when compiling with C++20. +277. [Alexander “weej” Jones](https://github.com/alexweej) fixed an example in the README. +278. [Eli Schwartz](https://github.com/eli-schwartz) added more files to the `include.zip` archive. +279. [Kevin Lu](https://github.com/kevinlul) fixed a compilation issue when typedefs with certain names were present. +280. [Trevor Hickey](https://github.com/luxe) improved the description of an example. +281. [Jef LeCompte](https://github.com/jef) updated the year in the README file. +282. [Alexandre Hamez](https://github.com/ahamez) fixed a warning. +283. [Maninderpal Badhan](https://github.com/mbadhan) fixed a typo. +284. [kevin--](https://github.com/kevin--) added a note to an example in the README file. +285. [I](https://github.com/wx257osn2) fixed a typo. +286. [Gregorio Litenstein](https://github.com/Lord-Kamina) fixed the Clang detection. +287. [Andreas Smas](https://github.com/andoma) added a Doozer badge. +288. [WanCW](https://github.com/wancw) fixed the string conversion with Clang. +289. [zhaohuaxishi](https://github.com/zhaohuaxishi) fixed a Doxygen error. +290. [emvivre](https://github.com/emvivre) removed an invalid parameter from CMake. +291. [Tobias Hermann](https://github.com/Dobiasd) fixed a link in the README file. +292. [Michael](https://github.com/traits) fixed a warning. +293. [Ryan Mulder](https://github.com/ryanjmulder) added `ensure_ascii` to the `dump` function. +294. [Muri Nicanor](https://github.com/murinicanor) fixed the `sed` discovery in the Makefile. +295. [David Avedissian](https://github.com/dgavedissian) implemented SFINAE-friendly `iterator_traits`. +296. [AQNOUCH Mohammed](https://github.com/aqnouch) fixed a typo in the README. +297. [Gareth Sylvester-Bradley](https://github.com/garethsb) added `operator/=` and `operator/` to construct JSON pointers. +298. [Michael Macnair](https://github.com/mykter) added support for afl-fuzz testing. +299. [Berkus Decker](https://github.com/berkus) fixed a typo in the README. +300. [Illia Polishchuk](https://github.com/ilqvya) improved the CMake testing. +301. [Ikko Ashimine](https://github.com/eltociear) fixed two typos. +302. [Raphael Grimm](https://github.com/barcode) added the possibility to define a custom base class. +303. [tocic](https://github.com/tocic) fixed typos in the documentation. +304. [Vertexwahn](https://github.com/Vertexwahn) added Bazel build support. +305. [DavidKorczynski](https://github.com/DavidKorczynski) added a CIFuzz CI GitHub action. +306. [Florian Segginger](https://github.com/floriansegginger) bumped the years in the README. +307. [haadfida](https://github.com/haadfida) cleaned up the badges of used services. +308. [Arsen Arsenović](https://github.com/ArsenArsen) fixed a build error. +309. [theevilone45](https://github.com/theevilone45) fixed a typo in a CMake file. +310. [Sergei Trofimovich](https://github.com/trofi) fixed the custom allocator support. +311. [Joyce](https://github.com/joycebrum) fixed some security issues in the GitHub workflows. +312. [Nicolas Jakob](https://github.com/njakob) add vcpkg version badge. +313. [Tomerkm](https://github.com/Tomerkm) added tests. +314. [No.](https://github.com/tusooa) fixed the use of `get<>` calls. +315. [taro](https://github.com/tarolling) fixed a typo in the `CODEOWNERS` file. +316. [Felix Yan](https://github.com/felixonmars) fixed a typo in the README. +317. [HO-COOH](https://github.com/HO-COOH) fixed a parenthesis in the documentation. +318. [miny1233](https://github.com/miny1233) fixed a parenthesis in the documentation. +319. [tomalakgeretkal](https://github.com/tomalakgeretkal) fixed a compilation error. +320. [alferov](https://github.com/ALF-ONE) fixed a compilation error and fixed a version in the documentation. +321. [Craig Scott](https://github.com/craigscott-crascit) fixed a deprecation warning in CMake. +322. [Vyacheslav Zhdanovskiy](https://github.com/ZeronSix) added macros for serialization-only types. +323. [Mathieu Westphal](https://github.com/mwestphal) fixed typos. +324. [scribam](https://github.com/scribam) fixed the MinGW workflow. +325. [Aleksei Sapitskii](https://github.com/aleksproger) added support for Apple's Swift Package Manager. +326. [Benjamin Buch](https://github.com/bebuch) fixed the installation path in CMake. +327. [Colby Haskell](https://github.com/colbychaskell) clarified the parse error message in case a file cannot be opened. +328. [Juan Carlos Arevalo Baeza](https://github.com/TheJCAB) fixed the enum conversion. +329. [ss](https://github.com/serge-s) fixed the amalgamation call. +330. [AniketDhemare](https://github.com/AniketDhemare) fixed a version in the documentation. +331. [Philip Müller](https://github.com/philip-paul-mueller) fixed an example. +332. [Leila Shcheglova](https://github.com/LeilaShcheglova) fixed a warning in a test. +333. [Alex Prabhat Bara](https://github.com/alexprabhat99) fixed a function name in the documentation. +334. [laterlaugh](https://github.com/laterlaugh) fixed some typos. +335. [Yuanhao Jia](https://github.com/MrJia1997) fixed the GDB pretty printer. +336. [Fallen_Breath](https://github.com/Fallen-Breath) fixed an example for JSON Pointer. +337. [Nikhil Idiculla](https://github.com/tsnl) fixed some typos. +338. [Griffin Myers](https://github.com/gmyers18) updated the Natvis file. +339. [thetimr](https://github.com/thetimr) fixed a typo in the documentation. +340. [Balazs Erseki](https://github.com/zerocukor287) fixed a URL in the contribution guidelines. +341. [Niccolò Iardella](https://github.com/rotolof) added `NLOHMANN_DEFINE_DERIVED_TYPE_*` macros. +342. [Borislav Stanimirov](https://github.com/iboB) allowed overriding the CMake target name. +343. [Captain Crutches](https://github.com/captaincrutches) made `iterator_proxy_value` a `std::forward_iterator`. +344. [Fredrik Sandhei](https://github.com/fsandhei) added type conversion support for `std::optional`. +345. [jh96](https://github.com/jordan-hoang) added exceptions when `nullptr` is passed to `parse`. +346. [Stuart Gorman](https://github.com/StuartGorman) fixed number parsing when `EINTR` set in `errno`. +347. [Dylan Baker](https://github.com/dcbaker) generated a pkg-config file that follows the pkg-config conventions. +348. [Tianyi Chen](https://github.com/TianyiChen) optimized the binary `get_number` implementation. +349. [peng-wang-cn](https://github.com/peng-wang-cn) added type conversion support for multidimensional arrays. +350. [Einars Netlis-Galejs](https://github.com/EinarsNG) added `ONLY_SERIALIZE` for `NLOHMANN_DEFINE_DERIVED_TYPE_*` macros. +351. [Marcel](https://github.com/mering) removed `alwayslink=True` Bazel flag. +352. [Harinath Nampally](https://github.com/hnampally) added diagnostic positions to exceptions. +353. [Nissim Armand Ben Danan](https://github.com/NissimBendanan) fixed `NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT` with an empty JSON instance. +354. [Michael Valladolid](https://github.com/codenut) added support for BSON uint64 serialization/deserialization. +355. [Nikhil](https://github.com/nikhilreddydev) updated the documentation. +356. [Nebojša Cvetković](https://github.com/nebkat) added support for BJDATA optimized binary array type. +357. [Sushrut Shringarputale](https://github.com/sushshring) added support for diagnostic positions. +358. [kimci86](https://github.com/kimci86) templated to `NLOHMANN_DEFINE_TYPE` macros to also support `ordered_json`. +359. [Richard Topchii](https://github.com/richardtop) added support for VisionOS in the Swift Package Manager. +360. [Robert Chisholm](https://github.com/Robadob) fixed a typo. +361. [zjyhjqs](https://github.com/zjyhjqs) added CPack support. +362. [bitFiedler](https://github.com/bitFiedler) made GDB pretty printer work with Python 3.8. +363. [Gianfranco Costamagna](https://github.com/LocutusOfBorg) fixed a compiler warning. +364. [risa2000](https://github.com/risa2000) made `std::filesystem::path` conversion to/from UTF-8 encoded string explicit. +365. [AM](https://github.com/maqnouch) fixed typos in the README. +366. [dmenendez-gruposantander](https://github.com/dmenendez-gruposantander) fixed typos in the comments of the examples. +367. [Mihai Stan](https://github.com/mstan-xx) fixed comparisons against the literal `0`. +368. [Matt Gumbel](https://github.com/intelmatt) fixed some `-Weffc++` warnings. +369. [vimpunk](https://github.com/vimpunk) moved a lambda out of an unevaluated context to support older compilers. +370. [Chris Harris](https://github.com/cjh1) fixed the compilation with GCC 4.8. +371. [Palmer Dabbelt](https://github.com/palmer-dabbelt) generated and installed a pkg-config file. +372. [Gus Pozuelo](https://github.com/ap-viavi) made `ordered_map` compatible with GCC 5.5, Clang 3.6, and Xcode 9. +373. [AK](https://github.com/Lioncky) fixed an MSVC build error caused by the `min`/`max` macros from `windows.h`. +374. [Sergiu Deitsch](https://github.com/sergiud) provided a fallback for missing `char8_t` support. +375. [Xiaochuan Ye](https://github.com/XueSongTap) fixed `from_msgpack` for `std::byte` input by specializing `std::char_traits`. +376. [Ville Vesilehto](https://github.com/thevilledev) fixed an overflow in the BJData size calculation and rejected overflowing negative integers in CBOR. +377. [NmPassTHFan](https://github.com/nmpassthf) replaced the deprecated `std::is_trivial` for C++26. +378. [Chris Ever](https://github.com/chirsz-ever) added the `ignore_trailing_commas` parser option. +379. [Kuan-Fu Wu](https://github.com/kfwu1999) fixed the example code for `json_pointer` initialization. +380. [David Kilzer](https://github.com/ddkilzer) added a missing header to the input adapters. +381. [Miko](https://github.com/mikomikotaishi) added proper C++20 module support, simplified the module API, and fixed missing exports. +382. [hitgirl](https://github.com/hitgil) fixed the CMake configuration when cross-compiling. +383. [Devon Thomas](https://github.com/ThomaDevOSU) mentioned the Artistic Style formatting in the contribution guidelines. +384. [Erik Hu](https://github.com/Erikhu1) made Coveralls upload errors non-fatal in the CI. +385. [co63oc](https://github.com/co63oc) fixed typos. +386. [DmitriBogdanov](https://github.com/DmitriBogdanov) fixed broken package manager links in the documentation. +387. [Bander](https://github.com/banderzhm) improved the MSVC compatibility of the C++ modules. +388. [Andy Choi](https://github.com/ccpong) removed an unnecessary `template` keyword before `get` in the README and the documentation. +389. [SamareshSingh](https://github.com/ssam18) fixed single-element brace initialization to copy/move instead of wrapping in an array, fixed the `WITH_DEFAULT` macros for `ordered_map`, and handled moved events in `serve_header.py`. +390. [Aditya](https://github.com/Lumowhisp) improved the documentation of the documentation generation. +391. [cheese1](https://github.com/cheese1) clarified the README. +392. [KhloodElhossiny](https://github.com/khloodelhossiny) enabled `std::string_view` keys in `operator[]`. +393. [Charles Cabergs](https://github.com/cacharle) fixed a `-Wtautological-constant-out-of-range-compare` warning. +394. [EALePain](https://github.com/EALePain) made the `std::tuple` conversion work with reference types such as `std::tie`. +395. [koala_oishi](https://github.com/chibi-dogs) fixed grammatical wording in the README. +396. [riccardoori11](https://github.com/riccardoori11) fixed a typo in the documentation. +397. [Swastik Bose](https://github.com/VasuBhakt) fixed the parent pointers after `update()` with `JSON_DIAGNOSTICS` and fixed the Doxygen autolinking of requirements. +398. [trdesilva](https://github.com/trdesilva) added `front`, `pop_front`, and `push_front` to `json_pointer`. +399. [Akhilesh Arora](https://github.com/akhilesharora) fixed an incomplete-type error with `ordered_json`. +400. [Hariom Phulre](https://github.com/hariomphulre) fixed the C++20 modules compilation with GCC. +401. [Kirill Lokotkov](https://github.com/RUSLoker) fixed printing `long double` values. +402. [George Sedov](https://github.com/radistmorse) added the `NLOHMANN_DEFINE_TYPE_*_WITH_NAMES` macros. +403. [Caillin Nugent](https://github.com/nugentcaillin) added the `NLOHMANN_JSON_SERIALIZE_ENUM_STRICT` macro. +404. [Cosmin D.](https://github.com/drcosmin) fixed `std::filesystem::path` conversions and added an MSVC workaround for `std::unique_ptr`. +405. [Paul Dreik](https://github.com/pauldreik) fixed a test relying on implementation-specific behavior. +406. [Daniel Falk](https://github.com/daniel-falk) added missing copyright notices to the SBOM. +407. [Federico Sfriso](https://github.com/federicosfriso05-dotcom) added support for constructing JSON values from C++20 range views. +408. [Luke Banicevic](https://github.com/banaboi) fixed corrupt BSON output for lengths exceeding `INT32_MAX`, cleaned up the BSON writer, and improved the documentation. +409. [Patrick Armstrong](https://github.com/Patrick10199) updated the CBOR references and the half-precision float assertions. +410. [Yash Bavadiya](https://github.com/xevrion) added checks to all BSON reads. +411. [hum4nBeing](https://github.com/hum4nBeing) fixed the overflow handling of high-precision numbers in UBJSON. +412. [tomatotomata](https://github.com/tomatotomata) added checks for reading CBOR tagged subtypes. +413. [YingqiDuan](https://github.com/YingqiDuan) documented the BSON interoperability. +414. [KBS](https://github.com/youdie006) documented the standards compliance and the strictness of `parse()` and `operator>>`. +415. [Petr Bělohlávek](https://github.com/petrbel) added Clang 21 and 22 to the CI. +416. [Dmitry Rantovov](https://github.com/darkdi) fixed the placement of a CBOR documentation block. +417. [ljcjclljc](https://github.com/ljcjclljc) fixed the comparison of large unsigned integers with signed integers. +418. [Sahil Kamate](https://github.com/sahilkamate03) fixed the handling of CBOR tags 0-5 and 21-23. +419. [Krishnanand G](https://github.com/Krishnanand-G) made the UBJSON writer reject `use_type` without `use_size`. +420. [whn](https://github.com/Whning0513) documented the lenient BSON input handling and corrected the complexity of `to_bson`. +421. [elix3r](https://github.com/22elix3r) fixed `update()` with `merge_objects` when merging a primitive into an object. +422. [Avionic Harshit](https://github.com/avionicharshit-byte) made `diff()` linear when an array shrinks. +423. [Qatadaha Bin Matloob](https://github.com/qatcod) fixed comparisons between integers and floats and fixed unparsable BJData output. +424. [Wu Shuwen](https://github.com/dajiaohuang) removed an unused include. Thanks a lot for helping out! Please [let me know](mailto:mail@nlohmann.me) if I forgot someone. @@ -1879,21 +1850,28 @@ The library itself consists of a single header file licensed under the MIT licen - [**AppVeyor**](https://www.appveyor.com) for [continuous integration](https://ci.appveyor.com/project/nlohmann/json) on Windows - [**Artistic Style**](https://astyle.sourceforge.net) for automatic source code indentation - [**Clang**](https://clang.llvm.org) for compilation with code sanitizers +- [**Clang-Tidy**](https://clang.llvm.org/extra/clang-tidy/) for static analysis and style checks - [**CMake**](https://cmake.org) for build automation - [**Codacy**](https://www.codacy.com) for further [code analysis](https://app.codacy.com/gh/nlohmann/json/dashboard) +- [**CodeQL**](https://codeql.github.com) for code scanning - [**Coveralls**](https://coveralls.io) to measure [code coverage](https://coveralls.io/github/nlohmann/json) - [**Coverity Scan**](https://scan.coverity.com) for [static analysis](https://scan.coverity.com/projects/nlohmann-json) - [**cppcheck**](https://cppcheck.sourceforge.io) for static analysis -- [**doctest**](https://github.com/onqtam/doctest) for the unit tests -- [**GitHub Changelog Generator**](https://github.com/skywinder/github-changelog-generator) to generate the [ChangeLog](https://github.com/nlohmann/json/blob/develop/ChangeLog.md) +- [**cpplint**](https://github.com/cpplint/cpplint) for style checks +- [**doctest**](https://github.com/doctest/doctest) for the unit tests +- [**Flawfinder**](https://dwheeler.com/flawfinder/) for static analysis +- [**GitHub Changelog Generator**](https://github.com/github-changelog-generator/github-changelog-generator) to generate the [ChangeLog](https://github.com/nlohmann/json/blob/develop/ChangeLog.md) - [**Google Benchmark**](https://github.com/google/benchmark) to implement the benchmarks - [**Hedley**](https://nemequ.github.io/hedley/) to avoid re-inventing several compiler-agnostic feature macros +- [**include-what-you-use**](https://include-what-you-use.org) to check the included headers +- [**Infer**](https://fbinfer.com) for static analysis - [**lcov**](https://github.com/linux-test-project/lcov) to process coverage information and create an HTML view - [**libFuzzer**](https://llvm.org/docs/LibFuzzer.html) to implement fuzz testing for OSS-Fuzz - [**Material for MkDocs**](https://squidfunk.github.io/mkdocs-material/) for the style of the documentation site - [**MkDocs**](https://www.mkdocs.org) for the documentation site - [**OSS-Fuzz**](https://github.com/google/oss-fuzz) for continuous fuzz testing of the library ([project repository](https://github.com/google/oss-fuzz/tree/master/projects/json)) - [**Probot**](https://probot.github.io) for automating maintainer tasks such as closing stale issues, requesting missing information, or detecting toxic comments. +- [**Semgrep**](https://semgrep.dev) for static analysis - [**Valgrind**](https://valgrind.org) to check for correct memory management ## Notes @@ -1987,10 +1965,10 @@ If the testdata is not found, several test suites will fail like this: ``` =============================================================================== -json/tests/src/make_test_data_available.hpp:21: +json/tests/src/make_test_data_available.hpp:26: TEST CASE: check test suite is downloaded -json/tests/src/make_test_data_available.hpp:23: FATAL ERROR: REQUIRE( utils::check_testsuite_downloaded() ) is NOT correct! +json/tests/src/make_test_data_available.hpp:28: FATAL ERROR: REQUIRE( utils::check_testsuite_downloaded() ) is NOT correct! values: REQUIRE( false ) logged: Test data not found in 'json/cmake-build-debug/json_test_data'. Please execute target 'download_test_data' before running this test suite. @@ -2001,10 +1979,10 @@ json/tests/src/make_test_data_available.hpp:23: FATAL ERROR: REQUIRE( utils::che In case you have downloaded the library rather than checked out the code via Git, test `cmake_fetch_content_configure` will fail. Please execute `ctest -LE git_required` to skip these tests. See [issue #2189](https://github.com/nlohmann/json/issues/2189) for more information. -Some tests are requiring network to be properly execute. They are labeled as `git_required`. Please execute `ctest -LE git_required` to skip these tests. See [issue #4851](https://github.com/nlohmann/json/issues/4851) for more information. +Some tests require network access to execute properly. They are labeled as `git_required`. Please execute `ctest -LE git_required` to skip these tests. See [issue #4851](https://github.com/nlohmann/json/issues/4851) for more information. Some tests change the installed files and hence make the whole process not reproducible. Please execute `ctest -LE not_reproducible` to skip these tests. See [issue #2324](https://github.com/nlohmann/json/issues/2324) for more information. Furthermore, assertions must be switched off to ensure reproducible builds (see [discussion 4494](https://github.com/nlohmann/json/discussions/4494)). -Note you need to call `cmake -LE "not_reproducible|git_required"` to exclude both labels. See [issue #2596](https://github.com/nlohmann/json/issues/2596) for more information. +Note you need to call `ctest -LE "not_reproducible|git_required"` to exclude both labels. See [issue #2596](https://github.com/nlohmann/json/issues/2596) for more information. As Intel compilers use unsafe floating point optimization by default, the unit tests may fail. Use flag [`/fp:precise`](https://www.intel.com/content/www/us/en/docs/cpp-compiler/developer-guide-reference/2021-8/fp-model-fp.html) then. diff --git a/REUSE.toml b/REUSE.toml index 47bcab16a..2fba496b2 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -10,10 +10,10 @@ SPDX-FileCopyrightText = "2013-2026 Niels Lohmann " SPDX-License-Identifier = "MIT" [[annotations]] -path = "include/nlohmann/thirdparty/hedley.hpp" +path = "include/nlohmann/thirdparty/hedley/hedley.hpp" precedence = "aggregate" SPDX-FileCopyrightText = "2016-2021 Evan Nemerson " -SPDX-License-Identifier = "CC0" +SPDX-License-Identifier = "CC0-1.0" [[annotations]] path = "include/nlohmann/detail/meta/cpp_future.hpp" @@ -50,3 +50,9 @@ path = "tools/gdb_pretty_printer/**" precedence = "aggregate" SPDX-FileCopyrightText = "2020 Hannes Domani " SPDX-License-Identifier = "MIT" + +[[annotations]] +path = "docs/docset/icon*.png" +precedence = "override" +SPDX-FileCopyrightText = "JSON logo (public domain), https://commons.wikimedia.org/wiki/File:JSON_vector_logo.svg" +SPDX-License-Identifier = "CC0-1.0" diff --git a/cmake/ci.cmake b/cmake/ci.cmake index 15345c5d6..5affec6c5 100644 --- a/cmake/ci.cmake +++ b/cmake/ci.cmake @@ -5,15 +5,14 @@ set(N 10) # Needed tools. ############################################################################### -include(FindPython3) find_package(Python3 COMPONENTS Interpreter) -find_program(CLANG_TOOL NAMES clang++-HEAD clang++ clang++-22 clang++-21 clang++-20 clang++-19 clang++-18 clang++-17 clang++-16 clang++-15 clang++-14 clang++-13 clang++-12 clang++-11 clang++) +find_program(CLANG_TOOL NAMES clang++ clang++-HEAD clang++-22 clang++-21 clang++-20 clang++-19 clang++-18 clang++-17 clang++-16 clang++-15 clang++-14 clang++-13 clang++-12 clang++-11) execute_process(COMMAND ${CLANG_TOOL} --version OUTPUT_VARIABLE CLANG_TOOL_VERSION ERROR_VARIABLE CLANG_TOOL_VERSION) string(REGEX MATCH "[0-9]+(\\.[0-9]+)+" CLANG_TOOL_VERSION "${CLANG_TOOL_VERSION}") message(STATUS "🔖 Clang ${CLANG_TOOL_VERSION} (${CLANG_TOOL})") -find_program(CLANG_TIDY_TOOL NAMES clang-tidy-22 clang-tidy-21 clang-tidy-20 clang-tidy-19 clang-tidy-18 clang-tidy-17 clang-tidy-16 clang-tidy-15 clang-tidy-14 clang-tidy-13 clang-tidy-12 clang-tidy-11 clang-tidy) +find_program(CLANG_TIDY_TOOL NAMES clang-tidy clang-tidy-22 clang-tidy-21 clang-tidy-20 clang-tidy-19 clang-tidy-18 clang-tidy-17 clang-tidy-16 clang-tidy-15 clang-tidy-14 clang-tidy-13 clang-tidy-12 clang-tidy-11) execute_process(COMMAND ${CLANG_TIDY_TOOL} --version OUTPUT_VARIABLE CLANG_TIDY_TOOL_VERSION ERROR_VARIABLE CLANG_TIDY_TOOL_VERSION) string(REGEX MATCH "[0-9]+(\\.[0-9]+)+" CLANG_TIDY_TOOL_VERSION "${CLANG_TIDY_TOOL_VERSION}") message(STATUS "🔖 Clang-Tidy ${CLANG_TIDY_TOOL_VERSION} (${CLANG_TIDY_TOOL})") @@ -69,9 +68,7 @@ string(REGEX MATCH "[0-9]+(\\.[0-9]+)+" VALGRIND_TOOL_VERSION "${VALGRIND_TOOL_V message(STATUS "🔖 Valgrind ${VALGRIND_TOOL_VERSION} (${VALGRIND_TOOL})") find_program(GENHTML_TOOL NAMES genhtml) -find_program(PLOG_CONVERTER_TOOL NAMES plog-converter) -find_program(PVS_STUDIO_ANALYZER_TOOL NAMES pvs-studio-analyzer) -find_program(SCAN_BUILD_TOOL NAMES scan-build-15 scan-build-14 scan-build-13 scan-build-12 scan-build-11 scan-build) +find_program(SCAN_BUILD_TOOL NAMES scan-build scan-build-22 scan-build-21 scan-build-20 scan-build-19 scan-build-18 scan-build-17 scan-build-16 scan-build-15 scan-build-14 scan-build-13 scan-build-12 scan-build-11) # the individual source files file(GLOB_RECURSE SRC_FILES ${PROJECT_SOURCE_DIR}/include/nlohmann/*.hpp) @@ -367,7 +364,7 @@ add_custom_target(ci_test_coverage COMMAND ${LCOV_TOOL} --directory . --capture --output-file json.info --rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 --ignore-errors mismatch --ignore-errors unused COMMAND ${LCOV_TOOL} -e json.info ${SRC_FILES} --output-file json.info.filtered --rc branch_coverage=1 --ignore-errors unused COMMAND ${CMAKE_SOURCE_DIR}/tests/thirdparty/imapdl/filterbr.py json.info.filtered > json.info.filtered.noexcept - COMMAND genhtml --title "JSON for Modern C++" --legend --demangle-cpp --output-directory html --show-details --branch-coverage json.info.filtered.noexcept + COMMAND ${GENHTML_TOOL} --title "JSON for Modern C++" --legend --demangle-cpp --output-directory html --show-details --branch-coverage json.info.filtered.noexcept COMMENT "Compile and test with coverage" ) @@ -392,8 +389,8 @@ add_custom_target(ci_test_clang_sanitizer # Check if header is amalgamated and sources are properly indented. ############################################################################### -# Same file set as .github/workflows/check_amalgamation.yml, so a direct push to develop/master/release/* -# (which only this CMake target checks, not the pull_request-only workflow) is held to the same standard. +# Same file set as .github/workflows/check_amalgamation.yml, so this CMake target (which can be run locally) is held +# to the same standard as the workflow (which runs for pull requests and for pushes to develop/master/release/*). file(GLOB_RECURSE INDENT_FILES ${PROJECT_SOURCE_DIR}/docs/mkdocs/docs/examples/*.hpp ${PROJECT_SOURCE_DIR}/docs/mkdocs/docs/examples/*.cpp @@ -423,7 +420,7 @@ add_custom_target(ci_test_amalgamation COMMAND ${Python3_EXECUTABLE} ${tool_dir}/amalgamate.py -c ${tool_dir}/config_json.json -s . COMMAND ${Python3_EXECUTABLE} ${tool_dir}/amalgamate.py -c ${tool_dir}/config_json_fwd.json -s . COMMAND cp ${PROJECT_SOURCE_DIR}/include/nlohmann/json_literals.hpp ${include_dir}/json_literals.hpp - COMMAND venv_astyle/bin/astyle --project=tools/astyle/.astylerc --suffix=none ${include_dir}/json.hpp ${include_dir}/json_fwd.hpp + COMMAND venv_astyle/bin/astyle --project=tools/astyle/.astylerc --suffix=none ${include_dir}/json.hpp ${include_dir}/json_fwd.hpp ${include_dir}/json_literals.hpp COMMAND ${CMAKE_COMMAND} -P ${PROJECT_SOURCE_DIR}/cmake/scripts/gen_bazel_build_file.cmake COMMAND diff ${include_dir}/json.hpp~ ${include_dir}/json.hpp @@ -434,6 +431,8 @@ add_custom_target(ci_test_amalgamation COMMAND venv_astyle/bin/astyle --project=tools/astyle/.astylerc --suffix=orig ${INDENT_FILES} COMMAND for FILE in `find . -name '*.orig'`\; do false \; done + COMMAND rm -f ${include_dir}/json.hpp~ ${include_dir}/json_fwd.hpp~ ${include_dir}/json_literals.hpp~ ${PROJECT_SOURCE_DIR}/BUILD.bazel~ + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMENT "Check amalgamation, formatting, and BUILD.bazel" ) @@ -719,7 +718,7 @@ ci_get_cmake(4.0.0 CMAKE_4_0_0_BINARY) # the tests require CMake 3.13 or later, so they are excluded for CMake 3.5.0 set(JSON_CMAKE_FLAGS_3_5_0 JSON_Diagnostics JSON_Diagnostic_Positions JSON_GlobalUDLs JSON_ImplicitConversions JSON_DisableEnumSerialization - JSON_LegacyDiscardedValueComparison JSON_Install JSON_MultipleHeaders JSON_SystemInclude JSON_Valgrind + JSON_DisableTupleReferenceConversion JSON_LegacyDiscardedValueComparison JSON_Install JSON_MultipleHeaders JSON_SystemInclude JSON_Valgrind JSON_StrictNulHandling JSON_StrictBinaryUTF8 JSON_DeleteDeprecatedFunctions) set(JSON_CMAKE_FLAGS_3_31_6 JSON_BuildTests ${JSON_CMAKE_FLAGS_3_5_0}) set(JSON_CMAKE_FLAGS_4_0_0 JSON_BuildTests ${JSON_CMAKE_FLAGS_3_5_0}) @@ -776,17 +775,14 @@ add_custom_target(ci_cmake_flags # Use more installed compilers. ############################################################################### -foreach(COMPILER g++-4.8 g++-4.9 g++-5 g++-6 g++-7 g++-8 g++-9 g++-10 g++-11 clang++-3.5 clang++-3.6 clang++-3.7 clang++-3.8 clang++-3.9 clang++-4.0 clang++-5.0 clang++-6.0 clang++-7 clang++-8 clang++-9 clang++-10 clang++-11 clang++-12 clang++-13 clang++-14 clang++-15 clang++-16 clang++-17 clang++-18 clang++-19 clang++-20) +foreach(COMPILER g++-4.8 g++-4.9 g++-5 g++-6) find_program(COMPILER_TOOL NAMES ${COMPILER}) if (COMPILER_TOOL) - unset(ADDITIONAL_FLAGS) - add_custom_target(ci_test_compiler_${COMPILER} COMMAND CXX=${COMPILER} ${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE=Debug -GNinja -DJSON_BuildTests=ON -DJSON_FastTests=ON -S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_compiler_${COMPILER} - ${ADDITIONAL_FLAGS} COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_compiler_${COMPILER} COMMAND cd ${PROJECT_BINARY_DIR}/build_compiler_${COMPILER} && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure COMMENT "Compile and test with ${COMPILER}" @@ -800,7 +796,6 @@ add_custom_target(ci_test_compiler_default -DCMAKE_BUILD_TYPE=Debug -GNinja -DJSON_BuildTests=ON -DJSON_FastTests=ON -S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_compiler_default - ${ADDITIONAL_FLAGS} COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_compiler_default --parallel ${N} COMMAND cd ${PROJECT_BINARY_DIR}/build_compiler_default && ${CMAKE_CTEST_COMMAND} --parallel ${N} -LE git_required --output-on-failure COMMENT "Compile and test with default C++ compiler" @@ -823,10 +818,12 @@ add_custom_target(ci_cuda_example add_custom_target(ci_module_cpp20 COMMAND ${CMAKE_COMMAND} - -DCMAKE_BUILD_TYPE=Debug -GNinja + -DCMAKE_BUILD_TYPE=Debug -GNinja -DJSON_CI=ON -DNLOHMANN_JSON_BUILD_MODULES=ON -DJSON_Install=ON + "-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}" -S${PROJECT_SOURCE_DIR}/tests/module_cpp20 -B${PROJECT_BINARY_DIR}/ci_module_cpp20 COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/ci_module_cpp20 + VERBATIM ) ############################################################################### diff --git a/cmake/scripts/gen_bazel_build_file.cmake b/cmake/scripts/gen_bazel_build_file.cmake index ca5069cb0..fc4e5bab4 100644 --- a/cmake/scripts/gen_bazel_build_file.cmake +++ b/cmake/scripts/gen_bazel_build_file.cmake @@ -49,6 +49,7 @@ cc_library( hdrs = [ "single_include/nlohmann/json.hpp", "single_include/nlohmann/json_fwd.hpp", + "single_include/nlohmann/json_literals.hpp", ], includes = ["single_include"], visibility = ["//visibility:public"], diff --git a/docs/README.md b/docs/README.md index 7cca7f12f..e4486857e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -7,12 +7,12 @@ This URL provides the most recent documentation and also applies to previous ver functions is not removed; instead, it is marked as deprecated. If you want to view the documentation for a specific tag or commit hash, you can generate it locally as follows (example -using tag `v3.10.2`): +using tag `v3.12.0`): ```shell git clone https://github.com/nlohmann/json.git cd json -git checkout v3.10.2 +git checkout v3.12.0 make install_venv serve -C docs/mkdocs ``` diff --git a/docs/mkdocs/docs/api/basic_json/contains.md b/docs/mkdocs/docs/api/basic_json/contains.md index 63ec87a1d..c982ab9b9 100644 --- a/docs/mkdocs/docs/api/basic_json/contains.md +++ b/docs/mkdocs/docs/api/basic_json/contains.md @@ -10,6 +10,10 @@ bool contains(KeyType&& key) const; // (3) bool contains(const json_pointer& ptr) const; + +// (4) +template +bool contains(T) const = delete; ``` 1. Check whether an element exists in a JSON object with a key equivalent to `key`. If the element is not found or the @@ -17,6 +21,9 @@ bool contains(const json_pointer& ptr) const; 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. +4. Deleted: this overload is only available if `T` is an integral type and is declared as deleted, so that a call with + an integer `key` (for example, `#!cpp j.contains(0)`) fails to compile. Otherwise, the integer literal `0` would convert to + a null `#!cpp const char*` and, from there, to the key type, causing undefined behavior at runtime. ## Template parameters @@ -39,6 +46,7 @@ bool contains(const json_pointer& ptr) const; is not an object, `#!cpp false` is returned. 2. See 1. 3. `#!cpp true` if the JSON pointer can be resolved to a stored value, `#!cpp false` otherwise. +4. Deleted; a call with an integral argument does not compile. ## Exception safety @@ -49,6 +57,7 @@ Strong exception safety: if an exception occurs, the original value stays intact 1. The function does not throw exceptions. 2. The function does not throw exceptions. 3. The function does not throw exceptions. +4. Deleted; a call with an integral argument does not compile. ## Complexity diff --git a/docs/mkdocs/docs/api/basic_json/count.md b/docs/mkdocs/docs/api/basic_json/count.md index 14b707525..707e1d439 100644 --- a/docs/mkdocs/docs/api/basic_json/count.md +++ b/docs/mkdocs/docs/api/basic_json/count.md @@ -7,12 +7,19 @@ size_type count(const typename object_t::key_type& key) const; // (2) template size_type count(KeyType&& key) const; + +// (3) +template +size_type count(T) const = delete; ``` 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. +3. Deleted: this overload is only available if `T` is an integral type and is declared as deleted, so that a call with + an integer `key` (for example, `#!cpp j.count(0)`) fails to compile. Otherwise, the integer literal `0` would convert to + a null `#!cpp const char*` and, from there, to the key type, causing undefined behavior at runtime. ## Template parameters diff --git a/docs/mkdocs/docs/api/basic_json/error_handler_t.md b/docs/mkdocs/docs/api/basic_json/error_handler_t.md index 17327fe26..a80f74a55 100644 --- a/docs/mkdocs/docs/api/basic_json/error_handler_t.md +++ b/docs/mkdocs/docs/api/basic_json/error_handler_t.md @@ -37,8 +37,8 @@ ignore : ignore invalid UTF-8 sequences; all valid bytes are copied to the output unchanged, and invalid bytes are dropped keep -: keep invalid UTF-8 sequences unchanged; only meaningful for the binary formats mentioned above, since [`dump`] - (dump.md) itself must produce text, and `keep` there writes the ill-formed bytes to the output as is, so the +: keep invalid UTF-8 sequences unchanged; only meaningful for the binary formats mentioned above, since + [`dump`](dump.md) itself must produce text, and `keep` there writes the ill-formed bytes to the output as is, so the result is then not valid UTF-8 (but still equals the input bytes exactly, including around any well-formed characters, which are still escaped as usual) diff --git a/docs/mkdocs/docs/api/basic_json/find.md b/docs/mkdocs/docs/api/basic_json/find.md index 59c2eab68..dc9a410d7 100644 --- a/docs/mkdocs/docs/api/basic_json/find.md +++ b/docs/mkdocs/docs/api/basic_json/find.md @@ -10,12 +10,21 @@ template iterator find(KeyType&& key); template const_iterator find(KeyType&& key) const; + +// (3) +template +iterator find(T) = delete; +template +const_iterator find(T) const = delete; ``` 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. +3. Deleted: this overload is only available if `T` is an integral type and is declared as deleted, so that a call with + an integer `key` (for example, `#!cpp j.find(0)`) fails to compile. Otherwise, the integer literal `0` would convert to + a null `#!cpp const char*` and, from there, to the key type, causing undefined behavior at runtime. ## Template parameters diff --git a/docs/mkdocs/docs/api/basic_json/from_bjdata.md b/docs/mkdocs/docs/api/basic_json/from_bjdata.md index f19f9e4a9..370124c8a 100644 --- a/docs/mkdocs/docs/api/basic_json/from_bjdata.md +++ b/docs/mkdocs/docs/api/basic_json/from_bjdata.md @@ -124,7 +124,8 @@ Linear in the size of the input. !!! warning "Deprecation" - Overload (2) replaces calls to `from_bjdata` with a pointer and a length as first two parameters, which has been - deprecated in version 3.13.0. This overload will be removed in version 4.0.0. Please replace all calls like + deprecated in version 3.13.0. In version 4.0.0, this overload + will be deleted (`= delete`) rather than removed. Please replace all calls like `#!cpp from_bjdata(ptr, len, ...);` with `#!cpp from_bjdata(ptr, ptr+len, ...);`. You should be warned by your compiler with a `-Wdeprecated-declarations` warning if you are using a deprecated diff --git a/docs/mkdocs/docs/api/basic_json/from_bon8.md b/docs/mkdocs/docs/api/basic_json/from_bon8.md index cac6b31eb..e566b8688 100644 --- a/docs/mkdocs/docs/api/basic_json/from_bon8.md +++ b/docs/mkdocs/docs/api/basic_json/from_bon8.md @@ -110,7 +110,8 @@ Linear in the size of the input. !!! warning "Deprecation" - Overload (2) replaces calls to `from_bon8` with a pointer and a length as first two parameters, which has been - deprecated in version 3.13.0. This overload will be removed in version 4.0.0. Please replace all calls like + deprecated in version 3.13.0. In version 4.0.0, this overload + will be deleted (`= delete`) rather than removed. Please replace all calls like `#!cpp from_bon8(ptr, len, ...);` with `#!cpp from_bon8(ptr, ptr+len, ...);`. You should be warned by your compiler with a `-Wdeprecated-declarations` warning if you are using a deprecated diff --git a/docs/mkdocs/docs/api/basic_json/from_bson.md b/docs/mkdocs/docs/api/basic_json/from_bson.md index b037e8e07..6e0beafce 100644 --- a/docs/mkdocs/docs/api/basic_json/from_bson.md +++ b/docs/mkdocs/docs/api/basic_json/from_bson.md @@ -126,7 +126,8 @@ Linear in the size of the input. !!! warning "Deprecation" - Overload (2) replaces calls to `from_bson` with a pointer and a length as first two parameters, which has been - deprecated in version 3.8.0. This overload will be removed in version 4.0.0. Please replace all calls like + deprecated in version 3.8.0. In version 4.0.0, this overload + will be deleted (`= delete`) rather than removed. Please replace all calls like `#!cpp from_bson(ptr, len, ...);` with `#!cpp from_bson(ptr, ptr+len, ...);`. - Overload (2) replaces calls to `from_bson` with a pair of iterators as their first parameter, which has been deprecated in version 3.8.0. This overload will be removed in version 4.0.0. Please replace all calls like diff --git a/docs/mkdocs/docs/api/basic_json/from_cbor.md b/docs/mkdocs/docs/api/basic_json/from_cbor.md index 7d7df08a5..4fcd7fe98 100644 --- a/docs/mkdocs/docs/api/basic_json/from_cbor.md +++ b/docs/mkdocs/docs/api/basic_json/from_cbor.md @@ -135,7 +135,8 @@ Linear in the size of the input. !!! warning "Deprecation" - Overload (2) replaces calls to `from_cbor` with a pointer and a length as first two parameters, which has been - deprecated in version 3.8.0. This overload will be removed in version 4.0.0. Please replace all calls like + deprecated in version 3.8.0. In version 4.0.0, this overload + will be deleted (`= delete`) rather than removed. Please replace all calls like `#!cpp from_cbor(ptr, len, ...);` with `#!cpp from_cbor(ptr, ptr+len, ...);`. - Overload (2) replaces calls to `from_cbor` with a pair of iterators as their first parameter, which has been deprecated in version 3.8.0. This overload will be removed in version 4.0.0. Please replace all calls like diff --git a/docs/mkdocs/docs/api/basic_json/from_msgpack.md b/docs/mkdocs/docs/api/basic_json/from_msgpack.md index 2e7fa5062..51e23de7b 100644 --- a/docs/mkdocs/docs/api/basic_json/from_msgpack.md +++ b/docs/mkdocs/docs/api/basic_json/from_msgpack.md @@ -127,7 +127,8 @@ Linear in the size of the input. !!! warning "Deprecation" - Overload (2) replaces calls to `from_msgpack` with a pointer and a length as first two parameters, which has been - deprecated in version 3.8.0. This overload will be removed in version 4.0.0. Please replace all calls like + deprecated in version 3.8.0. In version 4.0.0, this overload + will be deleted (`= delete`) rather than removed. Please replace all calls like `#!cpp from_msgpack(ptr, len, ...);` with `#!cpp from_msgpack(ptr, ptr+len, ...);`. - Overload (2) replaces calls to `from_msgpack` with a pair of iterators as their first parameter, which has been deprecated in version 3.8.0. This overload will be removed in version 4.0.0. Please replace all calls like diff --git a/docs/mkdocs/docs/api/basic_json/from_ubjson.md b/docs/mkdocs/docs/api/basic_json/from_ubjson.md index ac9404d4c..3881f9ea4 100644 --- a/docs/mkdocs/docs/api/basic_json/from_ubjson.md +++ b/docs/mkdocs/docs/api/basic_json/from_ubjson.md @@ -125,7 +125,8 @@ Linear in the size of the input. !!! warning "Deprecation" - Overload (2) replaces calls to `from_ubjson` with a pointer and a length as first two parameters, which has been - deprecated in version 3.8.0. This overload will be removed in version 4.0.0. Please replace all calls like + deprecated in version 3.8.0. In version 4.0.0, this overload + will be deleted (`= delete`) rather than removed. Please replace all calls like `#!cpp from_ubjson(ptr, len, ...);` with `#!cpp from_ubjson(ptr, ptr+len, ...);`. - Overload (2) replaces calls to `from_ubjson` with a pair of iterators as their first parameter, which has been deprecated in version 3.8.0. This overload will be removed in version 4.0.0. Please replace all calls like diff --git a/docs/mkdocs/docs/api/basic_json/json_base_class_t.md b/docs/mkdocs/docs/api/basic_json/json_base_class_t.md index 6f0026558..2beed5a22 100644 --- a/docs/mkdocs/docs/api/basic_json/json_base_class_t.md +++ b/docs/mkdocs/docs/api/basic_json/json_base_class_t.md @@ -62,5 +62,5 @@ same name. Hidden members remain accessible via [`as_base_class`](as_base_class. ## Version history -- Added in version 3.12.0. +- Added in version 3.11.3. - Made a public member type in version 3.13.0; it was private before, so it could not be named outside the class. diff --git a/docs/mkdocs/docs/api/macros/index.md b/docs/mkdocs/docs/api/macros/index.md index eb2807b69..be9f6332c 100644 --- a/docs/mkdocs/docs/api/macros/index.md +++ b/docs/mkdocs/docs/api/macros/index.md @@ -27,6 +27,7 @@ header. See also the [macro overview page](../../features/macros.md). - [**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_HAS_RANGE_VIEW_CONVERSION**](json_has_range_view_conversion.md) - control construction from `std::ranges` views - [**JSON_HAS_RANGES**](json_has_ranges.md) - control `std::ranges` support - [**JSON_HAS_STATIC_RTTI**](json_has_static_rtti.md) - control RTTI (run time type information) support - [**JSON_HAS_STD_FORMAT**](json_has_std_format.md) - control `std::format`/`std::formatter` support diff --git a/docs/mkdocs/docs/api/macros/json_has_cpp_11.md b/docs/mkdocs/docs/api/macros/json_has_cpp_11.md index 6ab33bfe0..f32fe0277 100644 --- a/docs/mkdocs/docs/api/macros/json_has_cpp_11.md +++ b/docs/mkdocs/docs/api/macros/json_has_cpp_11.md @@ -49,6 +49,6 @@ The default value is detected based on preprocessor macros such as `#!cpp __cplu ## Version history -- Added in version 3.10.5. +- Added in version 3.10.0. - Added `JSON_HAS_CPP_23` in version 3.12.0. - Added `JSON_HAS_CPP_26` in version 3.13.0. diff --git a/docs/mkdocs/docs/api/macros/json_has_range_view_conversion.md b/docs/mkdocs/docs/api/macros/json_has_range_view_conversion.md new file mode 100644 index 000000000..6392170c9 --- /dev/null +++ b/docs/mkdocs/docs/api/macros/json_has_range_view_conversion.md @@ -0,0 +1,43 @@ +# JSON_HAS_RANGE_VIEW_CONVERSION + +```cpp +#define JSON_HAS_RANGE_VIEW_CONVERSION /* value */ +``` + +This macro indicates whether a JSON array can be constructed directly from a C++20 range view (`std::ranges::view`), +such as the result of `std::views::filter` or `std::views::transform`. Possible values are `1` when supported or `0` +when unsupported. + +## Default definition + +The default value is `1` if [`JSON_HAS_RANGES`](json_has_ranges.md) is `1` and the compiler is not MinGW (that is, +`#!cpp __MINGW32__` is not defined), and `0` otherwise. + +When the macro is not defined, the library will define it to its default value. + +!!! info "Known compiler/stdlib exclusions" + + - **MinGW** -- disabled, because its `std::ranges` support is incomplete ([issue #4916](https://github.com/nlohmann/json/issues/4916)). + - All toolchains for which [`JSON_HAS_RANGES`](json_has_ranges.md#default-definition) is disabled. + +## Examples + +??? example + + The code below forces the library to disable the conversion from range views: + + ```cpp + #define JSON_HAS_RANGE_VIEW_CONVERSION 0 + #include + + ... + ``` + +## See also + +- [JSON_HAS_RANGES](json_has_ranges.md) - control `std::ranges` support +- [Constructing from a C++20 range view](../../features/conversions.md#putting-values-in) - usage of the feature + +## Version history + +- Added in version 3.13.0. diff --git a/docs/mkdocs/docs/api/macros/json_has_ranges.md b/docs/mkdocs/docs/api/macros/json_has_ranges.md index 0bb3c9b58..b22e816ad 100644 --- a/docs/mkdocs/docs/api/macros/json_has_ranges.md +++ b/docs/mkdocs/docs/api/macros/json_has_ranges.md @@ -20,7 +20,7 @@ When the macro is not defined, the library will define it to its default value. - **GCC 11.1.0** — disabled (the shipped `` header has a syntax error; [issue #4440](https://github.com/nlohmann/json/issues/4440)) - **libstdc++ < 11** — disabled (incomplete C++20 ranges support; [issue #4440](https://github.com/nlohmann/json/issues/4440)) - - **Clang < 16 with libstdc++** — disabled (incomplete ranges support; [issue #4440](https://github.com/nlohmann/json/issues/4440)) + - **Clang < 16 with libstdc++** — disabled (incomplete ranges support; [issue #5161](https://github.com/nlohmann/json/issues/5161)) - **libc++ < 160000** — disabled (incomplete C++20 ranges support; [issue #4440](https://github.com/nlohmann/json/issues/4440)) - **nvcc (CUDA) 12.0.x and 12.1.x** — disabled (the `enable_borrowed_range` variable-template syntax triggers a parse error under these two toolkit versions; fixed in CUDA 12.2; [issue #3907](https://github.com/nlohmann/json/issues/3907)) @@ -44,9 +44,12 @@ When the macro is not defined, the library will define it to its default value. - [JSON_HAS_CPP_11 / JSON_HAS_CPP_14 / JSON_HAS_CPP_17 / JSON_HAS_CPP_20 / JSON_HAS_CPP_23 / JSON_HAS_CPP_26](json_has_cpp_11.md) - set supported C++ standard +- [JSON_HAS_RANGE_VIEW_CONVERSION](json_has_range_view_conversion.md) - control construction from `std::ranges` views - [JSON_HAS_STD_FORMAT](json_has_std_format.md) - a similar feature-detection macro, for `std::format`/`std::formatter` support ## Version history - Added in version 3.11.0. +- Added the exclusions for libstdc++ < 11, Clang < 16 with libstdc++, libc++ < 16, and nvcc (CUDA) 12.0.x and 12.1.x in + version 3.13.0. Before, only GCC 11.1.0 was excluded. diff --git a/docs/mkdocs/docs/api/macros/json_strict_binary_utf8.md b/docs/mkdocs/docs/api/macros/json_strict_binary_utf8.md index 4c573d56e..2369f5f4d 100644 --- a/docs/mkdocs/docs/api/macros/json_strict_binary_utf8.md +++ b/docs/mkdocs/docs/api/macros/json_strict_binary_utf8.md @@ -20,7 +20,10 @@ The macro does not affect: - The binary readers ([`from_cbor`](../basic_json/from_cbor.md), [`from_msgpack`](../basic_json/from_msgpack.md), [`from_ubjson`](../basic_json/from_ubjson.md), [`from_bjdata`](../basic_json/from_bjdata.md), [`from_bson`](../basic_json/from_bson.md)): none of these formats requires a decoder to reject ill-formed UTF-8, so - they always return the bytes unchanged. + by default they return the bytes unchanged (`error_handler_t::keep`), independent of this macro. Pass an + `error_handler` argument explicitly to validate or sanitize the strings they read. +- [`from_bon8`](../basic_json/from_bon8.md): BON8 always validates, because the UTF-8 lead bytes mark where a string + ends. ## Default definition diff --git a/docs/mkdocs/docs/api/macros/nlohmann_define_derived_type.md b/docs/mkdocs/docs/api/macros/nlohmann_define_derived_type.md index f6a01af5b..c539e77a0 100644 --- a/docs/mkdocs/docs/api/macros/nlohmann_define_derived_type.md +++ b/docs/mkdocs/docs/api/macros/nlohmann_define_derived_type.md @@ -190,3 +190,5 @@ void to_json(BasicJsonType& j, const B& b) { 4. Added in version 3.12.0. 5. Added in version 3.12.0. 6. Added in version 3.12.0. + +All six macros were changed to allow an empty member list in version 3.13.0. diff --git a/docs/mkdocs/docs/api/macros/nlohmann_define_type_intrusive.md b/docs/mkdocs/docs/api/macros/nlohmann_define_type_intrusive.md index 3db7722d8..dab5c7070 100644 --- a/docs/mkdocs/docs/api/macros/nlohmann_define_type_intrusive.md +++ b/docs/mkdocs/docs/api/macros/nlohmann_define_type_intrusive.md @@ -186,3 +186,6 @@ See the examples below for the concrete generated code. 1. Added in version 3.9.0. 2. Added in version 3.11.0. 3. Added in version 3.11.3. + +All three macros were changed to work with any `basic_json` specialization (not only `nlohmann::json`) in version 3.12.0, +and to allow an empty member list in version 3.13.0. diff --git a/docs/mkdocs/docs/api/macros/nlohmann_define_type_non_intrusive.md b/docs/mkdocs/docs/api/macros/nlohmann_define_type_non_intrusive.md index 744a513b0..07d0166d0 100644 --- a/docs/mkdocs/docs/api/macros/nlohmann_define_type_non_intrusive.md +++ b/docs/mkdocs/docs/api/macros/nlohmann_define_type_non_intrusive.md @@ -185,3 +185,6 @@ See the examples below for the concrete generated code. 1. Added in version 3.9.0. 2. Added in version 3.11.0. 3. Added in version 3.11.3. + +All three macros were changed to work with any `basic_json` specialization (not only `nlohmann::json`) in version 3.12.0, +and to allow an empty member list in version 3.13.0. diff --git a/docs/mkdocs/docs/api/macros/nlohmann_json_namespace.md b/docs/mkdocs/docs/api/macros/nlohmann_json_namespace.md index 5c54dba52..669fabda2 100644 --- a/docs/mkdocs/docs/api/macros/nlohmann_json_namespace.md +++ b/docs/mkdocs/docs/api/macros/nlohmann_json_namespace.md @@ -38,4 +38,6 @@ the library. ## Version history -- Added in version 3.11.0. Changed inline namespace name in version 3.11.2. +- Added in version 3.11.0. Changed inline namespace name in version 3.11.2. Added the ABI tag `_dp` in + version 3.12.0, and the ABI tags `_bics`, `_psp`, `_snul`, `_sbu8`, and `_ekmo` in version 3.13.0; see + [`nlohmann` Namespace](../../features/namespace.md#structure). diff --git a/docs/mkdocs/docs/api/macros/nlohmann_json_namespace_begin.md b/docs/mkdocs/docs/api/macros/nlohmann_json_namespace_begin.md index 118fba63a..00911fb89 100644 --- a/docs/mkdocs/docs/api/macros/nlohmann_json_namespace_begin.md +++ b/docs/mkdocs/docs/api/macros/nlohmann_json_namespace_begin.md @@ -14,7 +14,7 @@ These macros can be used to open and close the `nlohmann` namespace. See ## Default definition The default definitions open and close the `nlohmann` namespace. The precise definition of -[`NLOHMANN_JSON_NAMESPACE_BEGIN`] varies as described [here](../../features/namespace.md#structure). +`NLOHMANN_JSON_NAMESPACE_BEGIN` varies as described [here](../../features/namespace.md#structure). 1. Default definition of `NLOHMANN_JSON_NAMESPACE_BEGIN`: diff --git a/docs/mkdocs/docs/community/assurance_case.md b/docs/mkdocs/docs/community/assurance_case.md index 9d9712a03..46ebd5ebe 100644 --- a/docs/mkdocs/docs/community/assurance_case.md +++ b/docs/mkdocs/docs/community/assurance_case.md @@ -10,7 +10,7 @@ The library parses, stores, and serializes JSON values in memory. It does not op files (it only reads from streams or `std::FILE*` handles that the caller has already opened), does not read environment variables, and does not implement cryptography or handle credentials. -The primary threat is therefore **untrusted input**: JSON text or binary data (BJData, BSON, CBOR, MessagePack, UBJSON) +The primary threat is therefore **untrusted input**: JSON text or binary data (BJData, BON8, BSON, CBOR, MessagePack, UBJSON) that an attacker controls, passed to [`parse`](../api/basic_json/parse.md), [`accept`](../api/basic_json/accept.md), [`sax_parse`](../api/basic_json/sax_parse.md), or one of the `from_*` functions such as [`from_cbor`](../api/basic_json/from_cbor.md). Such input may try to diff --git a/docs/mkdocs/docs/community/quality_assurance.md b/docs/mkdocs/docs/community/quality_assurance.md index bd8f55790..afef80ff8 100644 --- a/docs/mkdocs/docs/community/quality_assurance.md +++ b/docs/mkdocs/docs/community/quality_assurance.md @@ -12,7 +12,7 @@ violations will result in a failed build. Note: C++20 modules support may hit compiler-specific issues not covered by the general compiler matrix below. See [Modules](../features/modules.md#known-issues) for known issues and workarounds. -Note: Some modern features (like C++20 ranges or filesystem support) may be disabled on specific broken or incomplete toolchains even when standard feature-test macros indicate support. See [`JSON_HAS_RANGES`](../api/macros/json_has_ranges.md) and [`JSON_HAS_FILESYSTEM`](../api/macros/json_has_filesystem.md) for details on known exclusions. +Note: Some modern features (like C++20 ranges or filesystem support) may be disabled on specific broken or incomplete toolchains even when standard feature-test macros indicate support. See [`JSON_HAS_RANGES`](../api/macros/json_has_ranges.md), [`JSON_HAS_RANGE_VIEW_CONVERSION`](../api/macros/json_has_range_view_conversion.md), and [`JSON_HAS_FILESYSTEM`](../api/macros/json_has_filesystem.md) for details on known exclusions. - [x] The library is compiled with 50+ different C++ compilers with different operating systems and platforms, including the oldest versions known to compile the library. @@ -107,7 +107,7 @@ Note: Some modern features (like C++20 ranges or filesystem support) may be disa - [x] The library is compiled with all C++ language revisions (C++11, C++14, C++17, C++20, C++23, and C++26) to detect and fix language deprecations early. - [x] The library is checked for compiler warnings: - - On Clang, `-Weverything` is used with 8 exceptions. + - On Clang, `-Weverything` is used with 7 exceptions. ??? abstract "Clang warnings" diff --git a/docs/mkdocs/docs/features/conversions.md b/docs/mkdocs/docs/features/conversions.md index 94a660ec8..5ba42b7cf 100644 --- a/docs/mkdocs/docs/features/conversions.md +++ b/docs/mkdocs/docs/features/conversions.md @@ -217,7 +217,8 @@ json j = numbers; // [1,2,3] ``` This requires [`JSON_HAS_RANGES`](../api/macros/json_has_ranges.md) to be enabled and is unavailable on MinGW due - to incomplete C++20 ranges support there. + to incomplete C++20 ranges support there; see + [`JSON_HAS_RANGE_VIEW_CONVERSION`](../api/macros/json_has_range_view_conversion.md). ## Your own types diff --git a/docs/mkdocs/docs/features/macros.md b/docs/mkdocs/docs/features/macros.md index ef41ce0d5..5df3671e2 100644 --- a/docs/mkdocs/docs/features/macros.md +++ b/docs/mkdocs/docs/features/macros.md @@ -82,6 +82,45 @@ To override the built-in check, define `JSON_HAS_FILESYSTEM` or `JSON_HAS_EXPERI See [full documentation of `JSON_HAS_FILESYSTEM` and `JSON_HAS_EXPERIMENTAL_FILESYSTEM`](../api/macros/json_has_filesystem.md). +## `JSON_HAS_RANGES` + +The library uses `std::ranges` (and concepts) where available, for example, to parse from C++20 ranges and to +construct JSON arrays from range views. The library detects whether the standard library supports ranges and +disables the support on toolchains with an incomplete implementation. To override the built-in check, define +`JSON_HAS_RANGES` to `1` or `0`. + +See [full documentation of `JSON_HAS_RANGES`](../api/macros/json_has_ranges.md). + +## `JSON_HAS_RANGE_VIEW_CONVERSION` + +When `JSON_HAS_RANGES` is enabled (and the compiler is not MinGW), a JSON array can be constructed directly from a C++20 +range view such as `std::views::filter(...)`. To override the built-in check, define `JSON_HAS_RANGE_VIEW_CONVERSION` to +`1` or `0`. + +See [full documentation of `JSON_HAS_RANGE_VIEW_CONVERSION`](../api/macros/json_has_range_view_conversion.md). + +## `JSON_HAS_STATIC_RTTI` + +The library detects whether the compiler supports run time type information (RTTI), which it needs, for instance, to +exclude `std::any` from the candidate types of the implicit conversion on C++17. To override the built-in check, define +`JSON_HAS_STATIC_RTTI` to `1` or `0`. + +See [full documentation of `JSON_HAS_STATIC_RTTI`](../api/macros/json_has_static_rtti.md). + +## `JSON_HAS_STD_FORMAT` + +When compiling with C++20 and a standard library that provides ``, the library provides a `std::formatter` +specialization for JSON values. To override the built-in check, define `JSON_HAS_STD_FORMAT` to `1` or `0`. + +See [full documentation of `JSON_HAS_STD_FORMAT`](../api/macros/json_has_std_format.md). + +## `JSON_HAS_THREE_WAY_COMPARISON` + +When the compiler and standard library support 3-way comparison (the spaceship operator `<=>`), the library provides it +for JSON values. To override the built-in check, define `JSON_HAS_THREE_WAY_COMPARISON` to `1` or `0`. + +See [full documentation of `JSON_HAS_THREE_WAY_COMPARISON`](../api/macros/json_has_three_way_comparison.md). + ## `JSON_NOEXCEPTION` Exceptions can be switched off by defining the symbol `JSON_NOEXCEPTION`. diff --git a/docs/mkdocs/docs/features/namespace.md b/docs/mkdocs/docs/features/namespace.md index 26e89d09c..407d8a748 100644 --- a/docs/mkdocs/docs/features/namespace.md +++ b/docs/mkdocs/docs/features/namespace.md @@ -101,3 +101,9 @@ follows: - Introduced inline namespace (`json_v3_11_0[_abi-tag]*`) in version 3.11.0. - Changed structure of inline namespace in version 3.11.2. +- Added ABI tag `_dp` ([`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md)) in version 3.12.0. +- Added ABI tags `_bics` ([`JSON_BRACE_INIT_COPY_SEMANTICS`](../api/macros/json_brace_init_copy_semantics.md)), `_psp` + ([`JSON_PRECISE_STREAM_POSITION`](../api/macros/json_precise_stream_position.md)), `_snul` + ([`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md)), `_sbu8` + ([`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md)), and `_ekmo` + ([`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md)) in version 3.13.0. diff --git a/docs/mkdocs/docs/home/architecture.md b/docs/mkdocs/docs/home/architecture.md index 6c0892f11..ea47bab6e 100644 --- a/docs/mkdocs/docs/home/architecture.md +++ b/docs/mkdocs/docs/home/architecture.md @@ -34,7 +34,7 @@ flowchart LR - **JSON text** is read by an [input adapter](#input-adapters), tokenized by the lexer, and turned into SAX events by the parser. -- **Binary formats** (BJData, BSON, CBOR, MessagePack, UBJSON) are read by an input adapter and turned into the same SAX +- **Binary formats** (BJData, BON8, BSON, CBOR, MessagePack, UBJSON) are read by an input adapter and turned into the same SAX events by the `binary_reader`. - A [SAX consumer](#sax-interface) receives the events. The one used by [`parse`](../api/basic_json/parse.md) builds a `basic_json` value tree. diff --git a/docs/mkdocs/docs/home/design_goals.md b/docs/mkdocs/docs/home/design_goals.md index 549ba9a1a..987124378 100644 --- a/docs/mkdocs/docs/home/design_goals.md +++ b/docs/mkdocs/docs/home/design_goals.md @@ -6,7 +6,7 @@ There are myriads of [JSON](https://json.org) libraries out there, and each may - **Trivial integration**. Our whole code consists of a single header file [`json.hpp`](https://github.com/nlohmann/json/blob/develop/single_include/nlohmann/json.hpp). That's it. No library, no subproject, no dependencies, no complex build system. The class is written in vanilla C++11. All in all, everything should require no adjustment of your compiler flags or project settings. -- **Serious testing**. Our class is heavily [unit-tested](https://github.com/nlohmann/json/tree/develop/tests/src) and covers [100%](https://coveralls.io/r/nlohmann/json) of the code, including all exceptional behavior. Furthermore, we checked with [Valgrind](http://valgrind.org) and the [Clang Sanitizers](https://clang.llvm.org/docs/index.html) that there are no memory leaks. [Google OSS-Fuzz](https://github.com/google/oss-fuzz/tree/master/projects/json) additionally runs fuzz tests against all parsers 24/7, effectively executing billions of tests so far. To maintain high quality, the project is following the [OpenSSF Best Practices](https://www.bestpractices.dev/projects/289). +- **Serious testing**. Our class is heavily [unit-tested](https://github.com/nlohmann/json/tree/develop/tests/src) and covers [100%](https://coveralls.io/r/nlohmann/json) of the code, including all exceptional behavior. Furthermore, we checked with [Valgrind](https://valgrind.org) and the [Clang Sanitizers](https://clang.llvm.org/docs/index.html) that there are no memory leaks. [Google OSS-Fuzz](https://github.com/google/oss-fuzz/tree/master/projects/json) additionally runs fuzz tests against all parsers 24/7, effectively executing billions of tests so far. To maintain high quality, the project is following the [OpenSSF Best Practices](https://www.bestpractices.dev/projects/289). Other aspects were not so important to us: @@ -14,4 +14,4 @@ Other aspects were not so important to us: - **Speed**. There are certainly [faster JSON libraries](https://github.com/miloyip/nativejson-benchmark#parsing-time) out there. However, if your goal is to speed up your development by adding JSON support with a single header, then this library is the way to go. If you know how to use a `std::vector` or `std::map`, you are already set. -See the [contribution guidelines](https://github.com/nlohmann/json/blob/master/.github/CONTRIBUTING.md#please-dont) for more information. +See the [contribution guidelines](https://github.com/nlohmann/json/blob/develop/.github/CONTRIBUTING.md#please-dont) for more information. diff --git a/docs/mkdocs/docs/home/exceptions.md b/docs/mkdocs/docs/home/exceptions.md index 32312334a..4602009a7 100644 --- a/docs/mkdocs/docs/home/exceptions.md +++ b/docs/mkdocs/docs/home/exceptions.md @@ -104,7 +104,7 @@ See [documentation of `JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) for ## Parse errors The library throws this exception when a parse error occurs. Parse errors -can occur during the deserialization of JSON text, CBOR, MessagePack, as well +can occur during the deserialization of JSON text or of one of the binary formats (BJData, BON8, BSON, CBOR, MessagePack, UBJSON), as well as when using JSON Patch. Exceptions have ids 1xx. @@ -190,7 +190,7 @@ This error indicates a syntax error while deserializing a JSON text. The error m !!! tip - Make sure the input is correctly read. Try to write the input to standard output to check if, for instance, the input file was successfully opened. - - Paste the input to a JSON validator like or a tool like [jq](https://stedolan.github.io/jq/). + - Paste the input to a JSON validator like or a tool like [jq](https://jqlang.github.io/jq/). ### json.exception.parse_error.102 diff --git a/docs/mkdocs/docs/home/releases.md b/docs/mkdocs/docs/home/releases.md index f91f82dda..fabf95e8a 100644 --- a/docs/mkdocs/docs/home/releases.md +++ b/docs/mkdocs/docs/home/releases.md @@ -26,7 +26,7 @@ Fixes bugs found in 3.11.3 and adds several features. All changes are backward-c [BJData](../features/binary_formats/bjdata.md) draft 3 and unsigned 64-bit integers for [BSON](../features/binary_formats/bson.md). - Adds multidimensional C-array conversion and UTF-8 encoded `std::filesystem::path` conversions, and - lowers the minimum [CMake](../integration/cmake.md) version to allow CMake 4.0. + raises the minimum [CMake](../integration/cmake.md) version to 3.5 and supports CMake 4.0. [Full release notes](https://github.com/nlohmann/json/releases/tag/v3.12.0). diff --git a/docs/mkdocs/docs/integration/hunter/CMakeLists.txt b/docs/mkdocs/docs/integration/hunter/CMakeLists.txt index 4acc32586..46f249f30 100644 --- a/docs/mkdocs/docs/integration/hunter/CMakeLists.txt +++ b/docs/mkdocs/docs/integration/hunter/CMakeLists.txt @@ -2,8 +2,8 @@ cmake_minimum_required(VERSION 3.15) include("cmake/HunterGate.cmake") HunterGate( - URL "https://github.com/cpp-pm/hunter/archive/v0.23.297.tar.gz" - SHA1 "3319fe6a3b08090df7df98dee75134d68e2ef5a3" + URL "https://github.com/cpp-pm/hunter/archive/v0.26.12.tar.gz" + SHA1 "6498c5d0dec25d7fffb2b0574ebaf265179b894d" ) project(json_example) diff --git a/docs/mkdocs/hooks/copy_markdown_source.py b/docs/mkdocs/hooks/copy_markdown_source.py index dfdd9cc7b..967796c55 100644 --- a/docs/mkdocs/hooks/copy_markdown_source.py +++ b/docs/mkdocs/hooks/copy_markdown_source.py @@ -2,12 +2,30 @@ # Creates a `.md` sibling of each HTML output (for example, # `features/comments/` becomes `features/comments.md`) so agents and tools can -# fetch the raw Markdown directly instead of parsing rendered HTML. +# fetch the raw Markdown directly instead of parsing rendered HTML. The +# `--8<-- "path"` lines of pymdownx.snippets are expanded with the extension's +# own preprocessor and the settings from mkdocs.yml, so the copies contain the +# included example code and files instead of the include directives. +import io import os -import shutil + +import markdown +from pymdownx.snippets import SnippetMissingError _pages = [] +_snippets = None + + +def on_config(config): + global _snippets + md = markdown.Markdown( + extensions=["pymdownx.snippets"], + extension_configs={"pymdownx.snippets": config["mdx_configs"].get("pymdownx.snippets", {})}, + ) + _snippets = md.preprocessors["snippet"] + _snippets.auto_append = [] # the glossary is only needed to render abbreviations + return config def on_files(files, config): @@ -22,4 +40,11 @@ def on_post_build(config): url = file.url.rstrip("/") target = os.path.join(site_dir, (url or "index") + ".md") os.makedirs(os.path.dirname(target), exist_ok=True) - shutil.copyfile(file.abs_src_path, target) + with io.open(file.abs_src_path, encoding="utf-8", newline="") as source: + text = source.read() + try: + text = "\n".join(_snippets.run(text.split("\n"))) + except (SnippetMissingError, OSError): + pass # keep the include directives; the regular build reports the missing file + with io.open(target, "w", encoding="utf-8", newline="") as copy: + copy.write(text) diff --git a/docs/mkdocs/includes/glossary.md b/docs/mkdocs/includes/glossary.md index 9f2c6329d..86fbe313e 100644 --- a/docs/mkdocs/includes/glossary.md +++ b/docs/mkdocs/includes/glossary.md @@ -1,7 +1,7 @@ *[ADL]: Argument-dependent lookup -*[API]: Application Programming Interfaces +*[API]: Application Programming Interface *[ASCII]: American Standard Code for Information Interchange *[BDFL]: Benevolent Dictator for Life *[BJData]: Binary JData diff --git a/docs/mkdocs/mkdocs.yml b/docs/mkdocs/mkdocs.yml index f38d70038..111e0da81 100644 --- a/docs/mkdocs/mkdocs.yml +++ b/docs/mkdocs/mkdocs.yml @@ -299,6 +299,7 @@ nav: - 'JSON_DISABLE_TUPLE_REFERENCE_CONVERSION': api/macros/json_disable_tuple_reference_conversion.md - 'JSON_HAS_CPP_11, JSON_HAS_CPP_14, JSON_HAS_CPP_17, JSON_HAS_CPP_20, JSON_HAS_CPP_23, JSON_HAS_CPP_26': api/macros/json_has_cpp_11.md - 'JSON_HAS_EXPERIMENTAL_FILESYSTEM, JSON_HAS_FILESYSTEM': api/macros/json_has_filesystem.md + - 'JSON_HAS_RANGE_VIEW_CONVERSION': api/macros/json_has_range_view_conversion.md - 'JSON_HAS_RANGES': api/macros/json_has_ranges.md - 'JSON_HAS_STATIC_RTTI': api/macros/json_has_static_rtti.md - 'JSON_HAS_STD_FORMAT': api/macros/json_has_std_format.md diff --git a/tools/amalgamate/config_json_view.json b/tools/amalgamate/config_json_view.json deleted file mode 100644 index cd25df087..000000000 --- a/tools/amalgamate/config_json_view.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "project": "JSON for Modern C++", - "target": "single_include/nlohmann/json_view.hpp", - "sources": [ - "include/nlohmann/json_view.hpp" - ], - "include_paths": ["include"], - "external": ["nlohmann/json.hpp"] -} diff --git a/tools/generate_natvis/README.md b/tools/generate_natvis/README.md index 3995758c2..30ec550a4 100644 --- a/tools/generate_natvis/README.md +++ b/tools/generate_natvis/README.md @@ -15,7 +15,8 @@ file or the library version is bumped — otherwise the committed make natvis ``` -or, equivalently: +`make natvis` sets up a virtual environment `tools/generate_natvis/venv` with the dependencies from +`requirements.txt` (Jinja2) and runs the script with it. Alternatively, install the requirements yourself and run: ```shell ./generate_natvis.py [--version X.Y.Z] [repository_root/] From 74a03dec844d0f190945a3cef3aa3092e7d2e5fe Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 11 Oct 2026 08:22:00 +0200 Subject: [PATCH 6/9] Throw type_error.321 for discarded values in to_bon8 (#5802) write_bon8_value silently skipped discarded values, but the array/object count marker still counted them, so [1, discarded] produced 82 91: a marker announcing two elements followed by one. Throw type_error.321 like the other binary writers do, at any nesting level. Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/api/basic_json/to_bon8.md | 2 + docs/mkdocs/docs/home/exceptions.md | 2 +- .../nlohmann/detail/output/binary_writer.hpp | 6 ++- single_include/nlohmann/json.hpp | 6 ++- tests/src/unit-bon8.cpp | 51 ++++++++++++++++++- 5 files changed, 62 insertions(+), 5 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json/to_bon8.md b/docs/mkdocs/docs/api/basic_json/to_bon8.md index 2d58b2660..14cc085f6 100644 --- a/docs/mkdocs/docs/api/basic_json/to_bon8.md +++ b/docs/mkdocs/docs/api/basic_json/to_bon8.md @@ -41,6 +41,8 @@ With (2), the bytes written before the exception remain in the output adapter. above 9223372036854775807, which BON8 cannot represent - Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if `j` contains a string that is not valid UTF-8 +- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is + discarded; example: `"cannot serialize discarded value to BON8"` ## Complexity diff --git a/docs/mkdocs/docs/home/exceptions.md b/docs/mkdocs/docs/home/exceptions.md index 4602009a7..02fd85689 100644 --- a/docs/mkdocs/docs/home/exceptions.md +++ b/docs/mkdocs/docs/home/exceptions.md @@ -817,7 +817,7 @@ does not list an enumerator and it is therefore converted like the first listed A discarded value (one created by [`parse()`](../api/basic_json/parse.md) with a callback that returns `false` for the value, or by default-constructing a [`basic_json`](../api/basic_json/index.md) with [`value_t::discarded`](../api/basic_json/value_t.md)) was passed to a binary serialization function, either directly or -nested in an array or object. There is no way to represent a discarded value in CBOR, MessagePack, UBJSON, BJData, or BSON. +nested in an array or object. There is no way to represent a discarded value in CBOR, MessagePack, UBJSON, BJData, BSON, or BON8. !!! failure "Example message" diff --git a/include/nlohmann/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp index cbe771ee8..59a643edf 100644 --- a/include/nlohmann/detail/output/binary_writer.hpp +++ b/include/nlohmann/detail/output/binary_writer.hpp @@ -809,6 +809,8 @@ class binary_writer /*! @param[in] j JSON value to serialize + + @throw type_error.321 if @a j or a value nested in it is discarded */ void write_bon8(const BasicJsonType& j) { @@ -2570,6 +2572,8 @@ class binary_writer @param[in] j JSON value to serialize @param[in,out] string_open whether the output ends with a non-empty string that has not been terminated with 0xFF + + @throw type_error.321 if @a j or a value nested in it is discarded */ void write_bon8_value(const BasicJsonType& j, bool& string_open) { @@ -2678,7 +2682,7 @@ class binary_writer case value_t::discarded: default: - break; + throw_on_discarded(j, "BON8"); } } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index c4f7ab348..18ecb75fd 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -22538,6 +22538,8 @@ class binary_writer /*! @param[in] j JSON value to serialize + + @throw type_error.321 if @a j or a value nested in it is discarded */ void write_bon8(const BasicJsonType& j) { @@ -24299,6 +24301,8 @@ class binary_writer @param[in] j JSON value to serialize @param[in,out] string_open whether the output ends with a non-empty string that has not been terminated with 0xFF + + @throw type_error.321 if @a j or a value nested in it is discarded */ void write_bon8_value(const BasicJsonType& j, bool& string_open) { @@ -24407,7 +24411,7 @@ class binary_writer case value_t::discarded: default: - break; + throw_on_discarded(j, "BON8"); } } diff --git a/tests/src/unit-bon8.cpp b/tests/src/unit-bon8.cpp index b1d82ca29..1e24d32d4 100644 --- a/tests/src/unit-bon8.cpp +++ b/tests/src/unit-bon8.cpp @@ -82,9 +82,56 @@ TEST_CASE("BON8") { SECTION("discarded") { - // discarded values are not serialized + // a discarded value cannot be serialized to BON8 const json j = json::value_t::discarded; - CHECK(json::to_bon8(j).empty()); + CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] cannot serialize discarded value to BON8", json::type_error&); + } + + SECTION("discarded values nested in a container") + { + const json discarded = json::value_t::discarded; + + SECTION("in a small array") + { + const json j = {1, discarded}; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] (/1) cannot serialize discarded value to BON8", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] cannot serialize discarded value to BON8", json::type_error&); +#endif + } + + SECTION("as a small object value") + { + json j; + j["a"] = discarded; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] (/a) cannot serialize discarded value to BON8", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] cannot serialize discarded value to BON8", json::type_error&); +#endif + } + + SECTION("in a large array") + { + const json j = {1, 2, 3, 4, 5, discarded}; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] (/5) cannot serialize discarded value to BON8", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] cannot serialize discarded value to BON8", json::type_error&); +#endif + } + + SECTION("as a large object value") + { + json j = {{"a", 1}, {"b", 2}, {"c", 3}, {"d", 4}, {"e", 5}}; + j["f"] = discarded; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] (/f) cannot serialize discarded value to BON8", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] cannot serialize discarded value to BON8", json::type_error&); +#endif + } } SECTION("null") From 11ef352511a5cdcf5ff39c51dca16999904ae788 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 11 Oct 2026 08:22:10 +0200 Subject: [PATCH 7/9] =?UTF-8?q?Point=20users=20to=20update(=E2=80=A6,=20tr?= =?UTF-8?q?ue),=20JSON=20Pointer=20defaults,=20and=20built-in=20std::optio?= =?UTF-8?q?nal=20(#5804)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A code search of client code found many hand-written versions of functionality the library already has: recursive object merges (update(j, true) since 3.10.5), dotted-path getters (value() and contains() with a JSON Pointer), get_or helpers (value() throws for a member that is present but null), and adl_serializer specializations for std::optional (supported since 3.12.0). - modifying_values.md: describe both modes of update(), add a defaults + user settings recipe, and stop recommending Merge Patch for recursive merges. - merge_patch.md: note that null deletes keys, so a merge patch is not a general deep merge; link update(). - default_value.md: add "Nested values" (value/contains with a JSON Pointer, building a pointer from a dotted path with operator/=) and a warning that null and mistyped members are not missing. - value.md: note that a null member is converted, not replaced by the default. - json_pointer.md: link value/contains/at with JSON Pointers. - arbitrary_types.md: note that std::optional needs no serializer. Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/api/basic_json/value.md | 8 +++ docs/mkdocs/docs/features/arbitrary_types.md | 7 ++ .../features/element_access/default_value.md | 69 +++++++++++++++++++ docs/mkdocs/docs/features/json_pointer.md | 6 ++ docs/mkdocs/docs/features/merge_patch.md | 8 +++ docs/mkdocs/docs/features/modifying_values.md | 30 ++++++-- 6 files changed, 122 insertions(+), 6 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json/value.md b/docs/mkdocs/docs/api/basic_json/value.md index f8a1c5528..e81d0dc84 100644 --- a/docs/mkdocs/docs/api/basic_json/value.md +++ b/docs/mkdocs/docs/api/basic_json/value.md @@ -119,6 +119,14 @@ changes to any JSON value. ## Notes +!!! warning "`null` members are not missing" + + The default value is used only if the key (or JSON Pointer) does not exist. A member that exists but is + `#!json null` is converted like any other value, so `#!cpp j.value("k", 0)` throws a + [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if `"k"` is `#!json null`. See + [Access with default value](../../features/element_access/default_value.md) + for alternatives. + !!! warning "Return type" The value function is a template, and the return type of the function is determined by the type of the provided diff --git a/docs/mkdocs/docs/features/arbitrary_types.md b/docs/mkdocs/docs/features/arbitrary_types.md index 53f9cbd07..0ca165902 100644 --- a/docs/mkdocs/docs/features/arbitrary_types.md +++ b/docs/mkdocs/docs/features/arbitrary_types.md @@ -347,6 +347,13 @@ struct adl_serializer> { NLOHMANN_JSON_NAMESPACE_END ``` +!!! tip "`std::optional` needs no serializer" + + Since version 3.12.0, `std::optional` is supported out of the box when compiling with C++17 + (`std::nullopt` is converted to and from `null`). Do not write an `adl_serializer` for it; this pattern is only + needed for types such as `boost::optional` or for custom semantics. See [Conversions](conversions.md) and + [Omitting a field when serializing `std::optional`](conversions.md#omitting-a-field-when-serializing-stdoptional). + !!! note "ABI compatibility" Use [`NLOHMANN_JSON_NAMESPACE_BEGIN`](../api/macros/nlohmann_json_namespace_begin.md) and `NLOHMANN_JSON_NAMESPACE_END` diff --git a/docs/mkdocs/docs/features/element_access/default_value.md b/docs/mkdocs/docs/features/element_access/default_value.md index 481448469..be100b34b 100644 --- a/docs/mkdocs/docs/features/element_access/default_value.md +++ b/docs/mkdocs/docs/features/element_access/default_value.md @@ -30,6 +30,47 @@ you want to access and a default value in case there is no value stored with tha | `#!cpp j.value("append", false)` | `#!json true` | | `#!cpp j.value("logLevel", "verbose")` | `#!json "verbose"` | +## Nested values + +To read a value deep inside a document, pass a [JSON Pointer](../json_pointer.md) instead of a key. The default value is +returned if the value at the pointer does not exist, including the case that an intermediate key is missing. There is +no need to check each level with [`contains`](../../api/basic_json/contains.md) first. + +```cpp +json j = {{"server", {{"port", 8080}}}, {"list", {10, 20}}}; + +int port = j.value("/server/port"_json_pointer, 80); // 8080 +int timeout = j.value("/server/limits/timeout"_json_pointer, 30); // 30 (missing intermediate key) +int second = j.value("/list/1"_json_pointer, 0); // 20 (numeric tokens index arrays) +int third = j.value("/list/5"_json_pointer, 0); // 0 (index out of range) + +bool has_port = j.contains("/server/port"_json_pointer); // true +bool has_host = j.contains("/server/host/name"_json_pointer); // false +``` + +If the path is only available as a dotted string such as `#!cpp "server.port"`, do not build the pointer by +concatenating `#!cpp "/"` and the parts: keys containing `/` or `~` would be misinterpreted. Append each part as a +reference token with [`operator/=`](../../api/json_pointer/operator_slasheq.md) instead. It escapes the token for you. + +```cpp +json::json_pointer to_pointer(const std::string& dotted) +{ + json::json_pointer ptr; + std::istringstream in(dotted); + for (std::string token; std::getline(in, token, '.');) + { + ptr /= token; + } + return ptr; +} + +int port = j.value(to_pointer("server.port"), 80); // 8080 +int first = j.value(to_pointer("list.0"), 0); // 10 +``` + +The key `#!cpp "a/b"` yields the pointer `#!cpp "/a~1b"`; the dot-splitting itself is up to the caller, so keys +containing `.` need a different separator. + ## Notes !!! failure "Exceptions" @@ -37,6 +78,31 @@ you want to access and a default value in case there is no value stored with tha - With string keys, `value` can only be used with objects. For other types, a [`basic_json::type_error`](../../home/exceptions.md#jsonexceptiontype_error306) is thrown. - With JSON Pointers, `value` can be used with both objects and arrays. For other types (null, boolean, number, string), a [`basic_json::type_error`](../../home/exceptions.md#jsonexceptiontype_error306) is thrown. +!!! warning "`null` and mistyped members are not missing" + + `value` returns the default value only if the key is **absent**. If the member exists, it is converted to the type + of the default value, even if it is `#!json null`. For `#!json {"k": null}`, the call `#!cpp j.value("k", 0)` throws + a [`basic_json::type_error`](../../home/exceptions.md#jsonexceptiontype_error302), and so does a member of another + type such as a string where a number is expected. The same holds for JSON Pointers. + + To treat `#!json null` like a missing value, check for it explicitly: + + ```cpp + int n = (j.contains("k") && !j["k"].is_null()) ? j["k"].get() : 0; + ``` + + With C++17, [`get>()`](../../api/basic_json/get.md) maps `#!json null` to an empty optional. As + [`at`](../../api/basic_json/at.md) throws [`out_of_range`](../../home/exceptions.md#jsonexceptionout_of_range403) + for an absent key, use [`find`](../../api/basic_json/find.md) to cover both cases: + + ```cpp + std::optional n; // empty if "k" is absent or null + if (const auto it = j.find("k"); it != j.end()) + { + n = it->get>(); // still throws for a non-number such as "text" + } + ``` + !!! warning "Return type" The value function is a template, and the return type of the function is determined by the type of the provided @@ -63,3 +129,6 @@ you want to access and a default value in case there is no value stored with tha - [`value`](../../api/basic_json/value.md) for access with default value - documentation on [checked access](checked_access.md) +- documentation on [JSON Pointer](../json_pointer.md) +- [`contains`](../../api/basic_json/contains.md) to check whether a key or JSON Pointer exists +- [`json_pointer::operator/=`](../../api/json_pointer/operator_slasheq.md) to build a pointer token by token diff --git a/docs/mkdocs/docs/features/json_pointer.md b/docs/mkdocs/docs/features/json_pointer.md index 786832c96..1bc5eabdb 100644 --- a/docs/mkdocs/docs/features/json_pointer.md +++ b/docs/mkdocs/docs/features/json_pointer.md @@ -77,6 +77,10 @@ auto val2 = j.at(json::json_pointer("/nested/three/1")); // false auto val3 = j.value(json::json_pointer("/nested/four"), 0); // 0 ``` +To read a value with a fallback, use [`value`](../api/basic_json/value.md) with a JSON Pointer; to test for existence, +use [`contains`](../api/basic_json/contains.md). Neither needs intermediate checks, see +[Nested values](element_access/default_value.md#nested-values). + !!! note "Creating intermediate levels that don't exist" See the [`operator[]` notes](../api/basic_json/operator%5B%5D.md#return-value) for how array vs. object is @@ -126,6 +130,8 @@ auto j_original = j_flat.unflatten(); ## See also - Class [`json_pointer`](../api/json_pointer/index.md) +- Functions [`value`](../api/basic_json/value.md), [`contains`](../api/basic_json/contains.md), and + [`at`](../api/basic_json/at.md) accept JSON Pointers; see [Nested values](element_access/default_value.md#nested-values) - Function [`flatten`](../api/basic_json/flatten.md) - Function [`unflatten`](../api/basic_json/unflatten.md) - [JSON Patch](json_patch.md) - paths inside a patch are JSON Pointers diff --git a/docs/mkdocs/docs/features/merge_patch.md b/docs/mkdocs/docs/features/merge_patch.md index 46b0c5f04..c0a316e65 100644 --- a/docs/mkdocs/docs/features/merge_patch.md +++ b/docs/mkdocs/docs/features/merge_patch.md @@ -9,6 +9,13 @@ syntax that closely mimics the document being modified. Unlike [JSON Patch](json express every kind of change (e.g., it cannot reorder array elements or remove a specific array element), but it is easier to read and write for object-shaped documents. +!!! tip "Not a general deep merge" + + A merge patch is not a general deep merge: a `#!json null` value in the patch deletes the key from the target. + To merge two objects recursively (e.g., defaults and user settings), use + [`update`](../api/basic_json/update.md) with `merge_objects` set to `#!cpp true`; see + [Merging objects](modifying_values.md#merging-objects). + ??? example The following code shows how a JSON Merge Patch is applied to a JSON document. @@ -28,3 +35,4 @@ easier to read and write for object-shaped documents. - [JSON Patch and Diff](json_patch.md) - a more expressive alternative that describes a sequence of operations - [JSON Pointer](json_pointer.md) - the addressing scheme used by JSON Patch - Function [`merge_patch`](../api/basic_json/merge_patch.md) +- Function [`update`](../api/basic_json/update.md) - merge objects, optionally recursively diff --git a/docs/mkdocs/docs/features/modifying_values.md b/docs/mkdocs/docs/features/modifying_values.md index 23c0feb53..cf763a4b3 100644 --- a/docs/mkdocs/docs/features/modifying_values.md +++ b/docs/mkdocs/docs/features/modifying_values.md @@ -35,8 +35,12 @@ the insertion happened — useful for "add if absent" semantics. ## Merging objects -To merge one object into another, [`update`](../api/basic_json/update.md) copies all members from another object, -overwriting existing keys (similar to Python's `dict.update`). This is the idiomatic way to combine two objects. +To merge one object into another, [`update`](../api/basic_json/update.md) copies all members from another object +(similar to Python's `dict.update`). This is the idiomatic way to combine two objects. It has two modes: + +- By default, the merge is shallow: existing keys are overwritten, even if both values are objects. +- With `merge_objects = #!cpp true`, keys whose values are objects in both JSON values are merged recursively. + Everything else is overwritten. In particular, arrays are replaced, not concatenated. ??? example @@ -50,9 +54,22 @@ overwriting existing keys (similar to Python's `dict.update`). This is the idiom --8<-- "examples/update.output" ``` -For a recursive merge that follows [RFC 7386](https://tools.ietf.org/html/rfc7386), see -[JSON Merge Patch](merge_patch.md). To apply a sequence of well-defined edit operations, see -[JSON Patch](json_patch.md). +A common use of the recursive mode is combining defaults with user settings. Nested defaults that the user did not set +are kept: + +```cpp +json defaults = {{"log", {{"level", "info"}, {"file", "app.log"}}}, {"retries", 3}}; +json user_settings = {{"log", {{"level", "debug"}}}}; + +json config = defaults; +config.update(user_settings, true); +// {"log":{"file":"app.log","level":"debug"},"retries":3} +``` + +[JSON Merge Patch](merge_patch.md) ([RFC 7386](https://tools.ietf.org/html/rfc7386)) also merges objects recursively, +but it is a different tool: a `#!json null` in the patch means "remove this key". It is meant for applying merge patch +documents (e.g., received via HTTP PATCH). To merge configuration-like objects, use `#!cpp update(..., true)`. To apply +a sequence of well-defined edit operations, see [JSON Patch](json_patch.md). ## Removing elements @@ -72,6 +89,7 @@ a.erase(1); // [1,3,4] (erase by index) - [`push_back`](../api/basic_json/push_back.md) / [`emplace_back`](../api/basic_json/emplace_back.md) - append to an array - [`emplace`](../api/basic_json/emplace.md) - insert into an object if the key is absent -- [`update`](../api/basic_json/update.md) - merge objects +- [`update`](../api/basic_json/update.md) - merge objects (shallow, or recursive with `merge_objects`) +- [`merge_patch`](../api/basic_json/merge_patch.md) - apply an RFC 7386 merge patch - [`erase`](../api/basic_json/erase.md) / [`clear`](../api/basic_json/clear.md) - remove elements - [JSON Patch and Diff](json_patch.md) and [JSON Merge Patch](merge_patch.md) - structured modifications From eb8899a29cf078507f20e50197d4aa713937bbe5 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 11 Oct 2026 08:24:31 +0200 Subject: [PATCH 8/9] Restore v3.12.0 support for custom object key types (#5795) * Restore v3.12.0 support for custom object key types Custom object_t types whose key_type is not string_t compiled with v3.12.0 for several APIs that unreleased changes broke: - to_bson failed for every custom key type (#5553 kept a const string_t* to the key); the nested entry's header is now written where the entry is found. - Copying deep values (and parse, merge_patch, update, insert) required operator== on keys (#5389); keys without one are now paired via find(). - to_cbor/to_msgpack required an implicit conversion to string_t (#5746, #5328); keys without one go through a temporary basic_json again. - at() required a conversion to string_t for its error message (#5727); other keys are passed to concat() unchanged again. The new unit-custom-object-key-type.cpp covers five key types with different capabilities. Signed-off-by: Niels Lohmann * Use the with_object_t alias for the custom object key test types Signed-off-by: Niels Lohmann * Avoid floating-point equality in custom key type test GCC with -Werror=float-equal rejects comparing the double value with ==. Signed-off-by: Niels Lohmann * Write the head of nested BSON elements in one helper Signed-off-by: Niels Lohmann * Suppress bugprone-return-const-ref-from-parameter in key_for_message The reference is only passed to concat() within the full-expression that holds the key, like the similar helpers in binary_writer.hpp. Signed-off-by: Niels Lohmann * Make the value of the custom test key types private clang-tidy (cppcoreguidelines-non-private-member-variables-in-classes) rejects the protected member; the derived key types use a protected accessor instead. Signed-off-by: Niels Lohmann * Pass keys with data() and size() unchanged into the at() miss message key_for_message() converted every key that string_t can be constructed from, so a miss on a string_t or string_view key copied it before concat() copied it again. Keys that concat() can append through data() and size() are now passed through; only other keys (string literals, key types that just convert to string_t) are converted. Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/api/basic_json/object_t.md | 3 +- include/nlohmann/detail/meta/type_traits.hpp | 4 + .../nlohmann/detail/output/binary_writer.hpp | 79 ++- include/nlohmann/json.hpp | 39 +- tests/src/custom_key_test.hpp | 519 ++++++++++++++++++ tests/src/custom_object_key_type.hpp | 2 +- tests/src/unit-custom-key-type-c-str.cpp | 46 ++ tests/src/unit-custom-key-type-explicit.cpp | 46 ++ tests/src/unit-custom-key-type-full.cpp | 51 ++ tests/src/unit-custom-key-type-no-eq.cpp | 51 ++ tests/src/unit-custom-key-type-to-json.cpp | 41 ++ 11 files changed, 855 insertions(+), 26 deletions(-) create mode 100644 tests/src/custom_key_test.hpp create mode 100644 tests/src/unit-custom-key-type-c-str.cpp create mode 100644 tests/src/unit-custom-key-type-explicit.cpp create mode 100644 tests/src/unit-custom-key-type-full.cpp create mode 100644 tests/src/unit-custom-key-type-no-eq.cpp create mode 100644 tests/src/unit-custom-key-type-to-json.cpp diff --git a/docs/mkdocs/docs/api/basic_json/object_t.md b/docs/mkdocs/docs/api/basic_json/object_t.md index 5e7cab9e9..204e5ab28 100644 --- a/docs/mkdocs/docs/api/basic_json/object_t.md +++ b/docs/mkdocs/docs/api/basic_json/object_t.md @@ -26,8 +26,7 @@ To store objects in C++, a type is defined by the template parameters described `StringType` : the type of the keys or names (e.g., `std::string`). The comparison function `std::less` is used to - order elements inside the container. `object_t::key_type` must be implicitly convertible to `string_t` (required by the - binary formats). + order elements inside the container. `AllocatorType` : the allocator to use for objects (e.g., `std::allocator`) diff --git a/include/nlohmann/detail/meta/type_traits.hpp b/include/nlohmann/detail/meta/type_traits.hpp index 4c90c68a3..a08ad1500 100644 --- a/include/nlohmann/detail/meta/type_traits.hpp +++ b/include/nlohmann/detail/meta/type_traits.hpp @@ -172,6 +172,10 @@ struct has_to_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> template using detect_key_compare = typename T::key_compare; +// detects whether two values of type T can be compared with operator== +template +using detect_equal_comparable = decltype(static_cast(std::declval() == std::declval())); + // obtains the actual object key comparator: object_t::key_compare if the // object type defines it, and default_object_comparator_t otherwise // diff --git a/include/nlohmann/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp index 59a643edf..026ab9c13 100644 --- a/include/nlohmann/detail/output/binary_writer.hpp +++ b/include/nlohmann/detail/output/binary_writer.hpp @@ -313,12 +313,6 @@ class binary_writer case value_t::object: { - static_assert( - std::is_convertible < - typename BasicJsonType::object_t::key_type, - string_t >::value, - "object_t::key_type must be implicitly convertible to string_t"); - // step 1: write control byte and the object size write_cbor_head(0xA0, j.m_data.m_value.object->size()); @@ -326,7 +320,7 @@ class binary_writer { // el.first is written directly (not via a temporary // basic_json), with the object as diagnostics context - write_cbor_string(el.first, j); + write_cbor_key(el.first, j); write_cbor(el.second, depth + 1); } break; @@ -591,12 +585,6 @@ class binary_writer case value_t::object: { - static_assert( - std::is_convertible < - typename BasicJsonType::object_t::key_type, - string_t >::value, - "object_t::key_type must be implicitly convertible to string_t"); - // step 1: write control byte and the object size write_msgpack_object_prefix(j.m_data.m_value.object->size(), j); @@ -604,7 +592,7 @@ class binary_writer { // as in write_cbor, el.first is written directly with the // object as diagnostics context - write_msgpack_string(el.first, j); + write_msgpack_key(el.first, j); write_msgpack(el.second, depth + 1); } break; @@ -994,7 +982,7 @@ class binary_writer // the key is written directly (not via a temporary basic_json), // with the object as diagnostics context, as in write_cbor - write_cbor_string(current.object_it->first, *current.value); + write_cbor_key(current.object_it->first, *current.value); const BasicJsonType* child = &(current.object_it->second); ++stack.back().object_it; write_cbor_value_or_push(*child, stack); @@ -1071,7 +1059,7 @@ class binary_writer // as in write_cbor_iterative, the key is written directly with // the object as diagnostics context - write_msgpack_string(current.object_it->first, *current.value); + write_msgpack_key(current.object_it->first, *current.value); const BasicJsonType* child = &(current.object_it->second); ++stack.back().object_it; write_msgpack_value_or_push(*child, stack); @@ -1676,6 +1664,21 @@ class binary_writer } } + /*! + @brief Writes the head of a BSON element with key @a name whose value is + the object or array @a j: its type, key, and @a size + + Called while the key is in scope: an object key is a key_type, which + may only convert to a temporary string_t, so it cannot be kept by + pointer until after the loop over the entries. + */ + void write_bson_nested_head(const string_t& name, const BasicJsonType& j, + const std::size_t size) + { + write_bson_entry_header(name, j.is_object() ? 0x03 : 0x04); + write_number(to_bson_length(size), true); + } + /// @brief an object or array of the BSON document being sized or written struct bson_frame { @@ -1840,7 +1843,6 @@ class binary_writer { // write entries until the current object or array is done, or an // entry is an object or array itself - const string_t* nested_name = nullptr; const BasicJsonType* nested = nullptr; if (current.value->is_object()) { @@ -1851,7 +1853,7 @@ class binary_writer ++current.member; if (el.second.is_structured()) { - nested_name = &el.first; + write_bson_nested_head(el.first, el.second, nested_sizes[next_size++]); nested = &el.second; } else @@ -1870,7 +1872,7 @@ class binary_writer ++current.index; if (el.is_structured()) { - nested_name = &index_name; + write_bson_nested_head(index_name, el, nested_sizes[next_size++]); nested = ⪙ } else @@ -1882,8 +1884,6 @@ class binary_writer if (nested != nullptr) { - write_bson_entry_header(*nested_name, nested->is_object() ? 0x03 : 0x04); - write_number(to_bson_length(nested_sizes[next_size++]), true); parents.push_back(std::move(current)); current = bson_frame(nested); continue; @@ -1951,6 +1951,43 @@ class binary_writer } } + /*! + @brief write an object key as a CBOR text string + + A key convertible to string_t is written directly. Other key types (only + an explicit conversion, or only a to_json overload) go through a temporary + basic_json, as in version 3.12.0; the temporary is then the diagnostics + context for strict UTF-8 checks. + */ + template::value, int> = 0> + void write_cbor_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& context) + { + write_cbor_string(key, context); + } + + template < typename Key = typename BasicJsonType::object_t::key_type, + enable_if_t < !std::is_convertible::value, int > = 0 > + void write_cbor_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& /*context*/) + { + write_cbor(BasicJsonType(key)); + } + + /// @brief write an object key as a MessagePack str, as in @ref write_cbor_key + template::value, int> = 0> + void write_msgpack_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& context) + { + write_msgpack_string(key, context); + } + + template < typename Key = typename BasicJsonType::object_t::key_type, + enable_if_t < !std::is_convertible::value, int > = 0 > + void write_msgpack_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& /*context*/) + { + write_msgpack(BasicJsonType(key)); + } + /*! @brief write a CBOR text string diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index b841e9fa3..b91fa3e27 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -1421,6 +1421,24 @@ public: return create(first, last); } + /// @brief compare two object keys for equality, if the key type supports it + /// @note object_t only needs operator< for its keys (std::map), so operator== + /// may not exist; the keys are then reported as different, which makes + /// copy_object_level pair the values via object_t::find() + template::value, int> = 0> + static bool copy_keys_equal(const K& a, const K& b) + { + return a == b; + } + + template < typename K = typename object_t::key_type, + detail::enable_if_t < !detail::is_detected::value, int > = 0 > + static bool copy_keys_equal(const K& /*a*/, const K& /*b*/) + { + return false; + } + /// @brief create the copy of the object @a src in @a dst /// @note structured values are appended to @a worklist instead static void copy_object_level(const basic_json& src, basic_json& dst, @@ -1453,7 +1471,7 @@ public: auto src_it = src_object.cbegin(); for (auto& element : *dst.m_data.m_value.object) { - if (JSON_HEDLEY_LIKELY(src_it != src_object.cend() && src_it->first == element.first)) + if (JSON_HEDLEY_LIKELY(src_it != src_object.cend() && copy_keys_equal(src_it->first, element.first))) { copy_shallow(src_it->second, element.second, worklist); ++src_it; @@ -3330,11 +3348,28 @@ public: // std::map or ordered_map) never moves from its argument, so key is still // valid here regardless of whether KeyType was deduced as an rvalue reference // NOLINTNEXTLINE(bugprone-use-after-move,hicpp-invalid-access-moved) - JSON_THROW(out_of_range::create(detail::exception_id::key_not_found, detail::concat("key '", string_t(key), "' not found"), &j)); + JSON_THROW(out_of_range::create(detail::exception_id::key_not_found, detail::concat("key '", key_for_message(key), "' not found"), &j)); } return it->second; } + /// @brief key as it is passed to detail::concat for an error message + /// @note keys with data() and size() (such as string_t itself or a string + /// view) are passed through unchanged, so a miss does not copy them; + /// other keys (such as string literals or key types that only convert + /// to string_t) are converted to string_t + template < typename KeyType, detail::enable_if_t < detail::detect_string_can_append_data::value, int > = 0 > + static const KeyType & key_for_message(const KeyType& key) + { + return key; // NOLINT(bugprone-return-const-ref-from-parameter): the result is only passed to concat() within the full-expression that holds key + } + + template < typename KeyType, detail::enable_if_t < !detail::detect_string_can_append_data::value, int > = 0 > + static string_t key_for_message(const KeyType& key) + { + return string_t(key); + } + /// @brief checked array element access used by the at() overloads taking an index /// @throw type_error.304 if @a j is not an array /// @throw out_of_range.401 if @a idx is out of range diff --git a/tests/src/custom_key_test.hpp b/tests/src/custom_key_test.hpp new file mode 100644 index 000000000..224517b0f --- /dev/null +++ b/tests/src/custom_key_test.hpp @@ -0,0 +1,519 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ (supporting code) +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#pragma once + +#include "doctest_compatibility.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +// Object types with a user-defined key type. The key types differ in what they +// offer to the library: a conversion to std::string (implicit or explicit), a +// comparison with ==, a to_json overload, or a c_str() member. + +namespace custom_key_test +{ +class key_base +{ + public: + key_base() = default; + + key_base(const char* value) + : m_value(value) + {} + + key_base(std::string value) + : m_value(std::move(value)) + {} + + // Required by JSON_DIAGNOSTICS, which reads object keys through data() + // when building the path of an exception. + const char* data() const noexcept + { + return m_value.data(); + } + + friend bool operator<(const key_base& lhs, const key_base& rhs) + { + return lhs.m_value < rhs.m_value; + } + + protected: + const std::string& str() const noexcept + { + return m_value; + } + + private: + std::string m_value; +}; + +// implicit conversion to std::string and operator== +class key_full : public key_base +{ + public: + key_full() = default; + using key_base::key_base; + + operator std::string() const + { + return str(); + } + + friend bool operator==(const key_full& lhs, const key_full& rhs) + { + return lhs.str() == rhs.str(); + } +}; + +// implicit conversion to std::string, but no operator== +class key_no_eq : public key_base +{ + public: + key_no_eq() = default; + using key_base::key_base; + + operator std::string() const + { + return str(); + } +}; + +// explicit conversion to std::string, no operator== +class key_explicit : public key_base +{ + public: + key_explicit() = default; + using key_base::key_base; + + explicit operator std::string() const + { + return str(); + } +}; + +// no conversion at all, only a to_json overload, no operator== +class key_to_json : public key_base +{ + public: + key_to_json() = default; + using key_base::key_base; + + const std::string& value() const + { + return str(); + } +}; + +template +void to_json(BasicJsonType& j, const key_to_json& k) +{ + j = k.value(); +} + +// like key_to_json, but with size() and c_str() +class key_c_str : public key_base +{ + public: + key_c_str() = default; + using key_base::key_base; + + const std::string& value() const + { + return str(); + } + + std::size_t size() const + { + return str().size(); + } + + const char* c_str() const + { + return str().c_str(); + } +}; + +template +void to_json(BasicJsonType& j, const key_c_str& k) +{ + j = k.value(); +} + +// std::map with key type K, ignoring the key type basic_json passes +template +struct object_for +{ + template + using pair_allocator = typename std::allocator_traits::template rebind_alloc>; + + template + using type = std::map, pair_allocator>; // NOLINT(modernize-use-transparent-functors) +}; + +using json_full = nlohmann::json::with_object_t::type>; +using json_no_eq = nlohmann::json::with_object_t::type>; +using json_explicit = nlohmann::json::with_object_t::type>; +using json_to_json = nlohmann::json::with_object_t::type>; +using json_c_str = nlohmann::json::with_object_t::type>; + +// a key that is long enough to need a length byte in CBOR and MessagePack +inline const char* long_key_name(std::size_t i, std::string& storage) +{ + storage = "a key longer than thirty-one characters " + std::to_string(i); + return storage.c_str(); +} + +// name of the key at nesting level i of a deep value +inline std::string deep_name(std::size_t i, bool long_keys) +{ + std::string storage; + return (long_keys && i % 2 == 1) ? std::string(long_key_name(i, storage)) : "k" + std::to_string(i); +} + +// {"a": 1, "b": [true, null, "x"], "c": {"d": 2.5}, } +// 23 is the longest CBOR length stored in the initial byte; 36 needs one +// length byte in CBOR and MessagePack, 300 needs two +template +J make_shallow() +{ + using key_t = typename J::object_t::key_type; + + J array = J::array(); + array.push_back(J(true)); + array.push_back(J(nullptr)); + array.push_back(J("x")); + + typename J::object_t inner; + inner.emplace(key_t("d"), J(2.5)); + + typename J::object_t object; + object.emplace(key_t("a"), J(1)); + object.emplace(key_t("b"), std::move(array)); + object.emplace(key_t("c"), J(std::move(inner))); + object.emplace(key_t(std::string(23, 'x')), J(2)); + object.emplace(key_t(std::string(36, 'y')), J(3)); + object.emplace(key_t(std::string(300, 'z')), J(4)); + return J(std::move(object)); +} + +// {"k0": {"k1": {... {"k": 1} ...}}} +template +J make_deep(std::size_t depth, bool long_keys) +{ + using key_t = typename J::object_t::key_type; + + J value = 1; + for (std::size_t i = depth; i > 0; --i) + { + typename J::object_t object; + object.emplace(key_t(deep_name(i - 1, long_keys)), std::move(value)); + value = J(std::move(object)); + } + return value; +} + +inline std::size_t deep_depth() +{ + return nlohmann::detail::recursion_depth_limit() + 10; +} + +// walk down the nesting levels without recursion and check the leaf +template +bool check_deep(const J& value, std::size_t depth, bool long_keys) +{ + using key_t = typename J::object_t::key_type; + + const J* current = &value; + for (std::size_t i = 0; i < depth; ++i) + { + if (!current->is_object() || current->size() != 1) + { + return false; + } + const auto it = current->find(key_t(deep_name(i, long_keys))); + if (it == current->end()) + { + return false; + } + current = &it.value(); + } + return current->is_number_integer() && current->template get() == 1; +} + +template +bool check_shallow(const J& value) +{ + using key_t = typename J::object_t::key_type; + + if (!value.is_object() || value.size() != 6) + { + return false; + } + + const auto a = value.find(key_t("a")); + const auto b = value.find(key_t("b")); + const auto c = value.find(key_t("c")); + if (a == value.end() || b == value.end() || c == value.end()) + { + return false; + } + + const auto d = c->find(key_t("d")); + // basic_json::operator== needs operator== on the keys, which most of the + // key types do not have, so the values are checked through get<>() + return a->template get() == 1 + && b->is_array() && b->size() == 3 && (*b)[0].template get() && (*b)[1].is_null() + && (*b)[2].template get() == "x" + && d != c->end() && std::abs(d->template get() - 2.5) < 1e-9 + && value.find(key_t(std::string(23, 'x')))->template get() == 2 + && value.find(key_t(std::string(36, 'y')))->template get() == 3 + && value.find(key_t(std::string(300, 'z')))->template get() == 4; +} + +template +bool is_missing(const J& value, const char* name) +{ + return value.find(typename J::object_t::key_type(name)) == value.end(); +} + +// member access through find(): at() does not compile for key types without +// size() or a conversion to string_t (key_to_json), as in version 3.12.0 +template +const J& member(const J& value, const char* name) +{ + const auto it = value.find(typename J::object_t::key_type(name)); + REQUIRE(it != value.end()); + return *it; +} + +// The test cases, as function templates: each unit-custom-key-type-*.cpp file +// runs them for one object type only. The MinGW linker fails on objects with +// more than 65535 sections, and every basic_json specialization adds many +// (see .github/workflows/windows.yml). + +// copy +template +void test_copy() +{ + SECTION("shallow") + { + const J original = custom_key_test::make_shallow(); + REQUIRE(custom_key_test::check_shallow(original)); + + const J copy(original); // NOLINT(performance-unnecessary-copy-initialization) + CHECK(custom_key_test::check_shallow(copy)); + + J assigned; + assigned = original; + CHECK(custom_key_test::check_shallow(assigned)); + + // the original is unchanged + CHECK(custom_key_test::check_shallow(original)); + } + + SECTION("deep") + { + const std::size_t depth = custom_key_test::deep_depth(); + + const J original = custom_key_test::make_deep(depth, false); + REQUIRE(custom_key_test::check_deep(original, depth, false)); + + const J copy(original); // NOLINT(performance-unnecessary-copy-initialization) + CHECK(custom_key_test::check_deep(copy, depth, false)); + + J assigned; + assigned = original; + CHECK(custom_key_test::check_deep(assigned, depth, false)); + + CHECK(custom_key_test::check_deep(original, depth, false)); + } +} + +// parse +template +void test_parse() +{ + const J j = J::parse(R"({"a":1,"b":{"c":[1,2]}})"); + + CHECK(j.size() == 2); + CHECK(custom_key_test::member(j, "a").template get() == 1); + CHECK(custom_key_test::member(custom_key_test::member(j, "b"), "c").size() == 2); + CHECK(custom_key_test::member(custom_key_test::member(j, "b"), "c")[1].template get() == 2); + + // a deeply nested document + const std::size_t depth = custom_key_test::deep_depth(); + std::string text; + for (std::size_t i = 0; i < depth; ++i) + { + text += "{\"k" + std::to_string(i) + "\":"; + } + text += '1'; + text.append(depth, '}'); + CHECK(custom_key_test::check_deep(J::parse(text), depth, false)); +} + +// merge_patch, update, and insert +template +void test_patch() +{ + SECTION("merge_patch") + { + J j = J::parse(R"({"a":1,"b":2,"n":{"x":1,"y":2}})"); + j.merge_patch(J::parse(R"({"b":null,"c":3,"n":{"y":null,"z":3}})")); + + CHECK(j.size() == 3); + CHECK(custom_key_test::member(j, "a").template get() == 1); + CHECK(custom_key_test::is_missing(j, "b")); + CHECK(custom_key_test::member(j, "c").template get() == 3); + CHECK(custom_key_test::member(j, "n").size() == 2); + CHECK(custom_key_test::member(custom_key_test::member(j, "n"), "x").template get() == 1); + CHECK(custom_key_test::member(custom_key_test::member(j, "n"), "z").template get() == 3); + } + + SECTION("update") + { + J j = J::parse(R"({"a":1,"b":2,"n":{"x":1}})"); + const J other = J::parse(R"({"b":3,"c":4,"n":{"y":2}})"); + + J replaced = j; + replaced.update(other); + CHECK(replaced.size() == 4); + CHECK(custom_key_test::member(replaced, "a").template get() == 1); + CHECK(custom_key_test::member(replaced, "b").template get() == 3); + CHECK(custom_key_test::member(replaced, "c").template get() == 4); + CHECK(custom_key_test::member(replaced, "n").size() == 1); + CHECK(custom_key_test::member(custom_key_test::member(replaced, "n"), "y").template get() == 2); + + j.update(other, true); + CHECK(j.size() == 4); + CHECK(custom_key_test::member(j, "n").size() == 2); + CHECK(custom_key_test::member(custom_key_test::member(j, "n"), "x").template get() == 1); + CHECK(custom_key_test::member(custom_key_test::member(j, "n"), "y").template get() == 2); + } + + SECTION("insert") + { + J j = J::parse(R"({"a":1,"b":2})"); + const J other = J::parse(R"({"b":3,"c":4})"); + j.insert(other.begin(), other.end()); + + CHECK(j.size() == 3); + CHECK(custom_key_test::member(j, "b").template get() == 2); + CHECK(custom_key_test::member(j, "c").template get() == 4); + } +} + +// at() reports a missing key +template +void test_at() +{ + // not for key_to_json: at() needs the key's size() or a conversion to + // string_t for its error message, which also was the case in version 3.12.0 + J j = J::parse(R"({"a":1})"); + const J& j_const = j; + + CHECK(j.at("a").template get() == 1); + CHECK(j_const.at("a").template get() == 1); +#if JSON_DIAGNOSTIC_POSITIONS + CHECK_THROWS_WITH_AS(j.at("missing"), "[json.exception.out_of_range.403] (bytes 0-7) key 'missing' not found", typename J::out_of_range&); + CHECK_THROWS_WITH_AS(j_const.at("missing"), "[json.exception.out_of_range.403] (bytes 0-7) key 'missing' not found", typename J::out_of_range&); +#else + CHECK_THROWS_WITH_AS(j.at("missing"), "[json.exception.out_of_range.403] key 'missing' not found", typename J::out_of_range&); + CHECK_THROWS_WITH_AS(j_const.at("missing"), "[json.exception.out_of_range.403] key 'missing' not found", typename J::out_of_range&); +#endif +} + +// BSON +template +void test_bson() +{ + SECTION("shallow") + { + const J value = custom_key_test::make_shallow(); + const nlohmann::json expected = custom_key_test::make_shallow(); + + const std::vector encoded = J::to_bson(value); + CHECK(encoded == nlohmann::json::to_bson(expected)); + CHECK(nlohmann::json::from_bson(encoded) == expected); + } + + SECTION("deep") + { + const std::size_t depth = custom_key_test::deep_depth(); + const J value = custom_key_test::make_deep(depth, false); + const nlohmann::json expected = custom_key_test::make_deep(depth, false); + + const std::vector encoded = J::to_bson(value); + CHECK(encoded == nlohmann::json::to_bson(expected)); + CHECK(nlohmann::json::from_bson(encoded) == expected); + } +} + +// CBOR +template +void test_cbor() +{ + SECTION("shallow") + { + const J value = custom_key_test::make_shallow(); + const nlohmann::json expected = custom_key_test::make_shallow(); + + const std::vector encoded = J::to_cbor(value); + CHECK(encoded == nlohmann::json::to_cbor(expected)); + CHECK(nlohmann::json::from_cbor(encoded) == expected); + } + + SECTION("deeper than the recursion depth limit") + { + const std::size_t depth = custom_key_test::deep_depth(); + const J value = custom_key_test::make_deep(depth, true); + const nlohmann::json expected = custom_key_test::make_deep(depth, true); + + const std::vector encoded = J::to_cbor(value); + CHECK(encoded == nlohmann::json::to_cbor(expected)); + CHECK(nlohmann::json::from_cbor(encoded) == expected); + } +} + +// MessagePack +template +void test_msgpack() +{ + SECTION("shallow") + { + const J value = custom_key_test::make_shallow(); + const nlohmann::json expected = custom_key_test::make_shallow(); + + const std::vector encoded = J::to_msgpack(value); + CHECK(encoded == nlohmann::json::to_msgpack(expected)); + CHECK(nlohmann::json::from_msgpack(encoded) == expected); + } + + SECTION("deeper than the recursion depth limit") + { + const std::size_t depth = custom_key_test::deep_depth(); + const J value = custom_key_test::make_deep(depth, true); + const nlohmann::json expected = custom_key_test::make_deep(depth, true); + + const std::vector encoded = J::to_msgpack(value); + CHECK(encoded == nlohmann::json::to_msgpack(expected)); + CHECK(nlohmann::json::from_msgpack(encoded) == expected); + } +} + +} // namespace custom_key_test diff --git a/tests/src/custom_object_key_type.hpp b/tests/src/custom_object_key_type.hpp index fa23736d4..ebf499844 100644 --- a/tests/src/custom_object_key_type.hpp +++ b/tests/src/custom_object_key_type.hpp @@ -71,5 +71,5 @@ class object using base_type::base_type; }; -using json = nlohmann::basic_json; +using json = nlohmann::json::with_object_t; } // namespace custom_object_key_test diff --git a/tests/src/unit-custom-key-type-c-str.cpp b/tests/src/unit-custom-key-type-c-str.cpp new file mode 100644 index 000000000..5ad1edc90 --- /dev/null +++ b/tests/src/unit-custom-key-type-c-str.cpp @@ -0,0 +1,46 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ (supporting code) +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#include "doctest_compatibility.h" + +#include + +#include "custom_key_test.hpp" + +// object type whose key type has no conversion, but size() and c_str() (see custom_key_test.hpp) +using custom_key_test::json_c_str; + +TEST_CASE("custom object key types: copy (json_c_str)") +{ + custom_key_test::test_copy(); +} + +TEST_CASE("custom object key types: parse (json_c_str)") +{ + custom_key_test::test_parse(); +} + +TEST_CASE("custom object key types: merge_patch, update, and insert (json_c_str)") +{ + custom_key_test::test_patch(); +} + +TEST_CASE("custom object key types: at() reports a missing key (json_c_str)") +{ + custom_key_test::test_at(); +} + +TEST_CASE("custom object key types: CBOR (json_c_str)") +{ + custom_key_test::test_cbor(); +} + +TEST_CASE("custom object key types: MessagePack (json_c_str)") +{ + custom_key_test::test_msgpack(); +} diff --git a/tests/src/unit-custom-key-type-explicit.cpp b/tests/src/unit-custom-key-type-explicit.cpp new file mode 100644 index 000000000..2ef3e97f1 --- /dev/null +++ b/tests/src/unit-custom-key-type-explicit.cpp @@ -0,0 +1,46 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ (supporting code) +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#include "doctest_compatibility.h" + +#include + +#include "custom_key_test.hpp" + +// object type whose key type has explicit conversion to std::string, no operator== (see custom_key_test.hpp) +using custom_key_test::json_explicit; + +TEST_CASE("custom object key types: copy (json_explicit)") +{ + custom_key_test::test_copy(); +} + +TEST_CASE("custom object key types: parse (json_explicit)") +{ + custom_key_test::test_parse(); +} + +TEST_CASE("custom object key types: merge_patch, update, and insert (json_explicit)") +{ + custom_key_test::test_patch(); +} + +TEST_CASE("custom object key types: at() reports a missing key (json_explicit)") +{ + custom_key_test::test_at(); +} + +TEST_CASE("custom object key types: CBOR (json_explicit)") +{ + custom_key_test::test_cbor(); +} + +TEST_CASE("custom object key types: MessagePack (json_explicit)") +{ + custom_key_test::test_msgpack(); +} diff --git a/tests/src/unit-custom-key-type-full.cpp b/tests/src/unit-custom-key-type-full.cpp new file mode 100644 index 000000000..b72bd7ae0 --- /dev/null +++ b/tests/src/unit-custom-key-type-full.cpp @@ -0,0 +1,51 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ (supporting code) +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#include "doctest_compatibility.h" + +#include + +#include "custom_key_test.hpp" + +// object type whose key type has implicit conversion to std::string and operator== (see custom_key_test.hpp) +using custom_key_test::json_full; + +TEST_CASE("custom object key types: copy (json_full)") +{ + custom_key_test::test_copy(); +} + +TEST_CASE("custom object key types: parse (json_full)") +{ + custom_key_test::test_parse(); +} + +TEST_CASE("custom object key types: merge_patch, update, and insert (json_full)") +{ + custom_key_test::test_patch(); +} + +TEST_CASE("custom object key types: at() reports a missing key (json_full)") +{ + custom_key_test::test_at(); +} + +TEST_CASE("custom object key types: BSON (json_full)") +{ + custom_key_test::test_bson(); +} + +TEST_CASE("custom object key types: CBOR (json_full)") +{ + custom_key_test::test_cbor(); +} + +TEST_CASE("custom object key types: MessagePack (json_full)") +{ + custom_key_test::test_msgpack(); +} diff --git a/tests/src/unit-custom-key-type-no-eq.cpp b/tests/src/unit-custom-key-type-no-eq.cpp new file mode 100644 index 000000000..288b9fdd2 --- /dev/null +++ b/tests/src/unit-custom-key-type-no-eq.cpp @@ -0,0 +1,51 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ (supporting code) +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#include "doctest_compatibility.h" + +#include + +#include "custom_key_test.hpp" + +// object type whose key type has implicit conversion to std::string, no operator== (see custom_key_test.hpp) +using custom_key_test::json_no_eq; + +TEST_CASE("custom object key types: copy (json_no_eq)") +{ + custom_key_test::test_copy(); +} + +TEST_CASE("custom object key types: parse (json_no_eq)") +{ + custom_key_test::test_parse(); +} + +TEST_CASE("custom object key types: merge_patch, update, and insert (json_no_eq)") +{ + custom_key_test::test_patch(); +} + +TEST_CASE("custom object key types: at() reports a missing key (json_no_eq)") +{ + custom_key_test::test_at(); +} + +TEST_CASE("custom object key types: BSON (json_no_eq)") +{ + custom_key_test::test_bson(); +} + +TEST_CASE("custom object key types: CBOR (json_no_eq)") +{ + custom_key_test::test_cbor(); +} + +TEST_CASE("custom object key types: MessagePack (json_no_eq)") +{ + custom_key_test::test_msgpack(); +} diff --git a/tests/src/unit-custom-key-type-to-json.cpp b/tests/src/unit-custom-key-type-to-json.cpp new file mode 100644 index 000000000..fde2a8ad3 --- /dev/null +++ b/tests/src/unit-custom-key-type-to-json.cpp @@ -0,0 +1,41 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ (supporting code) +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#include "doctest_compatibility.h" + +#include + +#include "custom_key_test.hpp" + +// object type whose key type has no conversion, only a to_json overload (see custom_key_test.hpp) +using custom_key_test::json_to_json; + +TEST_CASE("custom object key types: copy (json_to_json)") +{ + custom_key_test::test_copy(); +} + +TEST_CASE("custom object key types: parse (json_to_json)") +{ + custom_key_test::test_parse(); +} + +TEST_CASE("custom object key types: merge_patch, update, and insert (json_to_json)") +{ + custom_key_test::test_patch(); +} + +TEST_CASE("custom object key types: CBOR (json_to_json)") +{ + custom_key_test::test_cbor(); +} + +TEST_CASE("custom object key types: MessagePack (json_to_json)") +{ + custom_key_test::test_msgpack(); +} From b38a2c9177bb6143b0352a55f79889a1ae6c0845 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 11 Oct 2026 08:24:35 +0200 Subject: [PATCH 9/9] Stop the BON8 writer overflowing the stack on deep values (#5806) * Stop the BON8 writer overflowing the stack on deep values to_bon8() recursed once per nesting level, so a value the iterative BON8 reader accepts (e.g. ~24k nested one-element arrays) crashed on the way back out. #5781 bounded the CBOR, MessagePack, and UBJSON/BJData writers, but BON8 was merged before it and was not covered. Apply the same scheme: recurse for the first recursion_depth_limit() levels, then finish the value with write_bon8_iterative, which keeps the open containers on a heap stack (reusing binary_container_frame) and writes the 0xFE closer when it leaves a container with more than four elements. The output is byte-for-byte unchanged. Fixes https://issues.oss-fuzz.com/issues/572238015 Signed-off-by: Niels Lohmann * Merge the array and object branches of the BON8 iterative writer write_bon8_iterative and write_bon8_value_or_push handled arrays and objects in separate branches that repeated the end-of-container check, the 0xFE closer and the marker computation. Share those parts and branch only where arrays and objects really differ. The output is unchanged. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Hz7VJi1FTKr6gpseLErbbS Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann Co-authored-by: Claude Opus 5.5 --- .../nlohmann/detail/output/binary_writer.hpp | 107 ++++++++++++++++-- tests/src/unit-large_json.cpp | 63 +++++++++++ 2 files changed, 163 insertions(+), 7 deletions(-) diff --git a/include/nlohmann/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp index 026ab9c13..2a18ea600 100644 --- a/include/nlohmann/detail/output/binary_writer.hpp +++ b/include/nlohmann/detail/output/binary_writer.hpp @@ -868,9 +868,9 @@ class binary_writer } } - /// @brief a CBOR or MessagePack array or object whose elements - /// @ref write_cbor_iterative or @ref write_msgpack_iterative is - /// still writing + /// @brief a CBOR, MessagePack, or BON8 array or object whose elements + /// @ref write_cbor_iterative, @ref write_msgpack_iterative, or + /// @ref write_bon8_iterative is still writing struct binary_container_frame { explicit binary_container_frame(const BasicJsonType* value_) noexcept @@ -2609,11 +2609,27 @@ class binary_writer @param[in] j JSON value to serialize @param[in,out] string_open whether the output ends with a non-empty string that has not been terminated with 0xFF - + @param[in] depth nesting level of @a j, counted from the + top-level value passed to @ref basic_json::to_bon8 + @throw type_error.316 if a string value or an object key is not valid + UTF-8 + @throw out_of_range.407 if an unsigned integer does not fit int64 @throw type_error.321 if @a j or a value nested in it is discarded + + Values nested deeper than @ref recursion_depth_limit are written by + @ref write_bon8_iterative without the call stack. + + @sa @ref write_cbor + @sa https://github.com/nlohmann/json/issues/5392 */ - void write_bon8_value(const BasicJsonType& j, bool& string_open) + void write_bon8_value(const BasicJsonType& j, bool& string_open, const std::size_t depth = 0) { + if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()) && (j.is_array() || j.is_object())) + { + write_bon8_iterative(j, string_open); + return; + } + switch (j.type()) { case value_t::null: @@ -2667,7 +2683,7 @@ class binary_writer for (const auto& el : *j.m_data.m_value.array) { - write_bon8_value(el, string_open); + write_bon8_value(el, string_open, depth + 1); } if (N > 4) @@ -2686,7 +2702,7 @@ class binary_writer for (const auto& el : *j.m_data.m_value.object) { write_bon8_string(el.first, string_open, j); - write_bon8_value(el.second, string_open); + write_bon8_value(el.second, string_open, depth + 1); } if (N > 4) @@ -2723,6 +2739,83 @@ class binary_writer } } + /*! + @brief write @a j with @ref write_bon8_value, or write its marker and push + a frame for @ref write_bon8_iterative to continue with its elements + + A scalar, and an empty array or object, are written out in full and not + pushed. + + @sa @ref write_cbor_value_or_push + */ + void write_bon8_value_or_push(const BasicJsonType& j, bool& string_open, std::vector& stack) + { + if (j.is_array() || j.is_object()) + { + // arrays use the markers 0x80..0x85, objects 0x86..0x8B; the last + // one stands for more than four elements, closed later with 0xFE + const auto N = j.size(); + const std::size_t base = j.is_array() ? 0x80 : 0x86; + write_bon8_marker(static_cast(base + (N <= 4 ? N : 5)), string_open); + if (N != 0) + { + stack.emplace_back(&j); + } + return; + } + + write_bon8_value(j, string_open); + } + + /*! + @brief write out @a root and everything below it without the call stack + + A container with more than four elements is closed with 0xFE once its + last element is written, as in @ref write_bon8_value. + + @sa @ref write_cbor_iterative + */ + void write_bon8_iterative(const BasicJsonType& root, bool& string_open) + { + // only a container with elements is ever pushed; see write_bon8_value_or_push + std::vector stack; + write_bon8_value_or_push(root, string_open, stack); + + while (!stack.empty()) + { + const binary_container_frame current = stack.back(); + const bool is_array = current.value->is_array(); + const bool at_end = is_array + ? current.array_it == current.value->m_data.m_value.array->cend() + : current.object_it == current.value->m_data.m_value.object->cend(); + + if (at_end) + { + if (current.value->size() > 4) + { + write_bon8_marker(0xFE, string_open); + } + stack.pop_back(); + continue; + } + + // read the child before pushing: entering it can move every frame + const BasicJsonType* child = nullptr; + if (is_array) + { + child = &(*current.array_it); + ++stack.back().array_it; + } + else + { + write_bon8_string(current.object_it->first, string_open, *current.value); + child = &(current.object_it->second); + ++stack.back().object_it; + } + write_bon8_value_or_push(*child, string_open, stack); + } + } + /*! @brief write a single byte that is not part of a string diff --git a/tests/src/unit-large_json.cpp b/tests/src/unit-large_json.cpp index cd615f4c3..34cf2a166 100644 --- a/tests/src/unit-large_json.cpp +++ b/tests/src/unit-large_json.cpp @@ -400,26 +400,31 @@ TEST_CASE("issue #5392 - binary writers on deeply nested values") CHECK(json::from_ubjson(json::to_ubjson(deep_array, true, false)) == deep_array); CHECK(json::from_ubjson(json::to_ubjson(deep_array, true, true)) == deep_array); CHECK(json::from_bjdata(json::to_bjdata(deep_array)) == deep_array); + CHECK(json::from_bon8(json::to_bon8(deep_array)) == deep_array); CHECK(json::from_cbor(json::to_cbor(deep_object)) == deep_object); CHECK(json::from_msgpack(json::to_msgpack(deep_object)) == deep_object); CHECK(json::from_ubjson(json::to_ubjson(deep_object)) == deep_object); CHECK(json::from_ubjson(json::to_ubjson(deep_object, true, true)) == deep_object); CHECK(json::from_bjdata(json::to_bjdata(deep_object)) == deep_object); + CHECK(json::from_bon8(json::to_bon8(deep_object)) == deep_object); CHECK(json::from_cbor(json::to_cbor(empty_array)) == empty_array); CHECK(json::from_msgpack(json::to_msgpack(empty_array)) == empty_array); CHECK(json::from_ubjson(json::to_ubjson(empty_array)) == empty_array); CHECK(json::from_ubjson(json::to_ubjson(empty_array, true, true)) == empty_array); + CHECK(json::from_bon8(json::to_bon8(empty_array)) == empty_array); CHECK(json::from_cbor(json::to_cbor(empty_object)) == empty_object); CHECK(json::from_msgpack(json::to_msgpack(empty_object)) == empty_object); CHECK(json::from_ubjson(json::to_ubjson(empty_object)) == empty_object); + CHECK(json::from_bon8(json::to_bon8(empty_object)) == empty_object); CHECK(json::from_cbor(json::to_cbor(mixed)) == mixed); CHECK(json::from_msgpack(json::to_msgpack(mixed)) == mixed); CHECK(json::from_ubjson(json::to_ubjson(mixed)) == mixed); CHECK(json::from_bjdata(json::to_bjdata(mixed)) == mixed); + CHECK(json::from_bon8(json::to_bon8(mixed)) == mixed); } SECTION("the two ways of writing a value meet at the bound") @@ -432,11 +437,13 @@ TEST_CASE("issue #5392 - binary writers on deeply nested values") CHECK(json::from_cbor(json::to_cbor(array)) == array); CHECK(json::from_msgpack(json::to_msgpack(array)) == array); CHECK(json::from_ubjson(json::to_ubjson(array, true, true)) == array); + CHECK(json::from_bon8(json::to_bon8(array)) == array); const json object = nested_object(depth, json(7)); CHECK(json::from_cbor(json::to_cbor(object)) == object); CHECK(json::from_msgpack(json::to_msgpack(object)) == object); CHECK(json::from_bjdata(json::to_bjdata(object)) == object); + CHECK(json::from_bon8(json::to_bon8(object)) == object); } } @@ -481,6 +488,10 @@ TEST_CASE("issue #5392 - binary writers on deeply nested values") expected_ubjson.append(depth, ']'); const auto packed_ubjson = json::to_ubjson(array); CHECK(std::string(packed_ubjson.begin(), packed_ubjson.end()) == expected_ubjson); + + std::vector expected_bon8(depth, 0x81); + expected_bon8.push_back(0x90); + CHECK(json::to_bon8(array) == expected_bon8); } } @@ -493,6 +504,7 @@ TEST_CASE("issue #5392 - binary writers on deeply nested values") CHECK(json::from_msgpack(json::to_msgpack(object)) == object); CHECK(json::from_ubjson(json::to_ubjson(object, true, true)) == object); CHECK(json::from_bjdata(json::to_bjdata(object)) == object); + CHECK(json::from_bon8(json::to_bon8(object)) == object); const json ndarray = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {2, 3}}, {"_ArrayData_", {1, 2, 3, 4, 5, 6}}}); const json deep_ndarray = nested_array(depth, ndarray); @@ -523,6 +535,27 @@ TEST_CASE("issue #5392 - binary writers on deeply nested values") CHECK_THROWS_WITH_AS(json::to_bjdata(deep_discarded), (prefix + "cannot serialize discarded value to BJData").c_str(), json::type_error); } + SECTION("BON8 past the recursion bound: discarded values and errors") + { + const std::size_t depth = nlohmann::detail::recursion_depth_limit() + 50; + + // BON8 writes nothing for a discarded value, deep or not + const json deep_discarded = nested_array(depth, json(json::value_t::discarded)); + CHECK(json::to_bon8(deep_discarded) == std::vector(depth, 0x81)); + + // errors are thrown from below the bound as from above it + const json deep_invalid_string = nested_array(depth, json(std::string("\x80"))); + CHECK_THROWS_AS(json::to_bon8(deep_invalid_string), json::type_error); + + json deep_invalid_key = json::object(); + deep_invalid_key[std::string("\x80")] = 1; + deep_invalid_key = nested_object(depth, deep_invalid_key); + CHECK_THROWS_AS(json::to_bon8(deep_invalid_key), json::type_error); + + const json deep_too_large = nested_array(depth, json(9223372036854775808u)); + CHECK_THROWS_AS(json::to_bon8(deep_too_large), json::out_of_range); + } + SECTION("does not overflow the C++ stack") { const std::size_t depth = 100000; @@ -543,6 +576,27 @@ TEST_CASE("issue #5392 - binary writers on deeply nested values") CHECK_NOTHROW(packed = json::to_bjdata(j)); CHECK(json::from_bjdata(packed) == j); + + CHECK_NOTHROW(packed = json::to_bon8(j)); + CHECK(json::from_bon8(packed) == j); + } + + SECTION("BON8 containers with more than four elements past the recursion bound") + { + // such containers are closed with 0xFE, which the iterative writer + // must emit when it leaves them; strings next to each other must + // still be separated with 0xFF + const std::size_t depth = nlohmann::detail::recursion_depth_limit() + 50; + json wide_array = json::array({1, "a", "b", json::array(), 5}); + json wide_object = json({{"k1", "v"}, {"k2", 2}, {"k3", ""}, {"k4", json::object()}, {"k5", nullptr}}); + for (std::size_t i = 0; i < depth; ++i) + { + wide_array = json::array({"s", wide_array, "t", wide_object, true}); + wide_object = json({{"a", wide_object}, {"b", "x"}, {"c", "y"}, {"d", wide_array.size()}, {"e", "z"}}); + } + + CHECK(json::from_bon8(json::to_bon8(wide_array)) == wide_array); + CHECK(json::from_bon8(json::to_bon8(wide_object)) == wide_object); } SECTION("regression test for https://issues.oss-fuzz.com/issues/566583014") @@ -561,5 +615,14 @@ TEST_CASE("issue #5392 - binary writers on deeply nested values") const json j_msgpack = json::from_msgpack(v_msgpack); CHECK(json::to_msgpack(j_msgpack) == v_msgpack); } + + SECTION("regression test for https://issues.oss-fuzz.com/issues/572238015") + { + // the BON8 analogue: 200000 nested one-element arrays down to null + std::vector v(200000, 0x81); + v.push_back(0xFA); + const json j = json::from_bon8(v); + CHECK(json::to_bon8(j) == v); + } }