mirror of
https://github.com/nlohmann/json.git
synced 2026-10-10 16:37:14 +00:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6aba4b34b | ||
|
|
e872ffdcf2 | ||
|
|
6c28a26123 | ||
|
|
37bee747e9 | ||
|
|
b9c46c5e8d | ||
|
|
c76d838cd5 | ||
|
|
8952057295 | ||
|
|
0db359cabc |
No files matched your search
@@ -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
@@ -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
|
||||
|
||||
@@ -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).
|
||||
|
||||
|
||||
@@ -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');
|
||||
@@ -1,532 +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)
|
||||
if u.scheme.lower() not in ('http', 'https'):
|
||||
raise ValueError(f'not an http(s) URL: {url}')
|
||||
req = urllib.request.Request(u.geturl(), headers={'User-Agent': USER_AGENT})
|
||||
# (the scheme is checked above)
|
||||
with urllib.request.urlopen(req, timeout=20) as r: # nosec B310
|
||||
data = r.read()
|
||||
ctype = r.headers.get_content_type()
|
||||
path = urllib.parse.unquote(u.path).lstrip('/')
|
||||
ext = os.path.splitext(path)[1]
|
||||
if u.query or not ext or path.endswith('/'):
|
||||
digest = hashlib.sha1(url.encode(), usedforsecurity=False).hexdigest()[:12]
|
||||
path = os.path.join(os.path.dirname(path), digest + CONTENT_TYPE_EXT.get(ctype, ext or '.bin'))
|
||||
rel = os.path.normpath(os.path.join('assets', 'external', u.hostname, path))
|
||||
out = os.path.join(docs, rel)
|
||||
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:
|
||||
# (Loader is a yaml.SafeLoader)
|
||||
return yaml.load(f, Loader=Loader) # nosec B506
|
||||
|
||||
|
||||
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())
|
||||
@@ -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).
|
||||
|
||||
@@ -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`)
|
||||
|
||||
@@ -123,6 +123,4 @@ Linear in the size of the JSON value `j`.
|
||||
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
|
||||
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
|
||||
array or object was silently skipped, producing invalid BJData.
|
||||
- Writes unsigned integers wider than 64 bits as high-precision numbers since version 3.13.0; previously, they were
|
||||
silently truncated to 64 bits.
|
||||
array or object was silently skipped, producing invalid BJData.
|
||||
@@ -37,9 +37,8 @@ With (2), the bytes written before the exception remain in the output adapter.
|
||||
|
||||
## Exceptions
|
||||
|
||||
- Throws [out_of_range.407](../../home/exceptions.md#jsonexceptionout_of_range407) if `j` contains an integer outside
|
||||
the range of int64 (an unsigned integer above 9223372036854775807, or, with a number type wider than 64 bits, any
|
||||
integer beyond int64), which BON8 cannot represent
|
||||
- Throws [out_of_range.407](../../home/exceptions.md#jsonexceptionout_of_range407) if `j` contains an unsigned integer
|
||||
above 9223372036854775807, which BON8 cannot represent
|
||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if `j` contains a string that is not
|
||||
valid UTF-8
|
||||
|
||||
|
||||
@@ -47,10 +47,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
|
||||
- Throws [`type_error.317`](../../home/exceptions.md#jsonexceptiontype_error317) if the top-level type of the JSON value
|
||||
is not an object; example: `"to serialize to BSON, top-level type must be object, but is string"`
|
||||
- Throws [`out_of_range.407`](../../home/exceptions.md#jsonexceptionout_of_range407) if `j` contains a signed integer
|
||||
outside the range of int64 or an unsigned integer outside the range of uint64, which is only possible with a number
|
||||
type wider than 64 bits; example:
|
||||
`"integer number 9223372036854775808 cannot be represented by BSON as it does not fit int64"`
|
||||
- Throws [`out_of_range.409`](../../home/exceptions.md#jsonexceptionout_of_range409) if a key in the JSON object contains
|
||||
a null byte (code point U+0000); example: `"BSON key cannot contain code point U+0000 (at byte 2)"`
|
||||
- Throws [`out_of_range.412`](../../home/exceptions.md#jsonexceptionout_of_range412) if the length of a document, array,
|
||||
@@ -123,5 +119,3 @@ pass before anything is written.
|
||||
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316` before anything
|
||||
is written.
|
||||
- Throws `out_of_range.407` for integers that do not fit 64 bits since version 3.13.0; previously, integers of a
|
||||
number type wider than 64 bits were silently truncated.
|
||||
@@ -46,9 +46,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
|
||||
## Exceptions
|
||||
|
||||
- Throws [`out_of_range.407`](../../home/exceptions.md#jsonexceptionout_of_range407) if `j` contains an integer
|
||||
outside [-2^64, 2^64-1], which is only possible with a number type wider than 64 bits; example:
|
||||
`"integer number 18446744073709551616 cannot be represented by CBOR as it does not fit [-2^64, 2^64-1]"`
|
||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
||||
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
||||
@@ -93,5 +90,3 @@ Linear in the size of the JSON value `j`.
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
|
||||
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
|
||||
array or object was silently skipped, producing invalid CBOR.
|
||||
- Throws `out_of_range.407` for integers that do not fit 64 bits since version 3.13.0; previously, integers of a
|
||||
number type wider than 64 bits were silently truncated.
|
||||
@@ -46,9 +46,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
|
||||
## Exceptions
|
||||
|
||||
- Throws [`out_of_range.407`](../../home/exceptions.md#jsonexceptionout_of_range407) if `j` contains an integer
|
||||
outside [-2^63, 2^64-1], which is only possible with a number type wider than 64 bits; example:
|
||||
`"integer number 18446744073709551616 cannot be represented by MessagePack as it does not fit [-2^63, 2^64-1]"`
|
||||
- Throws [`out_of_range.412`](../../home/exceptions.md#jsonexceptionout_of_range412) if the length of a string, binary
|
||||
value, array, or object exceeds 4294967295, the maximum MessagePack can store; example:
|
||||
`"MessagePack length 4294967296 exceeds maximum of 4294967295"`
|
||||
@@ -115,5 +112,3 @@ Linear in the size of the JSON value `j`.
|
||||
`number_unsigned_t`.
|
||||
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
|
||||
array or object was silently skipped, producing invalid MessagePack.
|
||||
- Throws `out_of_range.407` for integers that do not fit 64 bits since version 3.13.0; previously, integers of a
|
||||
number type wider than 64 bits were silently truncated.
|
||||
@@ -906,34 +906,14 @@ double-precision number when `number_float_t` is `#!cpp float`.
|
||||
|
||||
### json.exception.out_of_range.407
|
||||
|
||||
An integer number cannot be represented by the binary format it is serialized to:
|
||||
This exception previously indicated that the UBJSON and BSON binary formats did not support integer numbers greater than
|
||||
9223372036854775807 due to limitations in the implemented mapping. However, these limitations have since been resolved,
|
||||
and this exception no longer occurs.
|
||||
|
||||
- [BON8](../features/binary_formats/bon8.md) only stores integers that fit into int64.
|
||||
- [CBOR](../features/binary_formats/cbor.md), [MessagePack](../features/binary_formats/msgpack.md), and
|
||||
[BSON](../features/binary_formats/bson.md) store integers in at most 64 bits. With the default number types, every
|
||||
integer fits, but a [`number_integer_t`](../api/basic_json/number_integer_t.md) or
|
||||
[`number_unsigned_t`](../api/basic_json/number_unsigned_t.md) wider than 64 bits (e.g., `__int128`) can hold values
|
||||
outside the range of the format: [-2^64, 2^64-1] for CBOR, [-2^63, 2^64-1] for MessagePack, and the range of int64
|
||||
(signed integers) or uint64 (unsigned integers) for BSON.
|
||||
!!! success "Exception cannot occur any more"
|
||||
|
||||
[UBJSON](../features/binary_formats/ubjson.md) and [BJData](../features/binary_formats/bjdata.md) never throw this
|
||||
exception, because they serialize integers beyond 64 bits as high-precision numbers.
|
||||
|
||||
!!! failure "Example messages"
|
||||
|
||||
```
|
||||
integer number 9223372036854775808 cannot be represented by BON8 as it does not fit int64
|
||||
```
|
||||
```
|
||||
integer number 1267650600228229401496703205376 cannot be represented by CBOR as it does not fit [-2^64, 2^64-1]
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
Before version 3.13.0, CBOR, MessagePack, and BSON silently truncated integers wider than 64 bits, and BJData
|
||||
truncated unsigned integers wider than 64 bits. This exception was previously thrown by UBJSON and BSON for
|
||||
integers greater than 9223372036854775807; since version 3.9.0, such integers are serialized as high-precision
|
||||
UBJSON numbers, and since version 3.12.0 as uint64 BSON numbers.
|
||||
- Since version 3.9.0, integer numbers beyond int64 are serialized as high-precision UBJSON numbers.
|
||||
- Since version 3.12.0, integer numbers beyond int64 are serialized as uint64 BSON numbers.
|
||||
|
||||
### json.exception.out_of_range.408
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
//
|
||||
|
||||
@@ -207,10 +207,6 @@ class binary_writer
|
||||
{
|
||||
if (j.m_data.m_value.number_integer >= 0)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "CBOR", "[-2^64, 2^64-1]");
|
||||
}
|
||||
// CBOR does not differentiate between positive signed
|
||||
// integers and unsigned integers
|
||||
write_cbor_head(0x00, static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
|
||||
@@ -218,10 +214,6 @@ class binary_writer
|
||||
else
|
||||
{
|
||||
// a negative integer n is encoded as -1 - n
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(-1 - j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "CBOR", "[-2^64, 2^64-1]");
|
||||
}
|
||||
write_cbor_head(0x20, static_cast<std::uint64_t>(-1 - j.m_data.m_value.number_integer));
|
||||
}
|
||||
break;
|
||||
@@ -229,11 +221,7 @@ class binary_writer
|
||||
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_unsigned)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "CBOR", "uint64");
|
||||
}
|
||||
write_cbor_head(0x00, static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
write_cbor_head(0x00, j.m_data.m_value.number_unsigned);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -325,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());
|
||||
|
||||
@@ -338,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;
|
||||
@@ -441,20 +423,12 @@ class binary_writer
|
||||
{
|
||||
if (j.m_data.m_value.number_integer >= 0)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "MessagePack", "[-2^63, 2^64-1]");
|
||||
}
|
||||
// MessagePack does not differentiate between positive
|
||||
// signed integers and unsigned integers.
|
||||
write_msgpack_unsigned(static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_min<std::int64_t>(j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "MessagePack", "[-2^63, 2^64-1]");
|
||||
}
|
||||
if (j.m_data.m_value.number_integer >= -32)
|
||||
{
|
||||
// negative fixnum
|
||||
@@ -493,10 +467,6 @@ class binary_writer
|
||||
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_unsigned)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "MessagePack", "uint64");
|
||||
}
|
||||
write_msgpack_unsigned(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
break;
|
||||
}
|
||||
@@ -615,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);
|
||||
|
||||
@@ -628,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;
|
||||
@@ -858,84 +822,6 @@ class binary_writer
|
||||
JSON_THROW(type_error::create(321, concat("cannot serialize discarded value to ", format_name), &j));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief whether integer @a n does not exceed the maximum of @a TargetType
|
||||
|
||||
The binary formats store integers in at most 64 bits, but number_integer_t
|
||||
and number_unsigned_t may be wider (e.g., __int128). The range of the
|
||||
integer is taken from std::numeric_limits, so a number type whose range
|
||||
fits into @a TargetType is never checked: the function is then constant
|
||||
true, and the default int64_t/uint64_t types pay nothing.
|
||||
*/
|
||||
template<typename TargetType, typename NumberType>
|
||||
static constexpr bool integer_fits_max(const NumberType n) noexcept
|
||||
{
|
||||
return integer_fits_max<TargetType>(n, std::integral_constant < bool,
|
||||
(std::numeric_limits<NumberType>::digits > std::numeric_limits<TargetType>::digits) > ());
|
||||
}
|
||||
|
||||
template<typename TargetType, typename NumberType>
|
||||
static constexpr bool integer_fits_max(const NumberType /*unused*/, std::false_type /*may_exceed*/) noexcept
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename TargetType, typename NumberType>
|
||||
static constexpr bool integer_fits_max(const NumberType n, std::true_type /*may_exceed*/) noexcept
|
||||
{
|
||||
// NumberType has more digits than TargetType, so it can hold its maximum
|
||||
return n <= static_cast<NumberType>((std::numeric_limits<TargetType>::max)());
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief whether integer @a n is not below the minimum of @a TargetType;
|
||||
constant true if NumberType cannot hold such a value
|
||||
*/
|
||||
template<typename TargetType, typename NumberType>
|
||||
static constexpr bool integer_fits_min(const NumberType n) noexcept
|
||||
{
|
||||
return integer_fits_min<TargetType>(n, std::integral_constant < bool, std::numeric_limits<NumberType>::is_signed &&
|
||||
(!std::numeric_limits<TargetType>::is_signed || std::numeric_limits<NumberType>::digits > std::numeric_limits<TargetType>::digits) > ());
|
||||
}
|
||||
|
||||
template<typename TargetType, typename NumberType>
|
||||
static constexpr bool integer_fits_min(const NumberType /*unused*/, std::false_type /*may_fall_below*/) noexcept
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename TargetType, typename NumberType>
|
||||
static constexpr bool integer_fits_min(const NumberType n, std::true_type /*may_fall_below*/) noexcept
|
||||
{
|
||||
// NumberType is signed and either TargetType is unsigned (minimum 0)
|
||||
// or NumberType has more digits, so it can hold the minimum
|
||||
return n >= static_cast<NumberType>((std::numeric_limits<TargetType>::min)());
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief whether integer @a n lies in the range of @a TargetType
|
||||
*/
|
||||
template<typename TargetType, typename NumberType>
|
||||
static constexpr bool integer_fits(const NumberType n) noexcept
|
||||
{
|
||||
return integer_fits_min<TargetType>(n) && integer_fits_max<TargetType>(n);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief throws because the integer @a j is too large for @a format_name
|
||||
@throw out_of_range.407 always
|
||||
*/
|
||||
JSON_HEDLEY_NO_RETURN static void throw_integer_out_of_range(const BasicJsonType& j, const char* format_name, const char* range)
|
||||
{
|
||||
// dump() rather than std::to_string(), which has no overload for
|
||||
// integer types wider than 64 bits
|
||||
const auto number = j.dump();
|
||||
static_cast<void>(number); // unused when JSON_NOEXCEPTION is defined
|
||||
static_cast<void>(format_name);
|
||||
static_cast<void>(range);
|
||||
JSON_THROW(out_of_range::create(407, concat("integer number ", std::string(number.begin(), number.end()), " cannot be represented by ", format_name, " as it does not fit ", range), &j));
|
||||
}
|
||||
|
||||
void write_msgpack_array_prefix(const std::size_t N, const BasicJsonType& j)
|
||||
{
|
||||
const auto n = to_msgpack_length(N, j);
|
||||
@@ -1094,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);
|
||||
@@ -1171,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);
|
||||
@@ -1692,8 +1578,6 @@ class binary_writer
|
||||
is neither an object nor an array
|
||||
@throw out_of_range.415 if @a j is binary with a subtype that does not fit
|
||||
into a byte, before anything is written
|
||||
@throw out_of_range.407 if @a j is an integer that does not fit the 64 bits
|
||||
of a BSON integer, before anything is written
|
||||
@throw type_error.316 if @a j is a string that is not valid UTF-8, before
|
||||
anything is written
|
||||
@throw type_error.321 if @a j is discarded
|
||||
@@ -1712,18 +1596,10 @@ class binary_writer
|
||||
return 8ul;
|
||||
|
||||
case value_t::number_integer:
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits<std::int64_t>(j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "BSON", "int64");
|
||||
}
|
||||
return calc_bson_integer_size(static_cast<std::int64_t>(j.m_data.m_value.number_integer));
|
||||
return calc_bson_integer_size(j.m_data.m_value.number_integer);
|
||||
|
||||
case value_t::number_unsigned:
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_unsigned)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "BSON", "uint64");
|
||||
}
|
||||
return calc_bson_unsigned_size(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
return calc_bson_unsigned_size(j.m_data.m_value.number_unsigned);
|
||||
|
||||
case value_t::string:
|
||||
return calc_bson_string_size(*j.m_data.m_value.string, j);
|
||||
@@ -1761,12 +1637,11 @@ class binary_writer
|
||||
case value_t::number_float:
|
||||
return write_bson_double(name, j.m_data.m_value.number_float);
|
||||
|
||||
// calc_bson_value_size() checked that integers fit 64 bits
|
||||
case value_t::number_integer:
|
||||
return write_bson_integer(name, static_cast<std::int64_t>(j.m_data.m_value.number_integer));
|
||||
return write_bson_integer(name, j.m_data.m_value.number_integer);
|
||||
|
||||
case value_t::number_unsigned:
|
||||
return write_bson_unsigned(name, static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
return write_bson_unsigned(name, j.m_data.m_value.number_unsigned);
|
||||
|
||||
case value_t::string:
|
||||
return write_bson_string(name, *j.m_data.m_value.string);
|
||||
@@ -1787,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
|
||||
{
|
||||
@@ -1951,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())
|
||||
{
|
||||
@@ -1962,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
|
||||
@@ -1981,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 = ⪙
|
||||
}
|
||||
else
|
||||
@@ -1993,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;
|
||||
@@ -2062,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
|
||||
|
||||
@@ -2221,7 +2145,7 @@ class binary_writer
|
||||
{
|
||||
return 'L';
|
||||
}
|
||||
if (use_bjdata && std::is_unsigned<NumberType>::value && integer_fits_max<std::uint64_t>(n))
|
||||
if (use_bjdata && std::is_unsigned<NumberType>::value)
|
||||
{
|
||||
return 'M';
|
||||
}
|
||||
@@ -2346,16 +2270,14 @@ class binary_writer
|
||||
@brief checks whether a JSON number fits into @a TargetType
|
||||
@param[in] el a JSON number of either the signed or unsigned integer kind
|
||||
@return whether @a el's value can be represented by @a TargetType without
|
||||
wrapping, regardless of which of the two kinds it is stored as;
|
||||
false for a value that does not even fit the 64-bit type it is
|
||||
read as (possible for number types wider than 64 bits)
|
||||
wrapping, regardless of which of the two kinds it is stored as
|
||||
*/
|
||||
template<typename TargetType>
|
||||
static bool bjdata_ndarray_value_in_range(const BasicJsonType& el)
|
||||
{
|
||||
return el.is_number_unsigned()
|
||||
? integer_fits_max<std::uint64_t>(el.m_data.m_value.number_unsigned) && value_in_range_of<TargetType>(el.template get<std::uint64_t>())
|
||||
: integer_fits<std::int64_t>(el.m_data.m_value.number_integer) && value_in_range_of<TargetType>(el.template get<std::int64_t>());
|
||||
? value_in_range_of<TargetType>(el.template get<std::uint64_t>())
|
||||
: value_in_range_of<TargetType>(el.template get<std::int64_t>());
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -2587,11 +2509,10 @@ class binary_writer
|
||||
for (const auto& el : dims)
|
||||
{
|
||||
// a dimension is read as an unsigned value below, so anything that
|
||||
// is not a non-negative integer in the range of std::uint64_t is
|
||||
// rejected: a non-integer entry would pun unrelated bytes as the
|
||||
// dimension, and a negative or wider one would wrap into a
|
||||
// nonsensical length
|
||||
if (!el.is_number_integer() || !bjdata_ndarray_value_in_range<std::uint64_t>(el))
|
||||
// is not a non-negative integer is rejected: a non-integer entry
|
||||
// would pun unrelated bytes as the dimension, and a negative one
|
||||
// would wrap into a nonsensical length
|
||||
if (!el.is_number_integer() || (!el.is_number_unsigned() && el.template get<std::int64_t>() < 0))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -2705,9 +2626,9 @@ class binary_writer
|
||||
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::int64_t>(j.m_data.m_value.number_unsigned)))
|
||||
if (j.m_data.m_value.number_unsigned > static_cast<typename BasicJsonType::number_unsigned_t>((std::numeric_limits<std::int64_t>::max)()))
|
||||
{
|
||||
throw_integer_out_of_range(j, "BON8", "int64");
|
||||
JSON_THROW(out_of_range::create(407, concat("integer number ", std::to_string(j.m_data.m_value.number_unsigned), " cannot be represented by BON8 as it does not fit int64"), &j));
|
||||
}
|
||||
write_bon8_integer(static_cast<std::int64_t>(j.m_data.m_value.number_unsigned));
|
||||
string_open = false;
|
||||
@@ -2716,10 +2637,6 @@ class binary_writer
|
||||
|
||||
case value_t::number_integer:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits<std::int64_t>(j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "BON8", "int64");
|
||||
}
|
||||
write_bon8_integer(static_cast<std::int64_t>(j.m_data.m_value.number_integer));
|
||||
string_open = false;
|
||||
break;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <cstdint> // uint8_t
|
||||
#include <limits> // numeric_limits
|
||||
#include <string> // string
|
||||
#include <type_traits> // is_signed
|
||||
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
@@ -146,16 +147,13 @@ FloatType compare_integer_with_float(const IntegerType i, const FloatType f) noe
|
||||
|
||||
// values of IntegerType lie in [-bound, bound) when signed and in
|
||||
// [0, bound) when unsigned; digits excludes the sign bit, so bound is a
|
||||
// power of two that the float represents exactly; the signedness comes
|
||||
// from numeric_limits as well, because std::is_signed is false for class
|
||||
// types such as 128-bit or multiprecision integers
|
||||
using limits = std::numeric_limits<IntegerType>;
|
||||
const FloatType bound = std::ldexp(static_cast<FloatType>(1), limits::digits);
|
||||
// power of two that the float represents exactly
|
||||
const FloatType bound = std::ldexp(static_cast<FloatType>(1), std::numeric_limits<IntegerType>::digits);
|
||||
if (f >= bound)
|
||||
{
|
||||
return ordered(-1);
|
||||
}
|
||||
if (limits::is_signed ? (f < -bound) : (f < static_cast<FloatType>(0)))
|
||||
if (std::is_signed<IntegerType>::value ? (f < -bound) : (f < static_cast<FloatType>(0)))
|
||||
{
|
||||
return ordered(1);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
+118
-164
@@ -284,6 +284,7 @@
|
||||
#include <cstdint> // uint8_t
|
||||
#include <limits> // numeric_limits
|
||||
#include <string> // string
|
||||
#include <type_traits> // is_signed
|
||||
|
||||
// #include <nlohmann/detail/macro_scope.hpp>
|
||||
// __ _____ _____ _____
|
||||
@@ -3362,16 +3363,13 @@ FloatType compare_integer_with_float(const IntegerType i, const FloatType f) noe
|
||||
|
||||
// values of IntegerType lie in [-bound, bound) when signed and in
|
||||
// [0, bound) when unsigned; digits excludes the sign bit, so bound is a
|
||||
// power of two that the float represents exactly; the signedness comes
|
||||
// from numeric_limits as well, because std::is_signed is false for class
|
||||
// types such as 128-bit or multiprecision integers
|
||||
using limits = std::numeric_limits<IntegerType>;
|
||||
const FloatType bound = std::ldexp(static_cast<FloatType>(1), limits::digits);
|
||||
// power of two that the float represents exactly
|
||||
const FloatType bound = std::ldexp(static_cast<FloatType>(1), std::numeric_limits<IntegerType>::digits);
|
||||
if (f >= bound)
|
||||
{
|
||||
return ordered(-1);
|
||||
}
|
||||
if (limits::is_signed ? (f < -bound) : (f < static_cast<FloatType>(0)))
|
||||
if (std::is_signed<IntegerType>::value ? (f < -bound) : (f < static_cast<FloatType>(0)))
|
||||
{
|
||||
return ordered(1);
|
||||
}
|
||||
@@ -4173,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
|
||||
//
|
||||
@@ -21826,10 +21828,6 @@ class binary_writer
|
||||
{
|
||||
if (j.m_data.m_value.number_integer >= 0)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "CBOR", "[-2^64, 2^64-1]");
|
||||
}
|
||||
// CBOR does not differentiate between positive signed
|
||||
// integers and unsigned integers
|
||||
write_cbor_head(0x00, static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
|
||||
@@ -21837,10 +21835,6 @@ class binary_writer
|
||||
else
|
||||
{
|
||||
// a negative integer n is encoded as -1 - n
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(-1 - j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "CBOR", "[-2^64, 2^64-1]");
|
||||
}
|
||||
write_cbor_head(0x20, static_cast<std::uint64_t>(-1 - j.m_data.m_value.number_integer));
|
||||
}
|
||||
break;
|
||||
@@ -21848,11 +21842,7 @@ class binary_writer
|
||||
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_unsigned)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "CBOR", "uint64");
|
||||
}
|
||||
write_cbor_head(0x00, static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
write_cbor_head(0x00, j.m_data.m_value.number_unsigned);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -21944,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());
|
||||
|
||||
@@ -21957,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;
|
||||
@@ -22060,20 +22044,12 @@ class binary_writer
|
||||
{
|
||||
if (j.m_data.m_value.number_integer >= 0)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "MessagePack", "[-2^63, 2^64-1]");
|
||||
}
|
||||
// MessagePack does not differentiate between positive
|
||||
// signed integers and unsigned integers.
|
||||
write_msgpack_unsigned(static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_min<std::int64_t>(j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "MessagePack", "[-2^63, 2^64-1]");
|
||||
}
|
||||
if (j.m_data.m_value.number_integer >= -32)
|
||||
{
|
||||
// negative fixnum
|
||||
@@ -22112,10 +22088,6 @@ class binary_writer
|
||||
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_unsigned)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "MessagePack", "uint64");
|
||||
}
|
||||
write_msgpack_unsigned(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
break;
|
||||
}
|
||||
@@ -22234,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);
|
||||
|
||||
@@ -22247,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;
|
||||
@@ -22477,84 +22443,6 @@ class binary_writer
|
||||
JSON_THROW(type_error::create(321, concat("cannot serialize discarded value to ", format_name), &j));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief whether integer @a n does not exceed the maximum of @a TargetType
|
||||
|
||||
The binary formats store integers in at most 64 bits, but number_integer_t
|
||||
and number_unsigned_t may be wider (e.g., __int128). The range of the
|
||||
integer is taken from std::numeric_limits, so a number type whose range
|
||||
fits into @a TargetType is never checked: the function is then constant
|
||||
true, and the default int64_t/uint64_t types pay nothing.
|
||||
*/
|
||||
template<typename TargetType, typename NumberType>
|
||||
static constexpr bool integer_fits_max(const NumberType n) noexcept
|
||||
{
|
||||
return integer_fits_max<TargetType>(n, std::integral_constant < bool,
|
||||
(std::numeric_limits<NumberType>::digits > std::numeric_limits<TargetType>::digits) > ());
|
||||
}
|
||||
|
||||
template<typename TargetType, typename NumberType>
|
||||
static constexpr bool integer_fits_max(const NumberType /*unused*/, std::false_type /*may_exceed*/) noexcept
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename TargetType, typename NumberType>
|
||||
static constexpr bool integer_fits_max(const NumberType n, std::true_type /*may_exceed*/) noexcept
|
||||
{
|
||||
// NumberType has more digits than TargetType, so it can hold its maximum
|
||||
return n <= static_cast<NumberType>((std::numeric_limits<TargetType>::max)());
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief whether integer @a n is not below the minimum of @a TargetType;
|
||||
constant true if NumberType cannot hold such a value
|
||||
*/
|
||||
template<typename TargetType, typename NumberType>
|
||||
static constexpr bool integer_fits_min(const NumberType n) noexcept
|
||||
{
|
||||
return integer_fits_min<TargetType>(n, std::integral_constant < bool, std::numeric_limits<NumberType>::is_signed &&
|
||||
(!std::numeric_limits<TargetType>::is_signed || std::numeric_limits<NumberType>::digits > std::numeric_limits<TargetType>::digits) > ());
|
||||
}
|
||||
|
||||
template<typename TargetType, typename NumberType>
|
||||
static constexpr bool integer_fits_min(const NumberType /*unused*/, std::false_type /*may_fall_below*/) noexcept
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename TargetType, typename NumberType>
|
||||
static constexpr bool integer_fits_min(const NumberType n, std::true_type /*may_fall_below*/) noexcept
|
||||
{
|
||||
// NumberType is signed and either TargetType is unsigned (minimum 0)
|
||||
// or NumberType has more digits, so it can hold the minimum
|
||||
return n >= static_cast<NumberType>((std::numeric_limits<TargetType>::min)());
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief whether integer @a n lies in the range of @a TargetType
|
||||
*/
|
||||
template<typename TargetType, typename NumberType>
|
||||
static constexpr bool integer_fits(const NumberType n) noexcept
|
||||
{
|
||||
return integer_fits_min<TargetType>(n) && integer_fits_max<TargetType>(n);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief throws because the integer @a j is too large for @a format_name
|
||||
@throw out_of_range.407 always
|
||||
*/
|
||||
JSON_HEDLEY_NO_RETURN static void throw_integer_out_of_range(const BasicJsonType& j, const char* format_name, const char* range)
|
||||
{
|
||||
// dump() rather than std::to_string(), which has no overload for
|
||||
// integer types wider than 64 bits
|
||||
const auto number = j.dump();
|
||||
static_cast<void>(number); // unused when JSON_NOEXCEPTION is defined
|
||||
static_cast<void>(format_name);
|
||||
static_cast<void>(range);
|
||||
JSON_THROW(out_of_range::create(407, concat("integer number ", std::string(number.begin(), number.end()), " cannot be represented by ", format_name, " as it does not fit ", range), &j));
|
||||
}
|
||||
|
||||
void write_msgpack_array_prefix(const std::size_t N, const BasicJsonType& j)
|
||||
{
|
||||
const auto n = to_msgpack_length(N, j);
|
||||
@@ -22713,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);
|
||||
@@ -22790,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);
|
||||
@@ -23311,8 +23199,6 @@ class binary_writer
|
||||
is neither an object nor an array
|
||||
@throw out_of_range.415 if @a j is binary with a subtype that does not fit
|
||||
into a byte, before anything is written
|
||||
@throw out_of_range.407 if @a j is an integer that does not fit the 64 bits
|
||||
of a BSON integer, before anything is written
|
||||
@throw type_error.316 if @a j is a string that is not valid UTF-8, before
|
||||
anything is written
|
||||
@throw type_error.321 if @a j is discarded
|
||||
@@ -23331,18 +23217,10 @@ class binary_writer
|
||||
return 8ul;
|
||||
|
||||
case value_t::number_integer:
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits<std::int64_t>(j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "BSON", "int64");
|
||||
}
|
||||
return calc_bson_integer_size(static_cast<std::int64_t>(j.m_data.m_value.number_integer));
|
||||
return calc_bson_integer_size(j.m_data.m_value.number_integer);
|
||||
|
||||
case value_t::number_unsigned:
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_unsigned)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "BSON", "uint64");
|
||||
}
|
||||
return calc_bson_unsigned_size(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
return calc_bson_unsigned_size(j.m_data.m_value.number_unsigned);
|
||||
|
||||
case value_t::string:
|
||||
return calc_bson_string_size(*j.m_data.m_value.string, j);
|
||||
@@ -23380,12 +23258,11 @@ class binary_writer
|
||||
case value_t::number_float:
|
||||
return write_bson_double(name, j.m_data.m_value.number_float);
|
||||
|
||||
// calc_bson_value_size() checked that integers fit 64 bits
|
||||
case value_t::number_integer:
|
||||
return write_bson_integer(name, static_cast<std::int64_t>(j.m_data.m_value.number_integer));
|
||||
return write_bson_integer(name, j.m_data.m_value.number_integer);
|
||||
|
||||
case value_t::number_unsigned:
|
||||
return write_bson_unsigned(name, static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
return write_bson_unsigned(name, j.m_data.m_value.number_unsigned);
|
||||
|
||||
case value_t::string:
|
||||
return write_bson_string(name, *j.m_data.m_value.string);
|
||||
@@ -23406,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
|
||||
{
|
||||
@@ -23570,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())
|
||||
{
|
||||
@@ -23581,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
|
||||
@@ -23600,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 = ⪙
|
||||
}
|
||||
else
|
||||
@@ -23612,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;
|
||||
@@ -23681,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
|
||||
|
||||
@@ -23840,7 +23766,7 @@ class binary_writer
|
||||
{
|
||||
return 'L';
|
||||
}
|
||||
if (use_bjdata && std::is_unsigned<NumberType>::value && integer_fits_max<std::uint64_t>(n))
|
||||
if (use_bjdata && std::is_unsigned<NumberType>::value)
|
||||
{
|
||||
return 'M';
|
||||
}
|
||||
@@ -23965,16 +23891,14 @@ class binary_writer
|
||||
@brief checks whether a JSON number fits into @a TargetType
|
||||
@param[in] el a JSON number of either the signed or unsigned integer kind
|
||||
@return whether @a el's value can be represented by @a TargetType without
|
||||
wrapping, regardless of which of the two kinds it is stored as;
|
||||
false for a value that does not even fit the 64-bit type it is
|
||||
read as (possible for number types wider than 64 bits)
|
||||
wrapping, regardless of which of the two kinds it is stored as
|
||||
*/
|
||||
template<typename TargetType>
|
||||
static bool bjdata_ndarray_value_in_range(const BasicJsonType& el)
|
||||
{
|
||||
return el.is_number_unsigned()
|
||||
? integer_fits_max<std::uint64_t>(el.m_data.m_value.number_unsigned) && value_in_range_of<TargetType>(el.template get<std::uint64_t>())
|
||||
: integer_fits<std::int64_t>(el.m_data.m_value.number_integer) && value_in_range_of<TargetType>(el.template get<std::int64_t>());
|
||||
? value_in_range_of<TargetType>(el.template get<std::uint64_t>())
|
||||
: value_in_range_of<TargetType>(el.template get<std::int64_t>());
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -24206,11 +24130,10 @@ class binary_writer
|
||||
for (const auto& el : dims)
|
||||
{
|
||||
// a dimension is read as an unsigned value below, so anything that
|
||||
// is not a non-negative integer in the range of std::uint64_t is
|
||||
// rejected: a non-integer entry would pun unrelated bytes as the
|
||||
// dimension, and a negative or wider one would wrap into a
|
||||
// nonsensical length
|
||||
if (!el.is_number_integer() || !bjdata_ndarray_value_in_range<std::uint64_t>(el))
|
||||
// is not a non-negative integer is rejected: a non-integer entry
|
||||
// would pun unrelated bytes as the dimension, and a negative one
|
||||
// would wrap into a nonsensical length
|
||||
if (!el.is_number_integer() || (!el.is_number_unsigned() && el.template get<std::int64_t>() < 0))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -24324,9 +24247,9 @@ class binary_writer
|
||||
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::int64_t>(j.m_data.m_value.number_unsigned)))
|
||||
if (j.m_data.m_value.number_unsigned > static_cast<typename BasicJsonType::number_unsigned_t>((std::numeric_limits<std::int64_t>::max)()))
|
||||
{
|
||||
throw_integer_out_of_range(j, "BON8", "int64");
|
||||
JSON_THROW(out_of_range::create(407, concat("integer number ", std::to_string(j.m_data.m_value.number_unsigned), " cannot be represented by BON8 as it does not fit int64"), &j));
|
||||
}
|
||||
write_bon8_integer(static_cast<std::int64_t>(j.m_data.m_value.number_unsigned));
|
||||
string_open = false;
|
||||
@@ -24335,10 +24258,6 @@ class binary_writer
|
||||
|
||||
case value_t::number_integer:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits<std::int64_t>(j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "BON8", "int64");
|
||||
}
|
||||
write_bon8_integer(static_cast<std::int64_t>(j.m_data.m_value.number_integer));
|
||||
string_open = false;
|
||||
break;
|
||||
@@ -29413,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,
|
||||
@@ -29445,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;
|
||||
@@ -31322,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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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>();
|
||||
}
|
||||
@@ -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>();
|
||||
}
|
||||
@@ -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>();
|
||||
}
|
||||
@@ -1,350 +0,0 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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 <cstdint>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
#include <compare>
|
||||
#endif
|
||||
|
||||
namespace custom_number_types
|
||||
{
|
||||
|
||||
// a signed integer of class type: std::is_signed is only true for arithmetic
|
||||
// types, so the library has to take the signedness from std::numeric_limits,
|
||||
// as it does for 128-bit and multiprecision integer classes such as
|
||||
// absl::int128 or boost::multiprecision::cpp_int
|
||||
class class_int
|
||||
{
|
||||
public:
|
||||
// trivial, like the 128-bit integer classes: the value is stored in a union
|
||||
class_int() = default;
|
||||
|
||||
// implicit from built-in integers and explicit from floats, like absl::int128
|
||||
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
|
||||
class_int(T v) : value(static_cast<std::int64_t>(v)) {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
|
||||
|
||||
template<typename T, typename std::enable_if<std::is_floating_point<T>::value, int>::type = 0>
|
||||
explicit class_int(T v) : value(static_cast<std::int64_t>(v)) {}
|
||||
|
||||
template<typename T, typename std::enable_if<std::is_arithmetic<T>::value, int>::type = 0>
|
||||
explicit operator T() const
|
||||
{
|
||||
return static_cast<T>(value);
|
||||
}
|
||||
|
||||
friend bool operator==(class_int lhs, class_int rhs)
|
||||
{
|
||||
return lhs.value == rhs.value;
|
||||
}
|
||||
friend bool operator!=(class_int lhs, class_int rhs)
|
||||
{
|
||||
return lhs.value != rhs.value;
|
||||
}
|
||||
friend bool operator<(class_int lhs, class_int rhs)
|
||||
{
|
||||
return lhs.value < rhs.value;
|
||||
}
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
friend std::strong_ordering operator<=>(class_int lhs, class_int rhs) // *NOPAD*
|
||||
{
|
||||
return lhs.value <=> rhs.value; // *NOPAD*
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
std::int64_t value;
|
||||
};
|
||||
|
||||
} // namespace custom_number_types
|
||||
|
||||
using custom_number_types::class_int;
|
||||
|
||||
namespace std
|
||||
{
|
||||
// only the members the library uses
|
||||
template<>
|
||||
class numeric_limits<class_int>
|
||||
{
|
||||
public:
|
||||
static constexpr bool is_signed = true;
|
||||
static constexpr int digits = std::numeric_limits<std::int64_t>::digits;
|
||||
};
|
||||
} // namespace std
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using class_int_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, class_int, std::uint64_t, double>;
|
||||
|
||||
class_int_json make_class_int(std::int64_t v)
|
||||
{
|
||||
class_int_json j(class_int_json::value_t::number_integer);
|
||||
j.get_ref<class_int&>() = class_int(v);
|
||||
return j;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// the serializer cannot print an integer of class type, so doctest must not
|
||||
// try when an assertion fails
|
||||
namespace doctest
|
||||
{
|
||||
template<>
|
||||
struct StringMaker<class_int_json>
|
||||
{
|
||||
static String convert(const class_int_json& j)
|
||||
{
|
||||
return j.type_name();
|
||||
}
|
||||
};
|
||||
} // namespace doctest
|
||||
|
||||
// __int128 as number type needs the std::numeric_limits (for the range checks)
|
||||
// and std::is_integral (for the serializer) specializations, which libc++
|
||||
// always provides and libstdc++ only outside strict ISO modes; the MSVC
|
||||
// standard library has none
|
||||
#if defined(__SIZEOF_INT128__) && (defined(_LIBCPP_VERSION) || defined(__GLIBCXX_TYPE_INT_N_0))
|
||||
#define JSON_TEST_INT128_NUMBER_TYPES 1
|
||||
#else
|
||||
#define JSON_TEST_INT128_NUMBER_TYPES 0
|
||||
#endif
|
||||
|
||||
#if JSON_TEST_INT128_NUMBER_TYPES
|
||||
namespace
|
||||
{
|
||||
|
||||
// __extension__ keeps -Wpedantic from flagging the non-standard type
|
||||
__extension__ typedef __int128 int128; // NOLINT(modernize-use-using)
|
||||
__extension__ typedef unsigned __int128 uint128; // NOLINT(modernize-use-using)
|
||||
|
||||
static_assert(std::numeric_limits<int128>::digits == 127 && std::numeric_limits<uint128>::digits == 128 &&
|
||||
std::is_integral<int128>::value && std::is_integral<uint128>::value,
|
||||
"__int128 is not fully supported by the standard library");
|
||||
|
||||
using wide_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, int128, uint128, double>;
|
||||
|
||||
wide_json make_int(int128 v)
|
||||
{
|
||||
wide_json j(wide_json::value_t::number_integer);
|
||||
j.get_ref<int128&>() = v;
|
||||
return j;
|
||||
}
|
||||
|
||||
wide_json make_uint(uint128 v)
|
||||
{
|
||||
wide_json j(wide_json::value_t::number_unsigned);
|
||||
j.get_ref<uint128&>() = v;
|
||||
return j;
|
||||
}
|
||||
|
||||
wide_json wrap(const wide_json& value)
|
||||
{
|
||||
wide_json o(wide_json::value_t::object);
|
||||
o["v"] = value;
|
||||
return o;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("custom number types: integers beyond 64 bits")
|
||||
{
|
||||
using out_of_range = wide_json::out_of_range;
|
||||
|
||||
const int128 two_64 = static_cast<int128>(1) << 64;
|
||||
const int128 two_100 = static_cast<int128>(1) << 100;
|
||||
const int128 max_int64 = (std::numeric_limits<std::int64_t>::max)();
|
||||
const int128 min_int64 = (std::numeric_limits<std::int64_t>::min)();
|
||||
const uint128 max_uint64 = (std::numeric_limits<std::uint64_t>::max)();
|
||||
|
||||
// before the range checks, these values were silently truncated, e.g.,
|
||||
// 2^100 was written as 0
|
||||
const wide_json int_big = make_int(two_100);
|
||||
const wide_json int_big_negative = make_int(-two_100);
|
||||
const wide_json uint_big = make_uint(static_cast<uint128>(two_100));
|
||||
std::vector<std::uint8_t> _;
|
||||
|
||||
SECTION("CBOR")
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(_ = wide_json::to_cbor(int_big), "[json.exception.out_of_range.407] integer number 1267650600228229401496703205376 cannot be represented by CBOR as it does not fit [-2^64, 2^64-1]", out_of_range&);
|
||||
CHECK_THROWS_WITH_AS(_ = wide_json::to_cbor(int_big_negative), "[json.exception.out_of_range.407] integer number -1267650600228229401496703205376 cannot be represented by CBOR as it does not fit [-2^64, 2^64-1]", out_of_range&);
|
||||
CHECK_THROWS_WITH_AS(_ = wide_json::to_cbor(uint_big), "[json.exception.out_of_range.407] integer number 1267650600228229401496703205376 cannot be represented by CBOR as it does not fit uint64", out_of_range&);
|
||||
CHECK_THROWS_AS(_ = wide_json::to_cbor(make_int(two_64)), out_of_range&);
|
||||
CHECK_THROWS_AS(_ = wide_json::to_cbor(make_int(-two_64 - 1)), out_of_range&);
|
||||
CHECK_THROWS_AS(_ = wide_json::to_cbor(make_uint(static_cast<uint128>(max_uint64) + 1)), out_of_range&);
|
||||
|
||||
// CBOR's integers cover [-2^64, 2^64-1]
|
||||
CHECK(wide_json::to_cbor(make_int(two_64 - 1)) == std::vector<std::uint8_t>({0x1B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}));
|
||||
CHECK(wide_json::to_cbor(make_int(-two_64)) == std::vector<std::uint8_t>({0x3B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}));
|
||||
CHECK(wide_json::to_cbor(make_uint(max_uint64)) == std::vector<std::uint8_t>({0x1B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}));
|
||||
}
|
||||
|
||||
SECTION("MessagePack")
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(_ = wide_json::to_msgpack(int_big), "[json.exception.out_of_range.407] integer number 1267650600228229401496703205376 cannot be represented by MessagePack as it does not fit [-2^63, 2^64-1]", out_of_range&);
|
||||
CHECK_THROWS_WITH_AS(_ = wide_json::to_msgpack(int_big_negative), "[json.exception.out_of_range.407] integer number -1267650600228229401496703205376 cannot be represented by MessagePack as it does not fit [-2^63, 2^64-1]", out_of_range&);
|
||||
CHECK_THROWS_WITH_AS(_ = wide_json::to_msgpack(uint_big), "[json.exception.out_of_range.407] integer number 1267650600228229401496703205376 cannot be represented by MessagePack as it does not fit uint64", out_of_range&);
|
||||
CHECK_THROWS_AS(_ = wide_json::to_msgpack(make_int(two_64)), out_of_range&);
|
||||
CHECK_THROWS_AS(_ = wide_json::to_msgpack(make_int(min_int64 - 1)), out_of_range&);
|
||||
|
||||
// MessagePack's integers cover [-2^63, 2^64-1]
|
||||
CHECK(wide_json::to_msgpack(make_int(two_64 - 1)) == std::vector<std::uint8_t>({0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}));
|
||||
CHECK(wide_json::to_msgpack(make_int(min_int64)) == std::vector<std::uint8_t>({0xD3, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}));
|
||||
CHECK(wide_json::to_msgpack(make_uint(max_uint64)) == std::vector<std::uint8_t>({0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}));
|
||||
}
|
||||
|
||||
SECTION("BSON")
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(_ = wide_json::to_bson(wrap(int_big)), "[json.exception.out_of_range.407] integer number 1267650600228229401496703205376 cannot be represented by BSON as it does not fit int64", out_of_range&);
|
||||
CHECK_THROWS_WITH_AS(_ = wide_json::to_bson(wrap(int_big_negative)), "[json.exception.out_of_range.407] integer number -1267650600228229401496703205376 cannot be represented by BSON as it does not fit int64", out_of_range&);
|
||||
CHECK_THROWS_WITH_AS(_ = wide_json::to_bson(wrap(uint_big)), "[json.exception.out_of_range.407] integer number 1267650600228229401496703205376 cannot be represented by BSON as it does not fit uint64", out_of_range&);
|
||||
CHECK_THROWS_AS(_ = wide_json::to_bson(wrap(make_int(max_int64 + 1))), out_of_range&);
|
||||
CHECK_THROWS_AS(_ = wide_json::to_bson(wrap(make_int(min_int64 - 1))), out_of_range&);
|
||||
|
||||
// nested values are checked as well, before anything is written
|
||||
wide_json nested(wide_json::value_t::object);
|
||||
nested["a"] = wide_json::array({wrap(int_big)});
|
||||
std::vector<std::uint8_t> out;
|
||||
CHECK_THROWS_AS(wide_json::to_bson(nested, out), out_of_range&);
|
||||
CHECK(out.empty());
|
||||
|
||||
CHECK(wide_json::from_bson(wide_json::to_bson(wrap(make_int(max_int64)))) == wrap(make_int(max_int64)));
|
||||
CHECK(wide_json::from_bson(wide_json::to_bson(wrap(make_int(min_int64)))) == wrap(make_int(min_int64)));
|
||||
CHECK_NOTHROW(wide_json::to_bson(wrap(make_uint(max_uint64))));
|
||||
}
|
||||
|
||||
SECTION("BON8")
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(_ = wide_json::to_bon8(int_big), "[json.exception.out_of_range.407] integer number 1267650600228229401496703205376 cannot be represented by BON8 as it does not fit int64", out_of_range&);
|
||||
CHECK_THROWS_WITH_AS(_ = wide_json::to_bon8(int_big_negative), "[json.exception.out_of_range.407] integer number -1267650600228229401496703205376 cannot be represented by BON8 as it does not fit int64", out_of_range&);
|
||||
CHECK_THROWS_WITH_AS(_ = wide_json::to_bon8(uint_big), "[json.exception.out_of_range.407] integer number 1267650600228229401496703205376 cannot be represented by BON8 as it does not fit int64", out_of_range&);
|
||||
CHECK_THROWS_AS(_ = wide_json::to_bon8(make_int(max_int64 + 1)), out_of_range&);
|
||||
CHECK_THROWS_AS(_ = wide_json::to_bon8(make_int(min_int64 - 1)), out_of_range&);
|
||||
|
||||
CHECK(wide_json::from_bon8(wide_json::to_bon8(make_int(max_int64))) == make_int(max_int64));
|
||||
CHECK(wide_json::from_bon8(wide_json::to_bon8(make_int(min_int64))) == make_int(min_int64));
|
||||
}
|
||||
|
||||
SECTION("UBJSON and BJData")
|
||||
{
|
||||
// integers beyond 64 bits are written exactly as high-precision numbers
|
||||
const std::string digits = "1267650600228229401496703205376";
|
||||
std::vector<std::uint8_t> expected = {'H', 'i', static_cast<std::uint8_t>(digits.size())};
|
||||
expected.insert(expected.end(), digits.begin(), digits.end());
|
||||
CHECK(wide_json::to_ubjson(int_big) == expected);
|
||||
CHECK(wide_json::to_ubjson(uint_big) == expected);
|
||||
CHECK(wide_json::to_bjdata(int_big) == expected);
|
||||
// BJData's uint64 marker 'M' was used for any unsigned value beyond
|
||||
// int64, which truncated the ones beyond 64 bits
|
||||
CHECK(wide_json::to_bjdata(uint_big) == expected);
|
||||
|
||||
// the uint64 marker is still used where the value fits
|
||||
CHECK(wide_json::to_bjdata(make_uint(max_uint64)) == std::vector<std::uint8_t>({'M', 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}));
|
||||
|
||||
// the elements of an optimized container are written the same way;
|
||||
// BJData does not allow 'H' as the type of an optimized container
|
||||
std::vector<std::uint8_t> expected_ubjson_array = {'[', '$', 'H', '#', 'i', 2};
|
||||
std::vector<std::uint8_t> expected_bjdata_array = {'[', '#', 'i', 2};
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
expected_ubjson_array.insert(expected_ubjson_array.end(), expected.begin() + 1, expected.end());
|
||||
expected_bjdata_array.insert(expected_bjdata_array.end(), expected.begin(), expected.end());
|
||||
}
|
||||
CHECK(wide_json::to_ubjson(wide_json::array({uint_big, uint_big}), true, true) == expected_ubjson_array);
|
||||
CHECK(wide_json::to_bjdata(wide_json::array({uint_big, uint_big}), true, true) == expected_bjdata_array);
|
||||
}
|
||||
|
||||
SECTION("BJData ND-array")
|
||||
{
|
||||
// an ND-array element beyond 64 bits does not fit any dtype, so the
|
||||
// annotated object is written as a plain object instead of truncating
|
||||
// the element into range
|
||||
wide_json element(wide_json::value_t::object);
|
||||
element["_ArrayType_"] = "uint8";
|
||||
element["_ArraySize_"] = wide_json::array({make_uint(2), make_uint(1)});
|
||||
element["_ArrayData_"] = wide_json::array({make_uint(1), make_uint(static_cast<uint128>(two_64) + 1)});
|
||||
const auto element_bytes = wide_json::to_bjdata(element, true, true);
|
||||
REQUIRE(!element_bytes.empty());
|
||||
CHECK(element_bytes[0] == '{');
|
||||
|
||||
wide_json signed_element = element;
|
||||
signed_element["_ArrayType_"] = "int8";
|
||||
signed_element["_ArrayData_"] = wide_json::array({make_int(1), make_int(-two_64 + 1)});
|
||||
const auto signed_element_bytes = wide_json::to_bjdata(signed_element, true, true);
|
||||
REQUIRE(!signed_element_bytes.empty());
|
||||
CHECK(signed_element_bytes[0] == '{');
|
||||
|
||||
// the same for a dimension beyond 64 bits, which wrapped into a
|
||||
// dimension matching the size of _ArrayData_ (here: 1)
|
||||
wide_json dimension = element;
|
||||
dimension["_ArraySize_"] = wide_json::array({make_uint(2), make_uint(static_cast<uint128>(two_64) + 1)});
|
||||
dimension["_ArrayData_"] = wide_json::array({make_uint(1), make_uint(2)});
|
||||
const auto dimension_bytes = wide_json::to_bjdata(dimension, true, true);
|
||||
REQUIRE(!dimension_bytes.empty());
|
||||
CHECK(dimension_bytes[0] == '{');
|
||||
|
||||
// in range, the ND-array is still written
|
||||
wide_json fits = element;
|
||||
fits["_ArrayData_"] = wide_json::array({make_uint(1), make_uint(2)});
|
||||
const auto fits_bytes = wide_json::to_bjdata(fits, true, true);
|
||||
REQUIRE(!fits_bytes.empty());
|
||||
CHECK(fits_bytes[0] == '[');
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
TEST_CASE("custom number types")
|
||||
{
|
||||
SECTION("signed integer of class type compared with a float")
|
||||
{
|
||||
// std::is_signed is false for a class type, which made the comparison
|
||||
// treat any float below zero as less than every integer
|
||||
const class_int_json minus_five = make_class_int(-5);
|
||||
const class_int_json minus_two = make_class_int(-2);
|
||||
const class_int_json five = make_class_int(5);
|
||||
const class_int_json minus_two_and_a_half = -2.5;
|
||||
const class_int_json two_and_a_half = 2.5;
|
||||
const class_int_json huge_negative = -1e30;
|
||||
const class_int_json huge_positive = 1e30;
|
||||
|
||||
CHECK(minus_five < minus_two_and_a_half);
|
||||
CHECK_FALSE(minus_two_and_a_half < minus_five);
|
||||
CHECK(minus_two_and_a_half > minus_five);
|
||||
CHECK(minus_five <= minus_two_and_a_half);
|
||||
CHECK(minus_two_and_a_half >= minus_five);
|
||||
CHECK(minus_five != minus_two_and_a_half);
|
||||
|
||||
CHECK(minus_two_and_a_half < minus_two);
|
||||
CHECK_FALSE(minus_two < minus_two_and_a_half);
|
||||
|
||||
CHECK(two_and_a_half < five);
|
||||
CHECK(minus_five < two_and_a_half);
|
||||
|
||||
// floats beyond the integer's range
|
||||
CHECK(huge_negative < minus_five);
|
||||
CHECK_FALSE(minus_five < huge_negative);
|
||||
CHECK(five < huge_positive);
|
||||
CHECK_FALSE(huge_positive < five);
|
||||
|
||||
// equality
|
||||
const class_int_json minus_two_float = -2.0;
|
||||
CHECK(minus_two == minus_two_float);
|
||||
CHECK(minus_two_float == minus_two);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user