Compare commits

..
Author SHA1 Message Date
Niels Lohmann e6aba4b34b 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 <mail@nlohmann.me>
2026-10-10 08:40:06 +02:00
Niels Lohmann e872ffdcf2 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 <mail@nlohmann.me>
2026-10-10 08:38:01 +02:00
Niels Lohmann 6c28a26123 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 <mail@nlohmann.me>
2026-10-10 05:49:44 +02:00
Niels Lohmann 37bee747e9 Merge branch 'develop' into fix/custom-key-type-compat
develop (#5798) added tests/src/unit-custom-object-key-type.cpp with the
custom object key tests moved out of unit-cbor.cpp and unit-msgpack.cpp.
This branch added a file of the same name; keep develop's file and move
this branch's tests:

- the key types, helpers, and test bodies go to custom_key_test.hpp;
- unit-custom-key-type-{full,no-eq,explicit,to-json,c-str}.cpp run the
  tests for one object type each, as one file with all five
  specializations failed to link with MinGW ("relocation truncated to
  fit", more than 65535 sections, see .github/workflows/windows.yml);
- the at() test expects the "(bytes 0-7)" prefix with
  JSON_DIAGNOSTIC_POSITIONS.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-10 00:25:55 +02:00
Niels Lohmann b9c46c5e8d Write the head of nested BSON elements in one helper
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-09 23:27:34 +02:00
Niels Lohmann c76d838cd5 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 <mail@nlohmann.me>
2026-10-09 23:14:39 +02:00
Niels Lohmann 8952057295 Use the with_object_t alias for the custom object key test types
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-09 14:41:39 +02:00
Niels Lohmann 0db359cabc 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 <mail@nlohmann.me>
2026-10-09 14:37:07 +02:00
31 changed files with 1345 additions and 691 deletions

No files matched your search

+2 -4
View File
@@ -149,10 +149,8 @@ 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)
```
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.
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.
### Amalgamate the source code
+55 -11
View File
@@ -1,24 +1,48 @@
SHELL=/usr/bin/env bash
PYTHON=../mkdocs/venv/bin/python3
SED ?= $(shell which gsed 2>/dev/null || which sed)
MKDOCS_PAGES=$(shell cd ../mkdocs/docs/ && find * -type f -name '*.md' | sort)
.PHONY: all
all: JSON_for_Modern_C++.tgz
# generate the search index (the docset target does this itself, this is
# only handy for inspecting the index)
docSet.dsidx: generate_docset.py
$(PYTHON) generate_docset.py index docSet.dsidx
docSet.dsidx: docSet.sql
# generate index
sqlite3 docSet.dsidx <docSet.sql
# build the documentation and turn it into a self-contained docset
.PHONY: JSON_for_Modern_C++.docset
JSON_for_Modern_C++.docset:
JSON_for_Modern_C++.docset: Info.plist docSet.dsidx
rm -fr JSON_for_Modern_C++.docset JSON_for_Modern_C++.tgz
mkdir -p JSON_for_Modern_C++.docset/Contents/Resources/Documents/
cp icon*.png JSON_for_Modern_C++.docset
cp Info.plist JSON_for_Modern_C++.docset/Contents
# build and copy documentation
$(MAKE) install_venv -C ../mkdocs
$(MAKE) build -C ../mkdocs
$(PYTHON) generate_docset.py docset ../mkdocs/site .
cp -r ../mkdocs/site/* JSON_for_Modern_C++.docset/Contents/Resources/Documents
# patch CSS to hide navigation items
echo -e "\n\nheader, footer, nav.md-tabs, nav.md-tabs--active, div.md-sidebar--primary, a.md-content__button { display: none; }" >> "$$(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++</title>|</title>|' {} +
# 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>.*</title>%<title>$$title</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/
.PHONY: JSON_for_Modern_C++.tgz
JSON_for_Modern_C++.tgz: JSON_for_Modern_C++.docset
$(PYTHON) generate_docset.py tgz .
tar --exclude='.DS_Store' -cvzf JSON_for_Modern_C++.tgz JSON_for_Modern_C++.docset
# install docset for Zeal documentation browser (https://zealdocs.org/)
.PHONY: install_docset_zeal
@@ -28,6 +52,26 @@ 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
+1 -3
View File
@@ -4,8 +4,7 @@ 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 (pages and search index) is generated by `generate_docset.py` from the mkdocs site and `mkdocs.yml`. It
can be created with
The docset can be created with
```sh
make JSON_for_Modern_C++.docset
@@ -13,7 +12,6 @@ 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).
+289
View File
@@ -0,0 +1,289 @@
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<basic_json>', 'Class', 'api/basic_json/std_formatter/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('std::hash<basic_json>', 'Class', 'api/basic_json/std_hash/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('std::swap<basic_json>', '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');
-528
View File
@@ -1,528 +0,0 @@
#!/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++</title>'
# 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'</?[a-zA-Z][^>]*>', '', 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'<h1>(.*?)</h1>', 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, '</title>')
if rel in names:
title = html.escape(', '.join(names[rel]), quote=False)
text = re.sub(r'<title>.*?</title>', lambda _: f'<title>{title}</title>', text, count=1, flags=re.S)
write(file, text)
IMG_RE = re.compile(r'<img\b[^>]*>', 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'(?<![\w-])' + ATTR_RE.format(name), tag, flags=re.I)
return next(g for g in m.groups() if g is not None) if m else None
def is_remote(url) -> 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 "<site_url>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'<link\b', tag, flags=re.I):
rel_attr = (attr(tag, 'rel') or '').lower()
v = attr(tag, 'href')
if v and is_remote(v) and re.search(r'stylesheet|icon|preload|modulepreload|manifest', rel_attr):
urls.append(v)
urls += css_url.findall(text) + css_import.findall(text)
elif file.endswith('.css'):
text = read(path)
urls += css_url.findall(text) + css_import.findall(text)
elif file.endswith('.js'):
text = read(path)
urls += cdn.findall(text)
urls += re.findall(re.escape(site_url) + r'assets/[^\s"\'`)\\]*', text)
found += [f'{rel}: {u}' for u in urls if u not in ALLOWED_REMOTE]
return found
def make_docset(site, out_dir) -> 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())
-1
View File
@@ -17,7 +17,6 @@ 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).
+1 -3
View File
@@ -241,6 +241,4 @@ 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. 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.
4. Added in version 2.0.0.
+1 -2
View File
@@ -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<StringType>` 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`)
@@ -286,6 +286,4 @@ 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. 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.
version 3.13.0.
-3
View File
@@ -112,6 +112,3 @@ 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.
@@ -110,6 +110,3 @@ 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.
+2 -6
View File
@@ -36,9 +36,8 @@ 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 begins with a digit but is not a valid array index
(such as `1a`), or is `-`; example:
`"unresolved reference token '-'"`
(because one of its keys is `0`) and another key at that level cannot be an array index; example:
`"unresolved reference token 'x'"`
## Complexity
@@ -81,6 +80,3 @@ 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.
+1 -3
View File
@@ -227,6 +227,4 @@ 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). 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.
`default_value`, as documented).
+2 -10
View File
@@ -278,9 +278,7 @@ 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. 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.
A JSON Pointer array index must be a number.
!!! failure "Example messages"
@@ -291,11 +289,6 @@ where they cannot be resolved.
[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
@@ -878,8 +871,7 @@ 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, for instance an array index that begins with a digit but
contains other characters (e.g., `1a`), or `-` where it cannot be used.
A reference token in a JSON Pointer could not be resolved.
!!! failure "Example message"
+31
View File
@@ -288,6 +288,36 @@ 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"<h1>(.*?)</h1>", 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()
@@ -297,6 +327,7 @@ if __name__ == "__main__":
check_heading_levels()
check_image_alt_text()
check_header_links()
check_docset()
print(120 * "-")
if warnings > 0:
+3 -5
View File
@@ -255,7 +255,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 is neither empty nor "-" and does not begin with a digit
not_a_number, ///< @a s 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
};
@@ -282,10 +282,8 @@ class json_pointer
return array_index_status::leading_zero;
}
// error condition (cf. RFC 6901, Sect. 4); this also covers single-
// character tokens, so "/x" and "/xy" fail alike; "-" and the empty
// token are left to the conversion below and are reported as unresolved
if (JSON_HEDLEY_UNLIKELY(!s.empty() && s != "-" && !(s[0] >= '0' && s[0] <= '9')))
// error condition (cf. RFC 6901, Sect. 4)
if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && !(s[0] >= '1' && s[0] <= '9')))
{
return array_index_status::not_a_number;
}
@@ -172,6 +172,10 @@ struct has_to_json < BasicJsonType, T, enable_if_t < !is_basic_json<T>::value >>
template<typename T>
using detect_key_compare = typename T::key_compare;
// detects whether two values of type T can be compared with operator==
template<typename T>
using detect_equal_comparable = decltype(static_cast<bool>(std::declval<const T&>() == std::declval<const T&>()));
// obtains the actual object key comparator: object_t::key_compare if the
// object type defines it, and default_object_comparator_t otherwise
//
@@ -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;
@@ -992,7 +980,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);
@@ -1069,7 +1057,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);
@@ -1674,6 +1662,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<std::int32_t>(to_bson_length(size), true);
}
/// @brief an object or array of the BSON document being sized or written
struct bson_frame
{
@@ -1838,7 +1841,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())
{
@@ -1849,7 +1851,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
@@ -1868,7 +1870,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 = &el;
}
else
@@ -1880,8 +1882,6 @@ class binary_writer
if (nested != nullptr)
{
write_bson_entry_header(*nested_name, nested->is_object() ? 0x03 : 0x04);
write_number<std::int32_t>(to_bson_length(nested_sizes[next_size++]), true);
parents.push_back(std::move(current));
current = bson_frame(nested);
continue;
@@ -1949,6 +1949,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<typename Key = typename BasicJsonType::object_t::key_type,
enable_if_t<std::is_convertible<Key, string_t>::value, int> = 0>
void write_cbor_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& context)
{
write_cbor_string(key, context);
}
template < typename Key = typename BasicJsonType::object_t::key_type,
enable_if_t < !std::is_convertible<Key, string_t>::value, int > = 0 >
void write_cbor_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& /*context*/)
{
write_cbor(BasicJsonType(key));
}
/// @brief write an object key as a MessagePack str, as in @ref write_cbor_key
template<typename Key = typename BasicJsonType::object_t::key_type,
enable_if_t<std::is_convertible<Key, string_t>::value, int> = 0>
void write_msgpack_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& context)
{
write_msgpack_string(key, context);
}
template < typename Key = typename BasicJsonType::object_t::key_type,
enable_if_t < !std::is_convertible<Key, string_t>::value, int > = 0 >
void write_msgpack_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& /*context*/)
{
write_msgpack(BasicJsonType(key));
}
/*!
@brief write a CBOR text string
+37 -2
View File
@@ -1421,6 +1421,24 @@ public:
return create<object_t>(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<typename K = typename object_t::key_type,
detail::enable_if_t<detail::is_detected<detail::detect_equal_comparable, K>::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<detail::detect_equal_comparable, K>::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(403, detail::concat("key '", string_t(key), "' not found"), &j));
JSON_THROW(out_of_range::create(403, 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<string_t, KeyType>::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<string_t, KeyType>::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
-8
View File
@@ -247,14 +247,6 @@ 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:
+102 -36
View File
@@ -4171,6 +4171,10 @@ struct has_to_json < BasicJsonType, T, enable_if_t < !is_basic_json<T>::value >>
template<typename T>
using detect_key_compare = typename T::key_compare;
// detects whether two values of type T can be compared with operator==
template<typename T>
using detect_equal_comparable = decltype(static_cast<bool>(std::declval<const T&>() == std::declval<const T&>()));
// obtains the actual object key comparator: object_t::key_compare if the
// object type defines it, and default_object_comparator_t otherwise
//
@@ -20173,7 +20177,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 is neither empty nor "-" and does not begin with a digit
not_a_number, ///< @a s 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
};
@@ -20200,10 +20204,8 @@ class json_pointer
return array_index_status::leading_zero;
}
// error condition (cf. RFC 6901, Sect. 4); this also covers single-
// character tokens, so "/x" and "/xy" fail alike; "-" and the empty
// token are left to the conversion below and are reported as unresolved
if (JSON_HEDLEY_UNLIKELY(!s.empty() && s != "-" && !(s[0] >= '0' && s[0] <= '9')))
// error condition (cf. RFC 6901, Sect. 4)
if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && !(s[0] >= '1' && s[0] <= '9')))
{
return array_index_status::not_a_number;
}
@@ -21932,12 +21934,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());
@@ -21945,7 +21941,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;
@@ -22210,12 +22206,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);
@@ -22223,7 +22213,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;
@@ -22611,7 +22601,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);
@@ -22688,7 +22678,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);
@@ -23293,6 +23283,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<std::int32_t>(to_bson_length(size), true);
}
/// @brief an object or array of the BSON document being sized or written
struct bson_frame
{
@@ -23457,7 +23462,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())
{
@@ -23468,7 +23472,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
@@ -23487,7 +23491,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 = &el;
}
else
@@ -23499,8 +23503,6 @@ class binary_writer
if (nested != nullptr)
{
write_bson_entry_header(*nested_name, nested->is_object() ? 0x03 : 0x04);
write_number<std::int32_t>(to_bson_length(nested_sizes[next_size++]), true);
parents.push_back(std::move(current));
current = bson_frame(nested);
continue;
@@ -23568,6 +23570,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<typename Key = typename BasicJsonType::object_t::key_type,
enable_if_t<std::is_convertible<Key, string_t>::value, int> = 0>
void write_cbor_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& context)
{
write_cbor_string(key, context);
}
template < typename Key = typename BasicJsonType::object_t::key_type,
enable_if_t < !std::is_convertible<Key, string_t>::value, int > = 0 >
void write_cbor_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& /*context*/)
{
write_cbor(BasicJsonType(key));
}
/// @brief write an object key as a MessagePack str, as in @ref write_cbor_key
template<typename Key = typename BasicJsonType::object_t::key_type,
enable_if_t<std::is_convertible<Key, string_t>::value, int> = 0>
void write_msgpack_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& context)
{
write_msgpack_string(key, context);
}
template < typename Key = typename BasicJsonType::object_t::key_type,
enable_if_t < !std::is_convertible<Key, string_t>::value, int > = 0 >
void write_msgpack_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& /*context*/)
{
write_msgpack(BasicJsonType(key));
}
/*!
@brief write a CBOR text string
@@ -27782,14 +27821,6 @@ 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:
@@ -29301,6 +29332,24 @@ public:
return create<object_t>(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<typename K = typename object_t::key_type,
detail::enable_if_t<detail::is_detected<detail::detect_equal_comparable, K>::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<detail::detect_equal_comparable, K>::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,
@@ -29333,7 +29382,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;
@@ -31210,11 +31259,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(403, detail::concat("key '", string_t(key), "' not found"), &j));
JSON_THROW(out_of_range::create(403, 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<string_t, KeyType>::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<string_t, KeyType>::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
+519
View File
@@ -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 <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#pragma once
#include "doctest_compatibility.h"
#include <nlohmann/json.hpp>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
// 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<typename BasicJsonType>
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<typename BasicJsonType>
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<class K>
struct object_for
{
template<class Key, class Value, class Compare, class Allocator>
using pair_allocator = typename std::allocator_traits<Allocator>::template rebind_alloc<std::pair<const K, Value>>;
template<class Key, class Value, class Compare, class Allocator>
using type = std::map<K, Value, std::less<K>, pair_allocator<Key, Value, Compare, Allocator>>; // NOLINT(modernize-use-transparent-functors)
};
using json_full = nlohmann::json::with_object_t<object_for<key_full>::type>;
using json_no_eq = nlohmann::json::with_object_t<object_for<key_no_eq>::type>;
using json_explicit = nlohmann::json::with_object_t<object_for<key_explicit>::type>;
using json_to_json = nlohmann::json::with_object_t<object_for<key_to_json>::type>;
using json_c_str = nlohmann::json::with_object_t<object_for<key_c_str>::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}, <keys of 23, 36, and 300 characters>}
// 23 is the longest CBOR length stored in the initial byte; 36 needs one
// length byte in CBOR and MessagePack, 300 needs two
template<class J>
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<depth-1>": 1} ...}}}
template<class J>
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<class J>
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<int>() == 1;
}
template<class J>
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<int>() == 1
&& b->is_array() && b->size() == 3 && (*b)[0].template get<bool>() && (*b)[1].is_null()
&& (*b)[2].template get<std::string>() == "x"
&& d != c->end() && std::abs(d->template get<double>() - 2.5) < 1e-9
&& value.find(key_t(std::string(23, 'x')))->template get<int>() == 2
&& value.find(key_t(std::string(36, 'y')))->template get<int>() == 3
&& value.find(key_t(std::string(300, 'z')))->template get<int>() == 4;
}
template<class J>
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<class J>
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<class J>
void test_copy()
{
SECTION("shallow")
{
const J original = custom_key_test::make_shallow<J>();
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<J>(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<class J>
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<int>() == 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<int>() == 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<class J>
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<int>() == 1);
CHECK(custom_key_test::is_missing(j, "b"));
CHECK(custom_key_test::member(j, "c").template get<int>() == 3);
CHECK(custom_key_test::member(j, "n").size() == 2);
CHECK(custom_key_test::member(custom_key_test::member(j, "n"), "x").template get<int>() == 1);
CHECK(custom_key_test::member(custom_key_test::member(j, "n"), "z").template get<int>() == 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<int>() == 1);
CHECK(custom_key_test::member(replaced, "b").template get<int>() == 3);
CHECK(custom_key_test::member(replaced, "c").template get<int>() == 4);
CHECK(custom_key_test::member(replaced, "n").size() == 1);
CHECK(custom_key_test::member(custom_key_test::member(replaced, "n"), "y").template get<int>() == 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<int>() == 1);
CHECK(custom_key_test::member(custom_key_test::member(j, "n"), "y").template get<int>() == 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<int>() == 2);
CHECK(custom_key_test::member(j, "c").template get<int>() == 4);
}
}
// at() reports a missing key
template<class J>
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<int>() == 1);
CHECK(j_const.at("a").template get<int>() == 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<class J>
void test_bson()
{
SECTION("shallow")
{
const J value = custom_key_test::make_shallow<J>();
const nlohmann::json expected = custom_key_test::make_shallow<nlohmann::json>();
const std::vector<std::uint8_t> 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<J>(depth, false);
const nlohmann::json expected = custom_key_test::make_deep<nlohmann::json>(depth, false);
const std::vector<std::uint8_t> encoded = J::to_bson(value);
CHECK(encoded == nlohmann::json::to_bson(expected));
CHECK(nlohmann::json::from_bson(encoded) == expected);
}
}
// CBOR
template<class J>
void test_cbor()
{
SECTION("shallow")
{
const J value = custom_key_test::make_shallow<J>();
const nlohmann::json expected = custom_key_test::make_shallow<nlohmann::json>();
const std::vector<std::uint8_t> 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<J>(depth, true);
const nlohmann::json expected = custom_key_test::make_deep<nlohmann::json>(depth, true);
const std::vector<std::uint8_t> encoded = J::to_cbor(value);
CHECK(encoded == nlohmann::json::to_cbor(expected));
CHECK(nlohmann::json::from_cbor(encoded) == expected);
}
}
// MessagePack
template<class J>
void test_msgpack()
{
SECTION("shallow")
{
const J value = custom_key_test::make_shallow<J>();
const nlohmann::json expected = custom_key_test::make_shallow<nlohmann::json>();
const std::vector<std::uint8_t> 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<J>(depth, true);
const nlohmann::json expected = custom_key_test::make_deep<nlohmann::json>(depth, true);
const std::vector<std::uint8_t> encoded = J::to_msgpack(value);
CHECK(encoded == nlohmann::json::to_msgpack(expected));
CHECK(nlohmann::json::from_msgpack(encoded) == expected);
}
}
} // namespace custom_key_test
+1 -1
View File
@@ -71,5 +71,5 @@ class object
using base_type::base_type;
};
using json = nlohmann::basic_json<object>;
using json = nlohmann::json::with_object_t<object>;
} // namespace custom_object_key_test
+46
View File
@@ -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 <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#include "doctest_compatibility.h"
#include <nlohmann/json.hpp>
#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<json_c_str>();
}
TEST_CASE("custom object key types: parse (json_c_str)")
{
custom_key_test::test_parse<json_c_str>();
}
TEST_CASE("custom object key types: merge_patch, update, and insert (json_c_str)")
{
custom_key_test::test_patch<json_c_str>();
}
TEST_CASE("custom object key types: at() reports a missing key (json_c_str)")
{
custom_key_test::test_at<json_c_str>();
}
TEST_CASE("custom object key types: CBOR (json_c_str)")
{
custom_key_test::test_cbor<json_c_str>();
}
TEST_CASE("custom object key types: MessagePack (json_c_str)")
{
custom_key_test::test_msgpack<json_c_str>();
}
@@ -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 <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#include "doctest_compatibility.h"
#include <nlohmann/json.hpp>
#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<json_explicit>();
}
TEST_CASE("custom object key types: parse (json_explicit)")
{
custom_key_test::test_parse<json_explicit>();
}
TEST_CASE("custom object key types: merge_patch, update, and insert (json_explicit)")
{
custom_key_test::test_patch<json_explicit>();
}
TEST_CASE("custom object key types: at() reports a missing key (json_explicit)")
{
custom_key_test::test_at<json_explicit>();
}
TEST_CASE("custom object key types: CBOR (json_explicit)")
{
custom_key_test::test_cbor<json_explicit>();
}
TEST_CASE("custom object key types: MessagePack (json_explicit)")
{
custom_key_test::test_msgpack<json_explicit>();
}
+51
View File
@@ -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 <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#include "doctest_compatibility.h"
#include <nlohmann/json.hpp>
#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<json_full>();
}
TEST_CASE("custom object key types: parse (json_full)")
{
custom_key_test::test_parse<json_full>();
}
TEST_CASE("custom object key types: merge_patch, update, and insert (json_full)")
{
custom_key_test::test_patch<json_full>();
}
TEST_CASE("custom object key types: at() reports a missing key (json_full)")
{
custom_key_test::test_at<json_full>();
}
TEST_CASE("custom object key types: BSON (json_full)")
{
custom_key_test::test_bson<json_full>();
}
TEST_CASE("custom object key types: CBOR (json_full)")
{
custom_key_test::test_cbor<json_full>();
}
TEST_CASE("custom object key types: MessagePack (json_full)")
{
custom_key_test::test_msgpack<json_full>();
}
+51
View File
@@ -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 <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#include "doctest_compatibility.h"
#include <nlohmann/json.hpp>
#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<json_no_eq>();
}
TEST_CASE("custom object key types: parse (json_no_eq)")
{
custom_key_test::test_parse<json_no_eq>();
}
TEST_CASE("custom object key types: merge_patch, update, and insert (json_no_eq)")
{
custom_key_test::test_patch<json_no_eq>();
}
TEST_CASE("custom object key types: at() reports a missing key (json_no_eq)")
{
custom_key_test::test_at<json_no_eq>();
}
TEST_CASE("custom object key types: BSON (json_no_eq)")
{
custom_key_test::test_bson<json_no_eq>();
}
TEST_CASE("custom object key types: CBOR (json_no_eq)")
{
custom_key_test::test_cbor<json_no_eq>();
}
TEST_CASE("custom object key types: MessagePack (json_no_eq)")
{
custom_key_test::test_msgpack<json_no_eq>();
}
@@ -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 <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#include "doctest_compatibility.h"
#include <nlohmann/json.hpp>
#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<json_to_json>();
}
TEST_CASE("custom object key types: parse (json_to_json)")
{
custom_key_test::test_parse<json_to_json>();
}
TEST_CASE("custom object key types: merge_patch, update, and insert (json_to_json)")
{
custom_key_test::test_patch<json_to_json>();
}
TEST_CASE("custom object key types: CBOR (json_to_json)")
{
custom_key_test::test_cbor<json_to_json>();
}
TEST_CASE("custom object key types: MessagePack (json_to_json)")
{
custom_key_test::test_msgpack<json_to_json>();
}
-2
View File
@@ -535,8 +535,6 @@ 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&);
-15
View File
@@ -1713,21 +1713,6 @@ 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,
-21
View File
@@ -421,27 +421,6 @@ 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}));