diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index ea57e6505..66420e9ac 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -35,7 +35,7 @@ There are currently two files which need to be edited: To run [`re2c`](http://re2c.org) and generate/overwrite file `src/json.hpp` with your changes in file `src/json.hpp.re2c`. -2. [`test/unit.cpp`](https://github.com/nlohmann/json/blob/master/test/unit.cpp) - This contains the [Catch](https://github.com/philsquared/Catch) unit tests which currently cover [100 %](https://coveralls.io/github/nlohmann/json) of the library's code. +2. [`test/src/unit.cpp`](https://github.com/nlohmann/json/blob/master/test/unit.cpp) - This contains the [Catch](https://github.com/philsquared/Catch) unit tests which currently cover [100 %](https://coveralls.io/github/nlohmann/json) of the library's code. If you add or change a feature, please also add a unit test to this file. The unit tests can be compiled with @@ -70,9 +70,6 @@ Please understand that I cannot accept pull requests changing only file `src/jso The following areas really need contribution: -- Getting the code to compile without errors with the latest **Microsoft Visual Studio** version. I am not using Windows, so I cannot debug code with MSVC myself. There is a job on [AppVeyor](https://ci.appveyor.com/project/nlohmann/json) though. -- Extending the **continuous integration** beyond Linux running some versions of GCC and Clang on [Travis](https://travis-ci.org/nlohmann/json) and Microsoft Visual Studio on [AppVeyor](https://ci.appveyor.com/project/nlohmann/json). We have found a lot of bugs just because several compilers behave in a slightly different manner. +- Extending the **continuous integration** toward more exotic compilers such as Android NDK, Intel's Compiler, or the bleeding-edge versions of GCC or Clang. - Improving the efficiency of the **JSON parser**. The current parser is implemented as a naive recursive descent parser with hand coded string handling. More sophisticated approaches like LALR parsers would be really appreciated. That said, parser generators like Bison or ANTLR do not play nice with single-header files -- I really would like to keep the parser inside the `json.hpp` header, and I am not aware of approaches similar to [`re2c`](http://re2c.org) for parsing. - Extending and updating existing **benchmarks** to include (the most recent version of) this library. Though efficiency is not everything, speed and memory consumption are very important characteristics for C++ developers, so having proper comparisons would be interesting. -- Check the code with [**Coverity**](https://scan.coverity.com). -- Make the code **locale-independent**. The library currently only works for a locale where `.` is the decimal point character. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 27920581c..49d166595 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -14,7 +14,7 @@ There are currently two files which need to be edited: To run [`re2c`](http://re2c.org) and generate/overwrite file `src/json.hpp` with your changes in file `src/json.hpp.re2c`. -2. [`test/unit.cpp`](https://github.com/nlohmann/json/blob/master/test/unit.cpp) - This contains the [Catch](https://github.com/philsquared/Catch) unit tests which currently cover [100 %](https://coveralls.io/github/nlohmann/json) of the library's code. +2. [`test/src/unit.cpp`](https://github.com/nlohmann/json/blob/master/test/unit.cpp) - This contains the [Catch](https://github.com/philsquared/Catch) unit tests which currently cover [100 %](https://coveralls.io/github/nlohmann/json) of the library's code. If you add or change a feature, please also add a unit test to this file. The unit tests can be compiled with diff --git a/.gitignore b/.gitignore index e1011fc2a..d5bd2f7c9 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ working html me.nlohmann.json.docset + +android +doc/xml diff --git a/.travis.yml b/.travis.yml index 629bec876..b459f5d5a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,12 @@ language: cpp dist: trusty sudo: required +env: + global: + # The next declaration is the encrypted COVERITY_SCAN_TOKEN, created + # via the "travis encrypt" command using the project repo's public key + - secure: "m89SSgE+ASLO38rSKx7MTXK3n5NkP9bIx95jwY71YEiuFzib30PDJ/DifKnXxBjvy/AkCGztErQRk/8ZCvq+4HXozU2knEGnL/RUitvlwbhzfh2D4lmS3BvWBGS3N3NewoPBrRmdcvnT0xjOGXxtZaJ3P74TkB9GBnlz/HmKORA=" + # from http://stackoverflow.com/a/32127147/266378 matrix: include: @@ -19,49 +25,146 @@ matrix: - touch src/json.hpp - make json_unit CXXFLAGS="-fprofile-arcs -ftest-coverage -std=c++11 -lstdc++" CXX=$COMPILER - ./json_unit "*" - - coveralls --exclude test/catch.hpp --exclude test/unit.cpp --include src/json.hpp --gcov-options '\-lp' --gcov 'gcov-4.9' + - coveralls --exclude test/src/catch.hpp --exclude test/src/unit.cpp --include src/json.hpp --gcov-options '\-lp' --gcov 'gcov-4.9' + - bash <(curl -s https://codecov.io/bash) env: COMPILER=g++-4.9 + - os: linux + compiler: gcc + before_install: echo -n | openssl s_client -connect scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca-certificates.crt + addons: + apt: + sources: ['ubuntu-toolchain-r-test'] + packages: ['g++-5', 'valgrind'] + coverity_scan: + project: + name: "nlohmann/json" + description: "Build submitted via Travis CI" + notification_email: niels.lohmann@gmail.com + build_command_prepend: "make clean ; sudo cp $(which g++-5) $(which g++)" + build_command: "make" + branch_pattern: coverity_scan + env: COMPILER=g++-5 + - os: linux compiler: gcc addons: apt: sources: ['ubuntu-toolchain-r-test'] - packages: ['g++-5', 'valgrind'] - env: COMPILER=g++-5 + packages: ['g++-6', 'valgrind'] + env: COMPILER=g++-6 + # from https://github.com/travis-ci/travis-ci/issues/6120 - os: linux - compiler: clang - addons: - apt: - sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.6'] - packages: ['clang-3.6', 'valgrind'] - env: COMPILER=clang++-3.6 + env: + - LLVM_VERSION=3.8.0 + - LLVM_ARCHIVE_PATH=$HOME/clang+llvm.tar.xz + - COMPILER=clang++ + - CPPFLAGS="-I $HOME/clang-$LLVM_VERSION/include/c++/v1" + - CXXFLAGS=-lc++ + - PATH=$HOME/clang-$LLVM_VERSION/bin:$PATH + - LD_LIBRARY_PATH=$HOME/clang-$LLVM_VERSION/lib:$LD_LIBRARY_PATH + before_install: + - wget http://llvm.org/releases/$LLVM_VERSION/clang+llvm-$LLVM_VERSION-x86_64-linux-gnu-ubuntu-14.04.tar.xz -O $LLVM_ARCHIVE_PATH + - mkdir $HOME/clang-$LLVM_VERSION + - tar xf $LLVM_ARCHIVE_PATH -C $HOME/clang-$LLVM_VERSION --strip-components 1 - - os: linux + # Clang 3.5 is not able to compile the code, + # see https://travis-ci.org/nlohmann/json/jobs/126720186 + +# - os: linux +# compiler: clang +# addons: +# apt: +# sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.6'] +# packages: ['clang-3.6', 'valgrind'] +# env: COMPILER=clang++-3.6 +# +# - os: linux +# compiler: clang +# addons: +# apt: +# sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.7'] +# packages: ['clang-3.7', 'valgrind'] +# env: COMPILER=clang++-3.7 +# +# - os: linux +# compiler: clang +# addons: +# apt: +# sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.8'] +# packages: ['clang-3.8', 'valgrind'] +# env: COMPILER=clang++-3.8 + + # - os: linux + # compiler: clang + # addons: + # apt: + # sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-precise'] + # packages: ['clang-3.9', 'valgrind'] + # env: COMPILER=clang++-3.9 + + - os: osx + osx_image: beta-xcode6.1 compiler: clang - addons: - apt: - sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.7'] - packages: ['clang-3.7', 'valgrind'] - env: COMPILER=clang++-3.7 + env: + - COMPILER=clang + - CXXFLAGS=-lstdc++ + + - os: osx + osx_image: beta-xcode6.2 + compiler: clang + env: + - COMPILER=clang + - CXXFLAGS=-lstdc++ + + - os: osx + osx_image: beta-xcode6.3 + compiler: clang + env: + - COMPILER=clang + - CXXFLAGS=-lstdc++ + + - os: osx + osx_image: xcode6.4 + compiler: clang + env: + - COMPILER=clang + - CXXFLAGS=-lstdc++ + + - os: osx + osx_image: xcode7.1 + compiler: clang + env: + - COMPILER=clang + - CXXFLAGS=-lstdc++ - # - os: osx - # compiler: clang - # env: COMPILER=clang - # before_install: - # - brew update - # - brew install valgrind + - os: osx + osx_image: xcode7.2 + compiler: clang + env: + - COMPILER=clang + - CXXFLAGS=-lstdc++ + + - os: osx + osx_image: xcode7.3 + compiler: clang + env: + - COMPILER=clang + - CXXFLAGS=-lstdc++ + + - os: osx + osx_image: xcode8 + compiler: clang + env: + - COMPILER=clang + - CXXFLAGS=-lstdc++ script: - - make CXX=$COMPILER CXXFLAGS="-lstdc++" + - uname -a + - $COMPILER --version + - make CXX=$COMPILER - ./json_unit "*" - - valgrind --error-exitcode=1 --leak-check=full ./json_unit - -notifications: - webhooks: - urls: - - https://webhooks.gitter.im/e/f1196addb0e97a5ff396 - on_success: change - on_failure: always - on_start: never + - if [ `which valgrind` ]; then + valgrind --error-exitcode=1 --leak-check=full ./json_unit ; + fi diff --git a/CMakeLists.txt b/CMakeLists.txt index 16e9f960e..c0488b3b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,25 +1,54 @@ -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 3.0) -project(json CXX) +# define the project +project(nlohmann_json VERSION 2.0.0 LANGUAGES CXX) -add_executable(json_unit - src/json.hpp test/catch.hpp test/unit.cpp -) +enable_testing() -if(MSVC) - set(CMAKE_CXX_FLAGS - "/EHsc" - ) +option(BuildTests "Build the unit tests" ON) - STRING(REPLACE "/O2" "/Od" CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE}) +# define project variables +set(JSON_TARGET_NAME ${PROJECT_NAME}) +set(JSON_PACKAGE_NAME ${JSON_TARGET_NAME}) +set(JSON_TARGETS_FILENAME "${JSON_PACKAGE_NAME}Targets.cmake") +set(JSON_CONFIG_FILENAME "${JSON_PACKAGE_NAME}Config.cmake") +set(JSON_CONFIGVERSION_FILENAME "${JSON_PACKAGE_NAME}ConfigVersion.cmake") +set(JSON_CONFIG_DESTINATION "cmake") +set(JSON_INCLUDE_DESTINATION "include/nlohmann") - add_definitions(-D_SCL_SECURE_NO_WARNINGS) -else(MSVC) - set(CMAKE_CXX_FLAGS - "-std=c++11" - ) -endif(MSVC) +# create and configure the library target +add_library(${JSON_TARGET_NAME} INTERFACE) +target_include_directories(${JSON_TARGET_NAME} INTERFACE + $ + $) -include_directories( - src test -) +# create and configure the unit test target +if (BuildTests) + add_subdirectory(test) +endif() + +# generate a config and config version file for the package +include(CMakePackageConfigHelpers) +configure_package_config_file("cmake/config.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/${JSON_CONFIG_FILENAME}" + INSTALL_DESTINATION ${JSON_CONFIG_DESTINATION}) +write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/${JSON_CONFIGVERSION_FILENAME}" + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion) + +# export the library target and store build directory in package registry +export(TARGETS ${JSON_TARGET_NAME} + FILE "${CMAKE_CURRENT_BINARY_DIR}/${JSON_TARGETS_FILENAME}") +export(PACKAGE ${JSON_PACKAGE_NAME}) + +# install library target and config files +install(TARGETS ${JSON_TARGET_NAME} + EXPORT ${JSON_PACKAGE_NAME}) +install(FILES "src/json.hpp" + DESTINATION ${JSON_INCLUDE_DESTINATION}) +install(EXPORT ${JSON_PACKAGE_NAME} + FILE ${JSON_TARGETS_FILENAME} + DESTINATION ${JSON_CONFIG_DESTINATION}) +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${JSON_CONFIG_FILENAME}" + "${CMAKE_CURRENT_BINARY_DIR}/${JSON_CONFIGVERSION_FILENAME}" + DESTINATION ${JSON_CONFIG_DESTINATION}) diff --git a/ChangeLog.md b/ChangeLog.md index bb325598b..ae9e562f9 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -5,18 +5,52 @@ All notable changes to this project will be documented in this file. This projec [Full Changelog](https://github.com/nlohmann/json/compare/v1.1.0...HEAD) +- concatenate objects [\#252](https://github.com/nlohmann/json/issues/252) +- Unit test fails when doing a CMake out-of-tree build [\#241](https://github.com/nlohmann/json/issues/241) +- Additional integration options [\#237](https://github.com/nlohmann/json/issues/237) +- Can't use basic\_json::iterator as a base iterator for std::move\_iterator [\#233](https://github.com/nlohmann/json/issues/233) - Provide a FAQ [\#163](https://github.com/nlohmann/json/issues/163) +- Create PULL\_REQUEST\_TEMPLATE.md [\#213](https://github.com/nlohmann/json/pull/213) ([whackashoe](https://github.com/whackashoe)) +- fixed noexcept; added constexpr [\#208](https://github.com/nlohmann/json/pull/208) ([nlohmann](https://github.com/nlohmann)) +- Add support for afl-fuzz testing [\#207](https://github.com/nlohmann/json/pull/207) ([mykter](https://github.com/mykter)) - Issue \#178 - Extending support to full uint64\_t/int64\_t range and unsigned type \(updated\) [\#193](https://github.com/nlohmann/json/pull/193) ([twelsby](https://github.com/twelsby)) +- json::diff generates incorrect patch when removing multiple array elements. [\#269](https://github.com/nlohmann/json/issues/269) +- -Wconversion warning [\#259](https://github.com/nlohmann/json/issues/259) +- Maybe a bug [\#258](https://github.com/nlohmann/json/issues/258) +- -Wconversion warnings [\#239](https://github.com/nlohmann/json/issues/239) +- ambiguous overload for 'push\_back' and 'operator+=' [\#235](https://github.com/nlohmann/json/issues/235) +- Incorrect dumping of parsed numbers with exponents, but without decimal places [\#230](https://github.com/nlohmann/json/issues/230) +- double values are serialized with commas as decimal points [\#228](https://github.com/nlohmann/json/issues/228) +- Move semantics with std::initializer\_list [\#225](https://github.com/nlohmann/json/issues/225) +- abort during getline in yyfill [\#223](https://github.com/nlohmann/json/issues/223) +- Wrong link in the README.md [\#217](https://github.com/nlohmann/json/issues/217) - Small bugs in json.hpp \(get\_number\) and unit.cpp \(non-standard integer type test\) [\#199](https://github.com/nlohmann/json/issues/199) - GCC/clang floating point parsing bug in strtod\(\) [\#195](https://github.com/nlohmann/json/issues/195) - Bugs in miloyip/nativejson-benchmark: roundtrips [\#187](https://github.com/nlohmann/json/issues/187) - Floating point exceptions [\#181](https://github.com/nlohmann/json/issues/181) +- In basic\_json::basic\_json\(const CompatibleArrayType& val\), the requirement of CompatibleArrayType is not strict enough. [\#174](https://github.com/nlohmann/json/issues/174) - Implicit assignment to std::string fails [\#144](https://github.com/nlohmann/json/issues/144) +- Fix Issue \#265 [\#266](https://github.com/nlohmann/json/pull/266) ([06needhamt](https://github.com/06needhamt)) - Issue \#195 - update Travis to Trusty due to gcc/clang strtod\(\) bug [\#196](https://github.com/nlohmann/json/pull/196) ([twelsby](https://github.com/twelsby)) +- Compiler Errors With JSON.hpp [\#265](https://github.com/nlohmann/json/issues/265) +- VS2015 compile fail [\#260](https://github.com/nlohmann/json/issues/260) +- Error when using json in g++ [\#254](https://github.com/nlohmann/json/issues/254) +- Update long-term goals [\#246](https://github.com/nlohmann/json/issues/246) +- Error compile Android NDK error: 'strtof' is not a member of 'std' [\#219](https://github.com/nlohmann/json/issues/219) +- Conflicting typedef of ssize\_t on Windows 32 bit when using Boost.Python [\#204](https://github.com/nlohmann/json/issues/204) - Integer conversion to unsigned [\#178](https://github.com/nlohmann/json/issues/178) +- Define CMake/CTest tests [\#247](https://github.com/nlohmann/json/pull/247) ([robertmrk](https://github.com/robertmrk)) +- Out of tree builds and a few other miscellaneous CMake cleanups. [\#242](https://github.com/nlohmann/json/pull/242) ([ChrisKitching](https://github.com/ChrisKitching)) +- Implement additional integration options [\#238](https://github.com/nlohmann/json/pull/238) ([robertmrk](https://github.com/robertmrk)) +- make serialization locale-independent [\#232](https://github.com/nlohmann/json/pull/232) ([nlohmann](https://github.com/nlohmann)) +- fixes \#223 by updating README.md [\#227](https://github.com/nlohmann/json/pull/227) ([kevin--](https://github.com/kevin--)) +- Use namespace std for int64\_t and uint64\_t [\#226](https://github.com/nlohmann/json/pull/226) ([lv-zheng](https://github.com/lv-zheng)) +- Added missing cerrno header to fix ERANGE compile error on android [\#222](https://github.com/nlohmann/json/pull/222) ([Teemperor](https://github.com/Teemperor)) +- Corrected readme [\#218](https://github.com/nlohmann/json/pull/218) ([Annihil](https://github.com/Annihil)) +- replaced ssize\_t occurrences with auto \(addresses \#204\) [\#205](https://github.com/nlohmann/json/pull/205) ([nlohmann](https://github.com/nlohmann)) - Fixed issue \#199 - Small bugs in json.hpp \(get\_number\) and unit.cpp \(non-standard integer type test\) [\#200](https://github.com/nlohmann/json/pull/200) ([twelsby](https://github.com/twelsby)) - Fix broken link [\#197](https://github.com/nlohmann/json/pull/197) ([vog](https://github.com/vog)) diff --git a/Makefile b/Makefile index 91d4fc586..56e46d145 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,7 @@ all: json_unit # clean up clean: rm -fr json_unit json_benchmarks fuzz fuzz-testing *.dSYM + $(MAKE) clean -Cdoc ########################################################################## @@ -19,8 +20,8 @@ clean: # additional flags FLAGS = -Wall -Wextra -pedantic -Weffc++ -Wcast-align -Wcast-qual -Wctor-dtor-privacy -Wdisabled-optimization -Wformat=2 -Winit-self -Wmissing-declarations -Wmissing-include-dirs -Wold-style-cast -Woverloaded-virtual -Wredundant-decls -Wshadow -Wsign-conversion -Wsign-promo -Wstrict-overflow=5 -Wswitch -Wundef -Wno-unused -Wnon-virtual-dtor -Wreorder -Wdeprecated -Wfloat-equal -# build unit tests -json_unit: test/unit.cpp src/json.hpp test/catch.hpp +# build unit tests (TODO: Does this want its own makefile?) +json_unit: test/src/unit.cpp src/json.hpp test/src/catch.hpp $(CXX) -std=c++11 $(CXXFLAGS) $(FLAGS) $(CPPFLAGS) -I src -I test $< $(LDFLAGS) -o $@ @@ -30,7 +31,7 @@ json_unit: test/unit.cpp src/json.hpp test/catch.hpp # compile example files and check output doctest: - make check_output -C doc + $(MAKE) check_output -C doc ########################################################################## @@ -43,11 +44,11 @@ fuzz_testing: mkdir -p fuzz-testing fuzz-testing/testcases fuzz-testing/out $(MAKE) fuzz CXX=afl-clang++ mv fuzz fuzz-testing - find test/json_tests -size -5k -name *json | xargs -I{} cp "{}" fuzz-testing/testcases + find test/data/json_tests -size -5k -name *json | xargs -I{} cp "{}" fuzz-testing/testcases @echo "Execute: afl-fuzz -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzz" # the fuzzer binary -fuzz: test/fuzz.cpp src/json.hpp +fuzz: test/src/fuzz.cpp src/json.hpp $(CXX) -std=c++11 $(CXXFLAGS) $(FLAGS) $(CPPFLAGS) -I src $< $(LDFLAGS) -o $@ @@ -66,7 +67,7 @@ cppcheck: # create scanner with re2c re2c: src/json.hpp.re2c - $(RE2C) -b -s -i --no-generation-date $< | $(SED) '1d' > src/json.hpp + $(RE2C) -W --bit-vectors --nested-ifs --no-debug-info $< | $(SED) '1d' > src/json.hpp # pretty printer pretty: @@ -74,8 +75,8 @@ pretty: --indent-switches --indent-preproc-block --indent-preproc-define \ --indent-col1-comments --pad-oper --pad-header --align-pointer=type \ --align-reference=type --add-brackets --convert-tabs --close-templates \ - --lineend=linux --preserve-date --suffix=none \ - src/json.hpp src/json.hpp.re2c test/unit.cpp test/fuzz.cpp benchmarks/benchmarks.cpp doc/examples/*.cpp + --lineend=linux --preserve-date --suffix=none --formatted \ + src/json.hpp src/json.hpp.re2c test/src/unit.cpp test/src/fuzz.cpp benchmarks/benchmarks.cpp doc/examples/*.cpp ########################################################################## diff --git a/README.md b/README.md index 2fadefff6..21ff3c593 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,9 @@ [![Build Status](https://travis-ci.org/nlohmann/json.svg?branch=master)](https://travis-ci.org/nlohmann/json) [![Build Status](https://ci.appveyor.com/api/projects/status/1acb366xfyg3qybk?svg=true)](https://ci.appveyor.com/project/nlohmann/json) [![Coverage Status](https://img.shields.io/coveralls/nlohmann/json.svg)](https://coveralls.io/r/nlohmann/json) +[![Coverity Scan Build Status](https://scan.coverity.com/projects/5550/badge.svg)](https://scan.coverity.com/projects/nlohmann-json) [![Try online](https://img.shields.io/badge/try-online-blue.svg)](http://melpon.org/wandbox/permlink/wuiuqYiYqRTdI3rG) -[![Documentation Status](https://img.shields.io/badge/docs-doxygen-blue.svg)](http://nlohmann.github.io/json) +[![Documentation](https://img.shields.io/badge/docs-doxygen-blue.svg)](http://nlohmann.github.io/json) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/nlohmann/json/master/LICENSE.MIT) [![Github Releases](https://img.shields.io/github/release/nlohmann/json.svg)](https://github.com/nlohmann/json/releases) [![Github Issues](https://img.shields.io/github/issues/nlohmann/json.svg)](http://github.com/nlohmann/json/issues) @@ -15,9 +16,9 @@ There are myriads of [JSON](http://json.org) libraries out there, and each may e - **Intuitive syntax**. In languages such as Python, JSON feels like a first class data type. We used all the operator magic of modern C++ to achieve the same feeling in your code. Check out the [examples below](#examples) and you know, what I mean. -- **Trivial integration**. Our whole code consists of a single header file `json.hpp`. That's it. No library, no subproject, no dependencies, no complex build system. The class is written in vanilla C++11. All in all, everything should require no adjustment of your compiler flags or project settings. +- **Trivial integration**. Our whole code consists of a single header file [`json.hpp`](https://github.com/nlohmann/json/blob/develop/src/json.hpp). That's it. No library, no subproject, no dependencies, no complex build system. The class is written in vanilla C++11. All in all, everything should require no adjustment of your compiler flags or project settings. -- **Serious testing**. Our class is heavily [unit-tested](https://github.com/nlohmann/json/blob/master/test/unit.cpp) and covers [100%](https://coveralls.io/r/nlohmann/json) of the code, including all exceptional behavior. Furthermore, we checked with [Valgrind](http://valgrind.org) that there are no memory leaks. +- **Serious testing**. Our class is heavily [unit-tested](https://github.com/nlohmann/json/blob/master/test/src/unit.cpp) and covers [100%](https://coveralls.io/r/nlohmann/json) of the code, including all exceptional behavior. Furthermore, we checked with [Valgrind](http://valgrind.org) that there are no memory leaks. Other aspects were not so important to us: @@ -27,6 +28,7 @@ Other aspects were not so important to us: See the [contribution guidelines](https://github.com/nlohmann/json/blob/master/.github/CONTRIBUTING.md#please-dont) for more information. + ## Integration The single required source, file `json.hpp` is in the `src` directory or [released here](https://github.com/nlohmann/json/releases). All you need to do is add @@ -40,28 +42,8 @@ using json = nlohmann::json; to the files you want to use JSON objects. That's it. Do not forget to set the necessary switches to enable C++11 (e.g., `-std=c++11` for GCC and Clang). -## Supported compilers +:beer: If you are using OS X and [Homebrew](http://brew.sh), just type `brew tap nlohmann/json` and `brew install nlohmann_json` and you're set. If you want the bleeding edge rather than the latest release, use `brew install nlohmann_json --HEAD`. -Though it's 2016 already, the support for C++11 is still a bit sparse. Currently, the following compilers are known to work: - -- GCC 4.9 - 6.0 (and possibly later) -- Clang 3.4 - 3.9 (and possibly later) -- Microsoft Visual C++ 14.0 RC (and possibly later) - -I would be happy to learn about other compilers/versions. - -Please note: - -- GCC 4.8 does not work because of two bugs ([55817](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55817) and [57824](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=57824)) in the C++11 support. Note there is a [pull request](https://github.com/nlohmann/json/pull/212) to fix some of the issues. -- Android defaults to using very old compilers and C++ libraries. To fix this, add the following to your `Application.mk`. This will switch to the LLVM C++ library, the Clang compiler, and enable C++11 and other features disabled by default. - -``` -APP_STL := c++_shared -NDK_TOOLCHAIN_VERSION := clang3.6 -APP_CPPFLAGS += -frtti -fexceptions -``` - -- For GCC running on MinGW or Android SDK, the error `'to_string' is not a member of 'std'` (or similarly, for `strtod`) may occur. Note this is not an issue with the code, but rather with the compiler itself. On Android, see above to build with a newer environment. For MingW, please refer to [this site](http://tehsausage.com/mingw-to-string) and [this discussion](https://github.com/nlohmann/json/issues/136) for information on how to fix this bug. ## Examples @@ -144,6 +126,7 @@ json empty_object_explicit = json::object(); json array_not_object = { json::array({"currency", "USD"}), json::array({"value", 42.99}) }; ``` + ### Serialization / Deserialization You can create an object (deserialization) by appending `_json` to a string literal: @@ -152,7 +135,7 @@ You can create an object (deserialization) by appending `_json` to a string lite // create object from string literal json j = "{ \"happy\": true, \"pi\": 3.141 }"_json; -// or even nicer (thanks http://isocpp.org/blog/2015/01/json-for-modern-cpp) +// or even nicer with a raw string literal auto j2 = R"( { "happy": true, @@ -197,6 +180,7 @@ These operators work for any subclasses of `std::istream` or `std::ostream`. Please note that setting the exception bit for `failbit` is inappropriate for this use case. It will result in program termination due to the `noexcept` specifier in use. + ### STL-like access We designed the JSON class to behave just like an STL container. In fact, it satisfies the [**ReversibleContainer**](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) requirement. @@ -264,6 +248,7 @@ int fob_present = o.count("fob"); // 0 o.erase("foo"); ``` + ### Conversion from STL containers Any sequence container (`std::array`, `std::vector`, `std::deque`, `std::forward_list`, `std::list`) whose values can be used to construct JSON types (e.g., integers, floating point numbers, Booleans, string types, or again STL containers described in this section) can be used to create a JSON array. The same holds for similar associative containers (`std::set`, `std::multiset`, `std::unordered_set`, `std::unordered_multiset`), but in these cases the order of the elements of the array depends how the elements are ordered in the respective STL container. @@ -326,12 +311,51 @@ json j_ummap(c_ummap); // only one entry for key "three" is used // maybe {"one": true, "two": true, "three": true} ``` +### JSON Pointer and JSON Patch + +The library supports **JSON Pointer** ([RFC 6901](https://tools.ietf.org/html/rfc6901)) as alternative means to address structured values. On top of this, **JSON Patch** ([RFC 6902](https://tools.ietf.org/html/rfc6902)) allows to describe differences between two JSON values - effectively allowing patch and diff operations known from Unix. + +```cpp +// a JSON value +json j_original = R"({ + "baz": ["one", "two", "three"], + "foo": "bar" +})"_json; + +// access members with a JSON pointer (RFC 6901) +j_original["/baz/2"_json_pointer]; +// "two" + +// a JSON patch (RFC 6902) +json j_patch = R"([ + { "op": "replace", "path": "/baz", "value": "boo" }, + { "op": "add", "path": "/hello", "value": ["world"] }, + { "op": "remove", "path": "/foo"} +])"_json; + +// apply the patch +json j_result = j_original.patch(j_patch); +// { +// "baz": "boo", +// "hello": ["world"] +// } + +// calculate a JSON patch from two JSON values +json::diff(j_result, j_original); +// [ +// { "op":" replace", "path": "/baz", "value": ["one", "two", "three"] }, +// { "op":"remove","path":"/hello" }, +// { "op":"add","path":"/foo","value":"bar" } +// ] +``` + + ### Implicit conversions The type of the JSON object is determined automatically by the expression to store. Likewise, the stored value is implicitly converted. ```cpp -/// strings +// strings std::string s1 = "Hello, world!"; json js = s1; std::string s2 = js; @@ -359,6 +383,51 @@ int vi = jn.get(); // etc. ``` + +## Supported compilers + +Though it's 2016 already, the support for C++11 is still a bit sparse. Currently, the following compilers are known to work: + +- GCC 4.9 - 6.0 (and possibly later) +- Clang 3.4 - 3.9 (and possibly later) +- Microsoft Visual C++ 2015 / 14.0 (and possibly later) + +I would be happy to learn about other compilers/versions. + +Please note: + +- GCC 4.8 does not work because of two bugs ([55817](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55817) and [57824](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=57824)) in the C++11 support. Note there is a [pull request](https://github.com/nlohmann/json/pull/212) to fix some of the issues. +- Android defaults to using very old compilers and C++ libraries. To fix this, add the following to your `Application.mk`. This will switch to the LLVM C++ library, the Clang compiler, and enable C++11 and other features disabled by default. + + ``` + APP_STL := c++_shared + NDK_TOOLCHAIN_VERSION := clang3.6 + APP_CPPFLAGS += -frtti -fexceptions + ``` + + The code compiles successfully with [Android NDK](https://developer.android.com/ndk/index.html?hl=ml), Revision 9 - 11 (and possibly later) and [CrystaX's Android NDK](https://www.crystax.net/en/android/ndk) version 10. + +- For GCC running on MinGW or Android SDK, the error `'to_string' is not a member of 'std'` (or similarly, for `strtod`) may occur. Note this is not an issue with the code, but rather with the compiler itself. On Android, see above to build with a newer environment. For MinGW, please refer to [this site](http://tehsausage.com/mingw-to-string) and [this discussion](https://github.com/nlohmann/json/issues/136) for information on how to fix this bug. For Android NDK using `APP_STL := gnustl_static`, please refer to [this discussion](https://github.com/nlohmann/json/issues/219). + +The following compilers are currently used in continuous integration at [Travis](https://travis-ci.org/nlohmann/json) and [AppVeyor](https://ci.appveyor.com/project/nlohmann/json): + +| Compiler | Operating System | Version String | +|-----------------|------------------------------|----------------| +| GCC 4.9.3 | Ubuntu 14.04.4 LTS | g++-4.9 (Ubuntu 4.9.3-8ubuntu2~14.04) 4.9.3 | +| GCC 5.3.0 | Ubuntu 14.04.4 LTS | g++-5 (Ubuntu 5.3.0-3ubuntu1~14.04) 5.3.0 20151204 | +| GCC 6.1.1 | Ubuntu 14.04.4 LTS | g++-6 (Ubuntu 6.1.1-3ubuntu11~14.04.1) 6.1.1 20160511 | +| Clang 3.8.0 | Ubuntu 14.04.4 LTS | clang version 3.8.0 (tags/RELEASE_380/final) | +| Clang Xcode 6.1 | Darwin Kernel Version 13.4.0 (OSX 10.9.5) | Apple LLVM version 6.0 (clang-600.0.54) (based on LLVM 3.5svn) | +| Clang Xcode 6.2 | Darwin Kernel Version 13.4.0 (OSX 10.9.5) | Apple LLVM version 6.0 (clang-600.0.57) (based on LLVM 3.5svn) | +| Clang Xcode 6.3 | Darwin Kernel Version 14.3.0 (OSX 10.10.3) | Apple LLVM version 6.1.0 (clang-602.0.49) (based on LLVM 3.6.0svn) | +| Clang Xcode 6.4 | Darwin Kernel Version 14.3.0 (OSX 10.10.3) | Apple LLVM version 6.1.0 (clang-602.0.53) (based on LLVM 3.6.0svn) | +| Clang Xcode 7.1 | Darwin Kernel Version 14.5.0 (OSX 10.10.5) | Apple LLVM version 7.0.0 (clang-700.1.76) | +| Clang Xcode 7.2 | Darwin Kernel Version 15.0.0 (OSX 10.10.5) | Apple LLVM version 7.0.2 (clang-700.1.81) | +| Clang Xcode 7.3 | Darwin Kernel Version 15.0.0 (OSX 10.10.5) | Apple LLVM version 7.3.0 (clang-703.0.29) | +| Clang Xcode 8.0 | Darwin Kernel Version 15.5.0 (OSX 10.11.5) | Apple LLVM version 8.0.0 (clang-800.0.24.1) | +| Visual Studio 14 2015 | Windows Server 2012 R2 (x64) | Microsoft (R) Build Engine version 14.0.25123.0 | + + ## License @@ -367,11 +436,12 @@ The class is licensed under the [MIT License](http://opensource.org/licenses/MIT Copyright © 2013-2016 [Niels Lohmann](http://nlohmann.me) -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + ## Thanks @@ -410,14 +480,19 @@ I deeply appreciate the help of the following people. - [Lv Zheng](https://github.com/lv-zheng) fixed a namespace issue with `int64_t` and `uint64_t`. - [abc100m](https://github.com/abc100m) analyzed the issues with GCC 4.8 and proposed a [partial solution](https://github.com/nlohmann/json/pull/212). - [zewt](https://github.com/zewt) added useful notes to the README file about Android. +- [Róbert Márki](https://github.com/robertmrk) added a fix to use move iterators and improved the integration via CMake. +- [Chris Kitching](https://github.com/ChrisKitching) cleaned up the CMake files. +- [Tom Needham](https://github.com/06needhamt) fixed a subtle bug with MSVC 2015 which was also proposed by [Michael K.](https://github.com/Epidal). Thanks a lot for helping out! + ## Notes - The code contains numerous debug **assertions** which can be switched off by defining the preprocessor macro `NDEBUG`, see the [documentation of `assert`](http://en.cppreference.com/w/cpp/error/assert). - As the exact type of a number is not defined in the [JSON specification](http://rfc7159.net/rfc7159), this library tries to choose the best fitting C++ number type automatically. As a result, the type `double` may be used to store numbers which may yield [**floating-point exceptions**](https://github.com/nlohmann/json/issues/181) in certain rare situations if floating-point exceptions have been unmasked in the calling code. These exceptions are not caused by the library and need to be fixed in the calling code, such as by re-masking the exceptions prior to calling library functions. + ## Execute unit tests To compile and run the tests, you need to execute @@ -427,7 +502,7 @@ $ make $ ./json_unit "*" =============================================================================== -All tests passed (3344278 assertions in 29 test cases) +All tests passed (5568715 assertions in 32 test cases) ``` For more information, have a look at the file [.travis.yml](https://github.com/nlohmann/json/blob/master/.travis.yml). diff --git a/appveyor.yml b/appveyor.yml index aaf9c5f1a..a527ef766 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,5 +7,4 @@ build_script: - cmake . -G "Visual Studio 14 2015" - cmake --build . --config Release test_script: -- Release\json_unit.exe -- Release\json_unit.exe "*" +- ctest -C Release -V diff --git a/cmake/config.cmake.in b/cmake/config.cmake.in new file mode 100644 index 000000000..3ff97adda --- /dev/null +++ b/cmake/config.cmake.in @@ -0,0 +1,6 @@ +@PACKAGE_INIT@ + +cmake_policy(PUSH) +cmake_policy(SET CMP0024 OLD) +include(${CMAKE_CURRENT_LIST_DIR}/@JSON_TARGETS_FILENAME@) +cmake_policy(POP) \ No newline at end of file diff --git a/doc/Doxyfile b/doc/Doxyfile index 11faf6d03..e74a8a850 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -236,7 +236,7 @@ MAN_LINKS = NO #--------------------------------------------------------------------------- # Configuration options related to the XML output #--------------------------------------------------------------------------- -GENERATE_XML = NO +GENERATE_XML = YES XML_OUTPUT = xml XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- diff --git a/doc/Makefile b/doc/Makefile index dc10f1dd1..5e5459a00 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -2,9 +2,6 @@ SRCDIR = ../src all: doxygen -clean: - rm -fr me.nlohmann.json.docset html - ########################################################################## # example files @@ -44,6 +41,10 @@ create_links: $(EXAMPLES:.cpp=.link) check_output: $(EXAMPLES:.cpp=.test) +clean: + rm -fr me.nlohmann.json.docset html $(EXAMPLES:.cpp=) + + ########################################################################## # Doxygen HTML documentation ########################################################################## diff --git a/doc/examples/README.link b/doc/examples/README.link index a2526dc64..ed559e19f 100644 --- a/doc/examples/README.link +++ b/doc/examples/README.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/array.link b/doc/examples/array.link index 626dcd25c..cafca79cf 100644 --- a/doc/examples/array.link +++ b/doc/examples/array.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/at__object_t_key_type.link b/doc/examples/at__object_t_key_type.link index f7e44b204..e01197e69 100644 --- a/doc/examples/at__object_t_key_type.link +++ b/doc/examples/at__object_t_key_type.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/at__object_t_key_type_const.link b/doc/examples/at__object_t_key_type_const.link index 8ba211662..2588529b8 100644 --- a/doc/examples/at__object_t_key_type_const.link +++ b/doc/examples/at__object_t_key_type_const.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/at__size_type.link b/doc/examples/at__size_type.link index 76a409c20..00e42d300 100644 --- a/doc/examples/at__size_type.link +++ b/doc/examples/at__size_type.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/at__size_type_const.link b/doc/examples/at__size_type_const.link index 5a1cdde95..0fefb628c 100644 --- a/doc/examples/at__size_type_const.link +++ b/doc/examples/at__size_type_const.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/at_json_pointer.cpp b/doc/examples/at_json_pointer.cpp new file mode 100644 index 000000000..0665e608c --- /dev/null +++ b/doc/examples/at_json_pointer.cpp @@ -0,0 +1,35 @@ +#include + +using json = nlohmann::json; + +int main() +{ + // create a JSON value + json j = + { + {"number", 1}, {"string", "foo"}, {"array", {1, 2}} + }; + + // read-only access + + // output element with JSON pointer "/number" + std::cout << j.at("/number"_json_pointer) << '\n'; + // output element with JSON pointer "/string" + std::cout << j.at("/string"_json_pointer) << '\n'; + // output element with JSON pointer "/array" + std::cout << j.at("/array"_json_pointer) << '\n'; + // output element with JSON pointer "/array/1" + std::cout << j.at("/array/1"_json_pointer) << '\n'; + + // writing access + + // change the string + j.at("/string"_json_pointer) = "bar"; + // output the changed string + std::cout << j["string"] << '\n'; + + // change an array element + j.at("/array/1"_json_pointer) = 21; + // output the changed array + std::cout << j["array"] << '\n'; +} diff --git a/doc/examples/at_json_pointer.link b/doc/examples/at_json_pointer.link new file mode 100644 index 000000000..7a7efa268 --- /dev/null +++ b/doc/examples/at_json_pointer.link @@ -0,0 +1 @@ +online \ No newline at end of file diff --git a/doc/examples/at_json_pointer.output b/doc/examples/at_json_pointer.output new file mode 100644 index 000000000..11913c723 --- /dev/null +++ b/doc/examples/at_json_pointer.output @@ -0,0 +1,6 @@ +1 +"foo" +[1,2] +2 +"bar" +[1,21] diff --git a/doc/examples/at_json_pointer_const.cpp b/doc/examples/at_json_pointer_const.cpp new file mode 100644 index 000000000..e3cfc5154 --- /dev/null +++ b/doc/examples/at_json_pointer_const.cpp @@ -0,0 +1,23 @@ +#include + +using json = nlohmann::json; + +int main() +{ + // create a JSON value + json j = + { + {"number", 1}, {"string", "foo"}, {"array", {1, 2}} + }; + + // read-only access + + // output element with JSON pointer "/number" + std::cout << j.at("/number"_json_pointer) << '\n'; + // output element with JSON pointer "/string" + std::cout << j.at("/string"_json_pointer) << '\n'; + // output element with JSON pointer "/array" + std::cout << j.at("/array"_json_pointer) << '\n'; + // output element with JSON pointer "/array/1" + std::cout << j.at("/array/1"_json_pointer) << '\n'; +} diff --git a/doc/examples/at_json_pointer_const.link b/doc/examples/at_json_pointer_const.link new file mode 100644 index 000000000..9057e0b27 --- /dev/null +++ b/doc/examples/at_json_pointer_const.link @@ -0,0 +1 @@ +online \ No newline at end of file diff --git a/doc/examples/at_json_pointer_const.output b/doc/examples/at_json_pointer_const.output new file mode 100644 index 000000000..7b9306bbc --- /dev/null +++ b/doc/examples/at_json_pointer_const.output @@ -0,0 +1,4 @@ +1 +"foo" +[1,2] +2 diff --git a/doc/examples/back.link b/doc/examples/back.link index 49d008f5b..0b0097805 100644 --- a/doc/examples/back.link +++ b/doc/examples/back.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/basic_json.link b/doc/examples/basic_json.link index 1625c487a..e5c17c93a 100644 --- a/doc/examples/basic_json.link +++ b/doc/examples/basic_json.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/basic_json__CompatibleArrayType.link b/doc/examples/basic_json__CompatibleArrayType.link index 313ae3fed..85f64862f 100644 --- a/doc/examples/basic_json__CompatibleArrayType.link +++ b/doc/examples/basic_json__CompatibleArrayType.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/basic_json__CompatibleIntegerNumberType.link b/doc/examples/basic_json__CompatibleIntegerNumberType.link index f994babb3..7a37e9eb2 100644 --- a/doc/examples/basic_json__CompatibleIntegerNumberType.link +++ b/doc/examples/basic_json__CompatibleIntegerNumberType.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/basic_json__CompatibleNumberFloatType.link b/doc/examples/basic_json__CompatibleNumberFloatType.link index 9e08b6a64..9fbc73177 100644 --- a/doc/examples/basic_json__CompatibleNumberFloatType.link +++ b/doc/examples/basic_json__CompatibleNumberFloatType.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/basic_json__CompatibleObjectType.link b/doc/examples/basic_json__CompatibleObjectType.link index 6d65b17a4..7512fb35a 100644 --- a/doc/examples/basic_json__CompatibleObjectType.link +++ b/doc/examples/basic_json__CompatibleObjectType.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/basic_json__CompatibleStringType.link b/doc/examples/basic_json__CompatibleStringType.link index 2131a163b..351d6c0ce 100644 --- a/doc/examples/basic_json__CompatibleStringType.link +++ b/doc/examples/basic_json__CompatibleStringType.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/basic_json__InputIt_InputIt.link b/doc/examples/basic_json__InputIt_InputIt.link index 3ce3686ad..ae46528cc 100644 --- a/doc/examples/basic_json__InputIt_InputIt.link +++ b/doc/examples/basic_json__InputIt_InputIt.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/basic_json__array_t.link b/doc/examples/basic_json__array_t.link index 53322832d..70c9cb8cd 100644 --- a/doc/examples/basic_json__array_t.link +++ b/doc/examples/basic_json__array_t.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/basic_json__basic_json.link b/doc/examples/basic_json__basic_json.link index c3109d16e..78fcbbe54 100644 --- a/doc/examples/basic_json__basic_json.link +++ b/doc/examples/basic_json__basic_json.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/basic_json__boolean_t.link b/doc/examples/basic_json__boolean_t.link index 9fe211e42..c64e1fc47 100644 --- a/doc/examples/basic_json__boolean_t.link +++ b/doc/examples/basic_json__boolean_t.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/basic_json__const_int.link b/doc/examples/basic_json__const_int.link index 73d286286..68a9e235d 100644 --- a/doc/examples/basic_json__const_int.link +++ b/doc/examples/basic_json__const_int.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/basic_json__copyassignment.link b/doc/examples/basic_json__copyassignment.link index 96679cf45..efcac3062 100644 --- a/doc/examples/basic_json__copyassignment.link +++ b/doc/examples/basic_json__copyassignment.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/basic_json__istream.link b/doc/examples/basic_json__istream.link index e5475436d..20d1033c3 100644 --- a/doc/examples/basic_json__istream.link +++ b/doc/examples/basic_json__istream.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/basic_json__list_init_t.link b/doc/examples/basic_json__list_init_t.link index 5eca20502..bcdf66890 100644 --- a/doc/examples/basic_json__list_init_t.link +++ b/doc/examples/basic_json__list_init_t.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/basic_json__moveconstructor.link b/doc/examples/basic_json__moveconstructor.link index 88002bbd8..9580d02c0 100644 --- a/doc/examples/basic_json__moveconstructor.link +++ b/doc/examples/basic_json__moveconstructor.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/basic_json__nullptr_t.link b/doc/examples/basic_json__nullptr_t.link index bb26af46d..7e9177522 100644 --- a/doc/examples/basic_json__nullptr_t.link +++ b/doc/examples/basic_json__nullptr_t.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/basic_json__number_float_t.link b/doc/examples/basic_json__number_float_t.link index a7c6b6074..47aa2553a 100644 --- a/doc/examples/basic_json__number_float_t.link +++ b/doc/examples/basic_json__number_float_t.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/basic_json__number_integer_t.link b/doc/examples/basic_json__number_integer_t.link index 764aa649d..5d4499b59 100644 --- a/doc/examples/basic_json__number_integer_t.link +++ b/doc/examples/basic_json__number_integer_t.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/basic_json__object_t.link b/doc/examples/basic_json__object_t.link index f6bad665a..2e07a3efb 100644 --- a/doc/examples/basic_json__object_t.link +++ b/doc/examples/basic_json__object_t.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/basic_json__size_type_basic_json.link b/doc/examples/basic_json__size_type_basic_json.link index 6ae76b6f2..78305dcf6 100644 --- a/doc/examples/basic_json__size_type_basic_json.link +++ b/doc/examples/basic_json__size_type_basic_json.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/basic_json__string_t.link b/doc/examples/basic_json__string_t.link index a5051e582..d7d02f2fb 100644 --- a/doc/examples/basic_json__string_t.link +++ b/doc/examples/basic_json__string_t.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/basic_json__string_t_value_type.link b/doc/examples/basic_json__string_t_value_type.link index 4646b5b2a..569087684 100644 --- a/doc/examples/basic_json__string_t_value_type.link +++ b/doc/examples/basic_json__string_t_value_type.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/basic_json__value.link b/doc/examples/basic_json__value.link index 452a1fe4f..27e1ddebd 100644 --- a/doc/examples/basic_json__value.link +++ b/doc/examples/basic_json__value.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/basic_json__value_t.link b/doc/examples/basic_json__value_t.link index 774aaa255..e7e744c11 100644 --- a/doc/examples/basic_json__value_t.link +++ b/doc/examples/basic_json__value_t.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/begin.link b/doc/examples/begin.link index 6a76a5887..64eac7026 100644 --- a/doc/examples/begin.link +++ b/doc/examples/begin.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/cbegin.link b/doc/examples/cbegin.link index 83c75dd3c..c298c6530 100644 --- a/doc/examples/cbegin.link +++ b/doc/examples/cbegin.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/cend.link b/doc/examples/cend.link index acca215d2..61a3203d5 100644 --- a/doc/examples/cend.link +++ b/doc/examples/cend.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/clear.link b/doc/examples/clear.link index 8defa668d..9ad0a14f0 100644 --- a/doc/examples/clear.link +++ b/doc/examples/clear.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/count.link b/doc/examples/count.link index e13caf363..62833ff46 100644 --- a/doc/examples/count.link +++ b/doc/examples/count.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/crbegin.link b/doc/examples/crbegin.link index 27de8eb89..1188be1ea 100644 --- a/doc/examples/crbegin.link +++ b/doc/examples/crbegin.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/crend.link b/doc/examples/crend.link index c678236d9..4bdfef3ac 100644 --- a/doc/examples/crend.link +++ b/doc/examples/crend.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/diff.cpp b/doc/examples/diff.cpp new file mode 100644 index 000000000..d81a58dbd --- /dev/null +++ b/doc/examples/diff.cpp @@ -0,0 +1,34 @@ +#include + +using json = nlohmann::json; + +int main() +{ + // the source document + json source = R"( + { + "baz": "qux", + "foo": "bar" + } + )"_json; + + // the target document + json target = R"( + { + "baz": "boo", + "hello": [ + "world" + ] + } + )"_json; + + // create the patch + json patch = json::diff(source, target); + + // roundtrip + json patched_source = source.patch(patch); + + // output patch and roundtrip result + std::cout << std::setw(4) << patch << "\n\n" + << std::setw(4) << patched_source << std::endl; +} diff --git a/doc/examples/diff.link b/doc/examples/diff.link new file mode 100644 index 000000000..c3e3fa4d7 --- /dev/null +++ b/doc/examples/diff.link @@ -0,0 +1 @@ +online \ No newline at end of file diff --git a/doc/examples/diff.output b/doc/examples/diff.output new file mode 100644 index 000000000..7dc79791f --- /dev/null +++ b/doc/examples/diff.output @@ -0,0 +1,25 @@ +[ + { + "op": "replace", + "path": "/baz", + "value": "boo" + }, + { + "op": "remove", + "path": "/foo" + }, + { + "op": "add", + "path": "/hello", + "value": [ + "world" + ] + } +] + +{ + "baz": "boo", + "hello": [ + "world" + ] +} diff --git a/doc/examples/dump.link b/doc/examples/dump.link index 35ef46533..5cf3e634b 100644 --- a/doc/examples/dump.link +++ b/doc/examples/dump.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/empty.link b/doc/examples/empty.link index fd22770cf..12aa96782 100644 --- a/doc/examples/empty.link +++ b/doc/examples/empty.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/end.link b/doc/examples/end.link index 260696d5a..462a463f1 100644 --- a/doc/examples/end.link +++ b/doc/examples/end.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/erase__IteratorType.link b/doc/examples/erase__IteratorType.link index b523066ed..854a9be5f 100644 --- a/doc/examples/erase__IteratorType.link +++ b/doc/examples/erase__IteratorType.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/erase__IteratorType_IteratorType.link b/doc/examples/erase__IteratorType_IteratorType.link index b7f3da724..26e0b9034 100644 --- a/doc/examples/erase__IteratorType_IteratorType.link +++ b/doc/examples/erase__IteratorType_IteratorType.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/erase__key_type.link b/doc/examples/erase__key_type.link index bef018cab..e4c2fba1d 100644 --- a/doc/examples/erase__key_type.link +++ b/doc/examples/erase__key_type.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/erase__size_type.link b/doc/examples/erase__size_type.link index fe2d560d5..c63abe525 100644 --- a/doc/examples/erase__size_type.link +++ b/doc/examples/erase__size_type.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/find__key_type.link b/doc/examples/find__key_type.link index 2bc0512d1..cd3d4fd25 100644 --- a/doc/examples/find__key_type.link +++ b/doc/examples/find__key_type.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/flatten.cpp b/doc/examples/flatten.cpp new file mode 100644 index 000000000..ace53a21b --- /dev/null +++ b/doc/examples/flatten.cpp @@ -0,0 +1,30 @@ +#include + +using json = nlohmann::json; + +int main() +{ + // create JSON value + json j = + { + {"pi", 3.141}, + {"happy", true}, + {"name", "Niels"}, + {"nothing", nullptr}, + { + "answer", { + {"everything", 42} + } + }, + {"list", {1, 0, 2}}, + { + "object", { + {"currency", "USD"}, + {"value", 42.99} + } + } + }; + + // call flatten() + std::cout << std::setw(4) << j.flatten() << '\n'; +} diff --git a/doc/examples/flatten.link b/doc/examples/flatten.link new file mode 100644 index 000000000..50d3841f3 --- /dev/null +++ b/doc/examples/flatten.link @@ -0,0 +1 @@ +online \ No newline at end of file diff --git a/doc/examples/flatten.output b/doc/examples/flatten.output new file mode 100644 index 000000000..33bd4c4b9 --- /dev/null +++ b/doc/examples/flatten.output @@ -0,0 +1,12 @@ +{ + "/answer/everything": 42, + "/happy": true, + "/list/0": 1, + "/list/1": 0, + "/list/2": 2, + "/name": "Niels", + "/nothing": null, + "/object/currency": "USD", + "/object/value": 42.99, + "/pi": 3.141 +} diff --git a/doc/examples/front.link b/doc/examples/front.link index 22e99c7dd..d69c7f90c 100644 --- a/doc/examples/front.link +++ b/doc/examples/front.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/get__PointerType.link b/doc/examples/get__PointerType.link index a811b26df..68a46b571 100644 --- a/doc/examples/get__PointerType.link +++ b/doc/examples/get__PointerType.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/get__ValueType_const.link b/doc/examples/get__ValueType_const.link index fce774b2b..95862c671 100644 --- a/doc/examples/get__ValueType_const.link +++ b/doc/examples/get__ValueType_const.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/get_ptr.link b/doc/examples/get_ptr.link index e318d7780..13ed7d818 100644 --- a/doc/examples/get_ptr.link +++ b/doc/examples/get_ptr.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/get_ref.link b/doc/examples/get_ref.link index c0d8275a3..ef560c42c 100644 --- a/doc/examples/get_ref.link +++ b/doc/examples/get_ref.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/insert.link b/doc/examples/insert.link index 3886e04d5..9b0a1d90a 100644 --- a/doc/examples/insert.link +++ b/doc/examples/insert.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/insert__count.link b/doc/examples/insert__count.link index b97d0d69f..f6a6441d7 100644 --- a/doc/examples/insert__count.link +++ b/doc/examples/insert__count.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/insert__ilist.link b/doc/examples/insert__ilist.link index 4920588b5..eba2b5607 100644 --- a/doc/examples/insert__ilist.link +++ b/doc/examples/insert__ilist.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/insert__range.link b/doc/examples/insert__range.link index ac773f8b2..3a802b572 100644 --- a/doc/examples/insert__range.link +++ b/doc/examples/insert__range.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/is_array.link b/doc/examples/is_array.link index 8c8b9f825..ccd727507 100644 --- a/doc/examples/is_array.link +++ b/doc/examples/is_array.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/is_boolean.link b/doc/examples/is_boolean.link index 62f5fed14..5ebc9acb3 100644 --- a/doc/examples/is_boolean.link +++ b/doc/examples/is_boolean.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/is_discarded.link b/doc/examples/is_discarded.link index 7ce69a104..d2172392c 100644 --- a/doc/examples/is_discarded.link +++ b/doc/examples/is_discarded.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/is_null.link b/doc/examples/is_null.link index 6b22b9780..d40569d23 100644 --- a/doc/examples/is_null.link +++ b/doc/examples/is_null.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/is_number.link b/doc/examples/is_number.link index bf863a1cf..089d7893d 100644 --- a/doc/examples/is_number.link +++ b/doc/examples/is_number.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/is_number_float.link b/doc/examples/is_number_float.link index 38abb5985..3fa73533c 100644 --- a/doc/examples/is_number_float.link +++ b/doc/examples/is_number_float.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/is_number_integer.link b/doc/examples/is_number_integer.link index d6162ab0e..474cded4b 100644 --- a/doc/examples/is_number_integer.link +++ b/doc/examples/is_number_integer.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/is_number_unsigned.link b/doc/examples/is_number_unsigned.link index 41986cefa..0849b8e56 100644 --- a/doc/examples/is_number_unsigned.link +++ b/doc/examples/is_number_unsigned.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/is_object.link b/doc/examples/is_object.link index 1a304d09a..5bd827c21 100644 --- a/doc/examples/is_object.link +++ b/doc/examples/is_object.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/is_primitive.link b/doc/examples/is_primitive.link index eb73ffb1b..6de65c795 100644 --- a/doc/examples/is_primitive.link +++ b/doc/examples/is_primitive.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/is_string.link b/doc/examples/is_string.link index 6af78b0ba..86ef2b45c 100644 --- a/doc/examples/is_string.link +++ b/doc/examples/is_string.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/is_structured.link b/doc/examples/is_structured.link index 8ae6fd044..43385c8b7 100644 --- a/doc/examples/is_structured.link +++ b/doc/examples/is_structured.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/json_pointer.cpp b/doc/examples/json_pointer.cpp new file mode 100644 index 000000000..140eac3b6 --- /dev/null +++ b/doc/examples/json_pointer.cpp @@ -0,0 +1,46 @@ +#include + +using json = nlohmann::json; + +int main() +{ + // correct JSON pointers + json::json_pointer p1; + json::json_pointer p2(""); + json::json_pointer p3("/"); + json::json_pointer p4("//"); + json::json_pointer p5("/foo/bar"); + json::json_pointer p6("/foo/bar/-"); + json::json_pointer p7("/foo/~0"); + json::json_pointer p8("/foo/~1"); + + // error: JSON pointer does not begin with a slash + try + { + json::json_pointer p9("foo"); + } + catch (std::domain_error& e) + { + std::cout << "domain_error: " << e.what() << '\n'; + } + + // error: JSON pointer uses escape symbol ~ not followed by 0 or 1 + try + { + json::json_pointer p10("/foo/~"); + } + catch (std::domain_error& e) + { + std::cout << "domain_error: " << e.what() << '\n'; + } + + // error: JSON pointer uses escape symbol ~ not followed by 0 or 1 + try + { + json::json_pointer p11("/foo/~3"); + } + catch (std::domain_error& e) + { + std::cout << "domain_error: " << e.what() << '\n'; + } +} diff --git a/doc/examples/json_pointer.link b/doc/examples/json_pointer.link new file mode 100644 index 000000000..6602f0cf4 --- /dev/null +++ b/doc/examples/json_pointer.link @@ -0,0 +1 @@ +online \ No newline at end of file diff --git a/doc/examples/json_pointer.output b/doc/examples/json_pointer.output new file mode 100644 index 000000000..b81c8a201 --- /dev/null +++ b/doc/examples/json_pointer.output @@ -0,0 +1,3 @@ +domain_error: JSON pointer must be empty or begin with '/' +domain_error: escape error: '~' must be followed with '0' or '1' +domain_error: escape error: '~' must be followed with '0' or '1' diff --git a/doc/examples/json_pointer__to_string.cpp b/doc/examples/json_pointer__to_string.cpp new file mode 100644 index 000000000..4cb053c53 --- /dev/null +++ b/doc/examples/json_pointer__to_string.cpp @@ -0,0 +1,34 @@ +#include + +using json = nlohmann::json; + +int main() +{ + // different JSON Pointers + json::json_pointer ptr1(""); + json::json_pointer ptr2("/foo"); + json::json_pointer ptr3("/foo/0"); + json::json_pointer ptr4("/"); + json::json_pointer ptr5("/a~1b"); + json::json_pointer ptr6("/c%d"); + json::json_pointer ptr7("/e^f"); + json::json_pointer ptr8("/g|h"); + json::json_pointer ptr9("/i\\j"); + json::json_pointer ptr10("/k\"l"); + json::json_pointer ptr11("/ "); + json::json_pointer ptr12("/m~0n"); + + + std::cout << ptr1.to_string() << '\n' + << ptr2.to_string() << '\n' + << ptr3.to_string() << '\n' + << ptr4.to_string() << '\n' + << ptr5.to_string() << '\n' + << ptr6.to_string() << '\n' + << ptr7.to_string() << '\n' + << ptr8.to_string() << '\n' + << ptr9.to_string() << '\n' + << ptr10.to_string() << '\n' + << ptr11.to_string() << '\n' + << ptr12.to_string() << std::endl; +} diff --git a/doc/examples/json_pointer__to_string.link b/doc/examples/json_pointer__to_string.link new file mode 100644 index 000000000..407609b20 --- /dev/null +++ b/doc/examples/json_pointer__to_string.link @@ -0,0 +1 @@ +online \ No newline at end of file diff --git a/doc/examples/json_pointer__to_string.output b/doc/examples/json_pointer__to_string.output new file mode 100644 index 000000000..c4b5ea8fa --- /dev/null +++ b/doc/examples/json_pointer__to_string.output @@ -0,0 +1,12 @@ + +/foo +/foo/0 +/ +/a~1b +/c%d +/e^f +/g|h +/i\j +/k"l +/ +/m~0n diff --git a/doc/examples/max_size.link b/doc/examples/max_size.link index d91bc8cda..5719ccaac 100644 --- a/doc/examples/max_size.link +++ b/doc/examples/max_size.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/object.link b/doc/examples/object.link index 575473b35..fd0f424b9 100644 --- a/doc/examples/object.link +++ b/doc/examples/object.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/operator__ValueType.link b/doc/examples/operator__ValueType.link index 204d8f5e3..1428a679c 100644 --- a/doc/examples/operator__ValueType.link +++ b/doc/examples/operator__ValueType.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/operator__equal.link b/doc/examples/operator__equal.link index 41ae20ce3..867075204 100644 --- a/doc/examples/operator__equal.link +++ b/doc/examples/operator__equal.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/operator__equal.output b/doc/examples/operator__equal.output index 780673556..e9dfd7551 100644 --- a/doc/examples/operator__equal.output +++ b/doc/examples/operator__equal.output @@ -1,4 +1,4 @@ [1,2,3] == [1,2,4] false {"A":"a","B":"b"} == {"A":"a","B":"b"} true -17 == 17.0 true +17 == 17 true "foo" == "bar" false diff --git a/doc/examples/operator__equal.test b/doc/examples/operator__equal.test new file mode 100644 index 000000000..e9dfd7551 --- /dev/null +++ b/doc/examples/operator__equal.test @@ -0,0 +1,4 @@ +[1,2,3] == [1,2,4] false +{"A":"a","B":"b"} == {"A":"a","B":"b"} true +17 == 17 true +"foo" == "bar" false diff --git a/doc/examples/operator__equal__nullptr_t.link b/doc/examples/operator__equal__nullptr_t.link index 49e46fd65..d52acab41 100644 --- a/doc/examples/operator__equal__nullptr_t.link +++ b/doc/examples/operator__equal__nullptr_t.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/operator__greater.link b/doc/examples/operator__greater.link index 2299d6102..5499771d8 100644 --- a/doc/examples/operator__greater.link +++ b/doc/examples/operator__greater.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/operator__greaterequal.link b/doc/examples/operator__greaterequal.link index fdd676e6e..e476f3964 100644 --- a/doc/examples/operator__greaterequal.link +++ b/doc/examples/operator__greaterequal.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/operator__less.link b/doc/examples/operator__less.link index 5d7f6f566..d50c2207c 100644 --- a/doc/examples/operator__less.link +++ b/doc/examples/operator__less.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/operator__lessequal.link b/doc/examples/operator__lessequal.link index 620b6aa8a..3b71a46d0 100644 --- a/doc/examples/operator__lessequal.link +++ b/doc/examples/operator__lessequal.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/operator__notequal.link b/doc/examples/operator__notequal.link index 482b97212..49f1529ce 100644 --- a/doc/examples/operator__notequal.link +++ b/doc/examples/operator__notequal.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/operator__notequal.output b/doc/examples/operator__notequal.output index 9eba626e3..ddd838b4a 100644 --- a/doc/examples/operator__notequal.output +++ b/doc/examples/operator__notequal.output @@ -1,4 +1,4 @@ [1,2,3] == [1,2,4] true {"A":"a","B":"b"} == {"A":"a","B":"b"} false -17 == 17.0 false +17 == 17 false "foo" == "bar" true diff --git a/doc/examples/operator__notequal.test b/doc/examples/operator__notequal.test new file mode 100644 index 000000000..ddd838b4a --- /dev/null +++ b/doc/examples/operator__notequal.test @@ -0,0 +1,4 @@ +[1,2,3] == [1,2,4] true +{"A":"a","B":"b"} == {"A":"a","B":"b"} false +17 == 17 false +"foo" == "bar" true diff --git a/doc/examples/operator__notequal__nullptr_t.link b/doc/examples/operator__notequal__nullptr_t.link index 40f6bb4fe..210336234 100644 --- a/doc/examples/operator__notequal__nullptr_t.link +++ b/doc/examples/operator__notequal__nullptr_t.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/operator__value_t.link b/doc/examples/operator__value_t.link index a97072328..3139efa11 100644 --- a/doc/examples/operator__value_t.link +++ b/doc/examples/operator__value_t.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/operator_deserialize.link b/doc/examples/operator_deserialize.link index 27e998d9a..fea1503eb 100644 --- a/doc/examples/operator_deserialize.link +++ b/doc/examples/operator_deserialize.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/operator_serialize.link b/doc/examples/operator_serialize.link index 231306ac2..45b5f4b16 100644 --- a/doc/examples/operator_serialize.link +++ b/doc/examples/operator_serialize.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/operatorarray__key_type.link b/doc/examples/operatorarray__key_type.link index 40df071bf..d597ad787 100644 --- a/doc/examples/operatorarray__key_type.link +++ b/doc/examples/operatorarray__key_type.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/operatorarray__key_type_const.link b/doc/examples/operatorarray__key_type_const.link index bba664a5c..d34adb46c 100644 --- a/doc/examples/operatorarray__key_type_const.link +++ b/doc/examples/operatorarray__key_type_const.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/operatorarray__size_type.link b/doc/examples/operatorarray__size_type.link index b9d578810..aa5f718a9 100644 --- a/doc/examples/operatorarray__size_type.link +++ b/doc/examples/operatorarray__size_type.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/operatorarray__size_type_const.link b/doc/examples/operatorarray__size_type_const.link index a1f8c98b7..a5c351e62 100644 --- a/doc/examples/operatorarray__size_type_const.link +++ b/doc/examples/operatorarray__size_type_const.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/operatorjson_pointer.cpp b/doc/examples/operatorjson_pointer.cpp new file mode 100644 index 000000000..18e41c1f5 --- /dev/null +++ b/doc/examples/operatorjson_pointer.cpp @@ -0,0 +1,47 @@ +#include + +using json = nlohmann::json; + +int main() +{ + // create a JSON value + json j = + { + {"number", 1}, {"string", "foo"}, {"array", {1, 2}} + }; + + // read-only access + + // output element with JSON pointer "/number" + std::cout << j["/number"_json_pointer] << '\n'; + // output element with JSON pointer "/string" + std::cout << j["/string"_json_pointer] << '\n'; + // output element with JSON pointer "/array" + std::cout << j["/array"_json_pointer] << '\n'; + // output element with JSON pointer "/array/1" + std::cout << j["/array/1"_json_pointer] << '\n'; + + // writing access + + // change the string + j["/string"_json_pointer] = "bar"; + // output the changed string + std::cout << j["string"] << '\n'; + + // "change" a nonexisting object entry + j["/boolean"_json_pointer] = true; + // output the changed object + std::cout << j << '\n'; + + // change an array element + j["/array/1"_json_pointer] = 21; + // "change" an array element with nonexisting index + j["/array/4"_json_pointer] = 44; + // output the changed array + std::cout << j["array"] << '\n'; + + // "change" the arry element past the end + j["/array/-"_json_pointer] = 55; + // output the changed array + std::cout << j["array"] << '\n'; +} diff --git a/doc/examples/operatorjson_pointer.link b/doc/examples/operatorjson_pointer.link new file mode 100644 index 000000000..3a60e8a65 --- /dev/null +++ b/doc/examples/operatorjson_pointer.link @@ -0,0 +1 @@ +online \ No newline at end of file diff --git a/doc/examples/operatorjson_pointer.output b/doc/examples/operatorjson_pointer.output new file mode 100644 index 000000000..1fd1b032d --- /dev/null +++ b/doc/examples/operatorjson_pointer.output @@ -0,0 +1,8 @@ +1 +"foo" +[1,2] +2 +"bar" +{"array":[1,2],"boolean":true,"number":1,"string":"bar"} +[1,21,null,null,44] +[1,21,null,null,44,55] diff --git a/doc/examples/operatorjson_pointer_const.cpp b/doc/examples/operatorjson_pointer_const.cpp new file mode 100644 index 000000000..20ac36cb2 --- /dev/null +++ b/doc/examples/operatorjson_pointer_const.cpp @@ -0,0 +1,23 @@ +#include + +using json = nlohmann::json; + +int main() +{ + // create a JSON value + const json j = + { + {"number", 1}, {"string", "foo"}, {"array", {1, 2}} + }; + + // read-only access + + // output element with JSON pointer "/number" + std::cout << j["/number"_json_pointer] << '\n'; + // output element with JSON pointer "/string" + std::cout << j["/string"_json_pointer] << '\n'; + // output element with JSON pointer "/array" + std::cout << j["/array"_json_pointer] << '\n'; + // output element with JSON pointer "/array/1" + std::cout << j["/array/1"_json_pointer] << '\n'; +} diff --git a/doc/examples/operatorjson_pointer_const.link b/doc/examples/operatorjson_pointer_const.link new file mode 100644 index 000000000..1bc14234d --- /dev/null +++ b/doc/examples/operatorjson_pointer_const.link @@ -0,0 +1 @@ +online \ No newline at end of file diff --git a/doc/examples/operatorjson_pointer_const.output b/doc/examples/operatorjson_pointer_const.output new file mode 100644 index 000000000..7b9306bbc --- /dev/null +++ b/doc/examples/operatorjson_pointer_const.output @@ -0,0 +1,4 @@ +1 +"foo" +[1,2] +2 diff --git a/doc/examples/parse__istream__parser_callback_t.link b/doc/examples/parse__istream__parser_callback_t.link index 85960e159..4d2d0a9cd 100644 --- a/doc/examples/parse__istream__parser_callback_t.link +++ b/doc/examples/parse__istream__parser_callback_t.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/parse__string__parser_callback_t.link b/doc/examples/parse__string__parser_callback_t.link index 9db1c3b83..1ad3b7199 100644 --- a/doc/examples/parse__string__parser_callback_t.link +++ b/doc/examples/parse__string__parser_callback_t.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/patch.cpp b/doc/examples/patch.cpp new file mode 100644 index 000000000..24a52d596 --- /dev/null +++ b/doc/examples/patch.cpp @@ -0,0 +1,30 @@ +#include + +using json = nlohmann::json; + +int main() +{ + // the original document + json doc = R"( + { + "baz": "qux", + "foo": "bar" + } + )"_json; + + // the patch + json patch = R"( + [ + { "op": "replace", "path": "/baz", "value": "boo" }, + { "op": "add", "path": "/hello", "value": ["world"] }, + { "op": "remove", "path": "/foo"} + ] + )"_json; + + // apply the patch + json patched_doc = doc.patch(patch); + + // output original and patched document + std::cout << std::setw(4) << doc << "\n\n" + << std::setw(4) << patched_doc << std::endl; +} diff --git a/doc/examples/patch.link b/doc/examples/patch.link new file mode 100644 index 000000000..5d5032b7b --- /dev/null +++ b/doc/examples/patch.link @@ -0,0 +1 @@ +online \ No newline at end of file diff --git a/doc/examples/patch.output b/doc/examples/patch.output new file mode 100644 index 000000000..eb558fe25 --- /dev/null +++ b/doc/examples/patch.output @@ -0,0 +1,11 @@ +{ + "baz": "qux", + "foo": "bar" +} + +{ + "baz": "boo", + "hello": [ + "world" + ] +} diff --git a/doc/examples/push_back.link b/doc/examples/push_back.link index 8ec17dbac..d0ca11be9 100644 --- a/doc/examples/push_back.link +++ b/doc/examples/push_back.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/push_back__initializer_list.cpp b/doc/examples/push_back__initializer_list.cpp new file mode 100644 index 000000000..9fe01ad79 --- /dev/null +++ b/doc/examples/push_back__initializer_list.cpp @@ -0,0 +1,26 @@ +#include + +using json = nlohmann::json; + +int main() +{ + // create JSON values + json object = {{"one", 1}, {"two", 2}}; + json null; + + // print values + std::cout << object << '\n'; + std::cout << null << '\n'; + + // add values: + object.push_back({"three", 3}); // object is extended + object += {"four", 4}; // object is extended + null.push_back({"five", 5}); // null is converted to array + + // print values + std::cout << object << '\n'; + std::cout << null << '\n'; + + // would throw: + //object.push_back({1, 2, 3}); +} diff --git a/doc/examples/push_back__initializer_list.link b/doc/examples/push_back__initializer_list.link new file mode 100644 index 000000000..2b5abaeda --- /dev/null +++ b/doc/examples/push_back__initializer_list.link @@ -0,0 +1 @@ +online \ No newline at end of file diff --git a/doc/examples/push_back__initializer_list.output b/doc/examples/push_back__initializer_list.output new file mode 100644 index 000000000..668eb25d7 --- /dev/null +++ b/doc/examples/push_back__initializer_list.output @@ -0,0 +1,4 @@ +{"one":1,"two":2} +null +{"four":4,"one":1,"three":3,"two":2} +[["five",5]] diff --git a/doc/examples/push_back__object_t__value.link b/doc/examples/push_back__object_t__value.link index 24882e38c..04bdbd9b8 100644 --- a/doc/examples/push_back__object_t__value.link +++ b/doc/examples/push_back__object_t__value.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/rbegin.link b/doc/examples/rbegin.link index de269d74b..79ed80c38 100644 --- a/doc/examples/rbegin.link +++ b/doc/examples/rbegin.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/rend.link b/doc/examples/rend.link index 17faa9117..6e8850f06 100644 --- a/doc/examples/rend.link +++ b/doc/examples/rend.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/size.link b/doc/examples/size.link index 4cc0afa10..9b546197a 100644 --- a/doc/examples/size.link +++ b/doc/examples/size.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/swap__array_t.link b/doc/examples/swap__array_t.link index 8851fdb98..1dca248ca 100644 --- a/doc/examples/swap__array_t.link +++ b/doc/examples/swap__array_t.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/swap__object_t.link b/doc/examples/swap__object_t.link index 8bd7495ef..cdb94e9a6 100644 --- a/doc/examples/swap__object_t.link +++ b/doc/examples/swap__object_t.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/swap__reference.link b/doc/examples/swap__reference.link index 3ee4c0df8..262d1961c 100644 --- a/doc/examples/swap__reference.link +++ b/doc/examples/swap__reference.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/swap__string_t.link b/doc/examples/swap__string_t.link index 719313695..f3c448d50 100644 --- a/doc/examples/swap__string_t.link +++ b/doc/examples/swap__string_t.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/type.link b/doc/examples/type.link index f197d847a..ccb4422b0 100644 --- a/doc/examples/type.link +++ b/doc/examples/type.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/unflatten.cpp b/doc/examples/unflatten.cpp new file mode 100644 index 000000000..e2b9b6b86 --- /dev/null +++ b/doc/examples/unflatten.cpp @@ -0,0 +1,24 @@ +#include + +using json = nlohmann::json; + +int main() +{ + // create JSON value + json j_flattened = + { + {"/answer/everything", 42}, + {"/happy", true}, + {"/list/0", 1}, + {"/list/1", 0}, + {"/list/2", 2}, + {"/name", "Niels"}, + {"/nothing", nullptr}, + {"/object/currency", "USD"}, + {"/object/value", 42.99}, + {"/pi", 3.141} + }; + + // call unflatten() + std::cout << std::setw(4) << j_flattened.unflatten() << '\n'; +} diff --git a/doc/examples/unflatten.link b/doc/examples/unflatten.link new file mode 100644 index 000000000..386f5e11f --- /dev/null +++ b/doc/examples/unflatten.link @@ -0,0 +1 @@ +online \ No newline at end of file diff --git a/doc/examples/unflatten.output b/doc/examples/unflatten.output new file mode 100644 index 000000000..ed48385ae --- /dev/null +++ b/doc/examples/unflatten.output @@ -0,0 +1,18 @@ +{ + "answer": { + "everything": 42 + }, + "happy": true, + "list": [ + 1, + 0, + 2 + ], + "name": "Niels", + "nothing": null, + "object": { + "currency": "USD", + "value": 42.99 + }, + "pi": 3.141 +} diff --git a/doc/json.gif b/doc/json.gif index 7cd943717..7edafe018 100644 Binary files a/doc/json.gif and b/doc/json.gif differ diff --git a/src/json.hpp b/src/json.hpp index 8f671fb01..b02a1344d 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -198,6 +198,9 @@ class basic_json AllocatorType>; public: + // forward declarations + template class json_reverse_iterator; + class json_pointer; ///////////////////// // container types // @@ -227,9 +230,6 @@ class basic_json /// the type of an element const pointer using const_pointer = typename std::allocator_traits::const_pointer; - // forward declaration - template class json_reverse_iterator; - /// an iterator for a basic_json container class iterator; /// a const iterator for a basic_json container @@ -271,8 +271,8 @@ class basic_json @tparam ObjectType the container to store objects (e.g., `std::map` or `std::unordered_map`) - @tparam StringType the type of the keys or names (e.g., `std::string`). The - comparison function `std::less` is used to order elements + @tparam StringType the type of the keys or names (e.g., `std::string`). + The comparison function `std::less` is used to order elements inside the container. @tparam AllocatorType the allocator to use for objects (e.g., `std::allocator`) @@ -280,8 +280,8 @@ class basic_json #### Default type With the default values for @a ObjectType (`std::map`), @a StringType - (`std::string`), and @a AllocatorType (`std::allocator`), the default value - for @a object_t is: + (`std::string`), and @a AllocatorType (`std::allocator`), the default + value for @a object_t is: @code {.cpp} std::map< @@ -298,16 +298,16 @@ class basic_json the default type, objects have the following behavior: - When all names are unique, objects will be interoperable in the sense - that all software implementations receiving that object will agree on the - name-value mappings. + that all software implementations receiving that object will agree on + the name-value mappings. - When the names within an object are not unique, later stored name/value pairs overwrite previously stored name/value pairs, leaving the used names unique. For instance, `{"key": 1}` and `{"key": 2, "key": 1}` will be treated as equal and both stored as `{"key": 1}`. - Internally, name/value pairs are stored in lexicographical order of the - names. Objects will also be serialized (see @ref dump) in this order. For - instance, `{"b": 1, "a": 2}` and `{"a": 2, "b": 1}` will be stored and - serialized as `{"a": 2, "b": 1}`. + names. Objects will also be serialized (see @ref dump) in this order. + For instance, `{"b": 1, "a": 2}` and `{"a": 2, "b": 1}` will be stored + and serialized as `{"a": 2, "b": 1}`. - When comparing objects, the order of the name/value pairs is irrelevant. This makes objects interoperable in the sense that they will not be affected by these differences. For instance, `{"b": 1, "a": 2}` and @@ -320,23 +320,24 @@ class basic_json In this class, the object's limit of nesting is not constraint explicitly. However, a maximum depth of nesting may be introduced by the compiler or - runtime environment. A theoretical limit can be queried by calling the @ref - max_size function of a JSON object. + runtime environment. A theoretical limit can be queried by calling the + @ref max_size function of a JSON object. #### Storage Objects are stored as pointers in a @ref basic_json type. That is, for any - access to object values, a pointer of type `object_t*` must be dereferenced. + access to object values, a pointer of type `object_t*` must be + dereferenced. @sa @ref array_t -- type for an array value @since version 1.0.0 - @note The order name/value pairs are added to the object is *not* preserved - by the library. Therefore, iterating an object may return name/value pairs - in a different order than they were originally stored. In fact, keys will - be traversed in alphabetical order as `std::map` with `std::less` is used - by default. Please note this behavior conforms to [RFC + @note The order name/value pairs are added to the object is *not* + preserved by the library. Therefore, iterating an object may return + name/value pairs in a different order than they were originally stored. In + fact, keys will be traversed in alphabetical order as `std::map` with + `std::less` is used by default. Please note this behavior conforms to [RFC 7159](http://rfc7159.net/rfc7159), because any order implements the specified "unordered" nature of JSON objects. */ @@ -378,8 +379,8 @@ class basic_json In this class, the array's limit of nesting is not constraint explicitly. However, a maximum depth of nesting may be introduced by the compiler or - runtime environment. A theoretical limit can be queried by calling the @ref - max_size function of a JSON array. + runtime environment. A theoretical limit can be queried by calling the + @ref max_size function of a JSON array. #### Storage @@ -399,8 +400,8 @@ class basic_json > A string is a sequence of zero or more Unicode characters. To store objects in C++, a type is defined by the template parameter - described below. Unicode values are split by the JSON class into byte-sized - characters during deserialization. + described below. Unicode values are split by the JSON class into + byte-sized characters during deserialization. @tparam StringType the container to store strings (e.g., `std::string`). Note this container is used for keys/names in objects, see @ref object_t. @@ -469,19 +470,19 @@ class basic_json @brief a type for a number (integer) [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: - > The representation of numbers is similar to that used in most programming - > languages. A number is represented in base 10 using decimal digits. It - > contains an integer component that may be prefixed with an optional minus - > sign, which may be followed by a fraction part and/or an exponent part. - > Leading zeros are not allowed. (...) Numeric values that cannot be - > represented in the grammar below (such as Infinity and NaN) are not - > permitted. + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. - This description includes both integer and floating-point numbers. However, - C++ allows more precise storage if it is known whether the number is a - signed integer, an unsigned integer or a floating-point number. Therefore, - three different types, @ref number_integer_t, @ref number_unsigned_t and - @ref number_float_t are used. + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. To store integer numbers in C++, a type is defined by the template parameter @a NumberIntegerType which chooses the type to use. @@ -500,8 +501,8 @@ class basic_json - The restrictions about leading zeros is not enforced in C++. Instead, leading zeros in integer literals lead to an interpretation as octal number. Internally, the value will be stored as decimal number. For - instance, the C++ integer literal `010` will be serialized to `8`. During - deserialization, leading zeros yield an error. + instance, the C++ integer literal `010` will be serialized to `8`. + During deserialization, leading zeros yield an error. - Not-a-number (NaN) values will be serialized to `null`. #### Limits @@ -512,9 +513,10 @@ class basic_json When the default type is used, the maximal integer number that can be stored is `9223372036854775807` (INT64_MAX) and the minimal integer number that can be stored is `-9223372036854775808` (INT64_MIN). Integer numbers - that are out of range will yield over/underflow when used in a constructor. - During deserialization, too large or small integer numbers will be - automatically be stored as @ref number_unsigned_t or @ref number_float_t. + that are out of range will yield over/underflow when used in a + constructor. During deserialization, too large or small integer numbers + will be automatically be stored as @ref number_unsigned_t or @ref + number_float_t. [RFC 7159](http://rfc7159.net/rfc7159) further states: > Note that when such software is used, numbers that are integers and are @@ -540,27 +542,27 @@ class basic_json @brief a type for a number (unsigned) [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: - > The representation of numbers is similar to that used in most programming - > languages. A number is represented in base 10 using decimal digits. It - > contains an integer component that may be prefixed with an optional minus - > sign, which may be followed by a fraction part and/or an exponent part. - > Leading zeros are not allowed. (...) Numeric values that cannot be - > represented in the grammar below (such as Infinity and NaN) are not - > permitted. + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. - This description includes both integer and floating-point numbers. However, - C++ allows more precise storage if it is known whether the number is a - signed integer, an unsigned integer or a floating-point number. Therefore, - three different types, @ref number_integer_t, @ref number_unsigned_t and - @ref number_float_t are used. + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. - To store unsigned integer numbers in C++, a type is defined by the template - parameter @a NumberUnsignedType which chooses the type to use. + To store unsigned integer numbers in C++, a type is defined by the + template parameter @a NumberUnsignedType which chooses the type to use. #### Default type - With the default values for @a NumberUnsignedType (`uint64_t`), the default - value for @a number_unsigned_t is: + With the default values for @a NumberUnsignedType (`uint64_t`), the + default value for @a number_unsigned_t is: @code {.cpp} uint64_t @@ -571,8 +573,8 @@ class basic_json - The restrictions about leading zeros is not enforced in C++. Instead, leading zeros in integer literals lead to an interpretation as octal number. Internally, the value will be stored as decimal number. For - instance, the C++ integer literal `010` will be serialized to `8`. During - deserialization, leading zeros yield an error. + instance, the C++ integer literal `010` will be serialized to `8`. + During deserialization, leading zeros yield an error. - Not-a-number (NaN) values will be serialized to `null`. #### Limits @@ -612,19 +614,19 @@ class basic_json @brief a type for a number (floating-point) [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: - > The representation of numbers is similar to that used in most programming - > languages. A number is represented in base 10 using decimal digits. It - > contains an integer component that may be prefixed with an optional minus - > sign, which may be followed by a fraction part and/or an exponent part. - > Leading zeros are not allowed. (...) Numeric values that cannot be - > represented in the grammar below (such as Infinity and NaN) are not - > permitted. + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. - This description includes both integer and floating-point numbers. However, - C++ allows more precise storage if it is known whether the number is a - signed integer, an unsigned integer or a floating-point number. Therefore, - three different types, @ref number_integer_t, @ref number_unsigned_t and - @ref number_float_t are used. + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. To store floating-point numbers in C++, a type is defined by the template parameter @a NumberFloatType which chooses the type to use. @@ -641,8 +643,8 @@ class basic_json #### Default behavior - The restrictions about leading zeros is not enforced in C++. Instead, - leading zeros in floating-point literals will be ignored. Internally, the - value will be stored as decimal number. For instance, the C++ + leading zeros in floating-point literals will be ignored. Internally, + the value will be stored as decimal number. For instance, the C++ floating-point literal `01.2` will be serialized to `1.2`. During deserialization, leading zeros yield an error. - Not-a-number (NaN) values will be serialized to `null`. @@ -653,9 +655,9 @@ class basic_json > This specification allows implementations to set limits on the range and > precision of numbers accepted. Since software that implements IEEE > 754-2008 binary64 (double precision) numbers is generally available and - > widely used, good interoperability can be achieved by implementations that - > expect no more precision or range than these provide, in the sense that - > implementations will approximate JSON numbers within the expected + > widely used, good interoperability can be achieved by implementations + > that expect no more precision or range than these provide, in the sense + > that implementations will approximate JSON numbers within the expected > precision. This implementation does exactly follow this approach, as it uses double @@ -709,73 +711,6 @@ class basic_json private: - /*! - @brief a type to hold JSON type information - - This bitfield type holds information about JSON types. It is internally - used to hold the basic JSON type enumeration, as well as additional - information in the case of values that have been parsed from a string - including whether of not it was created directly or parsed, and in the - case of floating point numbers the number of significant figures in the - original representaiton and if it was in exponential form, if a '+' was - included in the exponent and the capitilization of the exponent marker. - The sole purpose of this information is to permit accurate round trips. - - @since version 2.0.0 - */ - union type_data_t - { - struct - { - /// the type of the value (@ref value_t) - uint16_t type : 4; - /// whether the number was parsed from a string - uint16_t parsed : 1; - /// whether parsed number contained an exponent ('e'/'E') - uint16_t has_exp : 1; - /// whether parsed number contained a plus in the exponent - uint16_t exp_plus : 1; - /// whether parsed number's exponent was capitalized ('E') - uint16_t exp_cap : 1; - /// the number of figures for a parsed number - uint16_t precision : 8; - } bits; - uint16_t data; - - /// return the type as value_t - operator value_t() const - { - return static_cast(bits.type); - } - - /// test type for equality (ignore other fields) - bool operator==(const value_t& rhs) const - { - return static_cast(bits.type) == rhs; - } - - /// assignment - type_data_t& operator=(value_t rhs) - { - bits.type = static_cast(rhs); - return *this; - } - - /// construct from value_t - type_data_t(value_t t) noexcept - { - *reinterpret_cast(this) = 0; - bits.type = static_cast(t); - } - - /// default constructor - type_data_t() noexcept - { - data = 0; - bits.type = reinterpret_cast(value_t::null); - } - }; - /// helper for exception-safe object creation template static T* create(Args&& ... args) @@ -938,9 +873,9 @@ class basic_json influenced. When passed to @ref parse(std::istream&, parser_callback_t) or @ref parse(const string_t&, parser_callback_t), it is called on certain events (passed as @ref parse_event_t via parameter @a event) with a set - recursion depth @a depth and context JSON value @a parsed. The return value - of the callback function is a boolean indicating whether the element that - emitted the callback shall be kept or not. + recursion depth @a depth and context JSON value @a parsed. The return + value of the callback function is a boolean indicating whether the element + that emitted the callback shall be kept or not. We distinguish six scenarios (determined by the event type) in which the callback function can be called. The following table describes the values @@ -960,8 +895,8 @@ class basic_json - Discarded values in structured types are skipped. That is, the parser will behave as if the discarded value was never read. - - In case a value outside a structured type is skipped, it is replaced with - `null`. This case happens if the top-level element is skipped. + - In case a value outside a structured type is skipped, it is replaced + with `null`. This case happens if the top-level element is skipped. @param[in] depth the depth of the recursion during parsing @@ -1065,8 +1000,8 @@ class basic_json Create a `null` JSON value. This is the explicitly version of the `null` value constructor as it takes a null pointer as parameter. It allows to create `null` values by explicitly assigning a `nullptr` to a JSON value. - The passed null pointer itself is not read -- it is only used to choose the - right constructor. + The passed null pointer itself is not read -- it is only used to choose + the right constructor. @complexity Constant. @@ -1096,8 +1031,8 @@ class basic_json @throw std::bad_alloc if allocation for object value fails - @liveexample{The following code shows the constructor with an @ref object_t - parameter.,basic_json__object_t} + @liveexample{The following code shows the constructor with an @ref + object_t parameter.,basic_json__object_t} @sa @ref basic_json(const CompatibleObjectType&) -- create an object value from a compatible STL container @@ -1177,11 +1112,11 @@ class basic_json any type @a CompatibleArrayType that can be used to construct values of type @ref array_t. - @tparam CompatibleArrayType An object type whose `value_type` is compatible - to @ref array_t. Examples include `std::vector`, `std::deque`, `std::list`, - `std::forward_list`, `std::array`, `std::set`, `std::unordered_set`, - `std::multiset`, and `unordered_multiset` with a `value_type` from which a - @ref basic_json value can be constructed. + @tparam CompatibleArrayType An object type whose `value_type` is + compatible to @ref array_t. Examples include `std::vector`, `std::deque`, + `std::list`, `std::forward_list`, `std::array`, `std::set`, + `std::unordered_set`, `std::multiset`, and `unordered_multiset` with a + `value_type` from which a @ref basic_json value can be constructed. @param[in] val a value for the array @@ -1225,8 +1160,8 @@ class basic_json @throw std::bad_alloc if allocation for string value fails - @liveexample{The following code shows the constructor with an @ref string_t - parameter.,basic_json__string_t} + @liveexample{The following code shows the constructor with an @ref + string_t parameter.,basic_json__string_t} @sa @ref basic_json(const typename string_t::value_type*) -- create a string value from a character pointer @@ -1318,9 +1253,9 @@ class basic_json Create an integer number JSON value with a given content. @tparam T A helper type to remove this function via SFINAE in case @ref - number_integer_t is the same as `int`. In this case, this constructor would - have the same signature as @ref basic_json(const int value). Note the - helper type @a T is not visible in this constructor's interface. + number_integer_t is the same as `int`. In this case, this constructor + would have the same signature as @ref basic_json(const int value). Note + the helper type @a T is not visible in this constructor's interface. @param[in] val an integer to create a JSON number from @@ -1355,8 +1290,8 @@ class basic_json @note This constructor allows to pass enums directly to a constructor. As C++ has no way of specifying the type of an anonymous enum explicitly, we can only rely on the fact that such values implicitly convert to int. As - int may already be the same type of number_integer_t, we may need to switch - off the constructor @ref basic_json(const number_integer_t). + int may already be the same type of number_integer_t, we may need to + switch off the constructor @ref basic_json(const number_integer_t). @complexity Constant. @@ -1383,8 +1318,8 @@ class basic_json construct values of type @ref number_integer_t. @tparam CompatibleNumberIntegerType An integer type which is compatible to - @ref number_integer_t. Examples include the types `int`, `int32_t`, `long`, - and `short`. + @ref number_integer_t. Examples include the types `int`, `int32_t`, + `long`, and `short`. @param[in] val an integer to create a JSON number from @@ -1442,12 +1377,12 @@ class basic_json /*! @brief create an unsigned number (implicit) - Create an unsigned number JSON value with a given content. This constructor - allows any type @a CompatibleNumberUnsignedType that can be used to - construct values of type @ref number_unsigned_t. + Create an unsigned number JSON value with a given content. This + constructor allows any type @a CompatibleNumberUnsignedType that can be + used to construct values of type @ref number_unsigned_t. - @tparam CompatibleNumberUnsignedType An integer type which is compatible to - @ref number_unsigned_t. Examples may include the types `unsigned int`, + @tparam CompatibleNumberUnsignedType An integer type which is compatible + to @ref number_unsigned_t. Examples may include the types `unsigned int`, `uint32_t`, or `unsigned short`. @param[in] val an unsigned integer to create a JSON number from @@ -1459,13 +1394,13 @@ class basic_json @since version 2.0.0 */ - template < typename CompatibleNumberUnsignedType, typename - std::enable_if < - std::is_constructible::value and - std::numeric_limits::is_integer and - !std::numeric_limits::is_signed, - CompatibleNumberUnsignedType >::type - = 0 > + template ::value and + std::numeric_limits::is_integer and + not std::numeric_limits::is_signed, + CompatibleNumberUnsignedType>::type + = 0> basic_json(const CompatibleNumberUnsignedType val) noexcept : m_type(value_t::number_unsigned), m_value(static_cast(val)) @@ -1480,8 +1415,8 @@ class basic_json @note [RFC 7159](http://www.rfc-editor.org/rfc/rfc7159.txt), section 6 disallows NaN values: - > Numeric values that cannot be represented in the grammar below (such - > as Infinity and NaN) are not permitted. + > Numeric values that cannot be represented in the grammar below (such as + > Infinity and NaN) are not permitted. In case the parameter @a val is not a number, a JSON null value is created instead. @@ -1513,15 +1448,16 @@ class basic_json constructor allows any type @a CompatibleNumberFloatType that can be used to construct values of type @ref number_float_t. - @tparam CompatibleNumberFloatType A floating-point type which is compatible - to @ref number_float_t. Examples may include the types `float` or `double`. + @tparam CompatibleNumberFloatType A floating-point type which is + compatible to @ref number_float_t. Examples may include the types `float` + or `double`. @param[in] val a floating-point to create a JSON number from @note [RFC 7159](http://www.rfc-editor.org/rfc/rfc7159.txt), section 6 disallows NaN values: - > Numeric values that cannot be represented in the grammar below (such - > as Infinity and NaN) are not permitted. + > Numeric values that cannot be represented in the grammar below (such as + > Infinity and NaN) are not permitted. In case the parameter @a val is not a number, a JSON null value is created instead. @@ -1592,13 +1528,13 @@ class basic_json used by the functions @ref array(std::initializer_list) and @ref object(std::initializer_list). - @param[in] manual_type internal parameter; when @a type_deduction is set to - `false`, the created JSON value will use the provided type (only @ref + @param[in] manual_type internal parameter; when @a type_deduction is set + to `false`, the created JSON value will use the provided type (only @ref value_t::array and @ref value_t::object are valid); when @a type_deduction is set to `true`, this parameter has no effect - @throw std::domain_error if @a type_deduction is `false`, @a manual_type is - `value_t::object`, but @a init contains an element which is not a pair + @throw std::domain_error if @a type_deduction is `false`, @a manual_type + is `value_t::object`, but @a init contains an element which is not a pair whose first element is a string; example: `"cannot create object from initializer list"` @@ -1679,8 +1615,8 @@ class basic_json list of values `a, b, c`, creates the JSON value `[a, b, c]`. If the initializer list is empty, the empty array `[]` is created. - @note This function is only needed to express two edge cases that cannot be - realized with the initializer list constructor (@ref + @note This function is only needed to express two edge cases that cannot + be realized with the initializer list constructor (@ref basic_json(std::initializer_list, bool, value_t)). These cases are: 1. creating an array whose elements are all pairs whose first element is a @@ -1723,8 +1659,8 @@ class basic_json related function @ref array(std::initializer_list), there are no cases which can only be expressed by this function. That is, any initializer list @a init can also be passed to the initializer list - constructor - @ref basic_json(std::initializer_list, bool, value_t). + constructor @ref basic_json(std::initializer_list, bool, + value_t). @param[in] init initializer list to create an object from (optional) @@ -1755,8 +1691,8 @@ class basic_json /*! @brief construct an array with count copies of given value - Constructs a JSON array value by creating @a cnt copies of a passed - value. In case @a cnt is `0`, an empty array is created. As postcondition, + Constructs a JSON array value by creating @a cnt copies of a passed value. + In case @a cnt is `0`, an empty array is created. As postcondition, `std::distance(begin(),end()) == cnt` holds. @param[in] cnt the number of JSON copies of @a val to create @@ -1784,8 +1720,8 @@ class basic_json - In case of primitive types (number, boolean, or string), @a first must be `begin()` and @a last must be `end()`. In this case, the value is copied. Otherwise, std::out_of_range is thrown. - - In case of structured types (array, object), the constructor behaves - as similar versions for `std::vector`. + - In case of structured types (array, object), the constructor behaves as + similar versions for `std::vector`. - In case of a null type, std::domain_error is thrown. @tparam InputIT an input iterator type (@ref iterator or @ref @@ -1800,8 +1736,8 @@ class basic_json boolean, or string) where an out of range error can be detected easily; example: `"iterators out of range"` @throw std::bad_alloc if allocation for object, array, or string fails - @throw std::domain_error if called with a null value; example: `"cannot use - construct with iterators from null"` + @throw std::domain_error if called with a null value; example: `"cannot + use construct with iterators from null"` @complexity Linear in distance between @a first and @a last. @@ -2042,8 +1978,8 @@ class basic_json @brief copy assignment Copy assignment operator. Copies a JSON value via the "copy and swap" - strategy: It is expressed in terms of the copy constructor, destructor, and - the swap() member function. + strategy: It is expressed in terms of the copy constructor, destructor, + and the swap() member function. @param[in] other value to copy from @@ -2143,8 +2079,8 @@ class basic_json parameter. @param[in] indent if indent is nonnegative, then array elements and object - members will be pretty-printed with that indent level. An indent level of 0 - will only insert newlines. -1 (the default) selects the most compact + members will be pretty-printed with that indent level. An indent level of + 0 will only insert newlines. -1 (the default) selects the most compact representation @return string containing the serialization of the JSON value @@ -2976,8 +2912,8 @@ class basic_json /*! @brief get a reference value (implicit) - Implict reference access to the internally stored JSON value. No copies are - made. + Implict reference access to the internally stored JSON value. No copies + are made. @warning Writing data to the referee of the result yields an undefined state. @@ -3027,14 +2963,14 @@ class basic_json /*! @brief get a value (implicit) - Implicit type conversion between the JSON value and a compatible value. The - call is realized by calling @ref get() const. + Implicit type conversion between the JSON value and a compatible value. + The call is realized by calling @ref get() const. @tparam ValueType non-pointer type compatible to the JSON value, for instance `int` for JSON integer numbers, `bool` for JSON booleans, or - `std::vector` types for JSON arrays. The character type of @ref string_t as - well as an initializer list of this type is excluded to avoid ambiguities - as these types implicitly convert to `std::string`. + `std::vector` types for JSON arrays. The character type of @ref string_t + as well as an initializer list of this type is excluded to avoid + ambiguities as these types implicitly convert to `std::string`. @return copy of the JSON value, converted to type @a ValueType @@ -3123,8 +3059,8 @@ class basic_json /*! @brief access specified array element with bounds checking - Returns a const reference to the element at specified location @a idx, with - bounds checking. + Returns a const reference to the element at specified location @a idx, + with bounds checking. @param[in] idx index of the element to access @@ -3215,8 +3151,8 @@ class basic_json /*! @brief access specified object element with bounds checking - Returns a const reference to the element at with specified key @a key, with - bounds checking. + Returns a const reference to the element at with specified key @a key, + with bounds checking. @param[in] key key of the element to access @@ -3273,8 +3209,8 @@ class basic_json @return reference to the element at index @a idx - @throw std::domain_error if JSON is not an array or null; example: `"cannot - use operator[] with string"` + @throw std::domain_error if JSON is not an array or null; example: + `"cannot use operator[] with string"` @complexity Constant if @a idx is in the range of the array. Otherwise linear in `idx - size()`. @@ -3690,8 +3626,8 @@ class basic_json @complexity Constant. - @pre The JSON value must not be `null` (would throw `std::out_of_range`) or - an empty array or object (undefined behavior, guarded by assertions). + @pre The JSON value must not be `null` (would throw `std::out_of_range`) + or an empty array or object (undefined behavior, guarded by assertions). @post The JSON value remains unchanged. @throw std::out_of_range when called on `null` value @@ -3732,8 +3668,8 @@ class basic_json @complexity Constant. - @pre The JSON value must not be `null` (would throw `std::out_of_range`) or - an empty array or object (undefined behavior, guarded by assertions). + @pre The JSON value must not be `null` (would throw `std::out_of_range`) + or an empty array or object (undefined behavior, guarded by assertions). @post The JSON value remains unchanged. @throw std::out_of_range when called on `null` value. @@ -3772,16 +3708,16 @@ class basic_json will be `null`. @param[in] pos iterator to the element to remove - @return Iterator following the last removed element. If the iterator @a pos - refers to the last element, the `end()` iterator is returned. + @return Iterator following the last removed element. If the iterator @a + pos refers to the last element, the `end()` iterator is returned. @tparam InteratorType an @ref iterator or @ref const_iterator @post Invalidates iterators and references at or after the point of the erase, including the `end()` iterator. - @throw std::domain_error if called on a `null` value; example: `"cannot use - erase() with null"` + @throw std::domain_error if called on a `null` value; example: `"cannot + use erase() with null"` @throw std::domain_error if called on an iterator which does not belong to the current JSON value; example: `"iterator does not fit current value"` @throw std::out_of_range if called on a primitive type with invalid @@ -3797,12 +3733,12 @@ class basic_json @liveexample{The example shows the result of `erase()` for different JSON types.,erase__IteratorType} - @sa @ref erase(InteratorType, InteratorType) -- removes the elements in the - given range + @sa @ref erase(InteratorType, InteratorType) -- removes the elements in + the given range @sa @ref erase(const typename object_t::key_type&) -- removes the element from an object at the given key - @sa @ref erase(const size_type) -- removes the element from an array at the - given index + @sa @ref erase(const size_type) -- removes the element from an array at + the given index @since version 1.0.0 */ @@ -3871,9 +3807,9 @@ class basic_json /*! @brief remove elements given an iterator range - Removes the element specified by the range `[first; last)`. The iterator @a - first does not need to be dereferenceable if `first == last`: erasing an - empty range is a no-op. + Removes the element specified by the range `[first; last)`. The iterator + @a first does not need to be dereferenceable if `first == last`: erasing + an empty range is a no-op. If called on a primitive type other than `null`, the resulting JSON value will be `null`. @@ -3888,8 +3824,8 @@ class basic_json @post Invalidates iterators and references at or after the point of the erase, including the `end()` iterator. - @throw std::domain_error if called on a `null` value; example: `"cannot use - erase() with null"` + @throw std::domain_error if called on a `null` value; example: `"cannot + use erase() with null"` @throw std::domain_error if called on iterators which does not belong to the current JSON value; example: `"iterators do not fit current value"` @throw std::out_of_range if called on a primitive type with invalid @@ -3909,8 +3845,8 @@ class basic_json @sa @ref erase(InteratorType) -- removes the element at a given position @sa @ref erase(const typename object_t::key_type&) -- removes the element from an object at the given key - @sa @ref erase(const size_type) -- removes the element from an array at the - given index + @sa @ref erase(const size_type) -- removes the element from an array at + the given index @since version 1.0.0 */ @@ -3986,8 +3922,8 @@ class basic_json @param[in] key value of the elements to remove @return Number of elements removed. If @a ObjectType is the default - `std::map` type, the return value will always be `0` (@a key was not found) - or `1` (@a key was found). + `std::map` type, the return value will always be `0` (@a key was not + found) or `1` (@a key was found). @post References and iterators to the erased elements are invalidated. Other references and iterators are not affected. @@ -4000,10 +3936,10 @@ class basic_json @liveexample{The example shows the effect of `erase()`.,erase__key_type} @sa @ref erase(InteratorType) -- removes the element at a given position - @sa @ref erase(InteratorType, InteratorType) -- removes the elements in the - given range - @sa @ref erase(const size_type) -- removes the element from an array at the - given index + @sa @ref erase(InteratorType, InteratorType) -- removes the elements in + the given range + @sa @ref erase(const size_type) -- removes the element from an array at + the given index @since version 1.0.0 */ @@ -4030,16 +3966,16 @@ class basic_json @throw std::domain_error when called on a type other than JSON array; example: `"cannot use erase() with null"` - @throw std::out_of_range when `idx >= size()`; example: `"index out of - range"` + @throw std::out_of_range when `idx >= size()`; example: `"array index 17 + is out of range"` @complexity Linear in distance between @a idx and the end of the container. @liveexample{The example shows the effect of `erase()`.,erase__size_type} @sa @ref erase(InteratorType) -- removes the element at a given position - @sa @ref erase(InteratorType, InteratorType) -- removes the elements in the - given range + @sa @ref erase(InteratorType, InteratorType) -- removes the elements in + the given range @sa @ref erase(const typename object_t::key_type&) -- removes the element from an object at the given key @@ -4052,7 +3988,7 @@ class basic_json { if (idx >= size()) { - throw std::out_of_range("index out of range"); + throw std::out_of_range("array index " + std::to_string(idx) + " is out of range"); } assert(m_value.array != nullptr); @@ -4078,7 +4014,8 @@ class basic_json @brief find an element in a JSON object Finds an element in a JSON object with key equivalent to @a key. If the - element is not found or the JSON value is not an object, end() is returned. + element is not found or the JSON value is not an object, end() is + returned. @param[in] key key value of the element to search for @@ -4438,9 +4375,9 @@ class basic_json @brief wrapper to access iterator member functions in range-based for This function allows to access @ref iterator::key() and @ref - iterator::value() during range-based for loops. In these loops, a reference - to the JSON values is returned, so there is no access to the underlying - iterator. + iterator::value() during range-based for loops. In these loops, a + reference to the JSON values is returned, so there is no access to the + underlying iterator. @note The name of this function is not yet final and may change in the future. @@ -4484,8 +4421,8 @@ class basic_json object | result of function `object_t::empty()` array | result of function `array_t::empty()` - @complexity Constant, as long as @ref array_t and @ref object_t satisfy the - Container concept; that is, their `empty()` functions have constant + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their `empty()` functions have constant complexity. @requirement This function helps `basic_json` satisfying the @@ -4547,8 +4484,9 @@ class basic_json object | result of function object_t::size() array | result of function array_t::size() - @complexity Constant, as long as @ref array_t and @ref object_t satisfy the - Container concept; that is, their size() functions have constant complexity. + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their size() functions have constant + complexity. @requirement This function helps `basic_json` satisfying the [Container](http://en.cppreference.com/w/cpp/concept/Container) @@ -4612,8 +4550,8 @@ class basic_json object | result of function `object_t::max_size()` array | result of function `array_t::max_size()` - @complexity Constant, as long as @ref array_t and @ref object_t satisfy the - Container concept; that is, their `max_size()` functions have constant + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their `max_size()` functions have constant complexity. @requirement This function helps `basic_json` satisfying the @@ -4835,8 +4773,8 @@ class basic_json @brief add an object to an object Inserts the given element @a val to the JSON object. If the function is - called on a JSON null value, an empty object is created before inserting @a - val. + called on a JSON null value, an empty object is created before inserting + @a val. @param[in] val the value to add to the JSON object @@ -4878,7 +4816,55 @@ class basic_json reference operator+=(const typename object_t::value_type& val) { push_back(val); - return operator[](val.first); + return *this; + } + + /*! + @brief add an object to an object + + This function allows to use `push_back` with an initializer list. In case + + 1. the current value is an object, + 2. the initializer list @a init contains only two elements, and + 3. the first element of @a init is a string, + + @a init is converted into an object element and added using + @ref push_back(const typename object_t::value_type&). Otherwise, @a init + is converted to a JSON value and added using @ref push_back(basic_json&&). + + @param init an initializer list + + @complexity Linear in the size of the initializer list @a init. + + @note This function is required to resolve an ambiguous overload error, + because pairs like `{"key", "value"}` can be both interpreted as + `object_t::value_type` or `std::initializer_list`, see + https://github.com/nlohmann/json/issues/235 for more information. + + @liveexample{The example shows how initializer lists are treated as + objects when possible.,push_back__initializer_list} + */ + void push_back(std::initializer_list init) + { + if (is_object() and init.size() == 2 and init.begin()->is_string()) + { + const string_t key = *init.begin(); + push_back(typename object_t::value_type(key, *(init.begin() + 1))); + } + else + { + push_back(basic_json(init)); + } + } + + /*! + @brief add an object to an object + @copydoc push_back(std::initializer_list) + */ + reference operator+=(std::initializer_list init) + { + push_back(init); + return *this; } /*! @@ -5026,6 +5012,7 @@ class basic_json throw std::domain_error("iterator does not fit current value"); } + // check if range iterators belong to the same JSON object if (first.m_object != last.m_object) { throw std::domain_error("iterators do not fit"); @@ -5063,8 +5050,8 @@ class basic_json @return iterator pointing to the first element inserted, or @a pos if `ilist` is empty - @complexity Linear in `ilist.size()` plus linear in the distance between @a - pos and end of the container. + @complexity Linear in `ilist.size()` plus linear in the distance between + @a pos and end of the container. @liveexample{The example shows how `insert()` is used.,insert__ilist} @@ -5709,8 +5696,8 @@ class basic_json @note A UTF-8 byte order mark is silently ignored. - @liveexample{The example below demonstrates the `parse()` function with and - without callback function.,parse__string__parser_callback_t} + @liveexample{The example below demonstrates the `parse()` function with + and without callback function.,parse__string__parser_callback_t} @sa @ref parse(std::istream&, parser_callback_t) for a version that reads from an input stream @@ -5738,11 +5725,11 @@ class basic_json @note A UTF-8 byte order mark is silently ignored. - @liveexample{The example below demonstrates the `parse()` function with and - without callback function.,parse__istream__parser_callback_t} + @liveexample{The example below demonstrates the `parse()` function with + and without callback function.,parse__istream__parser_callback_t} - @sa @ref parse(const string_t&, parser_callback_t) for a version that reads - from a string + @sa @ref parse(const string_t&, parser_callback_t) for a version that + reads from a string @since version 1.0.0 */ @@ -5875,8 +5862,8 @@ class basic_json /*! @brief escape a string - Escape a string by replacing certain special characters by a sequence of an - escape character (backslash) and another character and other control + Escape a string by replacing certain special characters by a sequence of + an escape character (backslash) and another character and other control characters by a sequence of "\u" followed by a four-digit hex representation. @@ -5963,9 +5950,11 @@ class basic_json { // convert a number 0..15 to its hex representation // (0..f) - auto hexify = [](const char v) -> char + const auto hexify = [](const int v) -> char { - return (v < 10) ? ('0' + v) : ('a' + v - 10); + return (v < 10) + ? ('0' + static_cast(v)) + : ('a' + static_cast((v - 10) & 0x1f)); }; // print character c as \uxxxx @@ -5996,8 +5985,8 @@ class basic_json This function is called by the public member function dump and organizes the serialization internally. The indentation level is propagated as - additional parameter. In case of arrays and objects, the function is called - recursively. Note that + additional parameter. In case of arrays and objects, the function is + called recursively. Note that - strings and object keys are escaped using `escape_string()` - integer numbers are converted implicitly via `operator<<` @@ -6127,79 +6116,23 @@ class basic_json case value_t::number_float: { - // check if number was parsed from a string - if (m_type.bits.parsed) + if (m_value.number_float == 0) { - // check if parsed number had an exponent given - if (m_type.bits.has_exp) - { - // buffer size: precision (2^8-1 = 255) + other ('-.e-xxx' = 7) + null (1) - char buf[263]; - int len; - - // handle capitalization of the exponent - if (m_type.bits.exp_cap) - { - len = snprintf(buf, sizeof(buf), "%.*E", - m_type.bits.precision, m_value.number_float) + 1; - } - else - { - len = snprintf(buf, sizeof(buf), "%.*e", - m_type.bits.precision, m_value.number_float) + 1; - } - - // remove '+' sign from the exponent if necessary - if (not m_type.bits.exp_plus) - { - if (len > static_cast(sizeof(buf))) - { - len = sizeof(buf); - } - for (int i = 0; i < len; i++) - { - if (buf[i] == '+') - { - for (; i + 1 < len; i++) - { - buf[i] = buf[i + 1]; - } - } - } - } - - o << buf; - } - else - { - // no exponent - output as a decimal - std::stringstream ss; - ss.imbue(std::locale(std::locale(), new DecimalSeparator)); // fix locale problems - ss << std::setprecision(m_type.bits.precision) - << std::fixed << m_value.number_float; - o << ss.str(); - } + // special case for zero to get "0.0"/"-0.0" + o << (std::signbit(m_value.number_float) ? "-0.0" : "0.0"); } else { - if (m_value.number_float == 0) - { - // special case for zero to get "0.0"/"-0.0" - o << (std::signbit(m_value.number_float) ? "-0.0" : "0.0"); - } - else - { - // Otherwise 6, 15 or 16 digits of precision allows - // round-trip IEEE 754 string->float->string, - // string->double->string or string->long double->string; - // to be safe, we read this value from - // std::numeric_limits::digits10 - std::stringstream ss; - ss.imbue(std::locale(std::locale(), new DecimalSeparator)); // fix locale problems - ss << std::setprecision(std::numeric_limits::digits10) - << m_value.number_float; - o << ss.str(); - } + // Otherwise 6, 15 or 16 digits of precision allows + // round-trip IEEE 754 string->float->string, + // string->double->string or string->long + // double->string; to be safe, we read this value from + // std::numeric_limits::digits10 + std::stringstream ss; + ss.imbue(std::locale(std::locale(), new DecimalSeparator)); // fix locale problems + ss << std::setprecision(std::numeric_limits::digits10) + << m_value.number_float; + o << ss.str(); } return; } @@ -6224,7 +6157,7 @@ class basic_json ////////////////////// /// the type of the current element - type_data_t m_type = value_t::null; + value_t m_type = value_t::null; /// the value of the current element json_value m_value = {}; @@ -7000,13 +6933,13 @@ class basic_json } /// return a reference to the value pointed to by the iterator - reference operator*() + reference operator*() const { return const_cast(base_iterator::operator*()); } /// dereference the iterator - pointer operator->() + pointer operator->() const { return const_cast(base_iterator::operator->()); } @@ -7212,8 +7145,8 @@ class basic_json @brief lexical analysis This class organizes the lexical analysis during JSON deserialization. The - core of it is a scanner generated by [re2c](http://re2c.org) that processes - a buffer and recognizes tokens according to RFC 7159. + core of it is a scanner generated by [re2c](http://re2c.org) that + processes a buffer and recognizes tokens according to RFC 7159. */ class lexer { @@ -7222,17 +7155,17 @@ class basic_json enum class token_type { uninitialized, ///< indicating the scanner is uninitialized - literal_true, ///< the "true" literal - literal_false, ///< the "false" literal - literal_null, ///< the "null" literal + literal_true, ///< the `true` literal + literal_false, ///< the `false` literal + literal_null, ///< the `null` literal value_string, ///< a string -- use get_string() for actual value value_number, ///< a number -- use get_number() for actual value - begin_array, ///< the character for array begin "[" - begin_object, ///< the character for object begin "{" - end_array, ///< the character for array end "]" - end_object, ///< the character for object end "}" - name_separator, ///< the name separator ":" - value_separator, ///< the value separator "," + begin_array, ///< the character for array begin `[` + begin_object, ///< the character for object begin `{` + end_array, ///< the character for array end `]` + end_object, ///< the character for object end `}` + name_separator, ///< the name separator `:` + value_separator, ///< the value separator `,` parse_error, ///< indicating a parse error end_of_input ///< indicating the end of the input buffer }; @@ -7277,7 +7210,7 @@ class basic_json @return string representation of the code point - @throw std::out_of_range if code point is >0x10ffff; example: `"code + @throw std::out_of_range if code point is > 0x10ffff; example: `"code points above 0x10FFFF are invalid"` @throw std::invalid_argument if the low surrogate is invalid; example: `""missing or wrong low surrogate""` @@ -7416,322 +7349,323 @@ class basic_json { 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 32, 0, 0, 32, 0, 0, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 96, 64, 0, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 160, 128, 0, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 0, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, + 192, 192, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 0, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, }; if ((m_limit - m_cursor) < 5) { yyfill(); // LCOV_EXCL_LINE; } yych = *m_cursor; - if (yych <= ':') + if (yybm[0 + yych] & 32) { - if (yych <= ' ') + goto basic_json_parser_6; + } + if (yych <= '\\') + { + if (yych <= '-') { - if (yych <= '\n') + if (yych <= '"') { if (yych <= 0x00) { - goto basic_json_parser_28; + goto basic_json_parser_2; } - if (yych <= 0x08) - { - goto basic_json_parser_30; - } - if (yych >= '\n') + if (yych <= '!') { goto basic_json_parser_4; } + goto basic_json_parser_9; } else { - if (yych == '\r') + if (yych <= '+') { - goto basic_json_parser_2; + goto basic_json_parser_4; } - if (yych <= 0x1F) + if (yych <= ',') { - goto basic_json_parser_30; + goto basic_json_parser_10; } + goto basic_json_parser_12; } } else { - if (yych <= ',') - { - if (yych == '"') - { - goto basic_json_parser_27; - } - if (yych <= '+') - { - goto basic_json_parser_30; - } - goto basic_json_parser_16; - } - else + if (yych <= '9') { if (yych <= '/') { - if (yych <= '-') - { - goto basic_json_parser_23; - } - goto basic_json_parser_30; + goto basic_json_parser_4; } - else + if (yych <= '0') { - if (yych <= '0') - { - goto basic_json_parser_24; - } - if (yych <= '9') - { - goto basic_json_parser_26; - } - goto basic_json_parser_18; + goto basic_json_parser_13; } + goto basic_json_parser_15; + } + else + { + if (yych <= ':') + { + goto basic_json_parser_17; + } + if (yych == '[') + { + goto basic_json_parser_19; + } + goto basic_json_parser_4; } } } else { - if (yych <= 'n') + if (yych <= 't') { - if (yych <= ']') + if (yych <= 'f') { - if (yych == '[') + if (yych <= ']') { - goto basic_json_parser_8; + goto basic_json_parser_21; } - if (yych <= '\\') + if (yych <= 'e') { - goto basic_json_parser_30; + goto basic_json_parser_4; } - goto basic_json_parser_10; + goto basic_json_parser_23; } else { - if (yych == 'f') + if (yych == 'n') { - goto basic_json_parser_22; + goto basic_json_parser_24; } - if (yych <= 'm') + if (yych <= 's') { - goto basic_json_parser_30; + goto basic_json_parser_4; } - goto basic_json_parser_20; + goto basic_json_parser_25; } } else { - if (yych <= '{') + if (yych <= '|') { - if (yych == 't') + if (yych == '{') { - goto basic_json_parser_21; + goto basic_json_parser_26; } - if (yych <= 'z') - { - goto basic_json_parser_30; - } - goto basic_json_parser_12; + goto basic_json_parser_4; } else { if (yych <= '}') { - if (yych <= '|') - { - goto basic_json_parser_30; - } - goto basic_json_parser_14; + goto basic_json_parser_28; } - else + if (yych == 0xEF) { - if (yych == 0xEF) - { - goto basic_json_parser_6; - } goto basic_json_parser_30; } + goto basic_json_parser_4; } } } basic_json_parser_2: ++m_cursor; - yych = *m_cursor; - goto basic_json_parser_5; -basic_json_parser_3: { - return scan(); + return token_type::end_of_input; } basic_json_parser_4: ++m_cursor; +basic_json_parser_5: + { + return token_type::parse_error; + } +basic_json_parser_6: + ++m_cursor; if (m_limit <= m_cursor) { yyfill(); // LCOV_EXCL_LINE; } yych = *m_cursor; -basic_json_parser_5: if (yybm[0 + yych] & 32) { - goto basic_json_parser_4; + goto basic_json_parser_6; } - goto basic_json_parser_3; -basic_json_parser_6: + { + return scan(); + } +basic_json_parser_9: yyaccept = 0; yych = *(m_marker = ++m_cursor); - if (yych == 0xBB) + if (yych <= 0x0F) { - goto basic_json_parser_64; - } -basic_json_parser_7: - { - return token_type::parse_error; - } -basic_json_parser_8: - ++m_cursor; - { - return token_type::begin_array; + goto basic_json_parser_5; } + goto basic_json_parser_32; basic_json_parser_10: - ++m_cursor; - { - return token_type::end_array; - } -basic_json_parser_12: - ++m_cursor; - { - return token_type::begin_object; - } -basic_json_parser_14: - ++m_cursor; - { - return token_type::end_object; - } -basic_json_parser_16: ++m_cursor; { return token_type::value_separator; } -basic_json_parser_18: - ++m_cursor; - { - return token_type::name_separator; - } -basic_json_parser_20: - yyaccept = 0; - yych = *(m_marker = ++m_cursor); - if (yych == 'u') - { - goto basic_json_parser_60; - } - goto basic_json_parser_7; -basic_json_parser_21: - yyaccept = 0; - yych = *(m_marker = ++m_cursor); - if (yych == 'r') - { - goto basic_json_parser_56; - } - goto basic_json_parser_7; -basic_json_parser_22: - yyaccept = 0; - yych = *(m_marker = ++m_cursor); - if (yych == 'a') - { - goto basic_json_parser_51; - } - goto basic_json_parser_7; -basic_json_parser_23: +basic_json_parser_12: yych = *++m_cursor; if (yych <= '/') { - goto basic_json_parser_7; + goto basic_json_parser_5; } if (yych <= '0') { - goto basic_json_parser_50; + goto basic_json_parser_13; } if (yych <= '9') { - goto basic_json_parser_41; + goto basic_json_parser_15; } - goto basic_json_parser_7; -basic_json_parser_24: + goto basic_json_parser_5; +basic_json_parser_13: yyaccept = 1; yych = *(m_marker = ++m_cursor); if (yych <= 'D') { if (yych == '.') { - goto basic_json_parser_43; + goto basic_json_parser_37; } } else { if (yych <= 'E') { - goto basic_json_parser_44; + goto basic_json_parser_38; } if (yych == 'e') { - goto basic_json_parser_44; + goto basic_json_parser_38; } } -basic_json_parser_25: +basic_json_parser_14: { return token_type::value_number; } -basic_json_parser_26: +basic_json_parser_15: yyaccept = 1; - yych = *(m_marker = ++m_cursor); - goto basic_json_parser_42; -basic_json_parser_27: + m_marker = ++m_cursor; + if ((m_limit - m_cursor) < 3) + { + yyfill(); // LCOV_EXCL_LINE; + } + yych = *m_cursor; + if (yybm[0 + yych] & 64) + { + goto basic_json_parser_15; + } + if (yych <= 'D') + { + if (yych == '.') + { + goto basic_json_parser_37; + } + goto basic_json_parser_14; + } + else + { + if (yych <= 'E') + { + goto basic_json_parser_38; + } + if (yych == 'e') + { + goto basic_json_parser_38; + } + goto basic_json_parser_14; + } +basic_json_parser_17: + ++m_cursor; + { + return token_type::name_separator; + } +basic_json_parser_19: + ++m_cursor; + { + return token_type::begin_array; + } +basic_json_parser_21: + ++m_cursor; + { + return token_type::end_array; + } +basic_json_parser_23: yyaccept = 0; yych = *(m_marker = ++m_cursor); - if (yych <= 0x0F) + if (yych == 'a') { - goto basic_json_parser_7; + goto basic_json_parser_39; + } + goto basic_json_parser_5; +basic_json_parser_24: + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych == 'u') + { + goto basic_json_parser_40; + } + goto basic_json_parser_5; +basic_json_parser_25: + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych == 'r') + { + goto basic_json_parser_41; + } + goto basic_json_parser_5; +basic_json_parser_26: + ++m_cursor; + { + return token_type::begin_object; } - goto basic_json_parser_32; basic_json_parser_28: ++m_cursor; { - return token_type::end_of_input; + return token_type::end_object; } basic_json_parser_30: - yych = *++m_cursor; - goto basic_json_parser_7; + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych == 0xBB) + { + goto basic_json_parser_42; + } + goto basic_json_parser_5; basic_json_parser_31: ++m_cursor; if (m_limit <= m_cursor) @@ -7740,7 +7674,7 @@ basic_json_parser_31: } yych = *m_cursor; basic_json_parser_32: - if (yybm[0 + yych] & 64) + if (yybm[0 + yych] & 128) { goto basic_json_parser_31; } @@ -7750,20 +7684,25 @@ basic_json_parser_32: } if (yych <= '"') { - goto basic_json_parser_35; + goto basic_json_parser_34; } - goto basic_json_parser_34; + goto basic_json_parser_36; basic_json_parser_33: m_cursor = m_marker; if (yyaccept == 0) { - goto basic_json_parser_7; + goto basic_json_parser_5; } else { - goto basic_json_parser_25; + goto basic_json_parser_14; } basic_json_parser_34: + ++m_cursor; + { + return token_type::value_string; + } +basic_json_parser_36: ++m_cursor; if (m_limit <= m_cursor) { @@ -7836,18 +7775,78 @@ basic_json_parser_34: } if (yych <= 'u') { - goto basic_json_parser_37; + goto basic_json_parser_43; } goto basic_json_parser_33; } } } -basic_json_parser_35: - ++m_cursor; - { - return token_type::value_string; - } basic_json_parser_37: + yych = *++m_cursor; + if (yych <= '/') + { + goto basic_json_parser_33; + } + if (yych <= '9') + { + goto basic_json_parser_44; + } + goto basic_json_parser_33; +basic_json_parser_38: + yych = *++m_cursor; + if (yych <= ',') + { + if (yych == '+') + { + goto basic_json_parser_46; + } + goto basic_json_parser_33; + } + else + { + if (yych <= '-') + { + goto basic_json_parser_46; + } + if (yych <= '/') + { + goto basic_json_parser_33; + } + if (yych <= '9') + { + goto basic_json_parser_47; + } + goto basic_json_parser_33; + } +basic_json_parser_39: + yych = *++m_cursor; + if (yych == 'l') + { + goto basic_json_parser_49; + } + goto basic_json_parser_33; +basic_json_parser_40: + yych = *++m_cursor; + if (yych == 'l') + { + goto basic_json_parser_50; + } + goto basic_json_parser_33; +basic_json_parser_41: + yych = *++m_cursor; + if (yych == 'u') + { + goto basic_json_parser_51; + } + goto basic_json_parser_33; +basic_json_parser_42: + yych = *++m_cursor; + if (yych == 0xBF) + { + goto basic_json_parser_52; + } + goto basic_json_parser_33; +basic_json_parser_43: ++m_cursor; if (m_limit <= m_cursor) { @@ -7860,27 +7859,113 @@ basic_json_parser_37: { goto basic_json_parser_33; } - if (yych >= ':') + if (yych <= '9') { - goto basic_json_parser_33; + goto basic_json_parser_54; } + goto basic_json_parser_33; } else { if (yych <= 'F') + { + goto basic_json_parser_54; + } + if (yych <= '`') + { + goto basic_json_parser_33; + } + if (yych <= 'f') + { + goto basic_json_parser_54; + } + goto basic_json_parser_33; + } +basic_json_parser_44: + yyaccept = 1; + m_marker = ++m_cursor; + if ((m_limit - m_cursor) < 3) + { + yyfill(); // LCOV_EXCL_LINE; + } + yych = *m_cursor; + if (yych <= 'D') + { + if (yych <= '/') + { + goto basic_json_parser_14; + } + if (yych <= '9') + { + goto basic_json_parser_44; + } + goto basic_json_parser_14; + } + else + { + if (yych <= 'E') { goto basic_json_parser_38; } - if (yych <= '`') + if (yych == 'e') { - goto basic_json_parser_33; - } - if (yych >= 'g') - { - goto basic_json_parser_33; + goto basic_json_parser_38; } + goto basic_json_parser_14; } -basic_json_parser_38: +basic_json_parser_46: + yych = *++m_cursor; + if (yych <= '/') + { + goto basic_json_parser_33; + } + if (yych >= ':') + { + goto basic_json_parser_33; + } +basic_json_parser_47: + ++m_cursor; + if (m_limit <= m_cursor) + { + yyfill(); // LCOV_EXCL_LINE; + } + yych = *m_cursor; + if (yych <= '/') + { + goto basic_json_parser_14; + } + if (yych <= '9') + { + goto basic_json_parser_47; + } + goto basic_json_parser_14; +basic_json_parser_49: + yych = *++m_cursor; + if (yych == 's') + { + goto basic_json_parser_55; + } + goto basic_json_parser_33; +basic_json_parser_50: + yych = *++m_cursor; + if (yych == 'l') + { + goto basic_json_parser_56; + } + goto basic_json_parser_33; +basic_json_parser_51: + yych = *++m_cursor; + if (yych == 'e') + { + goto basic_json_parser_58; + } + goto basic_json_parser_33; +basic_json_parser_52: + ++m_cursor; + { + return scan(); + } +basic_json_parser_54: ++m_cursor; if (m_limit <= m_cursor) { @@ -7893,27 +7978,46 @@ basic_json_parser_38: { goto basic_json_parser_33; } - if (yych >= ':') + if (yych <= '9') { - goto basic_json_parser_33; + goto basic_json_parser_60; } + goto basic_json_parser_33; } else { if (yych <= 'F') { - goto basic_json_parser_39; + goto basic_json_parser_60; } if (yych <= '`') { goto basic_json_parser_33; } - if (yych >= 'g') + if (yych <= 'f') { - goto basic_json_parser_33; + goto basic_json_parser_60; } + goto basic_json_parser_33; } -basic_json_parser_39: +basic_json_parser_55: + yych = *++m_cursor; + if (yych == 'e') + { + goto basic_json_parser_61; + } + goto basic_json_parser_33; +basic_json_parser_56: + ++m_cursor; + { + return token_type::literal_null; + } +basic_json_parser_58: + ++m_cursor; + { + return token_type::literal_true; + } +basic_json_parser_60: ++m_cursor; if (m_limit <= m_cursor) { @@ -7926,27 +8030,34 @@ basic_json_parser_39: { goto basic_json_parser_33; } - if (yych >= ':') + if (yych <= '9') { - goto basic_json_parser_33; + goto basic_json_parser_63; } + goto basic_json_parser_33; } else { if (yych <= 'F') { - goto basic_json_parser_40; + goto basic_json_parser_63; } if (yych <= '`') { goto basic_json_parser_33; } - if (yych >= 'g') + if (yych <= 'f') { - goto basic_json_parser_33; + goto basic_json_parser_63; } + goto basic_json_parser_33; } -basic_json_parser_40: +basic_json_parser_61: + ++m_cursor; + { + return token_type::literal_false; + } +basic_json_parser_63: ++m_cursor; if (m_limit <= m_cursor) { @@ -7981,215 +8092,6 @@ basic_json_parser_40: } goto basic_json_parser_33; } -basic_json_parser_41: - yyaccept = 1; - m_marker = ++m_cursor; - if ((m_limit - m_cursor) < 3) - { - yyfill(); // LCOV_EXCL_LINE; - } - yych = *m_cursor; -basic_json_parser_42: - if (yybm[0 + yych] & 128) - { - goto basic_json_parser_41; - } - if (yych <= 'D') - { - if (yych != '.') - { - goto basic_json_parser_25; - } - } - else - { - if (yych <= 'E') - { - goto basic_json_parser_44; - } - if (yych == 'e') - { - goto basic_json_parser_44; - } - goto basic_json_parser_25; - } -basic_json_parser_43: - yych = *++m_cursor; - if (yych <= '/') - { - goto basic_json_parser_33; - } - if (yych <= '9') - { - goto basic_json_parser_48; - } - goto basic_json_parser_33; -basic_json_parser_44: - yych = *++m_cursor; - if (yych <= ',') - { - if (yych != '+') - { - goto basic_json_parser_33; - } - } - else - { - if (yych <= '-') - { - goto basic_json_parser_45; - } - if (yych <= '/') - { - goto basic_json_parser_33; - } - if (yych <= '9') - { - goto basic_json_parser_46; - } - goto basic_json_parser_33; - } -basic_json_parser_45: - yych = *++m_cursor; - if (yych <= '/') - { - goto basic_json_parser_33; - } - if (yych >= ':') - { - goto basic_json_parser_33; - } -basic_json_parser_46: - ++m_cursor; - if (m_limit <= m_cursor) - { - yyfill(); // LCOV_EXCL_LINE; - } - yych = *m_cursor; - if (yych <= '/') - { - goto basic_json_parser_25; - } - if (yych <= '9') - { - goto basic_json_parser_46; - } - goto basic_json_parser_25; -basic_json_parser_48: - yyaccept = 1; - m_marker = ++m_cursor; - if ((m_limit - m_cursor) < 3) - { - yyfill(); // LCOV_EXCL_LINE; - } - yych = *m_cursor; - if (yych <= 'D') - { - if (yych <= '/') - { - goto basic_json_parser_25; - } - if (yych <= '9') - { - goto basic_json_parser_48; - } - goto basic_json_parser_25; - } - else - { - if (yych <= 'E') - { - goto basic_json_parser_44; - } - if (yych == 'e') - { - goto basic_json_parser_44; - } - goto basic_json_parser_25; - } -basic_json_parser_50: - yyaccept = 1; - yych = *(m_marker = ++m_cursor); - if (yych <= 'D') - { - if (yych == '.') - { - goto basic_json_parser_43; - } - goto basic_json_parser_25; - } - else - { - if (yych <= 'E') - { - goto basic_json_parser_44; - } - if (yych == 'e') - { - goto basic_json_parser_44; - } - goto basic_json_parser_25; - } -basic_json_parser_51: - yych = *++m_cursor; - if (yych != 'l') - { - goto basic_json_parser_33; - } - yych = *++m_cursor; - if (yych != 's') - { - goto basic_json_parser_33; - } - yych = *++m_cursor; - if (yych != 'e') - { - goto basic_json_parser_33; - } - ++m_cursor; - { - return token_type::literal_false; - } -basic_json_parser_56: - yych = *++m_cursor; - if (yych != 'u') - { - goto basic_json_parser_33; - } - yych = *++m_cursor; - if (yych != 'e') - { - goto basic_json_parser_33; - } - ++m_cursor; - { - return token_type::literal_true; - } -basic_json_parser_60: - yych = *++m_cursor; - if (yych != 'l') - { - goto basic_json_parser_33; - } - yych = *++m_cursor; - if (yych != 'l') - { - goto basic_json_parser_33; - } - ++m_cursor; - { - return token_type::literal_null; - } -basic_json_parser_64: - yych = *++m_cursor; - if (yych != 0xBF) - { - goto basic_json_parser_33; - } - ++m_cursor; - { - return scan(); - } } } @@ -8240,13 +8142,14 @@ basic_json_parser_64: 1. Escaped characters. In this case, a new character is constructed according to the nature of the escape. Some escapes create new - characters (e.g., `"\\n"` is replaced by `"\n"`), some are copied as - is (e.g., `"\\\\"`). Furthermore, Unicode escapes of the shape + characters (e.g., `"\\n"` is replaced by `"\n"`), some are copied + as is (e.g., `"\\\\"`). Furthermore, Unicode escapes of the shape `"\\uxxxx"` need special care. In this case, to_unicode takes care of the construction of the values. 2. Unescaped characters are copied as is. - @return string value of current token without opening and closing quotes + @return string value of current token without opening and closing + quotes @throw std::out_of_range if to_unicode fails */ string_t get_string() const @@ -8357,8 +8260,8 @@ basic_json_parser_64: This function (and its overloads) serves to select the most approprate standard floating point number parsing function based on the type - supplied via the first parameter. Set this to - @a static_cast(nullptr). + supplied via the first parameter. Set this to @a + static_cast(nullptr). @param[in] type the @ref number_float_t in use @@ -8382,8 +8285,8 @@ basic_json_parser_64: This function (and its overloads) serves to select the most approprate standard floating point number parsing function based on the type - supplied via the first parameter. Set this to - @a static_cast(nullptr). + supplied via the first parameter. Set this to @a + static_cast(nullptr). @param[in] type the @ref number_float_t in use @@ -8402,8 +8305,8 @@ basic_json_parser_64: This function (and its overloads) serves to select the most approprate standard floating point number parsing function based on the type - supplied via the first parameter. Set this to - @a static_cast(nullptr). + supplied via the first parameter. Set this to @a + static_cast(nullptr). @param[in] type the @ref number_float_t in use @@ -8427,22 +8330,16 @@ basic_json_parser_64: This function parses the integer component up to the radix point or exponent while collecting information about the 'floating point representation', which it stores in the result parameter. If there is - no radix point or exponent, and the number can fit into a - @ref number_integer_t or @ref number_unsigned_t then it sets the - result parameter accordingly. - - The 'floating point representation' includes the number of significant - figures after the radix point, whether the number is in exponential - or decimal form, the capitalization of the exponent marker, and if the - optional '+' is present in the exponent. This information is necessary - to perform accurate round trips of floating point numbers. + no radix point or exponent, and the number can fit into a @ref + number_integer_t or @ref number_unsigned_t then it sets the result + parameter accordingly. If the number is a floating point number the number is then parsed using @a std:strtod (or @a std:strtof or @a std::strtold). @param[out] result @ref basic_json object to receive the number, or - NAN if the conversion read past the current token. The latter case - needs to be treated by the caller function. + NAN if the conversion read past the current token. The latter case + needs to be treated by the caller function. */ void get_number(basic_json& result) const { @@ -8450,15 +8347,6 @@ basic_json_parser_64: const lexer::lexer_char_t* curptr = m_start; - // remember this number was parsed (for later serialization) - result.m_type.bits.parsed = true; - - // 'found_radix_point' will be set to 0xFF upon finding a radix - // point and later used to mask in/out the precision depending - // whether a radix is found i.e. 'precision &= found_radix_point' - uint8_t found_radix_point = 0; - uint8_t precision = 0; - // accumulate the integer conversion result (unsigned for now) number_unsigned_t value = 0; @@ -8472,17 +8360,13 @@ basic_json_parser_64: if (*curptr == '-') { type = value_t::number_integer; - max = static_cast(std::numeric_limits::max()) + 1; + max = static_cast((std::numeric_limits::max)()) + 1; curptr++; } else { type = value_t::number_unsigned; - max = static_cast(std::numeric_limits::max()); - if (*curptr == '+') - { - curptr++; - } + max = static_cast((std::numeric_limits::max)()); } // count the significant figures @@ -8495,22 +8379,11 @@ basic_json_parser_64: { // don't count '.' but change to float type = value_t::number_float; - - // reset precision count - precision = 0; - found_radix_point = 0xFF; continue; } // assume exponent (if not then will fail parse): change to // float, stop counting and record exponent details type = value_t::number_float; - result.m_type.bits.has_exp = true; - - // exponent capitalization - result.m_type.bits.exp_cap = (*curptr == 'E'); - - // exponent '+' sign - result.m_type.bits.exp_plus = (*(++curptr) == '+'); break; } @@ -8532,13 +8405,8 @@ basic_json_parser_64: value = temp; } } - ++precision; } - // If no radix point was found then precision would now be set to - // the number of digits, which is wrong - clear it. - result.m_type.bits.precision = precision & found_radix_point; - // save the value (if not a float) if (type == value_t::number_unsigned) { @@ -8844,6 +8712,1232 @@ basic_json_parser_64: /// the lexer lexer m_lexer; }; + + public: + /*! + @brief JSON Pointer + + A JSON pointer defines a string syntax for identifying a specific value + within a JSON document. It can be used with functions `at` and + `operator[]`. Furthermore, JSON pointers are the base for JSON patches. + + @sa [RFC 6901](https://tools.ietf.org/html/rfc6901) + + @since version 2.0.0 + */ + class json_pointer + { + /// allow basic_json to access private members + friend class basic_json; + + public: + /*! + @brief create JSON pointer + + Create a JSON pointer according to the syntax described in + [Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3). + + @param[in] s string representing the JSON pointer; if omitted, the + empty string is assumed which references the whole JSON + value + + @throw std::domain_error if reference token is nonempty and does not + begin with a slash (`/`); example: `"JSON pointer must be empty or + begin with /"` + @throw std::domain_error if a tilde (`~`) is not followed by `0` + (representing `~`) or `1` (representing `/`); example: `"escape error: + ~ must be followed with 0 or 1"` + + @liveexample{The example shows the construction several valid JSON + pointers as well as the exceptional behavior.,json_pointer} + + @since version 2.0.0 + */ + explicit json_pointer(const std::string& s = "") + : reference_tokens(split(s)) + {} + + /*! + @brief return a string representation of the JSON pointer + + @invariant For each JSON pointer `ptr`, it holds: + @code {.cpp} + ptr == json_pointer(ptr.to_string()); + @endcode + + @return a string representation of the JSON pointer + + @liveexample{The example shows the result of `to_string`., + json_pointer__to_string} + + @since version 2.0.0 + */ + std::string to_string() const noexcept + { + std::string result; + + for (const auto& reference_token : reference_tokens) + { + result += "/" + escape(reference_token); + } + + return result; + } + + /// @copydoc to_string() + operator std::string() const + { + return to_string(); + } + + private: + /// remove and return last reference pointer + std::string pop_back() + { + if (is_root()) + { + throw std::domain_error("JSON pointer has no parent"); + } + + auto last = reference_tokens.back(); + reference_tokens.pop_back(); + return last; + } + + /// return whether pointer points to the root document + bool is_root() const + { + return reference_tokens.empty(); + } + + json_pointer top() const + { + if (is_root()) + { + throw std::domain_error("JSON pointer has no parent"); + } + + json_pointer result = *this; + result.reference_tokens = {reference_tokens[0]}; + return result; + } + + /*! + @brief create and return a reference to the pointed to value + */ + reference get_and_create(reference j) const + { + pointer result = &j; + + // in case no reference tokens exist, return a reference to the + // JSON value j which will be overwritten by a primitive value + for (const auto& reference_token : reference_tokens) + { + switch (result->m_type) + { + case value_t::null: + { + if (reference_token == "0") + { + // start a new array if reference token is 0 + result = &result->operator[](0); + } + else + { + // start a new object otherwise + result = &result->operator[](reference_token); + } + break; + } + + case value_t::object: + { + // create an entry in the object + result = &result->operator[](reference_token); + break; + } + + case value_t::array: + { + // create an entry in the array + result = &result->operator[](static_cast(std::stoi(reference_token))); + break; + } + + /* + The following code is only reached if there exists a + reference token _and_ the current value is primitive. In + this case, we have an error situation, because primitive + values may only occur as single value; that is, with an + empty list of reference tokens. + */ + default: + { + throw std::domain_error("invalid value to unflatten"); + } + } + } + + return *result; + } + + /*! + @brief return a reference to the pointed to value + + @param[in] ptr a JSON value + + @return reference to the JSON value pointed to by the JSON pointer + + @complexity Linear in the length of the JSON pointer. + + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number + */ + reference get_unchecked(pointer ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case value_t::array: + { + // error condition (cf. RFC 6901, Sect. 4) + if (reference_token.size() > 1 and reference_token[0] == '0') + { + throw std::domain_error("array index must not begin with '0'"); + } + + if (reference_token == "-") + { + // explicityly treat "-" as index beyond the end + ptr = &ptr->operator[](ptr->m_value.array->size()); + } + else + { + // convert array index to number; unchecked access + ptr = &ptr->operator[](static_cast(std::stoi(reference_token))); + } + break; + } + + default: + { + throw std::out_of_range("unresolved reference token '" + reference_token + "'"); + } + } + } + + return *ptr; + } + + reference get_checked(pointer ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case value_t::array: + { + if (reference_token == "-") + { + // "-" always fails the range check + throw std::out_of_range("array index '-' (" + + std::to_string(ptr->m_value.array->size()) + + ") is out of range"); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (reference_token.size() > 1 and reference_token[0] == '0') + { + throw std::domain_error("array index must not begin with '0'"); + } + + // note: at performs range check + ptr = &ptr->at(static_cast(std::stoi(reference_token))); + break; + } + + default: + { + throw std::out_of_range("unresolved reference token '" + reference_token + "'"); + } + } + } + + return *ptr; + } + + /*! + @brief return a const reference to the pointed to value + + @param[in] ptr a JSON value + + @return const reference to the JSON value pointed to by the JSON + pointer + */ + const_reference get_unchecked(const_pointer ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case value_t::array: + { + if (reference_token == "-") + { + // "-" cannot be used for const access + throw std::out_of_range("array index '-' (" + + std::to_string(ptr->m_value.array->size()) + + ") is out of range"); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (reference_token.size() > 1 and reference_token[0] == '0') + { + throw std::domain_error("array index must not begin with '0'"); + } + + // use unchecked array access + ptr = &ptr->operator[](static_cast(std::stoi(reference_token))); + break; + } + + default: + { + throw std::out_of_range("unresolved reference token '" + reference_token + "'"); + } + } + } + + return *ptr; + } + + const_reference get_checked(const_pointer ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case value_t::array: + { + if (reference_token == "-") + { + // "-" always fails the range check + throw std::out_of_range("array index '-' (" + + std::to_string(ptr->m_value.array->size()) + + ") is out of range"); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (reference_token.size() > 1 and reference_token[0] == '0') + { + throw std::domain_error("array index must not begin with '0'"); + } + + // note: at performs range check + ptr = &ptr->at(static_cast(std::stoi(reference_token))); + break; + } + + default: + { + throw std::out_of_range("unresolved reference token '" + reference_token + "'"); + } + } + } + + return *ptr; + } + + /// split the string input to reference tokens + static std::vector split(std::string reference_string) + { + std::vector result; + + // special case: empty reference string -> no reference tokens + if (reference_string.empty()) + { + return result; + } + + // check if nonempty reference string begins with slash + if (reference_string[0] != '/') + { + throw std::domain_error("JSON pointer must be empty or begin with '/'"); + } + + // extract the reference tokens: + // - slash: position of the last read slash (or end of string) + // - start: position after the previous slash + for ( + // search for the first slash after the first character + size_t slash = reference_string.find_first_of("/", 1), + // set the beginning of the first reference token + start = 1; + // we can stop if start == string::npos+1 = 0 + start != 0; + // set the beginning of the next reference token + // (will eventually be 0 if slash == std::string::npos) + start = slash + 1, + // find next slash + slash = reference_string.find_first_of("/", start)) + { + // use the text between the beginning of the reference token + // (start) and the last slash (slash). + auto reference_token = reference_string.substr(start, slash - start); + + // check reference tokens are properly escaped + for (size_t pos = reference_token.find_first_of("~"); + pos != std::string::npos; + pos = reference_token.find_first_of("~", pos + 1)) + { + assert(reference_token[pos] == '~'); + + // ~ must be followed by 0 or 1 + if (pos == reference_token.size() - 1 or + (reference_token[pos + 1] != '0' and + reference_token[pos + 1] != '1')) + { + throw std::domain_error("escape error: '~' must be followed with '0' or '1'"); + } + } + + // finally, store the reference token + unescape(reference_token); + result.push_back(reference_token); + } + + return result; + } + + private: + /*! + @brief replace all occurrences of a substring by another string + + @param[in,out] s the string to manipulate + @param[in] f the substring to replace with @a t + @param[out] t the string to replace @a f + + @return The string @a s where all occurrences of @a f are replaced + with @a t. + + @pre The search string @a f must not be empty. + + @since version 2.0.0 + */ + static void replace_substring(std::string& s, + const std::string& f, + const std::string& t) + { + assert(not f.empty()); + + for ( + size_t pos = s.find(f); // find first occurrence of f + pos != std::string::npos; // make sure f was found + s.replace(pos, f.size(), t), // replace with t + pos = s.find(f, pos + t.size()) // find next occurrence of f + ); + } + + /// escape tilde and slash + static std::string escape(std::string s) + { + // escape "~"" to "~0" and "/" to "~1" + replace_substring(s, "~", "~0"); + replace_substring(s, "/", "~1"); + return s; + } + + /// unescape tilde and slash + static void unescape(std::string& s) + { + // first transform any occurrence of the sequence '~1' to '/' + replace_substring(s, "~1", "/"); + // then transform any occurrence of the sequence '~0' to '~' + replace_substring(s, "~0", "~"); + } + + /*! + @param[in] reference_string the reference string to the current value + @param[in] value the value to consider + @param[in,out] result the result object to insert values to + + @note Empty objects or arrays are flattened to `null`. + */ + static void flatten(const std::string& reference_string, + const basic_json& value, + basic_json& result) + { + switch (value.m_type) + { + case value_t::array: + { + if (value.m_value.array->empty()) + { + // flatten empty array as null + result[reference_string] = nullptr; + } + else + { + // iterate array and use index as reference string + for (size_t i = 0; i < value.m_value.array->size(); ++i) + { + flatten(reference_string + "/" + std::to_string(i), + value.m_value.array->operator[](i), result); + } + } + break; + } + + case value_t::object: + { + if (value.m_value.object->empty()) + { + // flatten empty object as null + result[reference_string] = nullptr; + } + else + { + // iterate object and use keys as reference string + for (const auto& element : *value.m_value.object) + { + flatten(reference_string + "/" + escape(element.first), + element.second, result); + } + } + break; + } + + default: + { + // add primitive value with its reference string + result[reference_string] = value; + break; + } + } + } + + /*! + @param[in] value flattened JSON + + @return unflattened JSON + */ + static basic_json unflatten(const basic_json& value) + { + if (not value.is_object()) + { + throw std::domain_error("only objects can be unflattened"); + } + + basic_json result; + + // iterate the JSON object values + for (const auto& element : *value.m_value.object) + { + if (not element.second.is_primitive()) + { + throw std::domain_error("values in object must be primitive"); + } + + // assign value to reference pointed to by JSON pointer; Note + // that if the JSON pointer is "" (i.e., points to the whole + // value), function get_and_create returns a reference to + // result itself. An assignment will then create a primitive + // value. + json_pointer(element.first).get_and_create(result) = element.second; + } + + return result; + } + + private: + /// the reference tokens + std::vector reference_tokens {}; + }; + + ////////////////////////// + // JSON Pointer support // + ////////////////////////// + + /// @name JSON Pointer functions + /// @{ + + /*! + @brief access specified element via JSON Pointer + + Uses a JSON pointer to retrieve a reference to the respective JSON value. + No bound checking is performed. Similar to @ref operator[](const typename + object_t::key_type&), `null` values are created in arrays and objects if + necessary. + + In particular: + - If the JSON pointer points to an object key that does not exist, it + is created an filled with a `null` value before a reference to it + is returned. + - If the JSON pointer points to an array index that does not exist, it + is created an filled with a `null` value before a reference to it + is returned. All indices between the current maximum and the given + index are also filled with `null`. + - The special value `-` is treated as a synonym for the index past the + end. + + @param[in] ptr a JSON pointer + + @return reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number + + @liveexample{The behavior is shown in the example.,operatorjson_pointer} + + @since version 2.0.0 + */ + reference operator[](const json_pointer& ptr) + { + return ptr.get_unchecked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Uses a JSON pointer to retrieve a reference to the respective JSON value. + No bound checking is performed. The function does not change the JSON + value; no `null` values are created. In particular, the the special value + `-` yields an exception. + + @param[in] ptr JSON pointer to the desired element + + @return const reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number + + @liveexample{The behavior is shown in the example.,operatorjson_pointer_const} + + @since version 2.0.0 + */ + const_reference operator[](const json_pointer& ptr) const + { + return ptr.get_unchecked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Returns a reference to the element at with specified JSON pointer @a ptr, + with bounds checking. + + @param[in] ptr JSON pointer to the desired element + + @return reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number + + @liveexample{The behavior is shown in the example.,at_json_pointer} + + @since version 2.0.0 + */ + reference at(const json_pointer& ptr) + { + return ptr.get_checked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Returns a const reference to the element at with specified JSON pointer @a + ptr, with bounds checking. + + @param[in] ptr JSON pointer to the desired element + + @return reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number + + @liveexample{The behavior is shown in the example.,at_json_pointer_const} + + @since version 2.0.0 + */ + const_reference at(const json_pointer& ptr) const + { + return ptr.get_checked(this); + } + + /*! + @brief return flattened JSON value + + The function creates a JSON object whose keys are JSON pointers (see [RFC + 6901](https://tools.ietf.org/html/rfc6901)) and whose values are all + primitive. The original JSON value can be restored using the @ref + unflatten() function. + + @return an object that maps JSON pointers to primitve values + + @note Empty objects and arrays are flattened to `null` and will not be + reconstructed correctly by the @ref unflatten() function. + + @complexity Linear in the size the JSON value. + + @liveexample{The following code shows how a JSON object is flattened to an + object whose keys consist of JSON pointers.,flatten} + + @sa @ref unflatten() for the reverse function + + @since version 2.0.0 + */ + basic_json flatten() const + { + basic_json result(value_t::object); + json_pointer::flatten("", *this, result); + return result; + } + + /*! + @brief unflatten a previously flattened JSON value + + The function restores the arbitrary nesting of a JSON value that has been + flattened before using the @ref flatten() function. The JSON value must + meet certain constraints: + 1. The value must be an object. + 2. The keys must be JSON pointers (see + [RFC 6901](https://tools.ietf.org/html/rfc6901)) + 3. The mapped values must be primitive JSON types. + + @return the original JSON from a flattened version + + @note Empty objects and arrays are flattened by @ref flatten() to `null` + values and can not unflattened to their original type. Apart from + this example, for a JSON value `j`, the following is always true: + `j == j.flatten().unflatten()`. + + @complexity Linear in the size the JSON value. + + @liveexample{The following code shows how a flattened JSON object is + unflattened into the original nested JSON object.,unflatten} + + @sa @ref flatten() for the reverse function + + @since version 2.0.0 + */ + basic_json unflatten() const + { + return json_pointer::unflatten(*this); + } + + /// @} + + ////////////////////////// + // JSON Patch functions // + ////////////////////////// + + /// @name JSON Patch functions + /// @{ + + /*! + @brief applies a JSON patch + + [JSON Patch](http://jsonpatch.com) defines a JSON document structure for + expressing a sequence of operations to apply to a JSON) document. With + this funcion, a JSON Patch is applied to the current JSON value by + executing all operations from the patch. + + @param[in] json_patch JSON patch document + @return patched document + + @note The application of a patch is atomic: Either all operations succeed + and the patched document is returned or an exception is thrown. In + any case, the original value is not changed: the patch is applied + to a copy of the value. + + @throw std::out_of_range if a JSON pointer inside the patch could not + be resolved successfully in the current JSON value; example: `"key baz + not found"` + @throw invalid_argument if the JSON patch is malformed (e.g., mandatory + attributes are missing); example: `"operation add must have member path"` + + @complexity Linear in the size of the JSON value and the length of the + JSON patch. As usually only a fraction of the JSON value is affected by + the patch, the complexity can usually be neglected. + + @liveexample{The following code shows how a JSON patch is applied to a + value.,patch} + + @sa @ref diff -- create a JSON patch by comparing two JSON values + + @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) + @sa [RFC 6901 (JSON Pointer)](https://tools.ietf.org/html/rfc6901) + + @since version 2.0.0 + */ + basic_json patch(const basic_json& json_patch) const + { + // make a working copy to apply the patch to + basic_json result = *this; + + // the valid JSON Patch operations + enum class patch_operations {add, remove, replace, move, copy, test, invalid}; + + const auto get_op = [](const std::string op) + { + if (op == "add") + { + return patch_operations::add; + } + if (op == "remove") + { + return patch_operations::remove; + } + if (op == "replace") + { + return patch_operations::replace; + } + if (op == "move") + { + return patch_operations::move; + } + if (op == "copy") + { + return patch_operations::copy; + } + if (op == "test") + { + return patch_operations::test; + } + + return patch_operations::invalid; + }; + + // wrapper for "add" operation; add value at ptr + const auto operation_add = [&result](json_pointer & ptr, basic_json val) + { + // adding to the root of the target document means replacing it + if (ptr.is_root()) + { + result = val; + } + else + { + // make sure the top element of the pointer exists + json_pointer top_pointer = ptr.top(); + if (top_pointer != ptr) + { + basic_json& x = result.at(top_pointer); + } + + // get reference to parent of JSON pointer ptr + const auto last_path = ptr.pop_back(); + basic_json& parent = result[ptr]; + + switch (parent.m_type) + { + case value_t::null: + case value_t::object: + { + // use operator[] to add value + parent[last_path] = val; + break; + } + + case value_t::array: + { + if (last_path == "-") + { + // special case: append to back + parent.push_back(val); + } + else + { + const auto idx = std::stoi(last_path); + if (static_cast(idx) > parent.size()) + { + // avoid undefined behavior + throw std::out_of_range("array index " + std::to_string(idx) + " is out of range"); + } + else + { + // default case: insert add offset + parent.insert(parent.begin() + static_cast(idx), val); + } + } + break; + } + + default: + { + // if there exists a parent it cannot be primitive + assert(false); // LCOV_EXCL_LINE + } + } + } + }; + + // wrapper for "remove" operation; remove value at ptr + const auto operation_remove = [&result](json_pointer & ptr) + { + // get reference to parent of JSON pointer ptr + const auto last_path = ptr.pop_back(); + basic_json& parent = result.at(ptr); + + // remove child + if (parent.is_object()) + { + // perform range check + auto it = parent.find(last_path); + if (it != parent.end()) + { + parent.erase(it); + } + else + { + throw std::out_of_range("key '" + last_path + "' not found"); + } + } + else if (parent.is_array()) + { + // note erase performs range check + parent.erase(static_cast(std::stoi(last_path))); + } + }; + + // type check + if (not json_patch.is_array()) + { + // a JSON patch must be an array of objects + throw std::invalid_argument("JSON patch must be an array of objects"); + } + + // iterate and apply th eoperations + for (const auto& val : json_patch) + { + // wrapper to get a value for an operation + const auto get_value = [&val](const std::string & op, + const std::string & member, + bool string_type) -> basic_json& + { + // find value + auto it = val.m_value.object->find(member); + + // context-sensitive error message + const auto error_msg = (op == "op") ? "operation" : "operation '" + op + "'"; + + // check if desired value is present + if (it == val.m_value.object->end()) + { + throw std::invalid_argument(error_msg + " must have member '" + member + "'"); + } + + // check if result is of type string + if (string_type and not it->second.is_string()) + { + throw std::invalid_argument(error_msg + " must have string member '" + member + "'"); + } + + // no error: return value + return it->second; + }; + + // type check + if (not val.is_object()) + { + throw std::invalid_argument("JSON patch must be an array of objects"); + } + + // collect mandatory members + const std::string op = get_value("op", "op", true); + const std::string path = get_value(op, "path", true); + json_pointer ptr(path); + + switch (get_op(op)) + { + case patch_operations::add: + { + operation_add(ptr, get_value("add", "value", false)); + break; + } + + case patch_operations::remove: + { + operation_remove(ptr); + break; + } + + case patch_operations::replace: + { + // the "path" location must exist - use at() + result.at(ptr) = get_value("replace", "value", false); + break; + } + + case patch_operations::move: + { + const std::string from_path = get_value("move", "from", true); + json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + basic_json v = result.at(from_ptr); + + // The move operation is functionally identical to a + // "remove" operation on the "from" location, followed + // immediately by an "add" operation at the target + // location with the value that was just removed. + operation_remove(from_ptr); + operation_add(ptr, v); + break; + } + + case patch_operations::copy: + { + const std::string from_path = get_value("copy", "from", true);; + const json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + result[ptr] = result.at(from_ptr); + break; + } + + case patch_operations::test: + { + bool success = false; + try + { + // check if "value" matches the one at "path" + // the "path" location must exist - use at() + success = (result.at(ptr) == get_value("test", "value", false)); + } + catch (std::out_of_range&) + { + // ignore out of range errors: success remains false + } + + // throw an exception if test fails + if (not success) + { + throw std::domain_error("unsuccessful: " + val.dump()); + } + + break; + } + + case patch_operations::invalid: + { + // op must be "add", "remove", "replace", "move", "copy", or + // "test" + throw std::invalid_argument("operation value '" + op + "' is invalid"); + } + } + } + + return result; + } + + /*! + @brief creates a diff as a JSON patch + + Creates a [JSON Patch](http://jsonpatch.com) so that value @a source can + be changed into the value @a target by calling @ref patch function. + + @invariant For two JSON values @a source and @a target, the following code + yields always `true`: + @code {.cpp} + source.patch(diff(source, target)) == target; + @endcode + + @note Currently, only `remove`, `add`, and `replace` operations are + generated. + + @param[in] source JSON value to copare from + @param[in] target JSON value to copare against + @param[in] path helper value to create JSON pointers + + @return a JSON patch to convert the @a source to @a target + + @complexity Linear in the lengths of @a source and @a target. + + @liveexample{The following code shows how a JSON patch is created as a + diff for two JSON values.,diff} + + @sa @ref patch -- apply a JSON patch + + @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) + + @since version 2.0.0 + */ + static basic_json diff(const basic_json& source, + const basic_json& target, + std::string path = "") + { + // the patch + basic_json result(value_t::array); + + // if the values are the same, return empty patch + if (source == target) + { + return result; + } + + if (source.type() != target.type()) + { + // different types: replace value + result.push_back( + { + {"op", "replace"}, + {"path", path}, + {"value", target} + }); + } + else + { + switch (source.type()) + { + case value_t::array: + { + // first pass: traverse common elements + size_t i = 0; + while (i < source.size() and i < target.size()) + { + // recursive call to compare array values at index i + auto temp_diff = diff(source[i], target[i], path + "/" + std::to_string(i)); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + ++i; + } + + // i now reached the end of at least one array + // in a second pass, traverse the remaining elements + + // remove my remaining elements + const auto end_index = static_cast(result.size()); + while (i < source.size()) + { + // add operations in reverse order to avoid invalid + // indices + result.insert(result.begin() + end_index, object( + { + {"op", "remove"}, + {"path", path + "/" + std::to_string(i)} + })); + ++i; + } + + // add other remaining elements + while (i < target.size()) + { + result.push_back( + { + {"op", "add"}, + {"path", path + "/" + std::to_string(i)}, + {"value", target[i]} + }); + ++i; + } + + break; + } + + case value_t::object: + { + // first pass: traverse this object's elements + for (auto it = source.begin(); it != source.end(); ++it) + { + // escape the key name to be used in a JSON patch + const auto key = json_pointer::escape(it.key()); + + if (target.find(it.key()) != target.end()) + { + // recursive call to compare object values at key it + auto temp_diff = diff(it.value(), target[it.key()], path + "/" + key); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + } + else + { + // found a key that is not in o -> remove it + result.push_back(object( + { + {"op", "remove"}, + {"path", path + "/" + key} + })); + } + } + + // second pass: traverse other object's elements + for (auto it = target.begin(); it != target.end(); ++it) + { + if (source.find(it.key()) == source.end()) + { + // found a key that is not in this -> add it + const auto key = json_pointer::escape(it.key()); + result.push_back( + { + {"op", "add"}, + {"path", path + "/" + key}, + {"value", it.value()} + }); + } + } + + break; + } + + default: + { + // both primitive type: replace value + result.push_back( + { + {"op", "replace"}, + {"path", path}, + {"value", target} + }); + break; + } + } + } + + return result; + } + + /// @} }; @@ -8854,8 +9948,8 @@ basic_json_parser_64: /*! @brief default JSON class -This type is the default specialization of the @ref basic_json class which uses -the standard template types. +This type is the default specialization of the @ref basic_json class which +uses the standard template types. @since version 1.0.0 */ @@ -8863,9 +9957,9 @@ using json = basic_json<>; } -///////////////////////// -// nonmember functions // -///////////////////////// +/////////////////////// +// nonmember support // +/////////////////////// // specialization of std::swap, and std::hash namespace std @@ -8906,9 +10000,9 @@ struct hash /*! @brief user-defined string literal for JSON values -This operator implements a user-defined string literal for JSON objects. It can -be used by adding \p "_json" to a string literal and returns a JSON object if -no parse error occurred. +This operator implements a user-defined string literal for JSON objects. It +can be used by adding \p "_json" to a string literal and returns a JSON object +if no parse error occurred. @param[in] s a string representation of a JSON object @return a JSON object @@ -8920,6 +10014,16 @@ inline nlohmann::json operator "" _json(const char* s, std::size_t) return nlohmann::json::parse(reinterpret_cast(s)); } +/*! +@brief user-defined string literal for JSON pointer + +@since version 2.0.0 +*/ +inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t) +{ + return nlohmann::json::json_pointer(s); +} + // restore GCC/clang diagnostic settings #if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) #pragma GCC diagnostic pop diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c index ebf83d831..aba10b496 100644 --- a/src/json.hpp.re2c +++ b/src/json.hpp.re2c @@ -198,6 +198,9 @@ class basic_json AllocatorType>; public: + // forward declarations + template class json_reverse_iterator; + class json_pointer; ///////////////////// // container types // @@ -227,9 +230,6 @@ class basic_json /// the type of an element const pointer using const_pointer = typename std::allocator_traits::const_pointer; - // forward declaration - template class json_reverse_iterator; - /// an iterator for a basic_json container class iterator; /// a const iterator for a basic_json container @@ -271,8 +271,8 @@ class basic_json @tparam ObjectType the container to store objects (e.g., `std::map` or `std::unordered_map`) - @tparam StringType the type of the keys or names (e.g., `std::string`). The - comparison function `std::less` is used to order elements + @tparam StringType the type of the keys or names (e.g., `std::string`). + The comparison function `std::less` is used to order elements inside the container. @tparam AllocatorType the allocator to use for objects (e.g., `std::allocator`) @@ -280,8 +280,8 @@ class basic_json #### Default type With the default values for @a ObjectType (`std::map`), @a StringType - (`std::string`), and @a AllocatorType (`std::allocator`), the default value - for @a object_t is: + (`std::string`), and @a AllocatorType (`std::allocator`), the default + value for @a object_t is: @code {.cpp} std::map< @@ -298,16 +298,16 @@ class basic_json the default type, objects have the following behavior: - When all names are unique, objects will be interoperable in the sense - that all software implementations receiving that object will agree on the - name-value mappings. + that all software implementations receiving that object will agree on + the name-value mappings. - When the names within an object are not unique, later stored name/value pairs overwrite previously stored name/value pairs, leaving the used names unique. For instance, `{"key": 1}` and `{"key": 2, "key": 1}` will be treated as equal and both stored as `{"key": 1}`. - Internally, name/value pairs are stored in lexicographical order of the - names. Objects will also be serialized (see @ref dump) in this order. For - instance, `{"b": 1, "a": 2}` and `{"a": 2, "b": 1}` will be stored and - serialized as `{"a": 2, "b": 1}`. + names. Objects will also be serialized (see @ref dump) in this order. + For instance, `{"b": 1, "a": 2}` and `{"a": 2, "b": 1}` will be stored + and serialized as `{"a": 2, "b": 1}`. - When comparing objects, the order of the name/value pairs is irrelevant. This makes objects interoperable in the sense that they will not be affected by these differences. For instance, `{"b": 1, "a": 2}` and @@ -320,23 +320,24 @@ class basic_json In this class, the object's limit of nesting is not constraint explicitly. However, a maximum depth of nesting may be introduced by the compiler or - runtime environment. A theoretical limit can be queried by calling the @ref - max_size function of a JSON object. + runtime environment. A theoretical limit can be queried by calling the + @ref max_size function of a JSON object. #### Storage Objects are stored as pointers in a @ref basic_json type. That is, for any - access to object values, a pointer of type `object_t*` must be dereferenced. + access to object values, a pointer of type `object_t*` must be + dereferenced. @sa @ref array_t -- type for an array value @since version 1.0.0 - @note The order name/value pairs are added to the object is *not* preserved - by the library. Therefore, iterating an object may return name/value pairs - in a different order than they were originally stored. In fact, keys will - be traversed in alphabetical order as `std::map` with `std::less` is used - by default. Please note this behavior conforms to [RFC + @note The order name/value pairs are added to the object is *not* + preserved by the library. Therefore, iterating an object may return + name/value pairs in a different order than they were originally stored. In + fact, keys will be traversed in alphabetical order as `std::map` with + `std::less` is used by default. Please note this behavior conforms to [RFC 7159](http://rfc7159.net/rfc7159), because any order implements the specified "unordered" nature of JSON objects. */ @@ -378,8 +379,8 @@ class basic_json In this class, the array's limit of nesting is not constraint explicitly. However, a maximum depth of nesting may be introduced by the compiler or - runtime environment. A theoretical limit can be queried by calling the @ref - max_size function of a JSON array. + runtime environment. A theoretical limit can be queried by calling the + @ref max_size function of a JSON array. #### Storage @@ -399,8 +400,8 @@ class basic_json > A string is a sequence of zero or more Unicode characters. To store objects in C++, a type is defined by the template parameter - described below. Unicode values are split by the JSON class into byte-sized - characters during deserialization. + described below. Unicode values are split by the JSON class into + byte-sized characters during deserialization. @tparam StringType the container to store strings (e.g., `std::string`). Note this container is used for keys/names in objects, see @ref object_t. @@ -469,19 +470,19 @@ class basic_json @brief a type for a number (integer) [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: - > The representation of numbers is similar to that used in most programming - > languages. A number is represented in base 10 using decimal digits. It - > contains an integer component that may be prefixed with an optional minus - > sign, which may be followed by a fraction part and/or an exponent part. - > Leading zeros are not allowed. (...) Numeric values that cannot be - > represented in the grammar below (such as Infinity and NaN) are not - > permitted. + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. - This description includes both integer and floating-point numbers. However, - C++ allows more precise storage if it is known whether the number is a - signed integer, an unsigned integer or a floating-point number. Therefore, - three different types, @ref number_integer_t, @ref number_unsigned_t and - @ref number_float_t are used. + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. To store integer numbers in C++, a type is defined by the template parameter @a NumberIntegerType which chooses the type to use. @@ -500,8 +501,8 @@ class basic_json - The restrictions about leading zeros is not enforced in C++. Instead, leading zeros in integer literals lead to an interpretation as octal number. Internally, the value will be stored as decimal number. For - instance, the C++ integer literal `010` will be serialized to `8`. During - deserialization, leading zeros yield an error. + instance, the C++ integer literal `010` will be serialized to `8`. + During deserialization, leading zeros yield an error. - Not-a-number (NaN) values will be serialized to `null`. #### Limits @@ -512,9 +513,10 @@ class basic_json When the default type is used, the maximal integer number that can be stored is `9223372036854775807` (INT64_MAX) and the minimal integer number that can be stored is `-9223372036854775808` (INT64_MIN). Integer numbers - that are out of range will yield over/underflow when used in a constructor. - During deserialization, too large or small integer numbers will be - automatically be stored as @ref number_unsigned_t or @ref number_float_t. + that are out of range will yield over/underflow when used in a + constructor. During deserialization, too large or small integer numbers + will be automatically be stored as @ref number_unsigned_t or @ref + number_float_t. [RFC 7159](http://rfc7159.net/rfc7159) further states: > Note that when such software is used, numbers that are integers and are @@ -540,27 +542,27 @@ class basic_json @brief a type for a number (unsigned) [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: - > The representation of numbers is similar to that used in most programming - > languages. A number is represented in base 10 using decimal digits. It - > contains an integer component that may be prefixed with an optional minus - > sign, which may be followed by a fraction part and/or an exponent part. - > Leading zeros are not allowed. (...) Numeric values that cannot be - > represented in the grammar below (such as Infinity and NaN) are not - > permitted. + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. - This description includes both integer and floating-point numbers. However, - C++ allows more precise storage if it is known whether the number is a - signed integer, an unsigned integer or a floating-point number. Therefore, - three different types, @ref number_integer_t, @ref number_unsigned_t and - @ref number_float_t are used. + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. - To store unsigned integer numbers in C++, a type is defined by the template - parameter @a NumberUnsignedType which chooses the type to use. + To store unsigned integer numbers in C++, a type is defined by the + template parameter @a NumberUnsignedType which chooses the type to use. #### Default type - With the default values for @a NumberUnsignedType (`uint64_t`), the default - value for @a number_unsigned_t is: + With the default values for @a NumberUnsignedType (`uint64_t`), the + default value for @a number_unsigned_t is: @code {.cpp} uint64_t @@ -571,8 +573,8 @@ class basic_json - The restrictions about leading zeros is not enforced in C++. Instead, leading zeros in integer literals lead to an interpretation as octal number. Internally, the value will be stored as decimal number. For - instance, the C++ integer literal `010` will be serialized to `8`. During - deserialization, leading zeros yield an error. + instance, the C++ integer literal `010` will be serialized to `8`. + During deserialization, leading zeros yield an error. - Not-a-number (NaN) values will be serialized to `null`. #### Limits @@ -612,19 +614,19 @@ class basic_json @brief a type for a number (floating-point) [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: - > The representation of numbers is similar to that used in most programming - > languages. A number is represented in base 10 using decimal digits. It - > contains an integer component that may be prefixed with an optional minus - > sign, which may be followed by a fraction part and/or an exponent part. - > Leading zeros are not allowed. (...) Numeric values that cannot be - > represented in the grammar below (such as Infinity and NaN) are not - > permitted. + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. - This description includes both integer and floating-point numbers. However, - C++ allows more precise storage if it is known whether the number is a - signed integer, an unsigned integer or a floating-point number. Therefore, - three different types, @ref number_integer_t, @ref number_unsigned_t and - @ref number_float_t are used. + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. To store floating-point numbers in C++, a type is defined by the template parameter @a NumberFloatType which chooses the type to use. @@ -641,8 +643,8 @@ class basic_json #### Default behavior - The restrictions about leading zeros is not enforced in C++. Instead, - leading zeros in floating-point literals will be ignored. Internally, the - value will be stored as decimal number. For instance, the C++ + leading zeros in floating-point literals will be ignored. Internally, + the value will be stored as decimal number. For instance, the C++ floating-point literal `01.2` will be serialized to `1.2`. During deserialization, leading zeros yield an error. - Not-a-number (NaN) values will be serialized to `null`. @@ -653,9 +655,9 @@ class basic_json > This specification allows implementations to set limits on the range and > precision of numbers accepted. Since software that implements IEEE > 754-2008 binary64 (double precision) numbers is generally available and - > widely used, good interoperability can be achieved by implementations that - > expect no more precision or range than these provide, in the sense that - > implementations will approximate JSON numbers within the expected + > widely used, good interoperability can be achieved by implementations + > that expect no more precision or range than these provide, in the sense + > that implementations will approximate JSON numbers within the expected > precision. This implementation does exactly follow this approach, as it uses double @@ -709,73 +711,6 @@ class basic_json private: - /*! - @brief a type to hold JSON type information - - This bitfield type holds information about JSON types. It is internally - used to hold the basic JSON type enumeration, as well as additional - information in the case of values that have been parsed from a string - including whether of not it was created directly or parsed, and in the - case of floating point numbers the number of significant figures in the - original representaiton and if it was in exponential form, if a '+' was - included in the exponent and the capitilization of the exponent marker. - The sole purpose of this information is to permit accurate round trips. - - @since version 2.0.0 - */ - union type_data_t - { - struct - { - /// the type of the value (@ref value_t) - uint16_t type : 4; - /// whether the number was parsed from a string - uint16_t parsed : 1; - /// whether parsed number contained an exponent ('e'/'E') - uint16_t has_exp : 1; - /// whether parsed number contained a plus in the exponent - uint16_t exp_plus : 1; - /// whether parsed number's exponent was capitalized ('E') - uint16_t exp_cap : 1; - /// the number of figures for a parsed number - uint16_t precision : 8; - } bits; - uint16_t data; - - /// return the type as value_t - operator value_t() const - { - return static_cast(bits.type); - } - - /// test type for equality (ignore other fields) - bool operator==(const value_t& rhs) const - { - return static_cast(bits.type) == rhs; - } - - /// assignment - type_data_t& operator=(value_t rhs) - { - bits.type = static_cast(rhs); - return *this; - } - - /// construct from value_t - type_data_t(value_t t) noexcept - { - *reinterpret_cast(this) = 0; - bits.type = static_cast(t); - } - - /// default constructor - type_data_t() noexcept - { - data = 0; - bits.type = reinterpret_cast(value_t::null); - } - }; - /// helper for exception-safe object creation template static T* create(Args&& ... args) @@ -938,9 +873,9 @@ class basic_json influenced. When passed to @ref parse(std::istream&, parser_callback_t) or @ref parse(const string_t&, parser_callback_t), it is called on certain events (passed as @ref parse_event_t via parameter @a event) with a set - recursion depth @a depth and context JSON value @a parsed. The return value - of the callback function is a boolean indicating whether the element that - emitted the callback shall be kept or not. + recursion depth @a depth and context JSON value @a parsed. The return + value of the callback function is a boolean indicating whether the element + that emitted the callback shall be kept or not. We distinguish six scenarios (determined by the event type) in which the callback function can be called. The following table describes the values @@ -960,8 +895,8 @@ class basic_json - Discarded values in structured types are skipped. That is, the parser will behave as if the discarded value was never read. - - In case a value outside a structured type is skipped, it is replaced with - `null`. This case happens if the top-level element is skipped. + - In case a value outside a structured type is skipped, it is replaced + with `null`. This case happens if the top-level element is skipped. @param[in] depth the depth of the recursion during parsing @@ -1065,8 +1000,8 @@ class basic_json Create a `null` JSON value. This is the explicitly version of the `null` value constructor as it takes a null pointer as parameter. It allows to create `null` values by explicitly assigning a `nullptr` to a JSON value. - The passed null pointer itself is not read -- it is only used to choose the - right constructor. + The passed null pointer itself is not read -- it is only used to choose + the right constructor. @complexity Constant. @@ -1096,8 +1031,8 @@ class basic_json @throw std::bad_alloc if allocation for object value fails - @liveexample{The following code shows the constructor with an @ref object_t - parameter.,basic_json__object_t} + @liveexample{The following code shows the constructor with an @ref + object_t parameter.,basic_json__object_t} @sa @ref basic_json(const CompatibleObjectType&) -- create an object value from a compatible STL container @@ -1177,11 +1112,11 @@ class basic_json any type @a CompatibleArrayType that can be used to construct values of type @ref array_t. - @tparam CompatibleArrayType An object type whose `value_type` is compatible - to @ref array_t. Examples include `std::vector`, `std::deque`, `std::list`, - `std::forward_list`, `std::array`, `std::set`, `std::unordered_set`, - `std::multiset`, and `unordered_multiset` with a `value_type` from which a - @ref basic_json value can be constructed. + @tparam CompatibleArrayType An object type whose `value_type` is + compatible to @ref array_t. Examples include `std::vector`, `std::deque`, + `std::list`, `std::forward_list`, `std::array`, `std::set`, + `std::unordered_set`, `std::multiset`, and `unordered_multiset` with a + `value_type` from which a @ref basic_json value can be constructed. @param[in] val a value for the array @@ -1225,8 +1160,8 @@ class basic_json @throw std::bad_alloc if allocation for string value fails - @liveexample{The following code shows the constructor with an @ref string_t - parameter.,basic_json__string_t} + @liveexample{The following code shows the constructor with an @ref + string_t parameter.,basic_json__string_t} @sa @ref basic_json(const typename string_t::value_type*) -- create a string value from a character pointer @@ -1318,9 +1253,9 @@ class basic_json Create an integer number JSON value with a given content. @tparam T A helper type to remove this function via SFINAE in case @ref - number_integer_t is the same as `int`. In this case, this constructor would - have the same signature as @ref basic_json(const int value). Note the - helper type @a T is not visible in this constructor's interface. + number_integer_t is the same as `int`. In this case, this constructor + would have the same signature as @ref basic_json(const int value). Note + the helper type @a T is not visible in this constructor's interface. @param[in] val an integer to create a JSON number from @@ -1355,8 +1290,8 @@ class basic_json @note This constructor allows to pass enums directly to a constructor. As C++ has no way of specifying the type of an anonymous enum explicitly, we can only rely on the fact that such values implicitly convert to int. As - int may already be the same type of number_integer_t, we may need to switch - off the constructor @ref basic_json(const number_integer_t). + int may already be the same type of number_integer_t, we may need to + switch off the constructor @ref basic_json(const number_integer_t). @complexity Constant. @@ -1383,8 +1318,8 @@ class basic_json construct values of type @ref number_integer_t. @tparam CompatibleNumberIntegerType An integer type which is compatible to - @ref number_integer_t. Examples include the types `int`, `int32_t`, `long`, - and `short`. + @ref number_integer_t. Examples include the types `int`, `int32_t`, + `long`, and `short`. @param[in] val an integer to create a JSON number from @@ -1442,12 +1377,12 @@ class basic_json /*! @brief create an unsigned number (implicit) - Create an unsigned number JSON value with a given content. This constructor - allows any type @a CompatibleNumberUnsignedType that can be used to - construct values of type @ref number_unsigned_t. + Create an unsigned number JSON value with a given content. This + constructor allows any type @a CompatibleNumberUnsignedType that can be + used to construct values of type @ref number_unsigned_t. - @tparam CompatibleNumberUnsignedType An integer type which is compatible to - @ref number_unsigned_t. Examples may include the types `unsigned int`, + @tparam CompatibleNumberUnsignedType An integer type which is compatible + to @ref number_unsigned_t. Examples may include the types `unsigned int`, `uint32_t`, or `unsigned short`. @param[in] val an unsigned integer to create a JSON number from @@ -1459,13 +1394,13 @@ class basic_json @since version 2.0.0 */ - template < typename CompatibleNumberUnsignedType, typename - std::enable_if < - std::is_constructible::value and - std::numeric_limits::is_integer and - !std::numeric_limits::is_signed, - CompatibleNumberUnsignedType >::type - = 0 > + template ::value and + std::numeric_limits::is_integer and + not std::numeric_limits::is_signed, + CompatibleNumberUnsignedType>::type + = 0> basic_json(const CompatibleNumberUnsignedType val) noexcept : m_type(value_t::number_unsigned), m_value(static_cast(val)) @@ -1480,8 +1415,8 @@ class basic_json @note [RFC 7159](http://www.rfc-editor.org/rfc/rfc7159.txt), section 6 disallows NaN values: - > Numeric values that cannot be represented in the grammar below (such - > as Infinity and NaN) are not permitted. + > Numeric values that cannot be represented in the grammar below (such as + > Infinity and NaN) are not permitted. In case the parameter @a val is not a number, a JSON null value is created instead. @@ -1513,15 +1448,16 @@ class basic_json constructor allows any type @a CompatibleNumberFloatType that can be used to construct values of type @ref number_float_t. - @tparam CompatibleNumberFloatType A floating-point type which is compatible - to @ref number_float_t. Examples may include the types `float` or `double`. + @tparam CompatibleNumberFloatType A floating-point type which is + compatible to @ref number_float_t. Examples may include the types `float` + or `double`. @param[in] val a floating-point to create a JSON number from @note [RFC 7159](http://www.rfc-editor.org/rfc/rfc7159.txt), section 6 disallows NaN values: - > Numeric values that cannot be represented in the grammar below (such - > as Infinity and NaN) are not permitted. + > Numeric values that cannot be represented in the grammar below (such as + > Infinity and NaN) are not permitted. In case the parameter @a val is not a number, a JSON null value is created instead. @@ -1592,13 +1528,13 @@ class basic_json used by the functions @ref array(std::initializer_list) and @ref object(std::initializer_list). - @param[in] manual_type internal parameter; when @a type_deduction is set to - `false`, the created JSON value will use the provided type (only @ref + @param[in] manual_type internal parameter; when @a type_deduction is set + to `false`, the created JSON value will use the provided type (only @ref value_t::array and @ref value_t::object are valid); when @a type_deduction is set to `true`, this parameter has no effect - @throw std::domain_error if @a type_deduction is `false`, @a manual_type is - `value_t::object`, but @a init contains an element which is not a pair + @throw std::domain_error if @a type_deduction is `false`, @a manual_type + is `value_t::object`, but @a init contains an element which is not a pair whose first element is a string; example: `"cannot create object from initializer list"` @@ -1679,8 +1615,8 @@ class basic_json list of values `a, b, c`, creates the JSON value `[a, b, c]`. If the initializer list is empty, the empty array `[]` is created. - @note This function is only needed to express two edge cases that cannot be - realized with the initializer list constructor (@ref + @note This function is only needed to express two edge cases that cannot + be realized with the initializer list constructor (@ref basic_json(std::initializer_list, bool, value_t)). These cases are: 1. creating an array whose elements are all pairs whose first element is a @@ -1723,8 +1659,8 @@ class basic_json related function @ref array(std::initializer_list), there are no cases which can only be expressed by this function. That is, any initializer list @a init can also be passed to the initializer list - constructor - @ref basic_json(std::initializer_list, bool, value_t). + constructor @ref basic_json(std::initializer_list, bool, + value_t). @param[in] init initializer list to create an object from (optional) @@ -1755,8 +1691,8 @@ class basic_json /*! @brief construct an array with count copies of given value - Constructs a JSON array value by creating @a cnt copies of a passed - value. In case @a cnt is `0`, an empty array is created. As postcondition, + Constructs a JSON array value by creating @a cnt copies of a passed value. + In case @a cnt is `0`, an empty array is created. As postcondition, `std::distance(begin(),end()) == cnt` holds. @param[in] cnt the number of JSON copies of @a val to create @@ -1784,8 +1720,8 @@ class basic_json - In case of primitive types (number, boolean, or string), @a first must be `begin()` and @a last must be `end()`. In this case, the value is copied. Otherwise, std::out_of_range is thrown. - - In case of structured types (array, object), the constructor behaves - as similar versions for `std::vector`. + - In case of structured types (array, object), the constructor behaves as + similar versions for `std::vector`. - In case of a null type, std::domain_error is thrown. @tparam InputIT an input iterator type (@ref iterator or @ref @@ -1800,8 +1736,8 @@ class basic_json boolean, or string) where an out of range error can be detected easily; example: `"iterators out of range"` @throw std::bad_alloc if allocation for object, array, or string fails - @throw std::domain_error if called with a null value; example: `"cannot use - construct with iterators from null"` + @throw std::domain_error if called with a null value; example: `"cannot + use construct with iterators from null"` @complexity Linear in distance between @a first and @a last. @@ -2042,8 +1978,8 @@ class basic_json @brief copy assignment Copy assignment operator. Copies a JSON value via the "copy and swap" - strategy: It is expressed in terms of the copy constructor, destructor, and - the swap() member function. + strategy: It is expressed in terms of the copy constructor, destructor, + and the swap() member function. @param[in] other value to copy from @@ -2143,8 +2079,8 @@ class basic_json parameter. @param[in] indent if indent is nonnegative, then array elements and object - members will be pretty-printed with that indent level. An indent level of 0 - will only insert newlines. -1 (the default) selects the most compact + members will be pretty-printed with that indent level. An indent level of + 0 will only insert newlines. -1 (the default) selects the most compact representation @return string containing the serialization of the JSON value @@ -2976,8 +2912,8 @@ class basic_json /*! @brief get a reference value (implicit) - Implict reference access to the internally stored JSON value. No copies are - made. + Implict reference access to the internally stored JSON value. No copies + are made. @warning Writing data to the referee of the result yields an undefined state. @@ -3027,14 +2963,14 @@ class basic_json /*! @brief get a value (implicit) - Implicit type conversion between the JSON value and a compatible value. The - call is realized by calling @ref get() const. + Implicit type conversion between the JSON value and a compatible value. + The call is realized by calling @ref get() const. @tparam ValueType non-pointer type compatible to the JSON value, for instance `int` for JSON integer numbers, `bool` for JSON booleans, or - `std::vector` types for JSON arrays. The character type of @ref string_t as - well as an initializer list of this type is excluded to avoid ambiguities - as these types implicitly convert to `std::string`. + `std::vector` types for JSON arrays. The character type of @ref string_t + as well as an initializer list of this type is excluded to avoid + ambiguities as these types implicitly convert to `std::string`. @return copy of the JSON value, converted to type @a ValueType @@ -3123,8 +3059,8 @@ class basic_json /*! @brief access specified array element with bounds checking - Returns a const reference to the element at specified location @a idx, with - bounds checking. + Returns a const reference to the element at specified location @a idx, + with bounds checking. @param[in] idx index of the element to access @@ -3215,8 +3151,8 @@ class basic_json /*! @brief access specified object element with bounds checking - Returns a const reference to the element at with specified key @a key, with - bounds checking. + Returns a const reference to the element at with specified key @a key, + with bounds checking. @param[in] key key of the element to access @@ -3273,8 +3209,8 @@ class basic_json @return reference to the element at index @a idx - @throw std::domain_error if JSON is not an array or null; example: `"cannot - use operator[] with string"` + @throw std::domain_error if JSON is not an array or null; example: + `"cannot use operator[] with string"` @complexity Constant if @a idx is in the range of the array. Otherwise linear in `idx - size()`. @@ -3690,8 +3626,8 @@ class basic_json @complexity Constant. - @pre The JSON value must not be `null` (would throw `std::out_of_range`) or - an empty array or object (undefined behavior, guarded by assertions). + @pre The JSON value must not be `null` (would throw `std::out_of_range`) + or an empty array or object (undefined behavior, guarded by assertions). @post The JSON value remains unchanged. @throw std::out_of_range when called on `null` value @@ -3732,8 +3668,8 @@ class basic_json @complexity Constant. - @pre The JSON value must not be `null` (would throw `std::out_of_range`) or - an empty array or object (undefined behavior, guarded by assertions). + @pre The JSON value must not be `null` (would throw `std::out_of_range`) + or an empty array or object (undefined behavior, guarded by assertions). @post The JSON value remains unchanged. @throw std::out_of_range when called on `null` value. @@ -3772,16 +3708,16 @@ class basic_json will be `null`. @param[in] pos iterator to the element to remove - @return Iterator following the last removed element. If the iterator @a pos - refers to the last element, the `end()` iterator is returned. + @return Iterator following the last removed element. If the iterator @a + pos refers to the last element, the `end()` iterator is returned. @tparam InteratorType an @ref iterator or @ref const_iterator @post Invalidates iterators and references at or after the point of the erase, including the `end()` iterator. - @throw std::domain_error if called on a `null` value; example: `"cannot use - erase() with null"` + @throw std::domain_error if called on a `null` value; example: `"cannot + use erase() with null"` @throw std::domain_error if called on an iterator which does not belong to the current JSON value; example: `"iterator does not fit current value"` @throw std::out_of_range if called on a primitive type with invalid @@ -3797,12 +3733,12 @@ class basic_json @liveexample{The example shows the result of `erase()` for different JSON types.,erase__IteratorType} - @sa @ref erase(InteratorType, InteratorType) -- removes the elements in the - given range + @sa @ref erase(InteratorType, InteratorType) -- removes the elements in + the given range @sa @ref erase(const typename object_t::key_type&) -- removes the element from an object at the given key - @sa @ref erase(const size_type) -- removes the element from an array at the - given index + @sa @ref erase(const size_type) -- removes the element from an array at + the given index @since version 1.0.0 */ @@ -3871,9 +3807,9 @@ class basic_json /*! @brief remove elements given an iterator range - Removes the element specified by the range `[first; last)`. The iterator @a - first does not need to be dereferenceable if `first == last`: erasing an - empty range is a no-op. + Removes the element specified by the range `[first; last)`. The iterator + @a first does not need to be dereferenceable if `first == last`: erasing + an empty range is a no-op. If called on a primitive type other than `null`, the resulting JSON value will be `null`. @@ -3888,8 +3824,8 @@ class basic_json @post Invalidates iterators and references at or after the point of the erase, including the `end()` iterator. - @throw std::domain_error if called on a `null` value; example: `"cannot use - erase() with null"` + @throw std::domain_error if called on a `null` value; example: `"cannot + use erase() with null"` @throw std::domain_error if called on iterators which does not belong to the current JSON value; example: `"iterators do not fit current value"` @throw std::out_of_range if called on a primitive type with invalid @@ -3909,8 +3845,8 @@ class basic_json @sa @ref erase(InteratorType) -- removes the element at a given position @sa @ref erase(const typename object_t::key_type&) -- removes the element from an object at the given key - @sa @ref erase(const size_type) -- removes the element from an array at the - given index + @sa @ref erase(const size_type) -- removes the element from an array at + the given index @since version 1.0.0 */ @@ -3986,8 +3922,8 @@ class basic_json @param[in] key value of the elements to remove @return Number of elements removed. If @a ObjectType is the default - `std::map` type, the return value will always be `0` (@a key was not found) - or `1` (@a key was found). + `std::map` type, the return value will always be `0` (@a key was not + found) or `1` (@a key was found). @post References and iterators to the erased elements are invalidated. Other references and iterators are not affected. @@ -4000,10 +3936,10 @@ class basic_json @liveexample{The example shows the effect of `erase()`.,erase__key_type} @sa @ref erase(InteratorType) -- removes the element at a given position - @sa @ref erase(InteratorType, InteratorType) -- removes the elements in the - given range - @sa @ref erase(const size_type) -- removes the element from an array at the - given index + @sa @ref erase(InteratorType, InteratorType) -- removes the elements in + the given range + @sa @ref erase(const size_type) -- removes the element from an array at + the given index @since version 1.0.0 */ @@ -4030,16 +3966,16 @@ class basic_json @throw std::domain_error when called on a type other than JSON array; example: `"cannot use erase() with null"` - @throw std::out_of_range when `idx >= size()`; example: `"index out of - range"` + @throw std::out_of_range when `idx >= size()`; example: `"array index 17 + is out of range"` @complexity Linear in distance between @a idx and the end of the container. @liveexample{The example shows the effect of `erase()`.,erase__size_type} @sa @ref erase(InteratorType) -- removes the element at a given position - @sa @ref erase(InteratorType, InteratorType) -- removes the elements in the - given range + @sa @ref erase(InteratorType, InteratorType) -- removes the elements in + the given range @sa @ref erase(const typename object_t::key_type&) -- removes the element from an object at the given key @@ -4052,7 +3988,7 @@ class basic_json { if (idx >= size()) { - throw std::out_of_range("index out of range"); + throw std::out_of_range("array index " + std::to_string(idx) + " is out of range"); } assert(m_value.array != nullptr); @@ -4078,7 +4014,8 @@ class basic_json @brief find an element in a JSON object Finds an element in a JSON object with key equivalent to @a key. If the - element is not found or the JSON value is not an object, end() is returned. + element is not found or the JSON value is not an object, end() is + returned. @param[in] key key value of the element to search for @@ -4438,9 +4375,9 @@ class basic_json @brief wrapper to access iterator member functions in range-based for This function allows to access @ref iterator::key() and @ref - iterator::value() during range-based for loops. In these loops, a reference - to the JSON values is returned, so there is no access to the underlying - iterator. + iterator::value() during range-based for loops. In these loops, a + reference to the JSON values is returned, so there is no access to the + underlying iterator. @note The name of this function is not yet final and may change in the future. @@ -4484,8 +4421,8 @@ class basic_json object | result of function `object_t::empty()` array | result of function `array_t::empty()` - @complexity Constant, as long as @ref array_t and @ref object_t satisfy the - Container concept; that is, their `empty()` functions have constant + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their `empty()` functions have constant complexity. @requirement This function helps `basic_json` satisfying the @@ -4547,8 +4484,9 @@ class basic_json object | result of function object_t::size() array | result of function array_t::size() - @complexity Constant, as long as @ref array_t and @ref object_t satisfy the - Container concept; that is, their size() functions have constant complexity. + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their size() functions have constant + complexity. @requirement This function helps `basic_json` satisfying the [Container](http://en.cppreference.com/w/cpp/concept/Container) @@ -4612,8 +4550,8 @@ class basic_json object | result of function `object_t::max_size()` array | result of function `array_t::max_size()` - @complexity Constant, as long as @ref array_t and @ref object_t satisfy the - Container concept; that is, their `max_size()` functions have constant + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their `max_size()` functions have constant complexity. @requirement This function helps `basic_json` satisfying the @@ -4835,8 +4773,8 @@ class basic_json @brief add an object to an object Inserts the given element @a val to the JSON object. If the function is - called on a JSON null value, an empty object is created before inserting @a - val. + called on a JSON null value, an empty object is created before inserting + @a val. @param[in] val the value to add to the JSON object @@ -4878,7 +4816,55 @@ class basic_json reference operator+=(const typename object_t::value_type& val) { push_back(val); - return operator[](val.first); + return *this; + } + + /*! + @brief add an object to an object + + This function allows to use `push_back` with an initializer list. In case + + 1. the current value is an object, + 2. the initializer list @a init contains only two elements, and + 3. the first element of @a init is a string, + + @a init is converted into an object element and added using + @ref push_back(const typename object_t::value_type&). Otherwise, @a init + is converted to a JSON value and added using @ref push_back(basic_json&&). + + @param init an initializer list + + @complexity Linear in the size of the initializer list @a init. + + @note This function is required to resolve an ambiguous overload error, + because pairs like `{"key", "value"}` can be both interpreted as + `object_t::value_type` or `std::initializer_list`, see + https://github.com/nlohmann/json/issues/235 for more information. + + @liveexample{The example shows how initializer lists are treated as + objects when possible.,push_back__initializer_list} + */ + void push_back(std::initializer_list init) + { + if (is_object() and init.size() == 2 and init.begin()->is_string()) + { + const string_t key = *init.begin(); + push_back(typename object_t::value_type(key, *(init.begin() + 1))); + } + else + { + push_back(basic_json(init)); + } + } + + /*! + @brief add an object to an object + @copydoc push_back(std::initializer_list) + */ + reference operator+=(std::initializer_list init) + { + push_back(init); + return *this; } /*! @@ -5026,6 +5012,7 @@ class basic_json throw std::domain_error("iterator does not fit current value"); } + // check if range iterators belong to the same JSON object if (first.m_object != last.m_object) { throw std::domain_error("iterators do not fit"); @@ -5063,8 +5050,8 @@ class basic_json @return iterator pointing to the first element inserted, or @a pos if `ilist` is empty - @complexity Linear in `ilist.size()` plus linear in the distance between @a - pos and end of the container. + @complexity Linear in `ilist.size()` plus linear in the distance between + @a pos and end of the container. @liveexample{The example shows how `insert()` is used.,insert__ilist} @@ -5709,8 +5696,8 @@ class basic_json @note A UTF-8 byte order mark is silently ignored. - @liveexample{The example below demonstrates the `parse()` function with and - without callback function.,parse__string__parser_callback_t} + @liveexample{The example below demonstrates the `parse()` function with + and without callback function.,parse__string__parser_callback_t} @sa @ref parse(std::istream&, parser_callback_t) for a version that reads from an input stream @@ -5738,11 +5725,11 @@ class basic_json @note A UTF-8 byte order mark is silently ignored. - @liveexample{The example below demonstrates the `parse()` function with and - without callback function.,parse__istream__parser_callback_t} + @liveexample{The example below demonstrates the `parse()` function with + and without callback function.,parse__istream__parser_callback_t} - @sa @ref parse(const string_t&, parser_callback_t) for a version that reads - from a string + @sa @ref parse(const string_t&, parser_callback_t) for a version that + reads from a string @since version 1.0.0 */ @@ -5875,8 +5862,8 @@ class basic_json /*! @brief escape a string - Escape a string by replacing certain special characters by a sequence of an - escape character (backslash) and another character and other control + Escape a string by replacing certain special characters by a sequence of + an escape character (backslash) and another character and other control characters by a sequence of "\u" followed by a four-digit hex representation. @@ -5963,9 +5950,11 @@ class basic_json { // convert a number 0..15 to its hex representation // (0..f) - auto hexify = [](const char v) -> char + const auto hexify = [](const int v) -> char { - return (v < 10) ? ('0' + v) : ('a' + v - 10); + return (v < 10) + ? ('0' + static_cast(v)) + : ('a' + static_cast((v - 10) & 0x1f)); }; // print character c as \uxxxx @@ -5996,8 +5985,8 @@ class basic_json This function is called by the public member function dump and organizes the serialization internally. The indentation level is propagated as - additional parameter. In case of arrays and objects, the function is called - recursively. Note that + additional parameter. In case of arrays and objects, the function is + called recursively. Note that - strings and object keys are escaped using `escape_string()` - integer numbers are converted implicitly via `operator<<` @@ -6127,79 +6116,23 @@ class basic_json case value_t::number_float: { - // check if number was parsed from a string - if (m_type.bits.parsed) + if (m_value.number_float == 0) { - // check if parsed number had an exponent given - if (m_type.bits.has_exp) - { - // buffer size: precision (2^8-1 = 255) + other ('-.e-xxx' = 7) + null (1) - char buf[263]; - int len; - - // handle capitalization of the exponent - if (m_type.bits.exp_cap) - { - len = snprintf(buf, sizeof(buf), "%.*E", - m_type.bits.precision, m_value.number_float) + 1; - } - else - { - len = snprintf(buf, sizeof(buf), "%.*e", - m_type.bits.precision, m_value.number_float) + 1; - } - - // remove '+' sign from the exponent if necessary - if (not m_type.bits.exp_plus) - { - if (len > static_cast(sizeof(buf))) - { - len = sizeof(buf); - } - for (int i = 0; i < len; i++) - { - if (buf[i] == '+') - { - for (; i + 1 < len; i++) - { - buf[i] = buf[i + 1]; - } - } - } - } - - o << buf; - } - else - { - // no exponent - output as a decimal - std::stringstream ss; - ss.imbue(std::locale(std::locale(), new DecimalSeparator)); // fix locale problems - ss << std::setprecision(m_type.bits.precision) - << std::fixed << m_value.number_float; - o << ss.str(); - } + // special case for zero to get "0.0"/"-0.0" + o << (std::signbit(m_value.number_float) ? "-0.0" : "0.0"); } else { - if (m_value.number_float == 0) - { - // special case for zero to get "0.0"/"-0.0" - o << (std::signbit(m_value.number_float) ? "-0.0" : "0.0"); - } - else - { - // Otherwise 6, 15 or 16 digits of precision allows - // round-trip IEEE 754 string->float->string, - // string->double->string or string->long double->string; - // to be safe, we read this value from - // std::numeric_limits::digits10 - std::stringstream ss; - ss.imbue(std::locale(std::locale(), new DecimalSeparator)); // fix locale problems - ss << std::setprecision(std::numeric_limits::digits10) - << m_value.number_float; - o << ss.str(); - } + // Otherwise 6, 15 or 16 digits of precision allows + // round-trip IEEE 754 string->float->string, + // string->double->string or string->long + // double->string; to be safe, we read this value from + // std::numeric_limits::digits10 + std::stringstream ss; + ss.imbue(std::locale(std::locale(), new DecimalSeparator)); // fix locale problems + ss << std::setprecision(std::numeric_limits::digits10) + << m_value.number_float; + o << ss.str(); } return; } @@ -6224,7 +6157,7 @@ class basic_json ////////////////////// /// the type of the current element - type_data_t m_type = value_t::null; + value_t m_type = value_t::null; /// the value of the current element json_value m_value = {}; @@ -7000,13 +6933,13 @@ class basic_json } /// return a reference to the value pointed to by the iterator - reference operator*() + reference operator*() const { return const_cast(base_iterator::operator*()); } /// dereference the iterator - pointer operator->() + pointer operator->() const { return const_cast(base_iterator::operator->()); } @@ -7212,8 +7145,8 @@ class basic_json @brief lexical analysis This class organizes the lexical analysis during JSON deserialization. The - core of it is a scanner generated by [re2c](http://re2c.org) that processes - a buffer and recognizes tokens according to RFC 7159. + core of it is a scanner generated by [re2c](http://re2c.org) that + processes a buffer and recognizes tokens according to RFC 7159. */ class lexer { @@ -7222,17 +7155,17 @@ class basic_json enum class token_type { uninitialized, ///< indicating the scanner is uninitialized - literal_true, ///< the "true" literal - literal_false, ///< the "false" literal - literal_null, ///< the "null" literal + literal_true, ///< the `true` literal + literal_false, ///< the `false` literal + literal_null, ///< the `null` literal value_string, ///< a string -- use get_string() for actual value value_number, ///< a number -- use get_number() for actual value - begin_array, ///< the character for array begin "[" - begin_object, ///< the character for object begin "{" - end_array, ///< the character for array end "]" - end_object, ///< the character for object end "}" - name_separator, ///< the name separator ":" - value_separator, ///< the value separator "," + begin_array, ///< the character for array begin `[` + begin_object, ///< the character for object begin `{` + end_array, ///< the character for array end `]` + end_object, ///< the character for object end `}` + name_separator, ///< the name separator `:` + value_separator, ///< the value separator `,` parse_error, ///< indicating a parse error end_of_input ///< indicating the end of the input buffer }; @@ -7277,7 +7210,7 @@ class basic_json @return string representation of the code point - @throw std::out_of_range if code point is >0x10ffff; example: `"code + @throw std::out_of_range if code point is > 0x10ffff; example: `"code points above 0x10FFFF are invalid"` @throw std::invalid_argument if the low surrogate is invalid; example: `""missing or wrong low surrogate""` @@ -7455,10 +7388,10 @@ class basic_json number { return token_type::value_number; } // string - quotation_mark = [\"]; + quotation_mark = ["]; escape = [\\]; - unescaped = [^\"\\\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F]; - single_escaped = [\"\\/bfnrt]; + unescaped = [^"\\\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F]; + single_escaped = ["\\/bfnrt]; unicode_escaped = [u][0-9a-fA-F]{4}; escaped = escape (single_escaped | unicode_escaped); char = unescaped | escaped; @@ -7519,13 +7452,14 @@ class basic_json 1. Escaped characters. In this case, a new character is constructed according to the nature of the escape. Some escapes create new - characters (e.g., `"\\n"` is replaced by `"\n"`), some are copied as - is (e.g., `"\\\\"`). Furthermore, Unicode escapes of the shape + characters (e.g., `"\\n"` is replaced by `"\n"`), some are copied + as is (e.g., `"\\\\"`). Furthermore, Unicode escapes of the shape `"\\uxxxx"` need special care. In this case, to_unicode takes care of the construction of the values. 2. Unescaped characters are copied as is. - @return string value of current token without opening and closing quotes + @return string value of current token without opening and closing + quotes @throw std::out_of_range if to_unicode fails */ string_t get_string() const @@ -7636,8 +7570,8 @@ class basic_json This function (and its overloads) serves to select the most approprate standard floating point number parsing function based on the type - supplied via the first parameter. Set this to - @a static_cast(nullptr). + supplied via the first parameter. Set this to @a + static_cast(nullptr). @param[in] type the @ref number_float_t in use @@ -7661,8 +7595,8 @@ class basic_json This function (and its overloads) serves to select the most approprate standard floating point number parsing function based on the type - supplied via the first parameter. Set this to - @a static_cast(nullptr). + supplied via the first parameter. Set this to @a + static_cast(nullptr). @param[in] type the @ref number_float_t in use @@ -7681,8 +7615,8 @@ class basic_json This function (and its overloads) serves to select the most approprate standard floating point number parsing function based on the type - supplied via the first parameter. Set this to - @a static_cast(nullptr). + supplied via the first parameter. Set this to @a + static_cast(nullptr). @param[in] type the @ref number_float_t in use @@ -7706,22 +7640,16 @@ class basic_json This function parses the integer component up to the radix point or exponent while collecting information about the 'floating point representation', which it stores in the result parameter. If there is - no radix point or exponent, and the number can fit into a - @ref number_integer_t or @ref number_unsigned_t then it sets the - result parameter accordingly. - - The 'floating point representation' includes the number of significant - figures after the radix point, whether the number is in exponential - or decimal form, the capitalization of the exponent marker, and if the - optional '+' is present in the exponent. This information is necessary - to perform accurate round trips of floating point numbers. + no radix point or exponent, and the number can fit into a @ref + number_integer_t or @ref number_unsigned_t then it sets the result + parameter accordingly. If the number is a floating point number the number is then parsed using @a std:strtod (or @a std:strtof or @a std::strtold). @param[out] result @ref basic_json object to receive the number, or - NAN if the conversion read past the current token. The latter case - needs to be treated by the caller function. + NAN if the conversion read past the current token. The latter case + needs to be treated by the caller function. */ void get_number(basic_json& result) const { @@ -7729,15 +7657,6 @@ class basic_json const lexer::lexer_char_t* curptr = m_start; - // remember this number was parsed (for later serialization) - result.m_type.bits.parsed = true; - - // 'found_radix_point' will be set to 0xFF upon finding a radix - // point and later used to mask in/out the precision depending - // whether a radix is found i.e. 'precision &= found_radix_point' - uint8_t found_radix_point = 0; - uint8_t precision = 0; - // accumulate the integer conversion result (unsigned for now) number_unsigned_t value = 0; @@ -7751,17 +7670,13 @@ class basic_json if (*curptr == '-') { type = value_t::number_integer; - max = static_cast(std::numeric_limits::max()) + 1; + max = static_cast((std::numeric_limits::max)()) + 1; curptr++; } else { type = value_t::number_unsigned; - max = static_cast(std::numeric_limits::max()); - if (*curptr == '+') - { - curptr++; - } + max = static_cast((std::numeric_limits::max)()); } // count the significant figures @@ -7774,22 +7689,11 @@ class basic_json { // don't count '.' but change to float type = value_t::number_float; - - // reset precision count - precision = 0; - found_radix_point = 0xFF; continue; } // assume exponent (if not then will fail parse): change to // float, stop counting and record exponent details type = value_t::number_float; - result.m_type.bits.has_exp = true; - - // exponent capitalization - result.m_type.bits.exp_cap = (*curptr == 'E'); - - // exponent '+' sign - result.m_type.bits.exp_plus = (*(++curptr) == '+'); break; } @@ -7811,13 +7715,8 @@ class basic_json value = temp; } } - ++precision; } - // If no radix point was found then precision would now be set to - // the number of digits, which is wrong - clear it. - result.m_type.bits.precision = precision & found_radix_point; - // save the value (if not a float) if (type == value_t::number_unsigned) { @@ -8123,6 +8022,1232 @@ class basic_json /// the lexer lexer m_lexer; }; + + public: + /*! + @brief JSON Pointer + + A JSON pointer defines a string syntax for identifying a specific value + within a JSON document. It can be used with functions `at` and + `operator[]`. Furthermore, JSON pointers are the base for JSON patches. + + @sa [RFC 6901](https://tools.ietf.org/html/rfc6901) + + @since version 2.0.0 + */ + class json_pointer + { + /// allow basic_json to access private members + friend class basic_json; + + public: + /*! + @brief create JSON pointer + + Create a JSON pointer according to the syntax described in + [Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3). + + @param[in] s string representing the JSON pointer; if omitted, the + empty string is assumed which references the whole JSON + value + + @throw std::domain_error if reference token is nonempty and does not + begin with a slash (`/`); example: `"JSON pointer must be empty or + begin with /"` + @throw std::domain_error if a tilde (`~`) is not followed by `0` + (representing `~`) or `1` (representing `/`); example: `"escape error: + ~ must be followed with 0 or 1"` + + @liveexample{The example shows the construction several valid JSON + pointers as well as the exceptional behavior.,json_pointer} + + @since version 2.0.0 + */ + explicit json_pointer(const std::string& s = "") + : reference_tokens(split(s)) + {} + + /*! + @brief return a string representation of the JSON pointer + + @invariant For each JSON pointer `ptr`, it holds: + @code {.cpp} + ptr == json_pointer(ptr.to_string()); + @endcode + + @return a string representation of the JSON pointer + + @liveexample{The example shows the result of `to_string`., + json_pointer__to_string} + + @since version 2.0.0 + */ + std::string to_string() const noexcept + { + std::string result; + + for (const auto& reference_token : reference_tokens) + { + result += "/" + escape(reference_token); + } + + return result; + } + + /// @copydoc to_string() + operator std::string() const + { + return to_string(); + } + + private: + /// remove and return last reference pointer + std::string pop_back() + { + if (is_root()) + { + throw std::domain_error("JSON pointer has no parent"); + } + + auto last = reference_tokens.back(); + reference_tokens.pop_back(); + return last; + } + + /// return whether pointer points to the root document + bool is_root() const + { + return reference_tokens.empty(); + } + + json_pointer top() const + { + if (is_root()) + { + throw std::domain_error("JSON pointer has no parent"); + } + + json_pointer result = *this; + result.reference_tokens = {reference_tokens[0]}; + return result; + } + + /*! + @brief create and return a reference to the pointed to value + */ + reference get_and_create(reference j) const + { + pointer result = &j; + + // in case no reference tokens exist, return a reference to the + // JSON value j which will be overwritten by a primitive value + for (const auto& reference_token : reference_tokens) + { + switch (result->m_type) + { + case value_t::null: + { + if (reference_token == "0") + { + // start a new array if reference token is 0 + result = &result->operator[](0); + } + else + { + // start a new object otherwise + result = &result->operator[](reference_token); + } + break; + } + + case value_t::object: + { + // create an entry in the object + result = &result->operator[](reference_token); + break; + } + + case value_t::array: + { + // create an entry in the array + result = &result->operator[](static_cast(std::stoi(reference_token))); + break; + } + + /* + The following code is only reached if there exists a + reference token _and_ the current value is primitive. In + this case, we have an error situation, because primitive + values may only occur as single value; that is, with an + empty list of reference tokens. + */ + default: + { + throw std::domain_error("invalid value to unflatten"); + } + } + } + + return *result; + } + + /*! + @brief return a reference to the pointed to value + + @param[in] ptr a JSON value + + @return reference to the JSON value pointed to by the JSON pointer + + @complexity Linear in the length of the JSON pointer. + + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number + */ + reference get_unchecked(pointer ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case value_t::array: + { + // error condition (cf. RFC 6901, Sect. 4) + if (reference_token.size() > 1 and reference_token[0] == '0') + { + throw std::domain_error("array index must not begin with '0'"); + } + + if (reference_token == "-") + { + // explicityly treat "-" as index beyond the end + ptr = &ptr->operator[](ptr->m_value.array->size()); + } + else + { + // convert array index to number; unchecked access + ptr = &ptr->operator[](static_cast(std::stoi(reference_token))); + } + break; + } + + default: + { + throw std::out_of_range("unresolved reference token '" + reference_token + "'"); + } + } + } + + return *ptr; + } + + reference get_checked(pointer ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case value_t::array: + { + if (reference_token == "-") + { + // "-" always fails the range check + throw std::out_of_range("array index '-' (" + + std::to_string(ptr->m_value.array->size()) + + ") is out of range"); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (reference_token.size() > 1 and reference_token[0] == '0') + { + throw std::domain_error("array index must not begin with '0'"); + } + + // note: at performs range check + ptr = &ptr->at(static_cast(std::stoi(reference_token))); + break; + } + + default: + { + throw std::out_of_range("unresolved reference token '" + reference_token + "'"); + } + } + } + + return *ptr; + } + + /*! + @brief return a const reference to the pointed to value + + @param[in] ptr a JSON value + + @return const reference to the JSON value pointed to by the JSON + pointer + */ + const_reference get_unchecked(const_pointer ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case value_t::array: + { + if (reference_token == "-") + { + // "-" cannot be used for const access + throw std::out_of_range("array index '-' (" + + std::to_string(ptr->m_value.array->size()) + + ") is out of range"); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (reference_token.size() > 1 and reference_token[0] == '0') + { + throw std::domain_error("array index must not begin with '0'"); + } + + // use unchecked array access + ptr = &ptr->operator[](static_cast(std::stoi(reference_token))); + break; + } + + default: + { + throw std::out_of_range("unresolved reference token '" + reference_token + "'"); + } + } + } + + return *ptr; + } + + const_reference get_checked(const_pointer ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case value_t::array: + { + if (reference_token == "-") + { + // "-" always fails the range check + throw std::out_of_range("array index '-' (" + + std::to_string(ptr->m_value.array->size()) + + ") is out of range"); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (reference_token.size() > 1 and reference_token[0] == '0') + { + throw std::domain_error("array index must not begin with '0'"); + } + + // note: at performs range check + ptr = &ptr->at(static_cast(std::stoi(reference_token))); + break; + } + + default: + { + throw std::out_of_range("unresolved reference token '" + reference_token + "'"); + } + } + } + + return *ptr; + } + + /// split the string input to reference tokens + static std::vector split(std::string reference_string) + { + std::vector result; + + // special case: empty reference string -> no reference tokens + if (reference_string.empty()) + { + return result; + } + + // check if nonempty reference string begins with slash + if (reference_string[0] != '/') + { + throw std::domain_error("JSON pointer must be empty or begin with '/'"); + } + + // extract the reference tokens: + // - slash: position of the last read slash (or end of string) + // - start: position after the previous slash + for ( + // search for the first slash after the first character + size_t slash = reference_string.find_first_of("/", 1), + // set the beginning of the first reference token + start = 1; + // we can stop if start == string::npos+1 = 0 + start != 0; + // set the beginning of the next reference token + // (will eventually be 0 if slash == std::string::npos) + start = slash + 1, + // find next slash + slash = reference_string.find_first_of("/", start)) + { + // use the text between the beginning of the reference token + // (start) and the last slash (slash). + auto reference_token = reference_string.substr(start, slash - start); + + // check reference tokens are properly escaped + for (size_t pos = reference_token.find_first_of("~"); + pos != std::string::npos; + pos = reference_token.find_first_of("~", pos + 1)) + { + assert(reference_token[pos] == '~'); + + // ~ must be followed by 0 or 1 + if (pos == reference_token.size() - 1 or + (reference_token[pos + 1] != '0' and + reference_token[pos + 1] != '1')) + { + throw std::domain_error("escape error: '~' must be followed with '0' or '1'"); + } + } + + // finally, store the reference token + unescape(reference_token); + result.push_back(reference_token); + } + + return result; + } + + private: + /*! + @brief replace all occurrences of a substring by another string + + @param[in,out] s the string to manipulate + @param[in] f the substring to replace with @a t + @param[out] t the string to replace @a f + + @return The string @a s where all occurrences of @a f are replaced + with @a t. + + @pre The search string @a f must not be empty. + + @since version 2.0.0 + */ + static void replace_substring(std::string& s, + const std::string& f, + const std::string& t) + { + assert(not f.empty()); + + for ( + size_t pos = s.find(f); // find first occurrence of f + pos != std::string::npos; // make sure f was found + s.replace(pos, f.size(), t), // replace with t + pos = s.find(f, pos + t.size()) // find next occurrence of f + ); + } + + /// escape tilde and slash + static std::string escape(std::string s) + { + // escape "~"" to "~0" and "/" to "~1" + replace_substring(s, "~", "~0"); + replace_substring(s, "/", "~1"); + return s; + } + + /// unescape tilde and slash + static void unescape(std::string& s) + { + // first transform any occurrence of the sequence '~1' to '/' + replace_substring(s, "~1", "/"); + // then transform any occurrence of the sequence '~0' to '~' + replace_substring(s, "~0", "~"); + } + + /*! + @param[in] reference_string the reference string to the current value + @param[in] value the value to consider + @param[in,out] result the result object to insert values to + + @note Empty objects or arrays are flattened to `null`. + */ + static void flatten(const std::string& reference_string, + const basic_json& value, + basic_json& result) + { + switch (value.m_type) + { + case value_t::array: + { + if (value.m_value.array->empty()) + { + // flatten empty array as null + result[reference_string] = nullptr; + } + else + { + // iterate array and use index as reference string + for (size_t i = 0; i < value.m_value.array->size(); ++i) + { + flatten(reference_string + "/" + std::to_string(i), + value.m_value.array->operator[](i), result); + } + } + break; + } + + case value_t::object: + { + if (value.m_value.object->empty()) + { + // flatten empty object as null + result[reference_string] = nullptr; + } + else + { + // iterate object and use keys as reference string + for (const auto& element : *value.m_value.object) + { + flatten(reference_string + "/" + escape(element.first), + element.second, result); + } + } + break; + } + + default: + { + // add primitive value with its reference string + result[reference_string] = value; + break; + } + } + } + + /*! + @param[in] value flattened JSON + + @return unflattened JSON + */ + static basic_json unflatten(const basic_json& value) + { + if (not value.is_object()) + { + throw std::domain_error("only objects can be unflattened"); + } + + basic_json result; + + // iterate the JSON object values + for (const auto& element : *value.m_value.object) + { + if (not element.second.is_primitive()) + { + throw std::domain_error("values in object must be primitive"); + } + + // assign value to reference pointed to by JSON pointer; Note + // that if the JSON pointer is "" (i.e., points to the whole + // value), function get_and_create returns a reference to + // result itself. An assignment will then create a primitive + // value. + json_pointer(element.first).get_and_create(result) = element.second; + } + + return result; + } + + private: + /// the reference tokens + std::vector reference_tokens {}; + }; + + ////////////////////////// + // JSON Pointer support // + ////////////////////////// + + /// @name JSON Pointer functions + /// @{ + + /*! + @brief access specified element via JSON Pointer + + Uses a JSON pointer to retrieve a reference to the respective JSON value. + No bound checking is performed. Similar to @ref operator[](const typename + object_t::key_type&), `null` values are created in arrays and objects if + necessary. + + In particular: + - If the JSON pointer points to an object key that does not exist, it + is created an filled with a `null` value before a reference to it + is returned. + - If the JSON pointer points to an array index that does not exist, it + is created an filled with a `null` value before a reference to it + is returned. All indices between the current maximum and the given + index are also filled with `null`. + - The special value `-` is treated as a synonym for the index past the + end. + + @param[in] ptr a JSON pointer + + @return reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number + + @liveexample{The behavior is shown in the example.,operatorjson_pointer} + + @since version 2.0.0 + */ + reference operator[](const json_pointer& ptr) + { + return ptr.get_unchecked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Uses a JSON pointer to retrieve a reference to the respective JSON value. + No bound checking is performed. The function does not change the JSON + value; no `null` values are created. In particular, the the special value + `-` yields an exception. + + @param[in] ptr JSON pointer to the desired element + + @return const reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number + + @liveexample{The behavior is shown in the example.,operatorjson_pointer_const} + + @since version 2.0.0 + */ + const_reference operator[](const json_pointer& ptr) const + { + return ptr.get_unchecked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Returns a reference to the element at with specified JSON pointer @a ptr, + with bounds checking. + + @param[in] ptr JSON pointer to the desired element + + @return reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number + + @liveexample{The behavior is shown in the example.,at_json_pointer} + + @since version 2.0.0 + */ + reference at(const json_pointer& ptr) + { + return ptr.get_checked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Returns a const reference to the element at with specified JSON pointer @a + ptr, with bounds checking. + + @param[in] ptr JSON pointer to the desired element + + @return reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number + + @liveexample{The behavior is shown in the example.,at_json_pointer_const} + + @since version 2.0.0 + */ + const_reference at(const json_pointer& ptr) const + { + return ptr.get_checked(this); + } + + /*! + @brief return flattened JSON value + + The function creates a JSON object whose keys are JSON pointers (see [RFC + 6901](https://tools.ietf.org/html/rfc6901)) and whose values are all + primitive. The original JSON value can be restored using the @ref + unflatten() function. + + @return an object that maps JSON pointers to primitve values + + @note Empty objects and arrays are flattened to `null` and will not be + reconstructed correctly by the @ref unflatten() function. + + @complexity Linear in the size the JSON value. + + @liveexample{The following code shows how a JSON object is flattened to an + object whose keys consist of JSON pointers.,flatten} + + @sa @ref unflatten() for the reverse function + + @since version 2.0.0 + */ + basic_json flatten() const + { + basic_json result(value_t::object); + json_pointer::flatten("", *this, result); + return result; + } + + /*! + @brief unflatten a previously flattened JSON value + + The function restores the arbitrary nesting of a JSON value that has been + flattened before using the @ref flatten() function. The JSON value must + meet certain constraints: + 1. The value must be an object. + 2. The keys must be JSON pointers (see + [RFC 6901](https://tools.ietf.org/html/rfc6901)) + 3. The mapped values must be primitive JSON types. + + @return the original JSON from a flattened version + + @note Empty objects and arrays are flattened by @ref flatten() to `null` + values and can not unflattened to their original type. Apart from + this example, for a JSON value `j`, the following is always true: + `j == j.flatten().unflatten()`. + + @complexity Linear in the size the JSON value. + + @liveexample{The following code shows how a flattened JSON object is + unflattened into the original nested JSON object.,unflatten} + + @sa @ref flatten() for the reverse function + + @since version 2.0.0 + */ + basic_json unflatten() const + { + return json_pointer::unflatten(*this); + } + + /// @} + + ////////////////////////// + // JSON Patch functions // + ////////////////////////// + + /// @name JSON Patch functions + /// @{ + + /*! + @brief applies a JSON patch + + [JSON Patch](http://jsonpatch.com) defines a JSON document structure for + expressing a sequence of operations to apply to a JSON) document. With + this funcion, a JSON Patch is applied to the current JSON value by + executing all operations from the patch. + + @param[in] json_patch JSON patch document + @return patched document + + @note The application of a patch is atomic: Either all operations succeed + and the patched document is returned or an exception is thrown. In + any case, the original value is not changed: the patch is applied + to a copy of the value. + + @throw std::out_of_range if a JSON pointer inside the patch could not + be resolved successfully in the current JSON value; example: `"key baz + not found"` + @throw invalid_argument if the JSON patch is malformed (e.g., mandatory + attributes are missing); example: `"operation add must have member path"` + + @complexity Linear in the size of the JSON value and the length of the + JSON patch. As usually only a fraction of the JSON value is affected by + the patch, the complexity can usually be neglected. + + @liveexample{The following code shows how a JSON patch is applied to a + value.,patch} + + @sa @ref diff -- create a JSON patch by comparing two JSON values + + @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) + @sa [RFC 6901 (JSON Pointer)](https://tools.ietf.org/html/rfc6901) + + @since version 2.0.0 + */ + basic_json patch(const basic_json& json_patch) const + { + // make a working copy to apply the patch to + basic_json result = *this; + + // the valid JSON Patch operations + enum class patch_operations {add, remove, replace, move, copy, test, invalid}; + + const auto get_op = [](const std::string op) + { + if (op == "add") + { + return patch_operations::add; + } + if (op == "remove") + { + return patch_operations::remove; + } + if (op == "replace") + { + return patch_operations::replace; + } + if (op == "move") + { + return patch_operations::move; + } + if (op == "copy") + { + return patch_operations::copy; + } + if (op == "test") + { + return patch_operations::test; + } + + return patch_operations::invalid; + }; + + // wrapper for "add" operation; add value at ptr + const auto operation_add = [&result](json_pointer & ptr, basic_json val) + { + // adding to the root of the target document means replacing it + if (ptr.is_root()) + { + result = val; + } + else + { + // make sure the top element of the pointer exists + json_pointer top_pointer = ptr.top(); + if (top_pointer != ptr) + { + basic_json& x = result.at(top_pointer); + } + + // get reference to parent of JSON pointer ptr + const auto last_path = ptr.pop_back(); + basic_json& parent = result[ptr]; + + switch (parent.m_type) + { + case value_t::null: + case value_t::object: + { + // use operator[] to add value + parent[last_path] = val; + break; + } + + case value_t::array: + { + if (last_path == "-") + { + // special case: append to back + parent.push_back(val); + } + else + { + const auto idx = std::stoi(last_path); + if (static_cast(idx) > parent.size()) + { + // avoid undefined behavior + throw std::out_of_range("array index " + std::to_string(idx) + " is out of range"); + } + else + { + // default case: insert add offset + parent.insert(parent.begin() + static_cast(idx), val); + } + } + break; + } + + default: + { + // if there exists a parent it cannot be primitive + assert(false); // LCOV_EXCL_LINE + } + } + } + }; + + // wrapper for "remove" operation; remove value at ptr + const auto operation_remove = [&result](json_pointer & ptr) + { + // get reference to parent of JSON pointer ptr + const auto last_path = ptr.pop_back(); + basic_json& parent = result.at(ptr); + + // remove child + if (parent.is_object()) + { + // perform range check + auto it = parent.find(last_path); + if (it != parent.end()) + { + parent.erase(it); + } + else + { + throw std::out_of_range("key '" + last_path + "' not found"); + } + } + else if (parent.is_array()) + { + // note erase performs range check + parent.erase(static_cast(std::stoi(last_path))); + } + }; + + // type check + if (not json_patch.is_array()) + { + // a JSON patch must be an array of objects + throw std::invalid_argument("JSON patch must be an array of objects"); + } + + // iterate and apply th eoperations + for (const auto& val : json_patch) + { + // wrapper to get a value for an operation + const auto get_value = [&val](const std::string & op, + const std::string & member, + bool string_type) -> basic_json& + { + // find value + auto it = val.m_value.object->find(member); + + // context-sensitive error message + const auto error_msg = (op == "op") ? "operation" : "operation '" + op + "'"; + + // check if desired value is present + if (it == val.m_value.object->end()) + { + throw std::invalid_argument(error_msg + " must have member '" + member + "'"); + } + + // check if result is of type string + if (string_type and not it->second.is_string()) + { + throw std::invalid_argument(error_msg + " must have string member '" + member + "'"); + } + + // no error: return value + return it->second; + }; + + // type check + if (not val.is_object()) + { + throw std::invalid_argument("JSON patch must be an array of objects"); + } + + // collect mandatory members + const std::string op = get_value("op", "op", true); + const std::string path = get_value(op, "path", true); + json_pointer ptr(path); + + switch (get_op(op)) + { + case patch_operations::add: + { + operation_add(ptr, get_value("add", "value", false)); + break; + } + + case patch_operations::remove: + { + operation_remove(ptr); + break; + } + + case patch_operations::replace: + { + // the "path" location must exist - use at() + result.at(ptr) = get_value("replace", "value", false); + break; + } + + case patch_operations::move: + { + const std::string from_path = get_value("move", "from", true); + json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + basic_json v = result.at(from_ptr); + + // The move operation is functionally identical to a + // "remove" operation on the "from" location, followed + // immediately by an "add" operation at the target + // location with the value that was just removed. + operation_remove(from_ptr); + operation_add(ptr, v); + break; + } + + case patch_operations::copy: + { + const std::string from_path = get_value("copy", "from", true);; + const json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + result[ptr] = result.at(from_ptr); + break; + } + + case patch_operations::test: + { + bool success = false; + try + { + // check if "value" matches the one at "path" + // the "path" location must exist - use at() + success = (result.at(ptr) == get_value("test", "value", false)); + } + catch (std::out_of_range&) + { + // ignore out of range errors: success remains false + } + + // throw an exception if test fails + if (not success) + { + throw std::domain_error("unsuccessful: " + val.dump()); + } + + break; + } + + case patch_operations::invalid: + { + // op must be "add", "remove", "replace", "move", "copy", or + // "test" + throw std::invalid_argument("operation value '" + op + "' is invalid"); + } + } + } + + return result; + } + + /*! + @brief creates a diff as a JSON patch + + Creates a [JSON Patch](http://jsonpatch.com) so that value @a source can + be changed into the value @a target by calling @ref patch function. + + @invariant For two JSON values @a source and @a target, the following code + yields always `true`: + @code {.cpp} + source.patch(diff(source, target)) == target; + @endcode + + @note Currently, only `remove`, `add`, and `replace` operations are + generated. + + @param[in] source JSON value to copare from + @param[in] target JSON value to copare against + @param[in] path helper value to create JSON pointers + + @return a JSON patch to convert the @a source to @a target + + @complexity Linear in the lengths of @a source and @a target. + + @liveexample{The following code shows how a JSON patch is created as a + diff for two JSON values.,diff} + + @sa @ref patch -- apply a JSON patch + + @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) + + @since version 2.0.0 + */ + static basic_json diff(const basic_json& source, + const basic_json& target, + std::string path = "") + { + // the patch + basic_json result(value_t::array); + + // if the values are the same, return empty patch + if (source == target) + { + return result; + } + + if (source.type() != target.type()) + { + // different types: replace value + result.push_back( + { + {"op", "replace"}, + {"path", path}, + {"value", target} + }); + } + else + { + switch (source.type()) + { + case value_t::array: + { + // first pass: traverse common elements + size_t i = 0; + while (i < source.size() and i < target.size()) + { + // recursive call to compare array values at index i + auto temp_diff = diff(source[i], target[i], path + "/" + std::to_string(i)); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + ++i; + } + + // i now reached the end of at least one array + // in a second pass, traverse the remaining elements + + // remove my remaining elements + const auto end_index = static_cast(result.size()); + while (i < source.size()) + { + // add operations in reverse order to avoid invalid + // indices + result.insert(result.begin() + end_index, object( + { + {"op", "remove"}, + {"path", path + "/" + std::to_string(i)} + })); + ++i; + } + + // add other remaining elements + while (i < target.size()) + { + result.push_back( + { + {"op", "add"}, + {"path", path + "/" + std::to_string(i)}, + {"value", target[i]} + }); + ++i; + } + + break; + } + + case value_t::object: + { + // first pass: traverse this object's elements + for (auto it = source.begin(); it != source.end(); ++it) + { + // escape the key name to be used in a JSON patch + const auto key = json_pointer::escape(it.key()); + + if (target.find(it.key()) != target.end()) + { + // recursive call to compare object values at key it + auto temp_diff = diff(it.value(), target[it.key()], path + "/" + key); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + } + else + { + // found a key that is not in o -> remove it + result.push_back(object( + { + {"op", "remove"}, + {"path", path + "/" + key} + })); + } + } + + // second pass: traverse other object's elements + for (auto it = target.begin(); it != target.end(); ++it) + { + if (source.find(it.key()) == source.end()) + { + // found a key that is not in this -> add it + const auto key = json_pointer::escape(it.key()); + result.push_back( + { + {"op", "add"}, + {"path", path + "/" + key}, + {"value", it.value()} + }); + } + } + + break; + } + + default: + { + // both primitive type: replace value + result.push_back( + { + {"op", "replace"}, + {"path", path}, + {"value", target} + }); + break; + } + } + } + + return result; + } + + /// @} }; @@ -8133,8 +9258,8 @@ class basic_json /*! @brief default JSON class -This type is the default specialization of the @ref basic_json class which uses -the standard template types. +This type is the default specialization of the @ref basic_json class which +uses the standard template types. @since version 1.0.0 */ @@ -8142,9 +9267,9 @@ using json = basic_json<>; } -///////////////////////// -// nonmember functions // -///////////////////////// +/////////////////////// +// nonmember support // +/////////////////////// // specialization of std::swap, and std::hash namespace std @@ -8185,9 +9310,9 @@ struct hash /*! @brief user-defined string literal for JSON values -This operator implements a user-defined string literal for JSON objects. It can -be used by adding \p "_json" to a string literal and returns a JSON object if -no parse error occurred. +This operator implements a user-defined string literal for JSON objects. It +can be used by adding \p "_json" to a string literal and returns a JSON object +if no parse error occurred. @param[in] s a string representation of a JSON object @return a JSON object @@ -8199,6 +9324,16 @@ inline nlohmann::json operator "" _json(const char* s, std::size_t) return nlohmann::json::parse(reinterpret_cast(s)); } +/*! +@brief user-defined string literal for JSON pointer + +@since version 2.0.0 +*/ +inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t) +{ + return nlohmann::json::json_pointer(s); +} + // restore GCC/clang diagnostic settings #if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) #pragma GCC diagnostic pop diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 000000000..c66b19c83 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,25 @@ +# The unit test executable. +set(JSON_UNITTEST_TARGET_NAME "json_unit") +add_executable(${JSON_UNITTEST_TARGET_NAME} + "src/catch.hpp" + "src/unit.cpp" +) + +set_target_properties(${JSON_UNITTEST_TARGET_NAME} PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED ON + COMPILE_DEFINITIONS "$<$:_SCL_SECURE_NO_WARNINGS>" + COMPILE_OPTIONS "$<$:/EHsc;$<$:/Od>>" +) + +target_include_directories(${JSON_UNITTEST_TARGET_NAME} PRIVATE "src") +target_link_libraries(${JSON_UNITTEST_TARGET_NAME} ${JSON_TARGET_NAME}) + +add_test(NAME "${JSON_UNITTEST_TARGET_NAME}_default" + COMMAND ${JSON_UNITTEST_TARGET_NAME} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} +) +add_test(NAME "${JSON_UNITTEST_TARGET_NAME}_all" + COMMAND ${JSON_UNITTEST_TARGET_NAME} "*" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} +) diff --git a/test/json.org/1.json b/test/data/json.org/1.json similarity index 100% rename from test/json.org/1.json rename to test/data/json.org/1.json diff --git a/test/json.org/2.json b/test/data/json.org/2.json similarity index 100% rename from test/json.org/2.json rename to test/data/json.org/2.json diff --git a/test/json.org/3.json b/test/data/json.org/3.json similarity index 100% rename from test/json.org/3.json rename to test/data/json.org/3.json diff --git a/test/json.org/4.json b/test/data/json.org/4.json similarity index 100% rename from test/json.org/4.json rename to test/data/json.org/4.json diff --git a/test/json.org/5.json b/test/data/json.org/5.json similarity index 100% rename from test/json.org/5.json rename to test/data/json.org/5.json diff --git a/test/json_nlohmann_tests/all_unicode.json b/test/data/json_nlohmann_tests/all_unicode.json similarity index 100% rename from test/json_nlohmann_tests/all_unicode.json rename to test/data/json_nlohmann_tests/all_unicode.json diff --git a/test/json_nlohmann_tests/bom.json b/test/data/json_nlohmann_tests/bom.json similarity index 100% rename from test/json_nlohmann_tests/bom.json rename to test/data/json_nlohmann_tests/bom.json diff --git a/test/json_roundtrip/roundtrip01.json b/test/data/json_roundtrip/roundtrip01.json similarity index 100% rename from test/json_roundtrip/roundtrip01.json rename to test/data/json_roundtrip/roundtrip01.json diff --git a/test/json_roundtrip/roundtrip02.json b/test/data/json_roundtrip/roundtrip02.json similarity index 100% rename from test/json_roundtrip/roundtrip02.json rename to test/data/json_roundtrip/roundtrip02.json diff --git a/test/json_roundtrip/roundtrip03.json b/test/data/json_roundtrip/roundtrip03.json similarity index 100% rename from test/json_roundtrip/roundtrip03.json rename to test/data/json_roundtrip/roundtrip03.json diff --git a/test/json_roundtrip/roundtrip04.json b/test/data/json_roundtrip/roundtrip04.json similarity index 100% rename from test/json_roundtrip/roundtrip04.json rename to test/data/json_roundtrip/roundtrip04.json diff --git a/test/json_roundtrip/roundtrip05.json b/test/data/json_roundtrip/roundtrip05.json similarity index 100% rename from test/json_roundtrip/roundtrip05.json rename to test/data/json_roundtrip/roundtrip05.json diff --git a/test/json_roundtrip/roundtrip06.json b/test/data/json_roundtrip/roundtrip06.json similarity index 100% rename from test/json_roundtrip/roundtrip06.json rename to test/data/json_roundtrip/roundtrip06.json diff --git a/test/json_roundtrip/roundtrip07.json b/test/data/json_roundtrip/roundtrip07.json similarity index 100% rename from test/json_roundtrip/roundtrip07.json rename to test/data/json_roundtrip/roundtrip07.json diff --git a/test/json_roundtrip/roundtrip08.json b/test/data/json_roundtrip/roundtrip08.json similarity index 100% rename from test/json_roundtrip/roundtrip08.json rename to test/data/json_roundtrip/roundtrip08.json diff --git a/test/json_roundtrip/roundtrip09.json b/test/data/json_roundtrip/roundtrip09.json similarity index 100% rename from test/json_roundtrip/roundtrip09.json rename to test/data/json_roundtrip/roundtrip09.json diff --git a/test/json_roundtrip/roundtrip10.json b/test/data/json_roundtrip/roundtrip10.json similarity index 100% rename from test/json_roundtrip/roundtrip10.json rename to test/data/json_roundtrip/roundtrip10.json diff --git a/test/json_roundtrip/roundtrip11.json b/test/data/json_roundtrip/roundtrip11.json similarity index 100% rename from test/json_roundtrip/roundtrip11.json rename to test/data/json_roundtrip/roundtrip11.json diff --git a/test/json_roundtrip/roundtrip12.json b/test/data/json_roundtrip/roundtrip12.json similarity index 100% rename from test/json_roundtrip/roundtrip12.json rename to test/data/json_roundtrip/roundtrip12.json diff --git a/test/json_roundtrip/roundtrip13.json b/test/data/json_roundtrip/roundtrip13.json similarity index 100% rename from test/json_roundtrip/roundtrip13.json rename to test/data/json_roundtrip/roundtrip13.json diff --git a/test/json_roundtrip/roundtrip14.json b/test/data/json_roundtrip/roundtrip14.json similarity index 100% rename from test/json_roundtrip/roundtrip14.json rename to test/data/json_roundtrip/roundtrip14.json diff --git a/test/json_roundtrip/roundtrip15.json b/test/data/json_roundtrip/roundtrip15.json similarity index 100% rename from test/json_roundtrip/roundtrip15.json rename to test/data/json_roundtrip/roundtrip15.json diff --git a/test/json_roundtrip/roundtrip16.json b/test/data/json_roundtrip/roundtrip16.json similarity index 100% rename from test/json_roundtrip/roundtrip16.json rename to test/data/json_roundtrip/roundtrip16.json diff --git a/test/json_roundtrip/roundtrip17.json b/test/data/json_roundtrip/roundtrip17.json similarity index 100% rename from test/json_roundtrip/roundtrip17.json rename to test/data/json_roundtrip/roundtrip17.json diff --git a/test/json_roundtrip/roundtrip18.json b/test/data/json_roundtrip/roundtrip18.json similarity index 100% rename from test/json_roundtrip/roundtrip18.json rename to test/data/json_roundtrip/roundtrip18.json diff --git a/test/json_roundtrip/roundtrip19.json b/test/data/json_roundtrip/roundtrip19.json similarity index 100% rename from test/json_roundtrip/roundtrip19.json rename to test/data/json_roundtrip/roundtrip19.json diff --git a/test/json_roundtrip/roundtrip20.json b/test/data/json_roundtrip/roundtrip20.json similarity index 100% rename from test/json_roundtrip/roundtrip20.json rename to test/data/json_roundtrip/roundtrip20.json diff --git a/test/json_roundtrip/roundtrip21.json b/test/data/json_roundtrip/roundtrip21.json similarity index 100% rename from test/json_roundtrip/roundtrip21.json rename to test/data/json_roundtrip/roundtrip21.json diff --git a/test/json_roundtrip/roundtrip22.json b/test/data/json_roundtrip/roundtrip22.json similarity index 100% rename from test/json_roundtrip/roundtrip22.json rename to test/data/json_roundtrip/roundtrip22.json diff --git a/test/json_roundtrip/roundtrip23.json b/test/data/json_roundtrip/roundtrip23.json similarity index 100% rename from test/json_roundtrip/roundtrip23.json rename to test/data/json_roundtrip/roundtrip23.json diff --git a/test/json_roundtrip/roundtrip24.json b/test/data/json_roundtrip/roundtrip24.json similarity index 100% rename from test/json_roundtrip/roundtrip24.json rename to test/data/json_roundtrip/roundtrip24.json diff --git a/test/json_roundtrip/roundtrip25.json b/test/data/json_roundtrip/roundtrip25.json similarity index 100% rename from test/json_roundtrip/roundtrip25.json rename to test/data/json_roundtrip/roundtrip25.json diff --git a/test/json_roundtrip/roundtrip26.json b/test/data/json_roundtrip/roundtrip26.json similarity index 100% rename from test/json_roundtrip/roundtrip26.json rename to test/data/json_roundtrip/roundtrip26.json diff --git a/test/json_roundtrip/roundtrip27.json b/test/data/json_roundtrip/roundtrip27.json similarity index 100% rename from test/json_roundtrip/roundtrip27.json rename to test/data/json_roundtrip/roundtrip27.json diff --git a/test/json_roundtrip/roundtrip28.json b/test/data/json_roundtrip/roundtrip28.json similarity index 100% rename from test/json_roundtrip/roundtrip28.json rename to test/data/json_roundtrip/roundtrip28.json diff --git a/test/json_roundtrip/roundtrip29.json b/test/data/json_roundtrip/roundtrip29.json similarity index 100% rename from test/json_roundtrip/roundtrip29.json rename to test/data/json_roundtrip/roundtrip29.json diff --git a/test/json_roundtrip/roundtrip30.json b/test/data/json_roundtrip/roundtrip30.json similarity index 100% rename from test/json_roundtrip/roundtrip30.json rename to test/data/json_roundtrip/roundtrip30.json diff --git a/test/json_roundtrip/roundtrip31.json b/test/data/json_roundtrip/roundtrip31.json similarity index 100% rename from test/json_roundtrip/roundtrip31.json rename to test/data/json_roundtrip/roundtrip31.json diff --git a/test/json_roundtrip/roundtrip32.json b/test/data/json_roundtrip/roundtrip32.json similarity index 100% rename from test/json_roundtrip/roundtrip32.json rename to test/data/json_roundtrip/roundtrip32.json diff --git a/test/json_tests/fail1.json b/test/data/json_tests/fail1.json similarity index 100% rename from test/json_tests/fail1.json rename to test/data/json_tests/fail1.json diff --git a/test/json_tests/fail10.json b/test/data/json_tests/fail10.json similarity index 100% rename from test/json_tests/fail10.json rename to test/data/json_tests/fail10.json diff --git a/test/json_tests/fail11.json b/test/data/json_tests/fail11.json similarity index 100% rename from test/json_tests/fail11.json rename to test/data/json_tests/fail11.json diff --git a/test/json_tests/fail12.json b/test/data/json_tests/fail12.json similarity index 100% rename from test/json_tests/fail12.json rename to test/data/json_tests/fail12.json diff --git a/test/json_tests/fail13.json b/test/data/json_tests/fail13.json similarity index 100% rename from test/json_tests/fail13.json rename to test/data/json_tests/fail13.json diff --git a/test/json_tests/fail14.json b/test/data/json_tests/fail14.json similarity index 100% rename from test/json_tests/fail14.json rename to test/data/json_tests/fail14.json diff --git a/test/json_tests/fail15.json b/test/data/json_tests/fail15.json similarity index 100% rename from test/json_tests/fail15.json rename to test/data/json_tests/fail15.json diff --git a/test/json_tests/fail16.json b/test/data/json_tests/fail16.json similarity index 100% rename from test/json_tests/fail16.json rename to test/data/json_tests/fail16.json diff --git a/test/json_tests/fail17.json b/test/data/json_tests/fail17.json similarity index 100% rename from test/json_tests/fail17.json rename to test/data/json_tests/fail17.json diff --git a/test/json_tests/fail18.json b/test/data/json_tests/fail18.json similarity index 100% rename from test/json_tests/fail18.json rename to test/data/json_tests/fail18.json diff --git a/test/json_tests/fail19.json b/test/data/json_tests/fail19.json similarity index 100% rename from test/json_tests/fail19.json rename to test/data/json_tests/fail19.json diff --git a/test/json_tests/fail2.json b/test/data/json_tests/fail2.json similarity index 100% rename from test/json_tests/fail2.json rename to test/data/json_tests/fail2.json diff --git a/test/json_tests/fail20.json b/test/data/json_tests/fail20.json similarity index 100% rename from test/json_tests/fail20.json rename to test/data/json_tests/fail20.json diff --git a/test/json_tests/fail21.json b/test/data/json_tests/fail21.json similarity index 100% rename from test/json_tests/fail21.json rename to test/data/json_tests/fail21.json diff --git a/test/json_tests/fail22.json b/test/data/json_tests/fail22.json similarity index 100% rename from test/json_tests/fail22.json rename to test/data/json_tests/fail22.json diff --git a/test/json_tests/fail23.json b/test/data/json_tests/fail23.json similarity index 100% rename from test/json_tests/fail23.json rename to test/data/json_tests/fail23.json diff --git a/test/json_tests/fail24.json b/test/data/json_tests/fail24.json similarity index 100% rename from test/json_tests/fail24.json rename to test/data/json_tests/fail24.json diff --git a/test/json_tests/fail25.json b/test/data/json_tests/fail25.json similarity index 100% rename from test/json_tests/fail25.json rename to test/data/json_tests/fail25.json diff --git a/test/json_tests/fail26.json b/test/data/json_tests/fail26.json similarity index 100% rename from test/json_tests/fail26.json rename to test/data/json_tests/fail26.json diff --git a/test/json_tests/fail27.json b/test/data/json_tests/fail27.json similarity index 100% rename from test/json_tests/fail27.json rename to test/data/json_tests/fail27.json diff --git a/test/json_tests/fail28.json b/test/data/json_tests/fail28.json similarity index 100% rename from test/json_tests/fail28.json rename to test/data/json_tests/fail28.json diff --git a/test/json_tests/fail29.json b/test/data/json_tests/fail29.json similarity index 100% rename from test/json_tests/fail29.json rename to test/data/json_tests/fail29.json diff --git a/test/json_tests/fail3.json b/test/data/json_tests/fail3.json similarity index 100% rename from test/json_tests/fail3.json rename to test/data/json_tests/fail3.json diff --git a/test/json_tests/fail30.json b/test/data/json_tests/fail30.json similarity index 100% rename from test/json_tests/fail30.json rename to test/data/json_tests/fail30.json diff --git a/test/json_tests/fail31.json b/test/data/json_tests/fail31.json similarity index 100% rename from test/json_tests/fail31.json rename to test/data/json_tests/fail31.json diff --git a/test/json_tests/fail32.json b/test/data/json_tests/fail32.json similarity index 100% rename from test/json_tests/fail32.json rename to test/data/json_tests/fail32.json diff --git a/test/json_tests/fail33.json b/test/data/json_tests/fail33.json similarity index 100% rename from test/json_tests/fail33.json rename to test/data/json_tests/fail33.json diff --git a/test/json_tests/fail4.json b/test/data/json_tests/fail4.json similarity index 100% rename from test/json_tests/fail4.json rename to test/data/json_tests/fail4.json diff --git a/test/json_tests/fail5.json b/test/data/json_tests/fail5.json similarity index 100% rename from test/json_tests/fail5.json rename to test/data/json_tests/fail5.json diff --git a/test/json_tests/fail6.json b/test/data/json_tests/fail6.json similarity index 100% rename from test/json_tests/fail6.json rename to test/data/json_tests/fail6.json diff --git a/test/json_tests/fail7.json b/test/data/json_tests/fail7.json similarity index 100% rename from test/json_tests/fail7.json rename to test/data/json_tests/fail7.json diff --git a/test/json_tests/fail8.json b/test/data/json_tests/fail8.json similarity index 100% rename from test/json_tests/fail8.json rename to test/data/json_tests/fail8.json diff --git a/test/json_tests/fail9.json b/test/data/json_tests/fail9.json similarity index 100% rename from test/json_tests/fail9.json rename to test/data/json_tests/fail9.json diff --git a/test/json_tests/pass1.json b/test/data/json_tests/pass1.json similarity index 100% rename from test/json_tests/pass1.json rename to test/data/json_tests/pass1.json diff --git a/test/json_tests/pass2.json b/test/data/json_tests/pass2.json similarity index 100% rename from test/json_tests/pass2.json rename to test/data/json_tests/pass2.json diff --git a/test/json_tests/pass3.json b/test/data/json_tests/pass3.json similarity index 100% rename from test/json_tests/pass3.json rename to test/data/json_tests/pass3.json diff --git a/test/json_testsuite/README.md b/test/data/json_testsuite/README.md similarity index 100% rename from test/json_testsuite/README.md rename to test/data/json_testsuite/README.md diff --git a/test/json_testsuite/sample.json b/test/data/json_testsuite/sample.json old mode 100755 new mode 100644 similarity index 100% rename from test/json_testsuite/sample.json rename to test/data/json_testsuite/sample.json diff --git a/test/catch.hpp b/test/src/catch.hpp similarity index 96% rename from test/catch.hpp rename to test/src/catch.hpp index 5cc33a838..879fc5b1d 100644 --- a/test/catch.hpp +++ b/test/src/catch.hpp @@ -1,6 +1,6 @@ /* - * Catch v1.3.4 - * Generated: 2016-02-10 19:24:03.089683 + * Catch v1.5.6 + * Generated: 2016-06-09 19:20:41.460328 * ---------------------------------------------------------- * This file has been merged from multiple headers. Please don't edit it directly * Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. @@ -62,7 +62,11 @@ #define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line #define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) -#define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) +#ifdef CATCH_CONFIG_COUNTER +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) +#else +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) +#endif #define INTERNAL_CATCH_STRINGIFY2( expr ) #expr #define INTERNAL_CATCH_STRINGIFY( expr ) INTERNAL_CATCH_STRINGIFY2( expr ) @@ -89,7 +93,7 @@ // CATCH_CONFIG_CPP11_OR_GREATER : Is C++11 supported? // CATCH_CONFIG_VARIADIC_MACROS : are variadic macros supported? - +// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? // **************** // Note to maintainers: if new toggles are added please document them // in configuration.md, too @@ -102,6 +106,18 @@ // All the C++11 features can be disabled with CATCH_CONFIG_NO_CPP11 +#ifdef __cplusplus + +# if __cplusplus >= 201103L +# define CATCH_CPP11_OR_GREATER +# endif + +# if __cplusplus >= 201402L +# define CATCH_CPP14_OR_GREATER +# endif + +#endif + #ifdef __clang__ # if __has_feature(cxx_nullptr) @@ -112,6 +128,10 @@ # define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT # endif +# if defined(CATCH_CPP11_OR_GREATER) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) +# endif + #endif // __clang__ //////////////////////////////////////////////////////////////////////////////// @@ -136,9 +156,13 @@ // GCC #ifdef __GNUC__ -#if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) -# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR -#endif +# if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +# endif + +# if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) && defined(CATCH_CPP11_OR_GREATER) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS _Pragma( "GCC diagnostic ignored \"-Wparentheses\"" ) +# endif // - otherwise more recent versions define __cplusplus >= 201103L // and will get picked up below @@ -173,13 +197,20 @@ #endif +// Use __COUNTER__ if the compiler supports it +#if ( defined _MSC_VER && _MSC_VER >= 1300 ) || \ + ( defined __GNUC__ && __GNUC__ >= 4 && __GNUC_MINOR__ >= 3 ) || \ + ( defined __clang__ && __clang_major__ >= 3 ) + +#define CATCH_INTERNAL_CONFIG_COUNTER + +#endif + //////////////////////////////////////////////////////////////////////////////// // C++ language feature support // catch all support for C++11 -#if defined(__cplusplus) && __cplusplus >= 201103L - -# define CATCH_CPP11_OR_GREATER +#if defined(CATCH_CPP11_OR_GREATER) # if !defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) # define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR @@ -246,6 +277,13 @@ #if defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_NO_UNIQUE_PTR) && !defined(CATCH_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_UNIQUE_PTR #endif +#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) +# define CATCH_CONFIG_COUNTER +#endif + +#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS +#endif // noexcept support: #if defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_NOEXCEPT) @@ -672,24 +710,28 @@ void registerTestCaseFunction #ifdef CATCH_CONFIG_VARIADIC_MACROS /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \ + static void TestName(); \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); }\ + static void TestName() #define INTERNAL_CATCH_TESTCASE( ... ) \ - static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )(); \ - namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); }\ - static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )() + INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__ ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); } /////////////////////////////////////////////////////////////////////////////// - #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... )\ + #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\ namespace{ \ - struct INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) : ClassName{ \ + struct TestName : ClassName{ \ void test(); \ }; \ - Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test, #ClassName, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestName::test, #ClassName, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); \ } \ - void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test() + void TestName::test() + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \ + INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, __VA_ARGS__ ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \ @@ -697,24 +739,28 @@ void registerTestCaseFunction #else /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE2( TestName, Name, Desc ) \ + static void TestName(); \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); }\ + static void TestName() #define INTERNAL_CATCH_TESTCASE( Name, Desc ) \ - static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )(); \ - namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); }\ - static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )() + INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), Name, Desc ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, Name, Desc ) \ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( Name, Desc ), CATCH_INTERNAL_LINEINFO ); } /////////////////////////////////////////////////////////////////////////////// - #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, TestName, Desc )\ + #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestCaseName, ClassName, TestName, Desc )\ namespace{ \ - struct INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) : ClassName{ \ + struct TestCaseName : ClassName{ \ void test(); \ }; \ - Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test, #ClassName, Catch::NameAndDesc( TestName, Desc ), CATCH_INTERNAL_LINEINFO ); \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestCaseName::test, #ClassName, Catch::NameAndDesc( TestName, Desc ), CATCH_INTERNAL_LINEINFO ); \ } \ - void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test() + void TestCaseName::test() + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, TestName, Desc )\ + INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, TestName, Desc ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, Name, Desc ) \ @@ -1287,37 +1333,37 @@ namespace Internal { template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs) { - return opCast( lhs ) == opCast( rhs ); + return bool( opCast( lhs ) == opCast( rhs ) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { - return opCast( lhs ) != opCast( rhs ); + return bool( opCast( lhs ) != opCast( rhs ) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { - return opCast( lhs ) < opCast( rhs ); + return bool( opCast( lhs ) < opCast( rhs ) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { - return opCast( lhs ) > opCast( rhs ); + return bool( opCast( lhs ) > opCast( rhs ) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { - return opCast( lhs ) >= opCast( rhs ); + return bool( opCast( lhs ) >= opCast( rhs ) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { - return opCast( lhs ) <= opCast( rhs ); + return bool( opCast( lhs ) <= opCast( rhs ) ); } }; @@ -2020,13 +2066,14 @@ namespace Catch { do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ try { \ + CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ ( __catchResult <= expr ).endExpression(); \ } \ catch( ... ) { \ __catchResult.useActiveException( Catch::ResultDisposition::Normal ); \ } \ INTERNAL_CATCH_REACT( __catchResult ) \ - } while( Catch::isTrue( false && static_cast(expr) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look + } while( Catch::isTrue( false && !!(expr) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_IF( expr, resultDisposition, macroName ) \ @@ -2563,10 +2610,12 @@ namespace Catch { } /////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) \ - static std::string INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator )( signature ); \ - namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ) ); }\ - static std::string INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator )( signature ) +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION2( translatorName, signature ) \ + static std::string translatorName( signature ); \ + namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); }\ + static std::string translatorName( signature ) + +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION2( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) // #included from: internal/catch_approx.hpp #define TWOBLUECUBES_CATCH_APPROX_HPP_INCLUDED @@ -3331,6 +3380,11 @@ namespace Catch { InLexicographicalOrder, InRandomOrder }; }; + struct UseColour { enum YesOrNo { + Auto, + Yes, + No + }; }; class TestSpec; @@ -3350,7 +3404,7 @@ namespace Catch { virtual TestSpec const& testSpec() const = 0; virtual RunTests::InWhatOrder runOrder() const = 0; virtual unsigned int rngSeed() const = 0; - virtual bool forceColour() const = 0; + virtual UseColour::YesOrNo useColour() const = 0; }; } @@ -3404,7 +3458,7 @@ namespace Catch { }; class DebugOutStream : public IStream { - std::auto_ptr m_streamBuf; + CATCH_AUTO_PTR( StreamBufBase ) m_streamBuf; mutable std::ostream m_os; public: DebugOutStream(); @@ -3439,14 +3493,14 @@ namespace Catch { noThrow( false ), showHelp( false ), showInvisibles( false ), - forceColour( false ), filenamesAsTags( false ), abortAfter( -1 ), rngSeed( 0 ), verbosity( Verbosity::Normal ), warnings( WarnAbout::Nothing ), showDurations( ShowDurations::DefaultForReporter ), - runOrder( RunTests::InDeclarationOrder ) + runOrder( RunTests::InDeclarationOrder ), + useColour( UseColour::Auto ) {} bool listTests; @@ -3459,7 +3513,6 @@ namespace Catch { bool noThrow; bool showHelp; bool showInvisibles; - bool forceColour; bool filenamesAsTags; int abortAfter; @@ -3469,6 +3522,7 @@ namespace Catch { WarnAbout::What warnings; ShowDurations::OrNot showDurations; RunTests::InWhatOrder runOrder; + UseColour::YesOrNo useColour; std::string outputFilename; std::string name; @@ -3534,7 +3588,7 @@ namespace Catch { virtual ShowDurations::OrNot showDurations() const { return m_data.showDurations; } virtual RunTests::InWhatOrder runOrder() const { return m_data.runOrder; } virtual unsigned int rngSeed() const { return m_data.rngSeed; } - virtual bool forceColour() const { return m_data.forceColour; } + virtual UseColour::YesOrNo useColour() const { return m_data.useColour; } private: @@ -3552,7 +3606,7 @@ namespace Catch { } ConfigData m_data; - std::auto_ptr m_stream; + CATCH_AUTO_PTR( IStream const ) m_stream; TestSpec m_testSpec; }; @@ -3572,7 +3626,7 @@ namespace Catch { #define STITCH_CLARA_OPEN_NAMESPACE namespace Catch { // #included from: ../external/clara.h -// Version 0.0.1.1 +// Version 0.0.2.4 // Only use header guard if we are not using an outer namespace #if !defined(TWOBLUECUBES_CLARA_H_INCLUDED) || defined(STITCH_CLARA_OPEN_NAMESPACE) @@ -3888,6 +3942,10 @@ namespace Tbc { #include #include +#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) +#define CLARA_PLATFORM_WINDOWS +#endif + // Use optional outer namespace #ifdef STITCH_CLARA_OPEN_NAMESPACE STITCH_CLARA_OPEN_NAMESPACE @@ -3911,9 +3969,6 @@ namespace Clara { const unsigned int consoleWidth = 80; #endif - // Use this to try and stop compiler from warning about unreachable code - inline bool isTrue( bool value ) { return value; } - using namespace Tbc; inline bool startsWith( std::string const& str, std::string const& prefix ) { @@ -3949,14 +4004,6 @@ namespace Clara { else throw std::runtime_error( "Expected a boolean value but did not recognise:\n '" + _source + "'" ); } - inline void convertInto( bool _source, bool& _dest ) { - _dest = _source; - } - template - inline void convertInto( bool, T& ) { - if( isTrue( true ) ) - throw std::runtime_error( "Invalid conversion" ); - } template struct IArgFunction { @@ -3966,7 +4013,6 @@ namespace Clara { IArgFunction( IArgFunction const& ) = default; #endif virtual void set( ConfigT& config, std::string const& value ) const = 0; - virtual void setFlag( ConfigT& config ) const = 0; virtual bool takesArg() const = 0; virtual IArgFunction* clone() const = 0; }; @@ -3988,9 +4034,6 @@ namespace Clara { void set( ConfigT& config, std::string const& value ) const { functionObj->set( config, value ); } - void setFlag( ConfigT& config ) const { - functionObj->setFlag( config ); - } bool takesArg() const { return functionObj->takesArg(); } bool isSet() const { @@ -4003,7 +4046,6 @@ namespace Clara { template struct NullBinder : IArgFunction{ virtual void set( C&, std::string const& ) const {} - virtual void setFlag( C& ) const {} virtual bool takesArg() const { return true; } virtual IArgFunction* clone() const { return new NullBinder( *this ); } }; @@ -4014,9 +4056,6 @@ namespace Clara { virtual void set( C& p, std::string const& stringValue ) const { convertInto( stringValue, p.*member ); } - virtual void setFlag( C& p ) const { - convertInto( true, p.*member ); - } virtual bool takesArg() const { return !IsBool::value; } virtual IArgFunction* clone() const { return new BoundDataMember( *this ); } M C::* member; @@ -4029,11 +4068,6 @@ namespace Clara { convertInto( stringValue, value ); (p.*member)( value ); } - virtual void setFlag( C& p ) const { - typename RemoveConstRef::type value; - convertInto( true, value ); - (p.*member)( value ); - } virtual bool takesArg() const { return !IsBool::value; } virtual IArgFunction* clone() const { return new BoundUnaryMethod( *this ); } void (C::*member)( M ); @@ -4047,9 +4081,6 @@ namespace Clara { if( value ) (p.*member)(); } - virtual void setFlag( C& p ) const { - (p.*member)(); - } virtual bool takesArg() const { return false; } virtual IArgFunction* clone() const { return new BoundNullaryMethod( *this ); } void (C::*member)(); @@ -4064,9 +4095,6 @@ namespace Clara { if( value ) function( obj ); } - virtual void setFlag( C& p ) const { - function( p ); - } virtual bool takesArg() const { return false; } virtual IArgFunction* clone() const { return new BoundUnaryFunction( *this ); } void (*function)( C& ); @@ -4080,11 +4108,6 @@ namespace Clara { convertInto( stringValue, value ); function( obj, value ); } - virtual void setFlag( C& obj ) const { - typename RemoveConstRef::type value; - convertInto( true, value ); - function( obj, value ); - } virtual bool takesArg() const { return !IsBool::value; } virtual IArgFunction* clone() const { return new BoundBinaryFunction( *this ); } void (*function)( C&, T ); @@ -4092,8 +4115,20 @@ namespace Clara { } // namespace Detail - struct Parser { - Parser() : separators( " \t=:" ) {} + inline std::vector argsToVector( int argc, char const* const* const argv ) { + std::vector args( static_cast( argc ) ); + for( std::size_t i = 0; i < static_cast( argc ); ++i ) + args[i] = argv[i]; + + return args; + } + + class Parser { + enum Mode { None, MaybeShortOpt, SlashOpt, ShortOpt, LongOpt, Positional }; + Mode mode; + std::size_t from; + bool inQuotes; + public: struct Token { enum Type { Positional, ShortOpt, LongOpt }; @@ -4102,38 +4137,75 @@ namespace Clara { std::string data; }; - void parseIntoTokens( int argc, char const* const argv[], std::vector& tokens ) const { + Parser() : mode( None ), from( 0 ), inQuotes( false ){} + + void parseIntoTokens( std::vector const& args, std::vector& tokens ) { const std::string doubleDash = "--"; - for( int i = 1; i < argc && argv[i] != doubleDash; ++i ) - parseIntoTokens( argv[i] , tokens); + for( std::size_t i = 1; i < args.size() && args[i] != doubleDash; ++i ) + parseIntoTokens( args[i], tokens); } - void parseIntoTokens( std::string arg, std::vector& tokens ) const { - while( !arg.empty() ) { - Parser::Token token( Parser::Token::Positional, arg ); - arg = ""; - if( token.data[0] == '-' ) { - if( token.data.size() > 1 && token.data[1] == '-' ) { - token = Parser::Token( Parser::Token::LongOpt, token.data.substr( 2 ) ); - } - else { - token = Parser::Token( Parser::Token::ShortOpt, token.data.substr( 1 ) ); - if( token.data.size() > 1 && separators.find( token.data[1] ) == std::string::npos ) { - arg = "-" + token.data.substr( 1 ); - token.data = token.data.substr( 0, 1 ); - } - } - } - if( token.type != Parser::Token::Positional ) { - std::size_t pos = token.data.find_first_of( separators ); - if( pos != std::string::npos ) { - arg = token.data.substr( pos+1 ); - token.data = token.data.substr( 0, pos ); - } - } - tokens.push_back( token ); + + void parseIntoTokens( std::string const& arg, std::vector& tokens ) { + for( std::size_t i = 0; i <= arg.size(); ++i ) { + char c = arg[i]; + if( c == '"' ) + inQuotes = !inQuotes; + mode = handleMode( i, c, arg, tokens ); } } - std::string separators; + Mode handleMode( std::size_t i, char c, std::string const& arg, std::vector& tokens ) { + switch( mode ) { + case None: return handleNone( i, c ); + case MaybeShortOpt: return handleMaybeShortOpt( i, c ); + case ShortOpt: + case LongOpt: + case SlashOpt: return handleOpt( i, c, arg, tokens ); + case Positional: return handlePositional( i, c, arg, tokens ); + default: throw std::logic_error( "Unknown mode" ); + } + } + + Mode handleNone( std::size_t i, char c ) { + if( inQuotes ) { + from = i; + return Positional; + } + switch( c ) { + case '-': return MaybeShortOpt; +#ifdef CLARA_PLATFORM_WINDOWS + case '/': from = i+1; return SlashOpt; +#endif + default: from = i; return Positional; + } + } + Mode handleMaybeShortOpt( std::size_t i, char c ) { + switch( c ) { + case '-': from = i+1; return LongOpt; + default: from = i; return ShortOpt; + } + } + Mode handleOpt( std::size_t i, char c, std::string const& arg, std::vector& tokens ) { + if( std::string( ":=\0", 3 ).find( c ) == std::string::npos ) + return mode; + + std::string optName = arg.substr( from, i-from ); + if( mode == ShortOpt ) + for( std::size_t j = 0; j < optName.size(); ++j ) + tokens.push_back( Token( Token::ShortOpt, optName.substr( j, 1 ) ) ); + else if( mode == SlashOpt && optName.size() == 1 ) + tokens.push_back( Token( Token::ShortOpt, optName ) ); + else + tokens.push_back( Token( Token::LongOpt, optName ) ); + return None; + } + Mode handlePositional( std::size_t i, char c, std::string const& arg, std::vector& tokens ) { + if( inQuotes || std::string( "\0", 1 ).find( c ) == std::string::npos ) + return mode; + + std::string data = arg.substr( from, i-from ); + tokens.push_back( Token( Token::Positional, data ) ); + return None; + } }; template @@ -4436,21 +4508,21 @@ namespace Clara { return oss.str(); } - ConfigT parse( int argc, char const* const argv[] ) const { + ConfigT parse( std::vector const& args ) const { ConfigT config; - parseInto( argc, argv, config ); + parseInto( args, config ); return config; } - std::vector parseInto( int argc, char const* argv[], ConfigT& config ) const { - std::string processName = argv[0]; + std::vector parseInto( std::vector const& args, ConfigT& config ) const { + std::string processName = args[0]; std::size_t lastSlash = processName.find_last_of( "/\\" ); if( lastSlash != std::string::npos ) processName = processName.substr( lastSlash+1 ); m_boundProcessName.set( config, processName ); std::vector tokens; Parser parser; - parser.parseIntoTokens( argc, argv, tokens ); + parser.parseIntoTokens( args, tokens ); return populate( tokens, config ); } @@ -4481,7 +4553,7 @@ namespace Clara { arg.boundField.set( config, tokens[++i].data ); } else { - arg.boundField.setFlag( config ); + arg.boundField.set( config, "true" ); } break; } @@ -4624,6 +4696,21 @@ namespace Catch { ? ShowDurations::Always : ShowDurations::Never; } + inline void setUseColour( ConfigData& config, std::string const& value ) { + std::string mode = toLower( value ); + + if( mode == "yes" ) + config.useColour = UseColour::Yes; + else if( mode == "no" ) + config.useColour = UseColour::No; + else if( mode == "auto" ) + config.useColour = UseColour::Auto; + else + throw std::runtime_error( "colour mode must be one of: auto, yes or no" ); + } + inline void forceColour( ConfigData& config ) { + config.useColour = UseColour::Yes; + } inline void loadTestNamesFromFile( ConfigData& config, std::string const& _filename ) { std::ifstream f( _filename.c_str() ); if( !f.is_open() ) @@ -4710,7 +4797,7 @@ namespace Catch { cli["-d"]["--durations"] .describe( "show test durations" ) - .bind( &setShowDurations, "yes/no" ); + .bind( &setShowDurations, "yes|no" ); cli["-f"]["--input-file"] .describe( "load test names to run from a file" ) @@ -4738,8 +4825,12 @@ namespace Catch { .bind( &setRngSeed, "'time'|number" ); cli["--force-colour"] - .describe( "force colourised output" ) - .bind( &ConfigData::forceColour ); + .describe( "force colourised output (deprecated)" ) + .bind( &forceColour ); + + cli["--use-colour"] + .describe( "should output be colourised" ) + .bind( &setUseColour, "yes|no" ); return cli; } @@ -5170,6 +5261,8 @@ namespace Catch bool aborting; }; + class MultipleReporters; + struct IStreamingReporter : IShared { virtual ~IStreamingReporter(); @@ -5197,6 +5290,8 @@ namespace Catch virtual void testRunEnded( TestRunStats const& testRunStats ) = 0; virtual void skipTest( TestCaseInfo const& testInfo ) = 0; + + virtual MultipleReporters* tryAsMulti() { return CATCH_NULL; } }; struct IReporterFactory : IShared { @@ -5414,6 +5509,10 @@ namespace TestCaseTracking { virtual void addChild( Ptr const& child ) = 0; virtual ITracker* findChild( std::string const& name ) = 0; virtual void openChild() = 0; + + // Debug/ checking + virtual bool isSectionTracker() const = 0; + virtual bool isIndexTracker() const = 0; }; class TrackerContext { @@ -5538,6 +5637,10 @@ namespace TestCaseTracking { m_parent->openChild(); } } + + virtual bool isSectionTracker() const CATCH_OVERRIDE { return false; } + virtual bool isIndexTracker() const CATCH_OVERRIDE { return false; } + void open() { m_runState = Executing; moveToThis(); @@ -5601,13 +5704,16 @@ namespace TestCaseTracking { {} virtual ~SectionTracker(); + virtual bool isSectionTracker() const CATCH_OVERRIDE { return true; } + static SectionTracker& acquire( TrackerContext& ctx, std::string const& name ) { SectionTracker* section = CATCH_NULL; ITracker& currentTracker = ctx.currentTracker(); if( ITracker* childTracker = currentTracker.findChild( name ) ) { - section = dynamic_cast( childTracker ); - assert( section ); + assert( childTracker ); + assert( childTracker->isSectionTracker() ); + section = static_cast( childTracker ); } else { section = new SectionTracker( name, ctx, ¤tTracker ); @@ -5632,13 +5738,16 @@ namespace TestCaseTracking { {} virtual ~IndexTracker(); + virtual bool isIndexTracker() const CATCH_OVERRIDE { return true; } + static IndexTracker& acquire( TrackerContext& ctx, std::string const& name, int size ) { IndexTracker* tracker = CATCH_NULL; ITracker& currentTracker = ctx.currentTracker(); if( ITracker* childTracker = currentTracker.findChild( name ) ) { - tracker = dynamic_cast( childTracker ); - assert( tracker ); + assert( childTracker ); + assert( childTracker->isIndexTracker() ); + tracker = static_cast( childTracker ); } else { tracker = new IndexTracker( name, ctx, ¤tTracker, size ); @@ -5845,6 +5954,11 @@ namespace Catch { while( getCurrentContext().advanceGeneratorsForCurrentTest() && !aborting() ); Totals deltaTotals = m_totals.delta( prevTotals ); + if( testInfo.expectedToFail() && deltaTotals.testCases.passed > 0 ) { + deltaTotals.assertions.failed++; + deltaTotals.testCases.passed--; + deltaTotals.testCases.failed++; + } m_totals.testCases += deltaTotals.testCases; m_reporter->testCaseEnded( TestCaseStats( testInfo, deltaTotals, @@ -6236,10 +6350,10 @@ namespace Catch { Catch::cout() << "For more detail usage please see the project docs\n" << std::endl; } - int applyCommandLine( int argc, char const* argv[], OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) { + int applyCommandLine( int argc, char const* const* const argv, OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) { try { m_cli.setThrowOnUnrecognisedTokens( unusedOptionBehaviour == OnUnusedOptions::Fail ); - m_unusedTokens = m_cli.parseInto( argc, argv, m_configData ); + m_unusedTokens = m_cli.parseInto( Clara::argsToVector( argc, argv ), m_configData ); if( m_configData.showHelp ) showHelp( m_configData.processName ); m_config.reset(); @@ -6263,16 +6377,13 @@ namespace Catch { m_config.reset(); } - int run( int argc, char const* argv[] ) { + int run( int argc, char const* const* const argv ) { int returnCode = applyCommandLine( argc, argv ); if( returnCode == 0 ) returnCode = run(); return returnCode; } - int run( int argc, char* argv[] ) { - return run( argc, const_cast( argv ) ); - } int run() { if( m_configData.showHelp ) @@ -6336,13 +6447,31 @@ namespace Catch { #include #include +#ifdef CATCH_CPP14_OR_GREATER +#include +#endif + namespace Catch { - struct LexSort { - bool operator() (TestCase i,TestCase j) const { return (i + static void shuffle( V& vector ) { + RandomNumberGenerator rng; +#ifdef CATCH_CPP14_OR_GREATER + std::shuffle( vector.begin(), vector.end(), rng ); +#else + std::random_shuffle( vector.begin(), vector.end(), rng ); +#endif + } }; inline std::vector sortTests( IConfig const& config, std::vector const& unsortedTestCases ) { @@ -6351,14 +6480,12 @@ namespace Catch { switch( config.runOrder() ) { case RunTests::InLexicographicalOrder: - std::sort( sorted.begin(), sorted.end(), LexSort() ); + std::sort( sorted.begin(), sorted.end() ); break; case RunTests::InRandomOrder: { seedRng( config ); - - RandomNumberGenerator rng; - std::random_shuffle( sorted.begin(), sorted.end(), rng ); + RandomNumberGenerator::shuffle( sorted ); } break; case RunTests::InDeclarationOrder: @@ -6377,13 +6504,15 @@ namespace Catch { it != itEnd; ++it ) { std::pair::const_iterator, bool> prev = seenFunctions.insert( *it ); - if( !prev.second ){ - Catch::cerr() - << Colour( Colour::Red ) - << "error: TEST_CASE( \"" << it->name << "\" ) already defined.\n" - << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << "\n" - << "\tRedefined at " << it->getTestCaseInfo().lineInfo << std::endl; - exit(1); + if( !prev.second ) { + std::ostringstream ss; + + ss << Colour( Colour::Red ) + << "error: TEST_CASE( \"" << it->name << "\" ) already defined.\n" + << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << "\n" + << "\tRedefined at " << it->getTestCaseInfo().lineInfo << std::endl; + + throw std::runtime_error(ss.str()); } } } @@ -6971,7 +7100,18 @@ namespace { IColourImpl* platformColourInstance() { static Win32ColourImpl s_instance; - return &s_instance; + + Ptr config = getCurrentContext().getConfig(); + UseColour::YesOrNo colourMode = config + ? config->useColour() + : UseColour::Auto; + if( colourMode == UseColour::Auto ) + colourMode = !isDebuggerActive() + ? UseColour::Yes + : UseColour::No; + return colourMode == UseColour::Yes + ? &s_instance + : NoColourImpl::instance(); } } // end anon namespace @@ -7022,7 +7162,14 @@ namespace { IColourImpl* platformColourInstance() { Ptr config = getCurrentContext().getConfig(); - return (config && config->forceColour()) || isatty(STDOUT_FILENO) + UseColour::YesOrNo colourMode = config + ? config->useColour() + : UseColour::Auto; + if( colourMode == UseColour::Auto ) + colourMode = (!isDebuggerActive() && isatty(STDOUT_FILENO) ) + ? UseColour::Yes + : UseColour::No; + return colourMode == UseColour::Yes ? PosixColourImpl::instance() : NoColourImpl::instance(); } @@ -7047,9 +7194,7 @@ namespace Catch { Colour::~Colour(){ if( !m_moved ) use( None ); } void Colour::use( Code _colourCode ) { - static IColourImpl* impl = isDebuggerActive() - ? NoColourImpl::instance() - : platformColourInstance(); + static IColourImpl* impl = platformColourInstance(); impl->use( _colourCode ); } @@ -7426,7 +7571,7 @@ namespace Catch { return os; } - Version libraryVersion( 1, 3, 4, "", 0 ); + Version libraryVersion( 1, 5, 6, "", 0 ); } @@ -8405,13 +8550,18 @@ public: // IStreamingReporter ++it ) (*it)->skipTest( testInfo ); } + + virtual MultipleReporters* tryAsMulti() CATCH_OVERRIDE { + return this; + } + }; Ptr addReporter( Ptr const& existingReporter, Ptr const& additionalReporter ) { Ptr resultingReporter; if( existingReporter ) { - MultipleReporters* multi = dynamic_cast( existingReporter.get() ); + MultipleReporters* multi = existingReporter->tryAsMulti(); if( !multi ) { multi = new MultipleReporters; resultingReporter = Ptr( multi ); @@ -8591,7 +8741,7 @@ namespace Catch { virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {} - virtual bool assertionEnded( AssertionStats const& assertionStats ) { + virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { assert( !m_sectionStack.empty() ); SectionNode& sectionNode = *m_sectionStack.back(); sectionNode.assertions.push_back( assertionStats ); @@ -9722,7 +9872,7 @@ namespace Catch { if( totals.testCases.total() == 0 ) { stream << Colour( Colour::Warning ) << "No tests ran\n"; } - else if( totals.assertions.total() > 0 && totals.assertions.allPassed() ) { + else if( totals.assertions.total() > 0 && totals.testCases.allPassed() ) { stream << Colour( Colour::ResultSuccess ) << "All tests passed"; stream << " (" << pluralise( totals.assertions.passed, "assertion" ) << " in " diff --git a/test/fuzz.cpp b/test/src/fuzz.cpp similarity index 100% rename from test/fuzz.cpp rename to test/src/fuzz.cpp diff --git a/test/unit.cpp b/test/src/unit.cpp similarity index 87% rename from test/unit.cpp rename to test/src/unit.cpp index ab96364c8..298a2bb1e 100644 --- a/test/unit.cpp +++ b/test/src/unit.cpp @@ -1285,7 +1285,7 @@ TEST_CASE("constructors") SECTION("create a JSON value from an input stream") { - SECTION("sts::stringstream") + SECTION("std::stringstream") { std::stringstream ss; ss << "[\"foo\",1,2,3,false,{\"one\":1}]"; @@ -1314,7 +1314,7 @@ TEST_CASE("constructors") SECTION("std::ifstream") { - std::ifstream f("test/json_tests/pass1.json"); + std::ifstream f("test/data/json_tests/pass1.json"); json j(f); } } @@ -3455,7 +3455,7 @@ TEST_CASE("element access") { json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; CHECK_THROWS_AS(jarray.erase(8), std::out_of_range); - CHECK_THROWS_WITH(jarray.erase(8), "index out of range"); + CHECK_THROWS_WITH(jarray.erase(8), "array index 8 is out of range"); } } @@ -7920,6 +7920,42 @@ TEST_CASE("modifiers") "cannot use push_back() with number"); } } + + SECTION("with initializer_list") + { + SECTION("null") + { + json j; + j.push_back({"foo", "bar"}); + CHECK(j == json::array({{"foo", "bar"}})); + + json k; + k.push_back({1, 2, 3}); + CHECK(k == json::array({{1, 2, 3}})); + } + + SECTION("array") + { + json j = {1, 2, 3}; + j.push_back({"foo", "bar"}); + CHECK(j == json({1, 2, 3, {"foo", "bar"}})); + + json k = {1, 2, 3}; + k.push_back({1, 2, 3}); + CHECK(k == json({1, 2, 3, {1, 2, 3}})); + } + + SECTION("object") + { + json j = {{"key1", 1}}; + j.push_back({"key2", "bar"}); + CHECK(j == json({{"key1", 1}, {"key2", "bar"}})); + + json k = {{"key1", 1}}; + CHECK_THROWS_AS(k.push_back({1, 2, 3, 4}), std::domain_error); + CHECK_THROWS_WITH(k.push_back({1, 2, 3, 4}), "cannot use push_back() with object"); + } + } } SECTION("operator+=") @@ -8016,6 +8052,42 @@ TEST_CASE("modifiers") "cannot use push_back() with number"); } } + + SECTION("with initializer_list") + { + SECTION("null") + { + json j; + j += {"foo", "bar"}; + CHECK(j == json::array({{"foo", "bar"}})); + + json k; + k += {1, 2, 3}; + CHECK(k == json::array({{1, 2, 3}})); + } + + SECTION("array") + { + json j = {1, 2, 3}; + j += {"foo", "bar"}; + CHECK(j == json({1, 2, 3, {"foo", "bar"}})); + + json k = {1, 2, 3}; + k += {1, 2, 3}; + CHECK(k == json({1, 2, 3, {1, 2, 3}})); + } + + SECTION("object") + { + json j = {{"key1", 1}}; + j += {"key2", "bar"}; + CHECK(j == json({{"key1", 1}, {"key2", "bar"}})); + + json k = {{"key1", 1}}; + CHECK_THROWS_AS((k += {1, 2, 3, 4}), std::domain_error); + CHECK_THROWS_WITH((k += {1, 2, 3, 4}), "cannot use push_back() with object"); + } + } } SECTION("insert") @@ -9776,6 +9848,10 @@ TEST_CASE("parser class") CHECK_THROWS_AS(json::parser("-0e-:").parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser("-0f").parse(), std::invalid_argument); + // numbers must not begin with "+" + CHECK_THROWS_AS(json::parser("+1").parse(), std::invalid_argument); + CHECK_THROWS_AS(json::parser("+0").parse(), std::invalid_argument); + CHECK_THROWS_WITH(json::parser("01").parse(), "parse error - unexpected number literal; expected end of input"); CHECK_THROWS_WITH(json::parser("--1").parse(), "parse error - unexpected '-'"); @@ -10295,7 +10371,7 @@ TEST_CASE("README", "[hide]") // create object from string literal json j = "{ \"happy\": true, \"pi\": 3.141 }"_json; - // or even nicer (thanks http://isocpp.org/blog/2015/01/json-for-modern-cpp) + // or even nicer with a raw string literal auto j2 = R"( { "happy": true, @@ -10423,7 +10499,7 @@ TEST_CASE("README", "[hide]") } { - /// strings + // strings std::string s1 = "Hello, world!"; json js = s1; std::string s2 = js; @@ -10446,6 +10522,40 @@ TEST_CASE("README", "[hide]") // etc. } + + { + // a JSON value + json j_original = R"({ + "baz": ["one", "two", "three"], + "foo": "bar" + })"_json; + + // access members with a JSON pointer (RFC 6901) + j_original["/baz/2"_json_pointer]; + // "two" + + // a JSON patch (RFC 6902) + json j_patch = R"([ + { "op": "replace", "path": "/baz", "value": "boo" }, + { "op": "add", "path": "/hello", "value": ["world"] }, + { "op": "remove", "path": "/foo"} + ])"_json; + + // apply the patch + json j_result = j_original.patch(j_patch); + // { + // "baz": "boo", + // "hello": ["world"] + // } + + // calculate a JSON patch from two JSON values + json::diff(j_result, j_original); + // [ + // { "op":" replace", "path": "/baz", "value": ["one", "two", "three"] }, + // { "op":"remove","path":"/hello" }, + // { "op":"add","path":"/foo","value":"bar" } + // ] + } } TEST_CASE("algorithms") @@ -11576,39 +11686,39 @@ TEST_CASE("compliance tests from json.org") { for (auto filename : { - //"test/json_tests/fail1.json", - "test/json_tests/fail2.json", - "test/json_tests/fail3.json", - "test/json_tests/fail4.json", - "test/json_tests/fail5.json", - "test/json_tests/fail6.json", - "test/json_tests/fail7.json", - "test/json_tests/fail8.json", - "test/json_tests/fail9.json", - "test/json_tests/fail10.json", - "test/json_tests/fail11.json", - "test/json_tests/fail12.json", - "test/json_tests/fail13.json", - "test/json_tests/fail14.json", - "test/json_tests/fail15.json", - "test/json_tests/fail16.json", - "test/json_tests/fail17.json", - //"test/json_tests/fail18.json", - "test/json_tests/fail19.json", - "test/json_tests/fail20.json", - "test/json_tests/fail21.json", - "test/json_tests/fail22.json", - "test/json_tests/fail23.json", - "test/json_tests/fail24.json", - "test/json_tests/fail25.json", - "test/json_tests/fail26.json", - "test/json_tests/fail27.json", - "test/json_tests/fail28.json", - "test/json_tests/fail29.json", - "test/json_tests/fail30.json", - "test/json_tests/fail31.json", - "test/json_tests/fail32.json", - "test/json_tests/fail33.json" + //"test/data/json_tests/fail1.json", + "test/data/json_tests/fail2.json", + "test/data/json_tests/fail3.json", + "test/data/json_tests/fail4.json", + "test/data/json_tests/fail5.json", + "test/data/json_tests/fail6.json", + "test/data/json_tests/fail7.json", + "test/data/json_tests/fail8.json", + "test/data/json_tests/fail9.json", + "test/data/json_tests/fail10.json", + "test/data/json_tests/fail11.json", + "test/data/json_tests/fail12.json", + "test/data/json_tests/fail13.json", + "test/data/json_tests/fail14.json", + "test/data/json_tests/fail15.json", + "test/data/json_tests/fail16.json", + "test/data/json_tests/fail17.json", + //"test/data/json_tests/fail18.json", + "test/data/json_tests/fail19.json", + "test/data/json_tests/fail20.json", + "test/data/json_tests/fail21.json", + "test/data/json_tests/fail22.json", + "test/data/json_tests/fail23.json", + "test/data/json_tests/fail24.json", + "test/data/json_tests/fail25.json", + "test/data/json_tests/fail26.json", + "test/data/json_tests/fail27.json", + "test/data/json_tests/fail28.json", + "test/data/json_tests/fail29.json", + "test/data/json_tests/fail30.json", + "test/data/json_tests/fail31.json", + "test/data/json_tests/fail32.json", + "test/data/json_tests/fail33.json" }) { CAPTURE(filename); @@ -11622,9 +11732,9 @@ TEST_CASE("compliance tests from json.org") { for (auto filename : { - "test/json_tests/pass1.json", - "test/json_tests/pass2.json", - "test/json_tests/pass3.json" + "test/data/json_tests/pass1.json", + "test/data/json_tests/pass2.json", + "test/data/json_tests/pass3.json" }) { CAPTURE(filename); @@ -11797,42 +11907,42 @@ TEST_CASE("compliance tests from nativejson-benchmark") SECTION("roundtrip") { - // test cases are from https://github.com/miloyip/nativejson-benchmark/tree/master/data/roundtrip + // test cases are from https://github.com/miloyip/nativejson-benchmark/tree/master/test/data/roundtrip for (auto filename : { - "test/json_roundtrip/roundtrip01.json", - "test/json_roundtrip/roundtrip02.json", - "test/json_roundtrip/roundtrip03.json", - "test/json_roundtrip/roundtrip04.json", - "test/json_roundtrip/roundtrip05.json", - "test/json_roundtrip/roundtrip06.json", - "test/json_roundtrip/roundtrip07.json", - "test/json_roundtrip/roundtrip08.json", - "test/json_roundtrip/roundtrip09.json", - "test/json_roundtrip/roundtrip10.json", - "test/json_roundtrip/roundtrip11.json", - "test/json_roundtrip/roundtrip12.json", - "test/json_roundtrip/roundtrip13.json", - "test/json_roundtrip/roundtrip14.json", - "test/json_roundtrip/roundtrip15.json", - "test/json_roundtrip/roundtrip16.json", - "test/json_roundtrip/roundtrip17.json", - "test/json_roundtrip/roundtrip18.json", - "test/json_roundtrip/roundtrip19.json", - "test/json_roundtrip/roundtrip20.json", - "test/json_roundtrip/roundtrip21.json", - "test/json_roundtrip/roundtrip22.json", - "test/json_roundtrip/roundtrip23.json", - "test/json_roundtrip/roundtrip24.json", - "test/json_roundtrip/roundtrip25.json", - "test/json_roundtrip/roundtrip26.json", - "test/json_roundtrip/roundtrip27.json", - "test/json_roundtrip/roundtrip28.json", - "test/json_roundtrip/roundtrip29.json", - "test/json_roundtrip/roundtrip30.json", - "test/json_roundtrip/roundtrip31.json", - "test/json_roundtrip/roundtrip32.json" + "test/data/json_roundtrip/roundtrip01.json", + "test/data/json_roundtrip/roundtrip02.json", + "test/data/json_roundtrip/roundtrip03.json", + "test/data/json_roundtrip/roundtrip04.json", + "test/data/json_roundtrip/roundtrip05.json", + "test/data/json_roundtrip/roundtrip06.json", + "test/data/json_roundtrip/roundtrip07.json", + "test/data/json_roundtrip/roundtrip08.json", + "test/data/json_roundtrip/roundtrip09.json", + "test/data/json_roundtrip/roundtrip10.json", + "test/data/json_roundtrip/roundtrip11.json", + "test/data/json_roundtrip/roundtrip12.json", + "test/data/json_roundtrip/roundtrip13.json", + "test/data/json_roundtrip/roundtrip14.json", + "test/data/json_roundtrip/roundtrip15.json", + "test/data/json_roundtrip/roundtrip16.json", + "test/data/json_roundtrip/roundtrip17.json", + "test/data/json_roundtrip/roundtrip18.json", + "test/data/json_roundtrip/roundtrip19.json", + "test/data/json_roundtrip/roundtrip20.json", + "test/data/json_roundtrip/roundtrip21.json", + "test/data/json_roundtrip/roundtrip22.json", + "test/data/json_roundtrip/roundtrip23.json", + //"test/data/json_roundtrip/roundtrip24.json", // roundtrip error + //"test/data/json_roundtrip/roundtrip25.json", // roundtrip error + //"test/data/json_roundtrip/roundtrip26.json", // roundtrip error + //"test/data/json_roundtrip/roundtrip27.json", // roundtrip error + //"test/data/json_roundtrip/roundtrip28.json", // roundtrip error + "test/data/json_roundtrip/roundtrip29.json", + //"test/data/json_roundtrip/roundtrip30.json", // roundtrip error + //"test/data/json_roundtrip/roundtrip31.json", // roundtrip error + "test/data/json_roundtrip/roundtrip32.json" }) { CAPTURE(filename); @@ -11852,7 +11962,7 @@ TEST_CASE("test suite from json-test-suite") { // read a file with all unicode characters stored as single-character // strings in a JSON array - std::ifstream f("test/json_testsuite/sample.json"); + std::ifstream f("test/data/json_testsuite/sample.json"); json j; CHECK_NOTHROW(j << f); @@ -11867,35 +11977,35 @@ TEST_CASE("json.org examples") SECTION("1.json") { - std::ifstream f("test/json.org/1.json"); + std::ifstream f("test/data/json.org/1.json"); json j; CHECK_NOTHROW(j << f); } SECTION("2.json") { - std::ifstream f("test/json.org/2.json"); + std::ifstream f("test/data/json.org/2.json"); json j; CHECK_NOTHROW(j << f); } SECTION("3.json") { - std::ifstream f("test/json.org/3.json"); + std::ifstream f("test/data/json.org/3.json"); json j; CHECK_NOTHROW(j << f); } SECTION("4.json") { - std::ifstream f("test/json.org/4.json"); + std::ifstream f("test/data/json.org/4.json"); json j; CHECK_NOTHROW(j << f); } SECTION("5.json") { - std::ifstream f("test/json.org/5.json"); + std::ifstream f("test/data/json.org/5.json"); json j; CHECK_NOTHROW(j << f); } @@ -12029,18 +12139,48 @@ TEST_CASE("Unicode", "[hide]") { // read a file with all unicode characters stored as single-character // strings in a JSON array - std::ifstream f("test/json_nlohmann_tests/all_unicode.json"); + std::ifstream f("test/data/json_nlohmann_tests/all_unicode.json"); json j; CHECK_NOTHROW(j << f); // the array has 1112064 + 1 elemnts (a terminating "null" value) CHECK(j.size() == 1112065); + + SECTION("check JSON Pointers") + { + for (auto s : j) + { + // skip non-string JSON values + if (not s.is_string()) + { + continue; + } + + std::string ptr = s; + + // tilde must be followed by 0 or 1 + if (ptr == "~") + { + ptr += "0"; + } + + // JSON Pointers must begin with "/" + ptr = "/" + ptr; + + CHECK_NOTHROW(json::json_pointer("/" + ptr)); + + // check escape/unescape roundtrip + auto escaped = json::json_pointer::escape(ptr); + json::json_pointer::unescape(escaped); + CHECK(escaped == ptr); + } + } } SECTION("ignore byte-order-mark") { // read a file with a UTF-8 BOM - std::ifstream f("test/json_nlohmann_tests/bom.json"); + std::ifstream f("test/data/json_nlohmann_tests/bom.json"); json j; CHECK_NOTHROW(j << f); } @@ -12052,6 +12192,1546 @@ TEST_CASE("Unicode", "[hide]") } } +TEST_CASE("JSON pointers") +{ + SECTION("errors") + { + CHECK_THROWS_AS(json::json_pointer("foo"), std::domain_error); + CHECK_THROWS_WITH(json::json_pointer("foo"), "JSON pointer must be empty or begin with '/'"); + + CHECK_THROWS_AS(json::json_pointer("/~~"), std::domain_error); + CHECK_THROWS_WITH(json::json_pointer("/~~"), "escape error: '~' must be followed with '0' or '1'"); + + CHECK_THROWS_AS(json::json_pointer("/~"), std::domain_error); + CHECK_THROWS_WITH(json::json_pointer("/~"), "escape error: '~' must be followed with '0' or '1'"); + + json::json_pointer p; + CHECK_THROWS_AS(p.top(), std::domain_error); + CHECK_THROWS_WITH(p.top(), "JSON pointer has no parent"); + CHECK_THROWS_AS(p.pop_back(), std::domain_error); + CHECK_THROWS_WITH(p.pop_back(), "JSON pointer has no parent"); + } + + SECTION("examples from RFC 6901") + { + SECTION("nonconst access") + { + json j = R"( + { + "foo": ["bar", "baz"], + "": 0, + "a/b": 1, + "c%d": 2, + "e^f": 3, + "g|h": 4, + "i\\j": 5, + "k\"l": 6, + " ": 7, + "m~n": 8 + } + )"_json; + + // the whole document + CHECK(j[json::json_pointer()] == j); + CHECK(j[json::json_pointer("")] == j); + + // array access + CHECK(j[json::json_pointer("/foo")] == j["foo"]); + CHECK(j[json::json_pointer("/foo/0")] == j["foo"][0]); + CHECK(j[json::json_pointer("/foo/1")] == j["foo"][1]); + CHECK(j["/foo/1"_json_pointer] == j["foo"][1]); + + // checked array access + CHECK(j.at(json::json_pointer("/foo/0")) == j["foo"][0]); + CHECK(j.at(json::json_pointer("/foo/1")) == j["foo"][1]); + + // empty string access + CHECK(j[json::json_pointer("/")] == j[""]); + + // other cases + CHECK(j[json::json_pointer("/ ")] == j[" "]); + CHECK(j[json::json_pointer("/c%d")] == j["c%d"]); + CHECK(j[json::json_pointer("/e^f")] == j["e^f"]); + CHECK(j[json::json_pointer("/g|h")] == j["g|h"]); + CHECK(j[json::json_pointer("/i\\j")] == j["i\\j"]); + CHECK(j[json::json_pointer("/k\"l")] == j["k\"l"]); + + // checked access + CHECK(j.at(json::json_pointer("/ ")) == j[" "]); + CHECK(j.at(json::json_pointer("/c%d")) == j["c%d"]); + CHECK(j.at(json::json_pointer("/e^f")) == j["e^f"]); + CHECK(j.at(json::json_pointer("/g|h")) == j["g|h"]); + CHECK(j.at(json::json_pointer("/i\\j")) == j["i\\j"]); + CHECK(j.at(json::json_pointer("/k\"l")) == j["k\"l"]); + + // escaped access + CHECK(j[json::json_pointer("/a~1b")] == j["a/b"]); + CHECK(j[json::json_pointer("/m~0n")] == j["m~n"]); + + // unescaped access + CHECK_THROWS_AS(j[json::json_pointer("/a/b")], std::out_of_range); + CHECK_THROWS_WITH(j[json::json_pointer("/a/b")], "unresolved reference token 'b'"); + // "/a/b" works for JSON {"a": {"b": 42}} + CHECK(json({{"a", {{"b", 42}}}})[json::json_pointer("/a/b")] == json(42)); + + // unresolved access + json j_primitive = 1; + CHECK_THROWS_AS(j_primitive["/foo"_json_pointer], std::out_of_range); + CHECK_THROWS_WITH(j_primitive["/foo"_json_pointer], "unresolved reference token 'foo'"); + CHECK_THROWS_AS(j_primitive.at("/foo"_json_pointer), std::out_of_range); + CHECK_THROWS_WITH(j_primitive.at("/foo"_json_pointer), "unresolved reference token 'foo'"); + } + + SECTION("const access") + { + const json j = R"( + { + "foo": ["bar", "baz"], + "": 0, + "a/b": 1, + "c%d": 2, + "e^f": 3, + "g|h": 4, + "i\\j": 5, + "k\"l": 6, + " ": 7, + "m~n": 8 + } + )"_json; + + // the whole document + CHECK(j[json::json_pointer()] == j); + CHECK(j[json::json_pointer("")] == j); + + // array access + CHECK(j[json::json_pointer("/foo")] == j["foo"]); + CHECK(j[json::json_pointer("/foo/0")] == j["foo"][0]); + CHECK(j[json::json_pointer("/foo/1")] == j["foo"][1]); + CHECK(j["/foo/1"_json_pointer] == j["foo"][1]); + + // checked array access + CHECK(j.at(json::json_pointer("/foo/0")) == j["foo"][0]); + CHECK(j.at(json::json_pointer("/foo/1")) == j["foo"][1]); + + // empty string access + CHECK(j[json::json_pointer("/")] == j[""]); + + // other cases + CHECK(j[json::json_pointer("/ ")] == j[" "]); + CHECK(j[json::json_pointer("/c%d")] == j["c%d"]); + CHECK(j[json::json_pointer("/e^f")] == j["e^f"]); + CHECK(j[json::json_pointer("/g|h")] == j["g|h"]); + CHECK(j[json::json_pointer("/i\\j")] == j["i\\j"]); + CHECK(j[json::json_pointer("/k\"l")] == j["k\"l"]); + + // checked access + CHECK(j.at(json::json_pointer("/ ")) == j[" "]); + CHECK(j.at(json::json_pointer("/c%d")) == j["c%d"]); + CHECK(j.at(json::json_pointer("/e^f")) == j["e^f"]); + CHECK(j.at(json::json_pointer("/g|h")) == j["g|h"]); + CHECK(j.at(json::json_pointer("/i\\j")) == j["i\\j"]); + CHECK(j.at(json::json_pointer("/k\"l")) == j["k\"l"]); + + // escaped access + CHECK(j[json::json_pointer("/a~1b")] == j["a/b"]); + CHECK(j[json::json_pointer("/m~0n")] == j["m~n"]); + + // unescaped access + CHECK_THROWS_AS(j.at(json::json_pointer("/a/b")), std::out_of_range); + CHECK_THROWS_WITH(j.at(json::json_pointer("/a/b")), "key 'a' not found"); + + // unresolved access + const json j_primitive = 1; + CHECK_THROWS_AS(j_primitive["/foo"_json_pointer], std::out_of_range); + CHECK_THROWS_WITH(j_primitive["/foo"_json_pointer], "unresolved reference token 'foo'"); + CHECK_THROWS_AS(j_primitive.at("/foo"_json_pointer), std::out_of_range); + CHECK_THROWS_WITH(j_primitive.at("/foo"_json_pointer), "unresolved reference token 'foo'"); + } + + SECTION("user-defined string literal") + { + json j = R"( + { + "foo": ["bar", "baz"], + "": 0, + "a/b": 1, + "c%d": 2, + "e^f": 3, + "g|h": 4, + "i\\j": 5, + "k\"l": 6, + " ": 7, + "m~n": 8 + } + )"_json; + + // the whole document + CHECK(j[""_json_pointer] == j); + + // array access + CHECK(j["/foo"_json_pointer] == j["foo"]); + CHECK(j["/foo/0"_json_pointer] == j["foo"][0]); + CHECK(j["/foo/1"_json_pointer] == j["foo"][1]); + } + } + + SECTION("array access") + { + SECTION("nonconst access") + { + json j = {1, 2, 3}; + const json j_const = j; + + // check reading access + CHECK(j["/0"_json_pointer] == j[0]); + CHECK(j["/1"_json_pointer] == j[1]); + CHECK(j["/2"_json_pointer] == j[2]); + + // assign to existing index + j["/1"_json_pointer] = 13; + CHECK(j[1] == json(13)); + + // assign to nonexisting index + j["/3"_json_pointer] = 33; + CHECK(j[3] == json(33)); + + // assign to nonexisting index (with gap) + j["/5"_json_pointer] = 55; + CHECK(j == json({1, 13, 3, 33, nullptr, 55})); + + // error with leading 0 + CHECK_THROWS_AS(j["/01"_json_pointer], std::domain_error); + CHECK_THROWS_WITH(j["/01"_json_pointer], "array index must not begin with '0'"); + CHECK_THROWS_AS(j_const["/01"_json_pointer], std::domain_error); + CHECK_THROWS_WITH(j_const["/01"_json_pointer], "array index must not begin with '0'"); + CHECK_THROWS_AS(j.at("/01"_json_pointer), std::domain_error); + CHECK_THROWS_WITH(j.at("/01"_json_pointer), "array index must not begin with '0'"); + CHECK_THROWS_AS(j_const.at("/01"_json_pointer), std::domain_error); + CHECK_THROWS_WITH(j_const.at("/01"_json_pointer), "array index must not begin with '0'"); + + // error with incorrect numbers + CHECK_THROWS_AS(j["/one"_json_pointer] = 1, std::invalid_argument); + + // assign to "-" + j["/-"_json_pointer] = 99; + CHECK(j == json({1, 13, 3, 33, nullptr, 55, 99})); + + // error when using "-" in const object + CHECK_THROWS_AS(j_const["/-"_json_pointer], std::out_of_range); + CHECK_THROWS_WITH(j_const["/-"_json_pointer], "array index '-' (3) is out of range"); + + // error when using "-" with at + CHECK_THROWS_AS(j.at("/-"_json_pointer), std::out_of_range); + CHECK_THROWS_WITH(j.at("/-"_json_pointer), "array index '-' (7) is out of range"); + CHECK_THROWS_AS(j_const.at("/-"_json_pointer), std::out_of_range); + CHECK_THROWS_WITH(j_const.at("/-"_json_pointer), "array index '-' (3) is out of range"); + } + + SECTION("const access") + { + const json j = {1, 2, 3}; + + // check reading access + CHECK(j["/0"_json_pointer] == j[0]); + CHECK(j["/1"_json_pointer] == j[1]); + CHECK(j["/2"_json_pointer] == j[2]); + + // assign to nonexisting index + CHECK_THROWS_AS(j.at("/3"_json_pointer), std::out_of_range); + CHECK_THROWS_WITH(j.at("/3"_json_pointer), "array index 3 is out of range"); + + // assign to nonexisting index (with gap) + CHECK_THROWS_AS(j.at("/5"_json_pointer), std::out_of_range); + CHECK_THROWS_WITH(j.at("/5"_json_pointer), "array index 5 is out of range"); + + // assign to "-" + CHECK_THROWS_AS(j["/-"_json_pointer], std::out_of_range); + CHECK_THROWS_WITH(j["/-"_json_pointer], "array index '-' (3) is out of range"); + CHECK_THROWS_AS(j.at("/-"_json_pointer), std::out_of_range); + CHECK_THROWS_WITH(j.at("/-"_json_pointer), "array index '-' (3) is out of range"); + } + + } + + SECTION("flatten") + { + json j = + { + {"pi", 3.141}, + {"happy", true}, + {"name", "Niels"}, + {"nothing", nullptr}, + { + "answer", { + {"everything", 42} + } + }, + {"list", {1, 0, 2}}, + { + "object", { + {"currency", "USD"}, + {"value", 42.99}, + {"", "empty string"}, + {"/", "slash"}, + {"~", "tilde"}, + {"~1", "tilde1"} + } + } + }; + + json j_flatten = + { + {"/pi", 3.141}, + {"/happy", true}, + {"/name", "Niels"}, + {"/nothing", nullptr}, + {"/answer/everything", 42}, + {"/list/0", 1}, + {"/list/1", 0}, + {"/list/2", 2}, + {"/object/currency", "USD"}, + {"/object/value", 42.99}, + {"/object/", "empty string"}, + {"/object/~1", "slash"}, + {"/object/~0", "tilde"}, + {"/object/~01", "tilde1"} + }; + + // check if flattened result is as expected + CHECK(j.flatten() == j_flatten); + + // check if unflattened result is as expected + CHECK(j_flatten.unflatten() == j); + + // error for nonobjects + CHECK_THROWS_AS(json(1).unflatten(), std::domain_error); + CHECK_THROWS_WITH(json(1).unflatten(), "only objects can be unflattened"); + + // error for nonprimitve values + CHECK_THROWS_AS(json({{"/1", {1, 2, 3}}}).unflatten(), std::domain_error); + CHECK_THROWS_WITH(json({{"/1", {1, 2, 3}}}).unflatten(), "values in object must be primitive"); + + // error for conflicting values + json j_error = {{"", 42}, {"/foo", 17}}; + CHECK_THROWS_AS(j_error.unflatten(), std::domain_error); + CHECK_THROWS_WITH(j_error.unflatten(), "invalid value to unflatten"); + + // explicit roundtrip check + CHECK(j.flatten().unflatten() == j); + + // roundtrip for primitive values + json j_null; + CHECK(j_null.flatten().unflatten() == j_null); + json j_number = 42; + CHECK(j_number.flatten().unflatten() == j_number); + json j_boolean = false; + CHECK(j_boolean.flatten().unflatten() == j_boolean); + json j_string = "foo"; + CHECK(j_string.flatten().unflatten() == j_string); + + // roundtrip for empty structured values (will be unflattened to null) + json j_array(json::value_t::array); + CHECK(j_array.flatten().unflatten() == json()); + json j_object(json::value_t::object); + CHECK(j_object.flatten().unflatten() == json()); + } + + SECTION("string representation") + { + for (auto ptr : + {"", "/foo", "/foo/0", "/", "/a~1b", "/c%d", "/e^f", "/g|h", "/i\\j", "/k\"l", "/ ", "/m~0n" + }) + { + CHECK(json::json_pointer(ptr).to_string() == ptr); + } + } +} + +TEST_CASE("JSON patch") +{ + SECTION("examples from RFC 6902") + { + SECTION("4. Operations") + { + // the ordering of members in JSON objects is not significant: + json op1 = R"({ "op": "add", "path": "/a/b/c", "value": "foo" })"_json; + json op2 = R"({ "path": "/a/b/c", "op": "add", "value": "foo" })"_json; + json op3 = R"({ "value": "foo", "path": "/a/b/c", "op": "add" })"_json; + + // check if the operation objects are equivalent + CHECK(op1 == op2); + CHECK(op1 == op3); + } + + SECTION("4.1 add") + { + json patch = R"([{ "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] }])"_json; + + // However, the object itself or an array containing it does need + // to exist, and it remains an error for that not to be the case. + // For example, an "add" with a target location of "/a/b" starting + // with this document + json doc1 = R"({ "a": { "foo": 1 } })"_json; + + // is not an error, because "a" exists, and "b" will be added to + // its value. + CHECK_NOTHROW(doc1.patch(patch)); + CHECK(doc1.patch(patch) == R"( + { + "a": { + "foo": 1, + "b": { + "c": [ "foo", "bar" ] + } + } + } + )"_json); + + // It is an error in this document: + json doc2 = R"({ "q": { "bar": 2 } })"_json; + + // because "a" does not exist. + CHECK_THROWS_AS(doc2.patch(patch), std::out_of_range); + CHECK_THROWS_WITH(doc2.patch(patch), "key 'a' not found"); + } + + SECTION("4.2 remove") + { + // If removing an element from an array, any elements above the + // specified index are shifted one position to the left. + json doc = {1, 2, 3, 4}; + json patch = {{{"op", "remove"}, {"path", "/1"}}}; + CHECK(doc.patch(patch) == json({1, 3, 4})); + } + + SECTION("A.1. Adding an Object Member") + { + // An example target JSON document: + json doc = R"( + { "foo": "bar"} + )"_json; + + // A JSON Patch document: + json patch = R"( + [ + { "op": "add", "path": "/baz", "value": "qux" } + ] + )"_json; + + // The resulting JSON document: + json expected = R"( + { + "baz": "qux", + "foo": "bar" + } + )"_json; + + // check if patched value is as expected + CHECK(doc.patch(patch) == expected); + + // check roundtrip + CHECK(doc.patch(json::diff(doc, expected)) == expected); + } + + SECTION("A.2. Adding an Array Element") + { + // An example target JSON document: + json doc = R"( + { "foo": [ "bar", "baz" ] } + )"_json; + + // A JSON Patch document: + json patch = R"( + [ + { "op": "add", "path": "/foo/1", "value": "qux" } + ] + )"_json; + + // The resulting JSON document: + json expected = R"( + { "foo": [ "bar", "qux", "baz" ] } + )"_json; + + // check if patched value is as expected + CHECK(doc.patch(patch) == expected); + + // check roundtrip + CHECK(doc.patch(json::diff(doc, expected)) == expected); + } + + SECTION("A.3. Removing an Object Member") + { + // An example target JSON document: + json doc = R"( + { + "baz": "qux", + "foo": "bar" + } + )"_json; + + // A JSON Patch document: + json patch = R"( + [ + { "op": "remove", "path": "/baz" } + ] + )"_json; + + // The resulting JSON document: + json expected = R"( + { "foo": "bar" } + )"_json; + + // check if patched value is as expected + CHECK(doc.patch(patch) == expected); + + // check roundtrip + CHECK(doc.patch(json::diff(doc, expected)) == expected); + } + + SECTION("A.4. Removing an Array Element") + { + // An example target JSON document: + json doc = R"( + { "foo": [ "bar", "qux", "baz" ] } + )"_json; + + // A JSON Patch document: + json patch = R"( + [ + { "op": "remove", "path": "/foo/1" } + ] + )"_json; + + // The resulting JSON document: + json expected = R"( + { "foo": [ "bar", "baz" ] } + )"_json; + + // check if patched value is as expected + CHECK(doc.patch(patch) == expected); + + // check roundtrip + CHECK(doc.patch(json::diff(doc, expected)) == expected); + } + + SECTION("A.5. Replacing a Value") + { + // An example target JSON document: + json doc = R"( + { + "baz": "qux", + "foo": "bar" + } + )"_json; + + // A JSON Patch document: + json patch = R"( + [ + { "op": "replace", "path": "/baz", "value": "boo" } + ] + )"_json; + + json expected = R"( + { + "baz": "boo", + "foo": "bar" + } + )"_json; + + // check if patched value is as expected + CHECK(doc.patch(patch) == expected); + + // check roundtrip + CHECK(doc.patch(json::diff(doc, expected)) == expected); + } + + SECTION("A.6. Moving a Value") + { + // An example target JSON document: + json doc = R"( + { + "foo": { + "bar": "baz", + "waldo": "fred" + }, + "qux": { + "corge": "grault" + } + } + )"_json; + + // A JSON Patch document: + json patch = R"( + [ + { "op": "move", "from": "/foo/waldo", "path": "/qux/thud" } + ] + )"_json; + + // The resulting JSON document: + json expected = R"( + { + "foo": { + "bar": "baz" + }, + "qux": { + "corge": "grault", + "thud": "fred" + } + } + )"_json; + + // check if patched value is as expected + CHECK(doc.patch(patch) == expected); + + // check roundtrip + CHECK(doc.patch(json::diff(doc, expected)) == expected); + } + + SECTION("A.7. Moving a Value") + { + // An example target JSON document: + json doc = R"( + { "foo": [ "all", "grass", "cows", "eat" ] } + )"_json; + + // A JSON Patch document: + json patch = R"( + [ + { "op": "move", "from": "/foo/1", "path": "/foo/3" } + ] + )"_json; + + // The resulting JSON document: + json expected = R"( + { "foo": [ "all", "cows", "eat", "grass" ] } + )"_json; + + // check if patched value is as expected + CHECK(doc.patch(patch) == expected); + + // check roundtrip + CHECK(doc.patch(json::diff(doc, expected)) == expected); + } + + SECTION("A.8. Testing a Value: Success") + { + // An example target JSON document: + json doc = R"( + { + "baz": "qux", + "foo": [ "a", 2, "c" ] + } + )"_json; + + // A JSON Patch document that will result in successful evaluation: + json patch = R"( + [ + { "op": "test", "path": "/baz", "value": "qux" }, + { "op": "test", "path": "/foo/1", "value": 2 } + ] + )"_json; + + // check if evaluation does not throw + CHECK_NOTHROW(doc.patch(patch)); + // check if patched document is unchanged + CHECK(doc.patch(patch) == doc); + } + + SECTION("A.9. Testing a Value: Error") + { + // An example target JSON document: + json doc = R"( + { "baz": "qux" } + )"_json; + + // A JSON Patch document that will result in an error condition: + json patch = R"( + [ + { "op": "test", "path": "/baz", "value": "bar" } + ] + )"_json; + + // check that evaluation throws + CHECK_THROWS_AS(doc.patch(patch), std::domain_error); + CHECK_THROWS_WITH(doc.patch(patch), "unsuccessful: " + patch[0].dump()); + } + + SECTION("A.10. Adding a Nested Member Object") + { + // An example target JSON document: + json doc = R"( + { "foo": "bar" } + )"_json; + + // A JSON Patch document: + json patch = R"( + [ + { "op": "add", "path": "/child", "value": { "grandchild": { } } } + ] + )"_json; + + // The resulting JSON document: + json expected = R"( + { + "foo": "bar", + "child": { + "grandchild": { + } + } + } + )"_json; + + // check if patched value is as expected + CHECK(doc.patch(patch) == expected); + + // check roundtrip + CHECK(doc.patch(json::diff(doc, expected)) == expected); + } + + SECTION("A.11. Ignoring Unrecognized Elements") + { + // An example target JSON document: + json doc = R"( + { "foo": "bar" } + )"_json; + + // A JSON Patch document: + json patch = R"( + [ + { "op": "add", "path": "/baz", "value": "qux", "xyz": 123 } + ] + )"_json; + + json expected = R"( + { + "foo": "bar", + "baz": "qux" + } + )"_json; + + // check if patched value is as expected + CHECK(doc.patch(patch) == expected); + + // check roundtrip + CHECK(doc.patch(json::diff(doc, expected)) == expected); + } + + SECTION("A.12. Adding to a Nonexistent Target") + { + // An example target JSON document: + json doc = R"( + { "foo": "bar" } + )"_json; + + // A JSON Patch document: + json patch = R"( + [ + { "op": "add", "path": "/baz/bat", "value": "qux" } + ] + )"_json; + + // This JSON Patch document, applied to the target JSON document + // above, would result in an error (therefore, it would not be + // applied), because the "add" operation's target location that + // references neither the root of the document, nor a member of + // an existing object, nor a member of an existing array. + + CHECK_THROWS_AS(doc.patch(patch), std::out_of_range); + CHECK_THROWS_WITH(doc.patch(patch), "key 'baz' not found"); + } + + // A.13. Invalid JSON Patch Document + // not applicable + + SECTION("A.14. Escape Ordering") + { + // An example target JSON document: + json doc = R"( + { + "/": 9, + "~1": 10 + } + )"_json; + + // A JSON Patch document: + json patch = R"( + [ + {"op": "test", "path": "/~01", "value": 10} + ] + )"_json; + + json expected = R"( + { + "/": 9, + "~1": 10 + } + )"_json; + + // check if patched value is as expected + CHECK(doc.patch(patch) == expected); + + // check roundtrip + CHECK(doc.patch(json::diff(doc, expected)) == expected); + } + + SECTION("A.15. Comparing Strings and Numbers") + { + // An example target JSON document: + json doc = R"( + { + "/": 9, + "~1": 10 + } + )"_json; + + // A JSON Patch document that will result in an error condition: + json patch = R"( + [ + {"op": "test", "path": "/~01", "value": "10"} + ] + )"_json; + + // check that evaluation throws + CHECK_THROWS_AS(doc.patch(patch), std::domain_error); + CHECK_THROWS_WITH(doc.patch(patch), "unsuccessful: " + patch[0].dump()); + } + + SECTION("A.16. Adding an Array Value") + { + // An example target JSON document: + json doc = R"( + { "foo": ["bar"] } + )"_json; + + // A JSON Patch document: + json patch = R"( + [ + { "op": "add", "path": "/foo/-", "value": ["abc", "def"] } + ] + )"_json; + + // The resulting JSON document: + json expected = R"( + { "foo": ["bar", ["abc", "def"]] } + )"_json; + + // check if patched value is as expected + CHECK(doc.patch(patch) == expected); + + // check roundtrip + CHECK(doc.patch(json::diff(doc, expected)) == expected); + } + } + + SECTION("own examples") + { + SECTION("add") + { + SECTION("add to the root element") + { + // If the path is the root of the target document - the + // specified value becomes the entire content of the target + // document. + + // An example target JSON document: + json doc = 17; + + // A JSON Patch document: + json patch = R"( + [ + { "op": "add", "path": "", "value": [1,2,3] } + ] + )"_json; + + // The resulting JSON document: + json expected = {1, 2, 3}; + + // check if patched value is as expected + CHECK(doc.patch(patch) == expected); + + // check roundtrip + CHECK(doc.patch(json::diff(doc, expected)) == expected); + } + + SECTION("add to end of the array") + { + // The specified index MUST NOT be greater than the number of + // elements in the array. The example below uses and index of + // exactly the number of elements in the array which is legal. + + // An example target JSON document: + json doc = {0, 1, 2}; + + // A JSON Patch document: + json patch = R"( + [ + { "op": "add", "path": "/3", "value": 3 } + ] + )"_json; + + // The resulting JSON document: + json expected = {0, 1, 2, 3}; + + // check if patched value is as expected + CHECK(doc.patch(patch) == expected); + + // check roundtrip + CHECK(doc.patch(json::diff(doc, expected)) == expected); + } + } + + SECTION("copy") + { + // An example target JSON document: + json doc = R"( + { + "foo": { + "bar": "baz", + "waldo": "fred" + }, + "qux": { + "corge": "grault" + } + } + )"_json; + + // A JSON Patch document: + json patch = R"( + [ + { "op": "copy", "from": "/foo/waldo", "path": "/qux/thud" } + ] + )"_json; + + // The resulting JSON document: + json expected = R"( + { + "foo": { + "bar": "baz", + "waldo": "fred" + }, + "qux": { + "corge": "grault", + "thud": "fred" + } + } + )"_json; + + // check if patched value is as expected + CHECK(doc.patch(patch) == expected); + + // check roundtrip + CHECK(doc.patch(json::diff(doc, expected)) == expected); + } + + SECTION("replace") + { + json j = "string"; + json patch = {{{"op", "replace"}, {"path", ""}, {"value", 1}}}; + CHECK(j.patch(patch) == json(1)); + } + + SECTION("documentation GIF") + { + { + // a JSON patch + json p1 = R"( + [{"op": "add", "path": "/GB", "value": "London"}] + )"_json; + + // a JSON value + json source = R"( + {"D": "Berlin", "F": "Paris"} + )"_json; + + // apply the patch + json target = source.patch(p1); + // target = { "D": "Berlin", "F": "Paris", "GB": "London" } + CHECK(target == R"({ "D": "Berlin", "F": "Paris", "GB": "London" })"_json); + + // create a diff from two JSONs + json p2 = json::diff(target, source); + // p2 = [{"op": "delete", "path": "/GB"}] + CHECK(p2 == R"([{"op":"remove","path":"/GB"}])"_json); + } + { + // a JSON value + json j = {"good", "bad", "ugly"}; + + // a JSON pointer + auto ptr = json::json_pointer("/2"); + + // use to access elements + j[ptr] = {{"it", "cattivo"}}; + CHECK(j == R"(["good","bad",{"it":"cattivo"}])"_json); + + // use user-defined string literal + j["/2/en"_json_pointer] = "ugly"; + CHECK(j == R"(["good","bad",{"en":"ugly","it":"cattivo"}])"_json); + + json flat = j.flatten(); + CHECK(flat == R"({"/0":"good","/1":"bad","/2/en":"ugly","/2/it":"cattivo"})"_json); + } + } + } + + SECTION("errors") + { + SECTION("unknown operation") + { + SECTION("not an array") + { + json j; + json patch = {{"op", "add"}, {"path", ""}, {"value", 1}}; + CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); + CHECK_THROWS_WITH(j.patch(patch), "JSON patch must be an array of objects"); + } + + SECTION("not an array of objects") + { + json j; + json patch = {"op", "add", "path", "", "value", 1}; + CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); + CHECK_THROWS_WITH(j.patch(patch), "JSON patch must be an array of objects"); + } + + SECTION("missing 'op'") + { + json j; + json patch = {{{"foo", "bar"}}}; + CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); + CHECK_THROWS_WITH(j.patch(patch), "operation must have member 'op'"); + } + + SECTION("non-string 'op'") + { + json j; + json patch = {{{"op", 1}}}; + CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); + CHECK_THROWS_WITH(j.patch(patch), "operation must have string member 'op'"); + } + + SECTION("invalid operation") + { + json j; + json patch = {{{"op", "foo"}, {"path", ""}}}; + CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); + CHECK_THROWS_WITH(j.patch(patch), "operation value 'foo' is invalid"); + } + } + + SECTION("add") + { + SECTION("missing 'path'") + { + json j; + json patch = {{{"op", "add"}}}; + CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); + CHECK_THROWS_WITH(j.patch(patch), "operation 'add' must have member 'path'"); + } + + SECTION("non-string 'path'") + { + json j; + json patch = {{{"op", "add"}, {"path", 1}}}; + CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); + CHECK_THROWS_WITH(j.patch(patch), "operation 'add' must have string member 'path'"); + } + + SECTION("missing 'value'") + { + json j; + json patch = {{{"op", "add"}, {"path", ""}}}; + CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); + CHECK_THROWS_WITH(j.patch(patch), "operation 'add' must have member 'value'"); + } + + SECTION("invalid array index") + { + json j = {1, 2}; + json patch = {{{"op", "add"}, {"path", "/4"}, {"value", 4}}}; + CHECK_THROWS_AS(j.patch(patch), std::out_of_range); + CHECK_THROWS_WITH(j.patch(patch), "array index 4 is out of range"); + } + } + + SECTION("remove") + { + SECTION("missing 'path'") + { + json j; + json patch = {{{"op", "remove"}}}; + CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); + CHECK_THROWS_WITH(j.patch(patch), "operation 'remove' must have member 'path'"); + } + + SECTION("non-string 'path'") + { + json j; + json patch = {{{"op", "remove"}, {"path", 1}}}; + CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); + CHECK_THROWS_WITH(j.patch(patch), "operation 'remove' must have string member 'path'"); + } + + SECTION("nonexisting target location (array)") + { + json j = {1, 2, 3}; + json patch = {{{"op", "remove"}, {"path", "/17"}}}; + CHECK_THROWS_AS(j.patch(patch), std::out_of_range); + CHECK_THROWS_WITH(j.patch(patch), "array index 17 is out of range"); + } + + SECTION("nonexisting target location (object)") + { + json j = {{"foo", 1}, {"bar", 2}}; + json patch = {{{"op", "remove"}, {"path", "/baz"}}}; + CHECK_THROWS_AS(j.patch(patch), std::out_of_range); + CHECK_THROWS_WITH(j.patch(patch), "key 'baz' not found"); + } + + SECTION("root element as target location") + { + json j = "string"; + json patch = {{{"op", "remove"}, {"path", ""}}}; + CHECK_THROWS_AS(j.patch(patch), std::domain_error); + CHECK_THROWS_WITH(j.patch(patch), "JSON pointer has no parent"); + } + } + + SECTION("replace") + { + SECTION("missing 'path'") + { + json j; + json patch = {{{"op", "replace"}}}; + CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); + CHECK_THROWS_WITH(j.patch(patch), "operation 'replace' must have member 'path'"); + } + + SECTION("non-string 'path'") + { + json j; + json patch = {{{"op", "replace"}, {"path", 1}}}; + CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); + CHECK_THROWS_WITH(j.patch(patch), "operation 'replace' must have string member 'path'"); + } + + SECTION("missing 'value'") + { + json j; + json patch = {{{"op", "replace"}, {"path", ""}}}; + CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); + CHECK_THROWS_WITH(j.patch(patch), "operation 'replace' must have member 'value'"); + } + + SECTION("nonexisting target location (array)") + { + json j = {1, 2, 3}; + json patch = {{{"op", "replace"}, {"path", "/17"}, {"value", 19}}}; + CHECK_THROWS_AS(j.patch(patch), std::out_of_range); + CHECK_THROWS_WITH(j.patch(patch), "array index 17 is out of range"); + } + + SECTION("nonexisting target location (object)") + { + json j = {{"foo", 1}, {"bar", 2}}; + json patch = {{{"op", "replace"}, {"path", "/baz"}, {"value", 3}}}; + CHECK_THROWS_AS(j.patch(patch), std::out_of_range); + CHECK_THROWS_WITH(j.patch(patch), "key 'baz' not found"); + } + } + + SECTION("move") + { + SECTION("missing 'path'") + { + json j; + json patch = {{{"op", "move"}}}; + CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); + CHECK_THROWS_WITH(j.patch(patch), "operation 'move' must have member 'path'"); + } + + SECTION("non-string 'path'") + { + json j; + json patch = {{{"op", "move"}, {"path", 1}}}; + CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); + CHECK_THROWS_WITH(j.patch(patch), "operation 'move' must have string member 'path'"); + } + + SECTION("missing 'from'") + { + json j; + json patch = {{{"op", "move"}, {"path", ""}}}; + CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); + CHECK_THROWS_WITH(j.patch(patch), "operation 'move' must have member 'from'"); + } + + SECTION("non-string 'from'") + { + json j; + json patch = {{{"op", "move"}, {"path", ""}, {"from", 1}}}; + CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); + CHECK_THROWS_WITH(j.patch(patch), "operation 'move' must have string member 'from'"); + } + + SECTION("nonexisting from location (array)") + { + json j = {1, 2, 3}; + json patch = {{{"op", "move"}, {"path", "/0"}, {"from", "/5"}}}; + CHECK_THROWS_AS(j.patch(patch), std::out_of_range); + CHECK_THROWS_WITH(j.patch(patch), "array index 5 is out of range"); + } + + SECTION("nonexisting from location (object)") + { + json j = {{"foo", 1}, {"bar", 2}}; + json patch = {{{"op", "move"}, {"path", "/baz"}, {"from", "/baz"}}}; + CHECK_THROWS_AS(j.patch(patch), std::out_of_range); + CHECK_THROWS_WITH(j.patch(patch), "key 'baz' not found"); + } + } + + SECTION("copy") + { + SECTION("missing 'path'") + { + json j; + json patch = {{{"op", "copy"}}}; + CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); + CHECK_THROWS_WITH(j.patch(patch), "operation 'copy' must have member 'path'"); + } + + SECTION("non-string 'path'") + { + json j; + json patch = {{{"op", "copy"}, {"path", 1}}}; + CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); + CHECK_THROWS_WITH(j.patch(patch), "operation 'copy' must have string member 'path'"); + } + + SECTION("missing 'from'") + { + json j; + json patch = {{{"op", "copy"}, {"path", ""}}}; + CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); + CHECK_THROWS_WITH(j.patch(patch), "operation 'copy' must have member 'from'"); + } + + SECTION("non-string 'from'") + { + json j; + json patch = {{{"op", "copy"}, {"path", ""}, {"from", 1}}}; + CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); + CHECK_THROWS_WITH(j.patch(patch), "operation 'copy' must have string member 'from'"); + } + + SECTION("nonexisting from location (array)") + { + json j = {1, 2, 3}; + json patch = {{{"op", "copy"}, {"path", "/0"}, {"from", "/5"}}}; + CHECK_THROWS_AS(j.patch(patch), std::out_of_range); + CHECK_THROWS_WITH(j.patch(patch), "array index 5 is out of range"); + } + + SECTION("nonexisting from location (object)") + { + json j = {{"foo", 1}, {"bar", 2}}; + json patch = {{{"op", "copy"}, {"path", "/fob"}, {"from", "/baz"}}}; + CHECK_THROWS_AS(j.patch(patch), std::out_of_range); + CHECK_THROWS_WITH(j.patch(patch), "key 'baz' not found"); + } + } + + SECTION("test") + { + SECTION("missing 'path'") + { + json j; + json patch = {{{"op", "test"}}}; + CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); + CHECK_THROWS_WITH(j.patch(patch), "operation 'test' must have member 'path'"); + } + + SECTION("non-string 'path'") + { + json j; + json patch = {{{"op", "test"}, {"path", 1}}}; + CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); + CHECK_THROWS_WITH(j.patch(patch), "operation 'test' must have string member 'path'"); + } + + SECTION("missing 'value'") + { + json j; + json patch = {{{"op", "test"}, {"path", ""}}}; + CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); + CHECK_THROWS_WITH(j.patch(patch), "operation 'test' must have member 'value'"); + } + } + } + + SECTION("Examples from jsonpatch.com") + { + SECTION("Simple Example") + { + // The original document + json doc = R"( + { + "baz": "qux", + "foo": "bar" + } + )"_json; + + // The patch + json patch = R"( + [ + { "op": "replace", "path": "/baz", "value": "boo" }, + { "op": "add", "path": "/hello", "value": ["world"] }, + { "op": "remove", "path": "/foo"} + ] + )"_json; + + // The result + json result = R"( + { + "baz": "boo", + "hello": ["world"] + } + )"_json; + + // check if patched value is as expected + CHECK(doc.patch(patch) == result); + + // check roundtrip + CHECK(doc.patch(json::diff(doc, result)) == result); + } + + SECTION("Operations") + { + // The original document + json doc = R"( + { + "biscuits": [ + {"name":"Digestive"}, + {"name": "Choco Liebniz"} + ] + } + )"_json; + + SECTION("add") + { + // The patch + json patch = R"( + [ + {"op": "add", "path": "/biscuits/1", "value": {"name": "Ginger Nut"}} + ] + )"_json; + + // The result + json result = R"( + { + "biscuits": [ + {"name": "Digestive"}, + {"name": "Ginger Nut"}, + {"name": "Choco Liebniz"} + ] + } + )"_json; + + // check if patched value is as expected + CHECK(doc.patch(patch) == result); + + // check roundtrip + CHECK(doc.patch(json::diff(doc, result)) == result); + } + + SECTION("remove") + { + // The patch + json patch = R"( + [ + {"op": "remove", "path": "/biscuits"} + ] + )"_json; + + // The result + json result = R"( + {} + )"_json; + + // check if patched value is as expected + CHECK(doc.patch(patch) == result); + + // check roundtrip + CHECK(doc.patch(json::diff(doc, result)) == result); + } + + SECTION("replace") + { + // The patch + json patch = R"( + [ + {"op": "replace", "path": "/biscuits/0/name", "value": "Chocolate Digestive"} + ] + )"_json; + + // The result + json result = R"( + { + "biscuits": [ + {"name": "Chocolate Digestive"}, + {"name": "Choco Liebniz"} + ] + } + )"_json; + + // check if patched value is as expected + CHECK(doc.patch(patch) == result); + + // check roundtrip + CHECK(doc.patch(json::diff(doc, result)) == result); + } + + SECTION("copy") + { + // The patch + json patch = R"( + [ + {"op": "copy", "from": "/biscuits/0", "path": "/best_biscuit"} + ] + )"_json; + + // The result + json result = R"( + { + "biscuits": [ + {"name": "Digestive"}, + {"name": "Choco Liebniz"} + ], + "best_biscuit": { + "name": "Digestive" + } + } + )"_json; + + // check if patched value is as expected + CHECK(doc.patch(patch) == result); + + // check roundtrip + CHECK(doc.patch(json::diff(doc, result)) == result); + } + + SECTION("move") + { + // The patch + json patch = R"( + [ + {"op": "move", "from": "/biscuits", "path": "/cookies"} + ] + )"_json; + + // The result + json result = R"( + { + "cookies": [ + {"name": "Digestive"}, + {"name": "Choco Liebniz"} + ] + } + )"_json; + + // check if patched value is as expected + CHECK(doc.patch(patch) == result); + + // check roundtrip + CHECK(doc.patch(json::diff(doc, result)) == result); + } + + SECTION("test") + { + // The patch + json patch = R"( + [ + {"op": "test", "path": "/best_biscuit/name", "value": "Choco Liebniz"} + ] + )"_json; + + // the test will fail + CHECK_THROWS_AS(doc.patch(patch), std::domain_error); + CHECK_THROWS_WITH(doc.patch(patch), "unsuccessful: " + patch[0].dump()); + } + } + } + + SECTION("Examples from bruth.github.io/jsonpatch-js") + { + SECTION("add") + { + CHECK(R"( {} )"_json.patch( + R"( [{"op": "add", "path": "/foo", "value": "bar"}] )"_json + ) == R"( {"foo": "bar"} )"_json); + + CHECK(R"( {"foo": [1, 3]} )"_json.patch( + R"( [{"op": "add", "path": "/foo", "value": "bar"}] )"_json + ) == R"( {"foo": "bar"} )"_json); + + CHECK(R"( {"foo": [{}]} )"_json.patch( + R"( [{"op": "add", "path": "/foo/0/bar", "value": "baz"}] )"_json + ) == R"( {"foo": [{"bar": "baz"}]} )"_json); + } + + SECTION("remove") + { + CHECK(R"( {"foo": "bar"} )"_json.patch( + R"( [{"op": "remove", "path": "/foo"}] )"_json + ) == R"( {} )"_json); + + CHECK(R"( {"foo": [1, 2, 3]} )"_json.patch( + R"( [{"op": "remove", "path": "/foo/1"}] )"_json + ) == R"( {"foo": [1, 3]} )"_json); + + CHECK(R"( {"foo": [{"bar": "baz"}]} )"_json.patch( + R"( [{"op": "remove", "path": "/foo/0/bar"}] )"_json + ) == R"( {"foo": [{}]} )"_json); + } + + SECTION("replace") + { + CHECK(R"( {"foo": "bar"} )"_json.patch( + R"( [{"op": "replace", "path": "/foo", "value": 1}] )"_json + ) == R"( {"foo": 1} )"_json); + + CHECK(R"( {"foo": [1, 2, 3]} )"_json.patch( + R"( [{"op": "replace", "path": "/foo/1", "value": 4}] )"_json + ) == R"( {"foo": [1, 4, 3]} )"_json); + + CHECK(R"( {"foo": [{"bar": "baz"}]} )"_json.patch( + R"( [{"op": "replace", "path": "/foo/0/bar", "value": 1}] )"_json + ) == R"( {"foo": [{"bar": 1}]} )"_json); + } + + SECTION("move") + { + CHECK(R"( {"foo": [1, 2, 3]} )"_json.patch( + R"( [{"op": "move", "from": "/foo", "path": "/bar"}] )"_json + ) == R"( {"bar": [1, 2, 3]} )"_json); + } + + SECTION("copy") + { + CHECK(R"( {"foo": [1, 2, 3]} )"_json.patch( + R"( [{"op": "copy", "from": "/foo/1", "path": "/bar"}] )"_json + ) == R"( {"foo": [1, 2, 3], "bar": 2} )"_json); + } + + SECTION("copy") + { + CHECK_NOTHROW(R"( {"foo": "bar"} )"_json.patch( + R"( [{"op": "test", "path": "/foo", "value": "bar"}] )"_json)); + } + } +} + TEST_CASE("regression tests") { SECTION("issue #60 - Double quotation mark is not parsed correctly") @@ -12404,8 +14084,69 @@ TEST_CASE("regression tests") //CHECK(j2b.dump() == "23.42"); CHECK(j3a.dump() == "10000"); - CHECK(j3b.dump() == "1E04"); - CHECK(j3c.dump() == "1e04"); + CHECK(j3b.dump() == "10000"); + CHECK(j3c.dump() == "10000"); + //CHECK(j3b.dump() == "1E04"); // roundtrip error + //CHECK(j3c.dump() == "1e04"); // roundtrip error + } + + SECTION("issue #233 - Can't use basic_json::iterator as a base iterator for std::move_iterator") + { + json source = {"a", "b", "c"}; + json expected = {"a", "b"}; + json dest; + + std::copy_n(std::make_move_iterator(source.begin()), 2, std::back_inserter(dest)); + + CHECK(dest == expected); + } + + SECTION("issue #235 - ambiguous overload for 'push_back' and 'operator+='") + { + json data = {{"key", "value"}}; + data.push_back({"key2", "value2"}); + data += {"key3", "value3"}; + + CHECK(data == json({{"key", "value"}, {"key2", "value2"}, {"key3", "value3"}})); + } + + SECTION("issue #269 - diff generates incorrect patch when removing multiple array elements") + { + json doc = R"( { "arr1": [1, 2, 3, 4] } )"_json; + json expected = R"( { "arr1": [1, 2] } )"_json; + + // check roundtrip + CHECK(doc.patch(json::diff(doc, expected)) == expected); } } +// special test case to check if memory is leaked if constructor throws + +template +struct my_allocator : std::allocator +{ + template + void construct(T*, Args&& ...) + { + throw std::bad_alloc(); + } +}; + +TEST_CASE("bad_alloc") +{ + SECTION("bad_alloc") + { + // create JSON type using the throwing allocator + using my_json = nlohmann::basic_json; + + // creating an object should throw + CHECK_THROWS_AS(my_json j(my_json::value_t::object), std::bad_alloc); + } +}