diff --git a/.clang-format b/.clang-format index 2a372fc5b..b7cf99793 100644 --- a/.clang-format +++ b/.clang-format @@ -1,47 +1,4 @@ ---- -# BasedOnStyle: LLVM -AccessModifierOffset: -2 -ConstructorInitializerIndentWidth: 4 -AlignEscapedNewlinesLeft: false -AlignTrailingComments: true -AllowAllParametersOfDeclarationOnNextLine: true -AllowShortIfStatementsOnASingleLine: false -AllowShortLoopsOnASingleLine: false -AlwaysBreakTemplateDeclarations: false -AlwaysBreakBeforeMultilineStrings: false -BreakBeforeBinaryOperators: false -BreakBeforeTernaryOperators: true -BreakConstructorInitializersBeforeComma: false -BinPackParameters: false -ColumnLimit: 80 -ConstructorInitializerAllOnOneLineOrOnePerLine: false -DerivePointerBinding: false -ExperimentalAutoDetectBinPacking: false -IndentCaseLabels: false -MaxEmptyLinesToKeep: 1 -NamespaceIndentation: None -ObjCSpaceBeforeProtocolList: true -PenaltyBreakBeforeFirstCallParameter: 19 -PenaltyBreakComment: 60 -PenaltyBreakString: 1000 -PenaltyBreakFirstLessLess: 120 -PenaltyExcessCharacter: 1000000 -PenaltyReturnTypeOnItsOwnLine: 60 -PointerBindsToType: true +BasedOnStyle: LLVM +DerivePointerAlignment: false +PointerAlignment: Left SpacesBeforeTrailingComments: 1 -Cpp11BracedListStyle: true -Standard: Cpp11 -IndentWidth: 2 -TabWidth: 8 -UseTab: Never -BreakBeforeBraces: Attach -IndentFunctionDeclarationAfterType: false -SpacesInParentheses: false -SpacesInAngles: false -SpaceInEmptyParentheses: false -SpacesInCStyleCastParentheses: false -SpaceAfterControlStatementKeyword: true -SpaceBeforeAssignmentOperators: true -ContinuationIndentWidth: 4 -... - diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 000000000..99e914df9 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,11 @@ +--- +Checks: 'google-readability-casting,modernize-deprecated-headers,modernize-loop-convert,modernize-use-auto,modernize-use-default-member-init,modernize-use-using,readability-else-after-return,readability-redundant-member-init,readability-redundant-string-cstr' +WarningsAsErrors: '' +HeaderFilterRegex: '' +AnalyzeTemporaryDtors: false +FormatStyle: none +CheckOptions: + - key: modernize-use-using.IgnoreMacros + value: '0' +... + diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml new file mode 100644 index 000000000..221f8b839 --- /dev/null +++ b/.github/workflows/clang-format.yml @@ -0,0 +1,20 @@ +name: clang-format check +on: [check_run, pull_request, push] + +jobs: + formatting-check: + name: formatting check + runs-on: ubuntu-latest + strategy: + matrix: + path: + - 'src' + - 'examples' + - 'include' + steps: + - uses: actions/checkout@v4 + - name: runs clang-format style check for C/C++/Protobuf programs. + uses: jidicula/clang-format-action@v4.13.0 + with: + clang-format-version: '18' + check-path: ${{ matrix.path }} diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml new file mode 100644 index 000000000..91f387a50 --- /dev/null +++ b/.github/workflows/cmake.yml @@ -0,0 +1,18 @@ +name: cmake +on: [check_run, push, pull_request] +jobs: + cmake-publish: + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + + steps: + - name: checkout project + uses: actions/checkout@v4 + + - name: build project + uses: threeal/cmake-action@v2.0.0 + diff --git a/.github/workflows/meson.yml b/.github/workflows/meson.yml new file mode 100644 index 000000000..22fe32f72 --- /dev/null +++ b/.github/workflows/meson.yml @@ -0,0 +1,65 @@ +name: meson build and test +run-name: update pushed to ${{ github.ref }} +on: [check_run, push, pull_request] + +jobs: + meson-publish: + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + + steps: + - name: checkout repository + uses: actions/checkout@v4 + + - name: setup python + uses: actions/setup-python@v5 + + - name: meson build + uses: BSFishy/meson-build@v1.0.3 + with: + meson-version: 1.5.1 + ninja-version: 1.11.1.1 + action: build + + - name: meson test + uses: BSFishy/meson-build@v1.0.3 + with: + meson-version: 1.5.1 + ninja-version: 1.11.1.1 + action: test + + meson-coverage: + runs-on: ubuntu-latest + + steps: + - name: checkout repository + uses: actions/checkout@v4 + + - name: setup python + uses: actions/setup-python@v5 + + - name: meson build + uses: BSFishy/meson-build@v1.0.3 + with: + meson-version: 1.5.1 + ninja-version: 1.11.1.1 + setup-options: -Db_coverage=true + action: build + + - name: meson test + uses: BSFishy/meson-build@v1.0.3 + with: + meson-version: 1.5.1 + ninja-version: 1.11.1.1 + setup-options: -Db_coverage=true + action: test + + - name: generate code coverage report + uses: threeal/gcovr-action@v1.0.0 + with: + coveralls-send: true + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 91121c230..69868f413 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ /libs/ /doc/doxyfile /dist/ +/.cache/ # MSVC project files: *.sln @@ -28,9 +29,9 @@ # CMake-generated files: CMakeFiles/ -*.cmake /pkg-config/jsoncpp.pc jsoncpp_lib_static.dir/ +compile_commands.json # In case someone runs cmake in the root-dir: /CMakeCache.txt @@ -51,3 +52,10 @@ jsoncpp_lib_static.dir/ # DS_Store .DS_Store + +# temps +/version + +# Bazel output paths +/bazel-* +/MODULE.bazel.lock diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b649b4633..000000000 --- a/.travis.yml +++ /dev/null @@ -1,65 +0,0 @@ -# Build matrix / environment variables are explained on: -# http://about.travis-ci.org/docs/user/build-configuration/ -# This file can be validated on: http://www.yamllint.com/ -# Or using the Ruby based travel command line tool: -# gem install travis --no-rdoc --no-ri -# travis lint .travis.yml -language: cpp -sudo: false -addons: - homebrew: - packages: - - meson - - ninja - update: false # do not update homebrew by default - apt: - sources: - - ubuntu-toolchain-r-test - - llvm-toolchain-xenial-8 - packages: - - clang-8 - - valgrind -matrix: - allow_failures: - - os: osx - include: - - name: Mac clang meson static release testing - os: osx - osx_image: xcode10.2 - compiler: clang - env: - CXX="clang++" - CC="clang" - LIB_TYPE=static - BUILD_TYPE=release - script: ./.travis_scripts/meson_builder.sh - - name: Linux xenial clang meson static release testing - os: linux - dist: xenial - compiler: clang - env: - CXX="clang++" - CC="clang" - LIB_TYPE=static - BUILD_TYPE=release - # before_install and install steps only needed for linux meson builds - before_install: - - source ./.travis_scripts/travis.before_install.${TRAVIS_OS_NAME}.sh - install: - - source ./.travis_scripts/travis.install.${TRAVIS_OS_NAME}.sh - script: ./.travis_scripts/meson_builder.sh - - name: Linux xenial gcc cmake coverage - os: linux - dist: xenial - compiler: gcc - env: - CXX=g++ - CC=gcc - DO_Coverage=ON - BUILD_TOOL="Unix Makefiles" - BUILD_TYPE=Debug - LIB_TYPE=shared - DESTDIR=/tmp/cmake_json_cpp - script: ./.travis_scripts/cmake_builder.sh -notifications: - email: false diff --git a/.travis_scripts/cmake_builder.sh b/.travis_scripts/cmake_builder.sh deleted file mode 100755 index ccb33312e..000000000 --- a/.travis_scripts/cmake_builder.sh +++ /dev/null @@ -1,130 +0,0 @@ -#!/usr/bin/env sh -# This script can be used on the command line directly to configure several -# different build environments. -# This is called by `.travis.yml` via Travis CI. -# Travis supplies $TRAVIS_OS_NAME. -# http://docs.travis-ci.com/user/multi-os/ -# Our .travis.yml also defines: - -# - BUILD_TYPE=Release/Debug -# - LIB_TYPE=static/shared -# -# Optional environmental variables -# - DESTDIR <- used for setting the install prefix -# - BUILD_TOOL=["Unix Makefile"|"Ninja"] -# - BUILDNAME <- how to identify this build on the dashboard -# - DO_MemCheck <- if set, try to use valgrind -# - DO_Coverage <- if set, try to do dashboard coverage testing -# - -env_set=1 -if ${BUILD_TYPE+false}; then - echo "BUILD_TYPE not set in environment." - env_set=0 -fi -if ${LIB_TYPE+false}; then - echo "LIB_TYPE not set in environment." - env_set=0 -fi -if ${CXX+false}; then - echo "CXX not set in environment." - env_set=0 -fi - - -if [ ${env_set} -eq 0 ]; then - echo "USAGE: CXX=$(which clang++) BUILD_TYPE=[Release|Debug] LIB_TYPE=[static|shared] $0" - echo "" - echo "Examples:" - echo " CXX=$(which clang++) BUILD_TYPE=Release LIB_TYPE=shared DESTDIR=/tmp/cmake_json_cpp $0" - echo " CXX=$(which clang++) BUILD_TYPE=Debug LIB_TYPE=shared DESTDIR=/tmp/cmake_json_cpp $0" - echo " CXX=$(which clang++) BUILD_TYPE=Release LIB_TYPE=static DESTDIR=/tmp/cmake_json_cpp $0" - echo " CXX=$(which clang++) BUILD_TYPE=Debug LIB_TYPE=static DESTDIR=/tmp/cmake_json_cpp $0" - - echo " CXX=$(which g++) BUILD_TYPE=Release LIB_TYPE=shared DESTDIR=/tmp/cmake_json_cpp $0" - echo " CXX=$(which g++) BUILD_TYPE=Debug LIB_TYPE=shared DESTDIR=/tmp/cmake_json_cpp $0" - echo " CXX=$(which g++) BUILD_TYPE=Release LIB_TYPE=static DESTDIR=/tmp/cmake_json_cpp $0" - echo " CXX=$(which g++) BUILD_TYPE=Debug LIB_TYPE=static DESTDIR=/tmp/cmake_json_cpp $0" - - exit -1 -fi - -if ${DESTDIR+false}; then - DESTDIR="/usr/local" -fi - -# -e: fail on error -# -v: show commands -# -x: show expanded commands -set -vex - -env | sort - -which cmake -cmake --version - -echo ${CXX} -${CXX} --version -_COMPILER_NAME=`basename ${CXX}` -if [ "${BUILD_TYPE}" == "shared" ]; then - _CMAKE_BUILD_SHARED_LIBS=ON -else - _CMAKE_BUILD_SHARED_LIBS=OFF -fi - -CTEST_TESTING_OPTION="-D ExperimentalTest" -# - DO_MemCheck <- if set, try to use valgrind -if ! ${DO_MemCheck+false}; then - valgrind --version - CTEST_TESTING_OPTION="-D ExperimentalMemCheck" -else -# - DO_Coverage <- if set, try to do dashboard coverage testing - if ! ${DO_Coverage+false}; then - export CXXFLAGS="-fprofile-arcs -ftest-coverage" - export LDFLAGS="-fprofile-arcs -ftest-coverage" - CTEST_TESTING_OPTION="-D ExperimentalTest -D ExperimentalCoverage" - #gcov --version - fi -fi - -# Ninja = Generates build.ninja files. -if ${BUILD_TOOL+false}; then - BUILD_TOOL="Ninja" - export _BUILD_EXE=ninja - which ninja - ninja --version -else -# Unix Makefiles = Generates standard UNIX makefiles. - export _BUILD_EXE=make -fi - -_BUILD_DIR_NAME="build-cmake_${BUILD_TYPE}_${LIB_TYPE}_${_COMPILER_NAME}_${_BUILD_EXE}" -mkdir -p ${_BUILD_DIR_NAME} -cd "${_BUILD_DIR_NAME}" - if ${BUILDNAME+false}; then - _HOSTNAME=`hostname -s` - BUILDNAME="${_HOSTNAME}_${BUILD_TYPE}_${LIB_TYPE}_${_COMPILER_NAME}_${_BUILD_EXE}" - fi - cmake \ - -G "${BUILD_TOOL}" \ - -DBUILDNAME:STRING="${BUILDNAME}" \ - -DCMAKE_CXX_COMPILER:PATH=${CXX} \ - -DCMAKE_BUILD_TYPE:STRING=${BUILD_TYPE} \ - -DBUILD_SHARED_LIBS:BOOL=${_CMAKE_BUILD_SHARED_LIBS} \ - -DCMAKE_INSTALL_PREFIX:PATH=${DESTDIR} \ - ../ - - ctest -C ${BUILD_TYPE} -D ExperimentalStart -D ExperimentalConfigure -D ExperimentalBuild ${CTEST_TESTING_OPTION} -D ExperimentalSubmit - # Final step is to verify that installation succeeds - cmake --build . --config ${BUILD_TYPE} --target install - - if [ "${DESTDIR}" != "/usr/local" ]; then - ${_BUILD_EXE} install - fi -cd - - -if ${CLEANUP+false}; then - echo "Skipping cleanup: build directory will persist." -else - rm -r "${_BUILD_DIR_NAME}" -fi diff --git a/.travis_scripts/meson_builder.sh b/.travis_scripts/meson_builder.sh deleted file mode 100755 index dbf03cb2f..000000000 --- a/.travis_scripts/meson_builder.sh +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env sh -# This script can be used on the command line directly to configure several -# different build environments. -# This is called by `.travis.yml` via Travis CI. -# Travis supplies $TRAVIS_OS_NAME. -# http://docs.travis-ci.com/user/multi-os/ -# Our .travis.yml also defines: - -# - BUILD_TYPE=release/debug -# - LIB_TYPE=static/shared - -env_set=1 -if ${BUILD_TYPE+false}; then - echo "BUILD_TYPE not set in environment." - env_set=0 -fi -if ${LIB_TYPE+false}; then - echo "LIB_TYPE not set in environment." - env_set=0 -fi -if ${CXX+false}; then - echo "CXX not set in environment." - env_set=0 -fi - - -if [ ${env_set} -eq 0 ]; then - echo "USAGE: CXX=$(which clang++) BUILD_TYPE=[release|debug] LIB_TYPE=[static|shared] $0" - echo "" - echo "Examples:" - echo " CXX=$(which clang++) BUILD_TYPE=release LIB_TYPE=shared DESTDIR=/tmp/meson_json_cpp $0" - echo " CXX=$(which clang++) BUILD_TYPE=debug LIB_TYPE=shared DESTDIR=/tmp/meson_json_cpp $0" - echo " CXX=$(which clang++) BUILD_TYPE=release LIB_TYPE=static DESTDIR=/tmp/meson_json_cpp $0" - echo " CXX=$(which clang++) BUILD_TYPE=debug LIB_TYPE=static DESTDIR=/tmp/meson_json_cpp $0" - - echo " CXX=$(which g++) BUILD_TYPE=release LIB_TYPE=shared DESTDIR=/tmp/meson_json_cpp $0" - echo " CXX=$(which g++) BUILD_TYPE=debug LIB_TYPE=shared DESTDIR=/tmp/meson_json_cpp $0" - echo " CXX=$(which g++) BUILD_TYPE=release LIB_TYPE=static DESTDIR=/tmp/meson_json_cpp $0" - echo " CXX=$(which g++) BUILD_TYPE=debug LIB_TYPE=static DESTDIR=/tmp/meson_json_cpp $0" - - exit -1 -fi - -if ${DESTDIR+false}; then - DESTDIR="/usr/local" -fi - -# -e: fail on error -# -v: show commands -# -x: show expanded commands -set -vex - - -env | sort - -which python3 -which meson -which ninja -echo ${CXX} -${CXX} --version -python3 --version -meson --version -ninja --version -_COMPILER_NAME=`basename ${CXX}` -_BUILD_DIR_NAME="build-${BUILD_TYPE}_${LIB_TYPE}_${_COMPILER_NAME}" -meson --buildtype ${BUILD_TYPE} --default-library ${LIB_TYPE} . "${_BUILD_DIR_NAME}" -ninja -v -j 2 -C "${_BUILD_DIR_NAME}" -#ninja -v -j 2 -C "${_BUILD_DIR_NAME}" test -cd "${_BUILD_DIR_NAME}" - meson test --no-rebuild --print-errorlogs - - if [ "${DESTDIR}" != "/usr/local" ]; then - ninja install - fi -cd - - -if ${CLEANUP+false}; then - echo "Skipping cleanup: build directory will persist." -else - rm -r "${_BUILD_DIR_NAME}" -fi diff --git a/.travis_scripts/travis.before_install.linux.sh b/.travis_scripts/travis.before_install.linux.sh deleted file mode 100644 index 9b556de15..000000000 --- a/.travis_scripts/travis.before_install.linux.sh +++ /dev/null @@ -1,8 +0,0 @@ -set -vex - -# Preinstalled versions of python are dependent on which Ubuntu distribution -# you are running. The below version needs to be updated whenever we roll -# the Ubuntu version used in Travis. -# https://docs.travis-ci.com/user/languages/python/ - -pyenv global 3.7.1 diff --git a/.travis_scripts/travis.before_install.osx.sh b/.travis_scripts/travis.before_install.osx.sh deleted file mode 100644 index 5d83c0c71..000000000 --- a/.travis_scripts/travis.before_install.osx.sh +++ /dev/null @@ -1 +0,0 @@ -# NOTHING TO DO HERE diff --git a/.travis_scripts/travis.install.linux.sh b/.travis_scripts/travis.install.linux.sh deleted file mode 100644 index 7c5846f1a..000000000 --- a/.travis_scripts/travis.install.linux.sh +++ /dev/null @@ -1,10 +0,0 @@ -set -vex - -wget https://github.com/ninja-build/ninja/releases/download/v1.9.0/ninja-linux.zip -unzip -q ninja-linux.zip -d build - -pip3 install meson -echo ${PATH} -ls /usr/local -ls /usr/local/bin -export PATH="${PWD}"/build:/usr/local/bin:/usr/bin:${PATH} diff --git a/.travis_scripts/travis.install.osx.sh b/.travis_scripts/travis.install.osx.sh deleted file mode 100644 index 5d83c0c71..000000000 --- a/.travis_scripts/travis.install.osx.sh +++ /dev/null @@ -1 +0,0 @@ -# NOTHING TO DO HERE diff --git a/AUTHORS b/AUTHORS index 3723d546a..7a3def276 100644 --- a/AUTHORS +++ b/AUTHORS @@ -16,11 +16,12 @@ Baruch Siach Ben Boeckel Benjamin Knecht Bernd Kuhls -Billy Donahue +Billy Donahue Braden McDorman Brandon Myers Brendan Drew chason +chenguoping Chris Gilling Christopher Dawes Christopher Dunn @@ -97,6 +98,7 @@ selaselah Sergiy80 sergzub Stefan Schweter +Stefano Fiorentino Steffen Kieß Steven Hahn Stuart Eichert diff --git a/BUILD.bazel b/BUILD.bazel new file mode 100644 index 000000000..6d7ac3da9 --- /dev/null +++ b/BUILD.bazel @@ -0,0 +1,37 @@ +licenses(["unencumbered"]) # Public Domain or MIT + +exports_files(["LICENSE"]) + +cc_library( + name = "jsoncpp", + srcs = [ + "src/lib_json/json_reader.cpp", + "src/lib_json/json_tool.h", + "src/lib_json/json_value.cpp", + "src/lib_json/json_writer.cpp", + ], + hdrs = [ + "include/json/allocator.h", + "include/json/assertions.h", + "include/json/config.h", + "include/json/json_features.h", + "include/json/forwards.h", + "include/json/json.h", + "include/json/reader.h", + "include/json/value.h", + "include/json/version.h", + "include/json/writer.h", + ], + copts = [ + "-DJSON_USE_EXCEPTION=0", + "-DJSON_HAS_INT64", + ], + includes = ["include"], + visibility = ["//visibility:public"], + deps = [":private"], +) + +cc_library( + name = "private", + textual_hdrs = ["src/lib_json/json_valueiterator.inl"], +) diff --git a/CMakeLists.txt b/CMakeLists.txt index b4eb83039..5ab9c52a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ # policies that provide successful builds. By setting JSONCPP_NEWEST_VALIDATED_POLICIES_VERSION # to a value greater than the oldest policies, all policies between # JSONCPP_OLDEST_VALIDATED_POLICIES_VERSION and CMAKE_VERSION (used for this build) -# are set to their NEW behaivor, thereby suppressing policy warnings related to policies +# are set to their NEW behavior, thereby suppressing policy warnings related to policies # between the JSONCPP_OLDEST_VALIDATED_POLICIES_VERSION and CMAKE_VERSION. # # CMake versions greater than the JSONCPP_NEWEST_VALIDATED_POLICIES_VERSION policies will @@ -22,6 +22,9 @@ else() set(JSONCPP_CMAKE_POLICY_VERSION "${JSONCPP_NEWEST_VALIDATED_POLICIES_VERSION}") endif() cmake_policy(VERSION ${JSONCPP_CMAKE_POLICY_VERSION}) +if(POLICY CMP0091) + cmake_policy(SET CMP0091 NEW) +endif() # # Now enumerate specific policies newer than JSONCPP_NEWEST_VALIDATED_POLICIES_VERSION # that may need to be individually set to NEW/OLD @@ -37,44 +40,37 @@ foreach(pold "") # Currently Empty endif() endforeach() -# ==== Define language standard configurations requiring at least c++11 standard -if(CMAKE_CXX_STANDARD EQUAL "98" ) - message(FATAL_ERROR "CMAKE_CXX_STANDARD:STRING=98 is not supported.") -endif() - -##### -## Set the default target properties -if(NOT CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 11) # Supported values are ``11``, ``14``, and ``17``. -endif() -if(NOT CMAKE_CXX_STANDARD_REQUIRED) - set(CMAKE_CXX_STANDARD_REQUIRED ON) -endif() -if(NOT CMAKE_CXX_EXTENSIONS) - set(CMAKE_CXX_EXTENSIONS OFF) -endif() - -# ==== +# Build the library with C++11 standard support, independent from other including +# software which may use a different CXX_STANDARD or CMAKE_CXX_STANDARD. +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_CXX_STANDARD_REQUIRED ON) -# Ensures that CMAKE_BUILD_TYPE has a default value -if(NOT DEFINED CMAKE_BUILD_TYPE) +# Ensure that CMAKE_BUILD_TYPE has a value specified for single configuration generators. +if(NOT DEFINED CMAKE_BUILD_TYPE AND NOT DEFINED CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel Coverage.") endif() -project(JSONCPP - # Note: version must be updated in three places when doing a release. This +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +project(jsoncpp + # Note: version must be updated in four places when doing a release. This # annoying process ensures that amalgamate, CMake, and meson all report the # correct version. - # 1. /meson.build - # 2. /include/json/version.h - # 3. /CMakeLists.txt - # IMPORTANT: also update the SOVERSION!! - VERSION 1.9.2 # [.[.[.]]] + # 1. ./meson.build + # 2. ./include/json/version.h + # 3. ./CMakeLists.txt + # 4. ./MODULE.bazel + # IMPORTANT: also update the PROJECT_SOVERSION!! + VERSION 1.9.7 # [.[.[.]]] LANGUAGES CXX) -message(STATUS "JsonCpp Version: ${JSONCPP_VERSION_MAJOR}.${JSONCPP_VERSION_MINOR}.${JSONCPP_VERSION_PATCH}") -set( JSONCPP_SOVERSION 22 ) +message(STATUS "JsonCpp Version: ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") +set(PROJECT_SOVERSION 27) + +include(${CMAKE_CURRENT_SOURCE_DIR}/include/PreventInSourceBuilds.cmake) +include(${CMAKE_CURRENT_SOURCE_DIR}/include/PreventInBuildInstalls.cmake) option(JSONCPP_WITH_TESTS "Compile and (for jsoncpp_check) run JsonCpp test executables" ON) option(JSONCPP_WITH_POST_BUILD_UNITTEST "Automatically run unit-tests as a post build step" ON) @@ -82,25 +78,37 @@ option(JSONCPP_WITH_WARNING_AS_ERROR "Force compilation to fail if a warning occ option(JSONCPP_WITH_STRICT_ISO "Issue all the warnings demanded by strict ISO C and ISO C++" ON) option(JSONCPP_WITH_PKGCONFIG_SUPPORT "Generate and install .pc files" ON) option(JSONCPP_WITH_CMAKE_PACKAGE "Generate and install cmake package files" ON) -option(BUILD_SHARED_LIBS "Build jsoncpp_lib as a shared library." OFF) - -# Enable runtime search path support for dynamic libraries on OSX -if(APPLE) - set(CMAKE_MACOSX_RPATH 1) -endif() +option(JSONCPP_WITH_EXAMPLE "Compile JsonCpp example" OFF) +option(JSONCPP_STATIC_WINDOWS_RUNTIME "Use static (MT/MTd) Windows runtime" OFF) +option(BUILD_SHARED_LIBS "Build jsoncpp_lib as a shared library." ON) +option(BUILD_STATIC_LIBS "Build jsoncpp_lib as a static library." ON) +option(BUILD_OBJECT_LIBS "Build jsoncpp_lib as a object library." ON) # Adhere to GNU filesystem layout conventions include(GNUInstallDirs) -set(DEBUG_LIBNAME_SUFFIX "" CACHE STRING "Optional suffix to append to the library name for a debug build") +if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" CACHE PATH "Archive output dir.") + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" CACHE PATH "Library output dir.") + set(CMAKE_PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" CACHE PATH "PDB (MSVC debug symbol)output dir.") + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" CACHE PATH "Executable/dll output dir.") +endif() + +include(CheckFunctionExists) +check_function_exists(memset_s HAVE_MEMSET_S) +if(HAVE_MEMSET_S) + add_definitions("-DHAVE_MEMSET_S=1") +endif() -set(JSONCPP_USE_SECURE_MEMORY "0" CACHE STRING "-D...=1 to use memory-wiping allocator for STL" ) +if(JSONCPP_USE_SECURE_MEMORY) + add_definitions("-DJSONCPP_USE_SECURE_MEMORY=1") +endif() -configure_file( "${PROJECT_SOURCE_DIR}/version.in" - "${PROJECT_BINARY_DIR}/version" - NEWLINE_STYLE UNIX ) +configure_file("${PROJECT_SOURCE_DIR}/version.in" + "${PROJECT_BINARY_DIR}/version" + NEWLINE_STYLE UNIX) -macro(UseCompilationWarningAsError) +macro(use_compilation_warning_as_error) if(MSVC) # Only enabled in debug because some old versions of VS STL generate # warnings when compiled in release configuration. @@ -114,48 +122,56 @@ macro(UseCompilationWarningAsError) endmacro() # Include our configuration header -include_directories( ${jsoncpp_SOURCE_DIR}/include ) +include_directories(${jsoncpp_SOURCE_DIR}/include) if(MSVC) # Only enabled in debug because some old versions of VS STL generate # unreachable code warning when compiled in release configuration. add_compile_options($<$:/W4>) + if (JSONCPP_STATIC_WINDOWS_RUNTIME) + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + endif() endif() if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") # using regular Clang or AppleClang - add_compile_options(-Wall -Wconversion -Wshadow -Werror=conversion -Werror=sign-compare) + add_compile_options(-Wall -Wconversion -Wshadow) + + if(JSONCPP_WITH_WARNING_AS_ERROR) + add_compile_options(-Werror=conversion -Werror=sign-compare) + endif() elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") # using GCC add_compile_options(-Wall -Wconversion -Wshadow -Wextra) # not yet ready for -Wsign-conversion if(JSONCPP_WITH_STRICT_ISO) - add_compile_options(-pedantic) + add_compile_options(-Wpedantic) endif() if(JSONCPP_WITH_WARNING_AS_ERROR) add_compile_options(-Werror=conversion) endif() elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Intel") # using Intel compiler - add_compile_options(-Wall -Wconversion -Wshadow -Wextra -Werror=conversion) + add_compile_options(-Wall -Wconversion -Wshadow -Wextra) - if(JSONCPP_WITH_STRICT_ISO AND NOT JSONCPP_WITH_WARNING_AS_ERROR) - add_compile_options(-pedantic) + if(JSONCPP_WITH_WARNING_AS_ERROR) + add_compile_options(-Werror=conversion) + elseif(JSONCPP_WITH_STRICT_ISO) + add_compile_options(-Wpedantic) endif() endif() -find_program(CCACHE_FOUND ccache) -if(CCACHE_FOUND) - set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) - set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) -endif(CCACHE_FOUND) - if(JSONCPP_WITH_WARNING_AS_ERROR) - UseCompilationWarningAsError() + use_compilation_warning_as_error() endif() if(JSONCPP_WITH_PKGCONFIG_SUPPORT) + include(JoinPaths) + + join_paths(libdir_for_pc_file "\${exec_prefix}" "${CMAKE_INSTALL_LIBDIR}") + join_paths(includedir_for_pc_file "\${prefix}" "${CMAKE_INSTALL_INCLUDEDIR}") + configure_file( "pkg-config/jsoncpp.pc.in" "pkg-config/jsoncpp.pc" @@ -165,25 +181,34 @@ if(JSONCPP_WITH_PKGCONFIG_SUPPORT) endif() if(JSONCPP_WITH_CMAKE_PACKAGE) - include (CMakePackageConfigHelpers) - install(EXPORT jsoncpp - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/jsoncpp - FILE jsoncppConfig.cmake) - write_basic_package_version_file ("${CMAKE_CURRENT_BINARY_DIR}/jsoncppConfigVersion.cmake" - VERSION ${PROJECT_VERSION} - COMPATIBILITY SameMajorVersion) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/jsoncppConfigVersion.cmake - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/jsoncpp) + include(CMakePackageConfigHelpers) + install(EXPORT jsoncpp + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/jsoncpp + FILE jsoncpp-targets.cmake) + configure_package_config_file(jsoncppConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/jsoncppConfig.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/jsoncpp) + + write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/jsoncppConfigVersion.cmake" + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion) + install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/jsoncppConfigVersion.cmake ${CMAKE_CURRENT_BINARY_DIR}/jsoncppConfig.cmake + ${CMAKE_CURRENT_SOURCE_DIR}/jsoncpp-namespaced-targets.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/jsoncpp) endif() if(JSONCPP_WITH_TESTS) - enable_testing() - include(CTest) + enable_testing() + include(CTest) endif() # Build the different applications -add_subdirectory( src ) +add_subdirectory(src) #install the includes -add_subdirectory( include ) +add_subdirectory(include) +#install the example +if(JSONCPP_WITH_EXAMPLE) + add_subdirectory(example) +endif() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3e14b2b31..5f5c032a8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,7 @@ An example of a common Meson/Ninja environment is described next. Thanks to David Seifert (@SoapGentoo), we (the maintainers) now use [meson](http://mesonbuild.com/) and [ninja](https://ninja-build.org/) to build for debugging, as well as for continuous integration (see -[`./travis_scripts/meson_builder.sh`](./travis_scripts/meson_builder.sh) ). Other systems may work, but minor +[`./.travis_scripts/meson_builder.sh`](./.travis_scripts/meson_builder.sh) ). Other systems may work, but minor things like version strings might break. First, install both meson (which requires Python3) and ninja. @@ -19,7 +19,7 @@ If you wish to install to a directory other than /usr/local, set an environment DESTDIR=/path/to/install/dir Then, - +```sh cd jsoncpp/ BUILD_TYPE=debug #BUILD_TYPE=release @@ -27,9 +27,15 @@ Then, #LIB_TYPE=static meson --buildtype ${BUILD_TYPE} --default-library ${LIB_TYPE} . build-${LIB_TYPE} ninja -v -C build-${LIB_TYPE} - cd build-${LIB_TYPE} - meson test --no-rebuild --print-errorlogs + + ninja -C build-static/ test + + # Or + #cd build-${LIB_TYPE} + #meson test --no-rebuild --print-errorlogs + sudo ninja install +``` ## Building and testing with other build systems See https://github.com/open-source-parsers/jsoncpp/wiki/Building @@ -71,7 +77,7 @@ See `doxybuild.py --help` for options. To add a test, you need to create two files in test/data: * a `TESTNAME.json` file, that contains the input document in JSON format. -* a `TESTNAME.expected` file, that contains a flatened representation of the +* a `TESTNAME.expected` file, that contains a flattened representation of the input document. The `TESTNAME.expected` file format is as follows: @@ -138,7 +144,9 @@ bool Reader::decodeNumber(Token& token) { ``` Before submitting your code, ensure that you meet the versioning requirements above, follow the style guide of the file you are modifying (or the above rules for new files), and run clang format. Meson exposes clang format with the following command: - ``` ninja -v -C build-${LIB_TYPE}/ clang-format ``` + +For convenience, you can also run the `reformat.sh` script located in the root directory. + diff --git a/LICENSE b/LICENSE index 89280a6c4..c41a1d1c7 100644 --- a/LICENSE +++ b/LICENSE @@ -1,25 +1,25 @@ -The JsonCpp library's source code, including accompanying documentation, +The JsonCpp library's source code, including accompanying documentation, tests and demonstration applications, are licensed under the following conditions... -Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all -jurisdictions which recognize such a disclaimer. In such jurisdictions, +Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all +jurisdictions which recognize such a disclaimer. In such jurisdictions, this software is released into the Public Domain. In jurisdictions which do not recognize Public Domain property (e.g. Germany as of 2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur and The JsonCpp Authors, and is released under the terms of the MIT License (see below). -In jurisdictions which recognize Public Domain property, the user of this -software may choose to accept it either as 1) Public Domain, 2) under the -conditions of the MIT License (see below), or 3) under the terms of dual +In jurisdictions which recognize Public Domain property, the user of this +software may choose to accept it either as 1) Public Domain, 2) under the +conditions of the MIT License (see below), or 3) under the terms of dual Public Domain/MIT License conditions described here, as they choose. The MIT License is about as close to Public Domain as a license can get, and is described in clear, concise terms at: http://en.wikipedia.org/wiki/MIT_License - + The full text of the MIT License follows: ======================================================================== diff --git a/MODULE.bazel b/MODULE.bazel new file mode 100644 index 000000000..03f192dd4 --- /dev/null +++ b/MODULE.bazel @@ -0,0 +1,14 @@ +module( + name = "jsoncpp", + + # Note: version must be updated in four places when doing a release. This + # annoying process ensures that amalgamate, CMake, and meson all report the + # correct version. + # 1. /meson.build + # 2. /include/json/version.h + # 3. /CMakeLists.txt + # 4. /MODULE.bazel + # IMPORTANT: also update the SOVERSION!! + version = "1.9.7", + compatibility_level = 1, +) diff --git a/README.md b/README.md index 8c8587060..5bff8dca2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ # JsonCpp [![badge](https://img.shields.io/badge/conan.io-jsoncpp%2F1.8.0-green.svg?logo=data:image/png;base64%2CiVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAMAAAAolt3jAAAA1VBMVEUAAABhlctjlstkl8tlmMtlmMxlmcxmmcxnmsxpnMxpnM1qnc1sn85voM91oM11oc1xotB2oc56pNF6pNJ2ptJ8ptJ8ptN9ptN8p9N5qNJ9p9N9p9R8qtOBqdSAqtOAqtR%2BrNSCrNJ/rdWDrNWCsNWCsNaJs9eLs9iRvNuVvdyVv9yXwd2Zwt6axN6dxt%2Bfx%2BChyeGiyuGjyuCjyuGly%2BGlzOKmzOGozuKoz%2BKqz%2BOq0OOv1OWw1OWw1eWx1eWy1uay1%2Baz1%2Baz1%2Bez2Oe02Oe12ee22ujUGwH3AAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfgBQkREyOxFIh/AAAAiklEQVQI12NgAAMbOwY4sLZ2NtQ1coVKWNvoc/Eq8XDr2wB5Ig62ekza9vaOqpK2TpoMzOxaFtwqZua2Bm4makIM7OzMAjoaCqYuxooSUqJALjs7o4yVpbowvzSUy87KqSwmxQfnsrPISyFzWeWAXCkpMaBVIC4bmCsOdgiUKwh3JojLgAQ4ZCE0AMm2D29tZwe6AAAAAElFTkSuQmCC)](https://bintray.com/theirix/conan-repo/jsoncpp%3Atheirix) +[![badge](https://img.shields.io/badge/license-MIT-blue)](https://github.com/open-source-parsers/jsoncpp/blob/master/LICENSE) +[![badge](https://img.shields.io/badge/document-doxygen-brightgreen)](http://open-source-parsers.github.io/jsoncpp-docs/doxygen/index.html) +[![Coverage Status](https://coveralls.io/repos/github/open-source-parsers/jsoncpp/badge.svg?branch=master)](https://coveralls.io/github/open-source-parsers/jsoncpp?branch=master) + [JSON][json-org] is a lightweight data-interchange format. It can represent numbers, strings, ordered sequences of values, and collections of name/value @@ -26,19 +30,36 @@ format to store user input files. * `1.y.z` is built with C++11. * `0.y.z` can be used with older compilers. +* `00.11.z` can be used both in old and new compilers. * Major versions maintain binary-compatibility. +### Special note +The branch `00.11.z`is a new branch, its major version number `00` is to show that it is +different from `0.y.z` and `1.y.z`, the main purpose of this branch is to make a balance +between the other two branches. Thus, users can use some new features in this new branch +that introduced in 1.y.z, but can hardly applied into 0.y.z. ## Using JsonCpp in your project +### The vcpkg dependency manager +You can download and install JsonCpp using the [vcpkg](https://github.com/Microsoft/vcpkg/) dependency manager: + + git clone https://github.com/Microsoft/vcpkg.git + cd vcpkg + ./bootstrap-vcpkg.sh + ./vcpkg integrate install + ./vcpkg install jsoncpp + +The JsonCpp port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository. + ### Amalgamated source -https://github.com/open-source-parsers/jsoncpp/wiki/Amalgamated +https://github.com/open-source-parsers/jsoncpp/wiki/Amalgamated-(Possibly-outdated) ### The Meson Build System If you are using the [Meson Build System](http://mesonbuild.com), then you can get a wrap file by downloading it from [Meson WrapDB](https://wrapdb.mesonbuild.com/jsoncpp), or simply use `meson wrap install jsoncpp`. ### Other ways -If you have trouble, see the Wiki, or post a question as an Issue. +If you have trouble, see the [Wiki](https://github.com/open-source-parsers/jsoncpp/wiki), or post a question as an Issue. ## License diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..67af8830b --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,17 @@ +# Security Policy + +If you have discovered a security vulnerability in this project, please report it +privately. **Do not disclose it as a public issue.** This gives us time to work with you +to fix the issue before public exposure, reducing the chance that the exploit will be +used before a patch is released. + +Please submit the report by filling out +[this form](https://github.com/open-source-parsers/jsoncpp/security/advisories/new). + +Please provide the following information in your report: + +- A description of the vulnerability and its impact +- How to reproduce the issue + +This project is maintained by volunteers on a reasonable-effort basis. As such, +we ask that you give us 90 days to work on a fix before public exposure. diff --git a/amalgamate.py b/amalgamate.py index 7b41e2e5b..1d1e48810 100755 --- a/amalgamate.py +++ b/amalgamate.py @@ -63,7 +63,7 @@ def amalgamate_source(source_top_dir=None, """ print("Amalgamating header...") header = AmalgamationFile(source_top_dir) - header.add_text("/// Json-cpp amalgamated header (http://jsoncpp.sourceforge.net/).") + header.add_text("/// Json-cpp amalgamated header (https://github.com/open-source-parsers/jsoncpp/).") header.add_text('/// It is intended to be used with #include "%s"' % header_include_path) header.add_file("LICENSE", wrap_in_comment=True) header.add_text("#ifndef JSON_AMALGAMATED_H_INCLUDED") @@ -75,7 +75,7 @@ def amalgamate_source(source_top_dir=None, header.add_file(os.path.join(INCLUDE_PATH, "allocator.h")) header.add_file(os.path.join(INCLUDE_PATH, "config.h")) header.add_file(os.path.join(INCLUDE_PATH, "forwards.h")) - header.add_file(os.path.join(INCLUDE_PATH, "features.h")) + header.add_file(os.path.join(INCLUDE_PATH, "json_features.h")) header.add_file(os.path.join(INCLUDE_PATH, "value.h")) header.add_file(os.path.join(INCLUDE_PATH, "reader.h")) header.add_file(os.path.join(INCLUDE_PATH, "writer.h")) @@ -90,7 +90,7 @@ def amalgamate_source(source_top_dir=None, forward_header_include_path = base + "-forwards" + ext print("Amalgamating forward header...") header = AmalgamationFile(source_top_dir) - header.add_text("/// Json-cpp amalgamated forward header (http://jsoncpp.sourceforge.net/).") + header.add_text("/// Json-cpp amalgamated forward header (https://github.com/open-source-parsers/jsoncpp/).") header.add_text('/// It is intended to be used with #include "%s"' % forward_header_include_path) header.add_text("/// This header provides forward declaration for all JsonCpp types.") header.add_file("LICENSE", wrap_in_comment=True) @@ -99,6 +99,8 @@ def amalgamate_source(source_top_dir=None, header.add_text("/// If defined, indicates that the source file is amalgamated") header.add_text("/// to prevent private header inclusion.") header.add_text("#define JSON_IS_AMALGAMATION") + header.add_file(os.path.join(INCLUDE_PATH, "version.h")) + header.add_file(os.path.join(INCLUDE_PATH, "allocator.h")) header.add_file(os.path.join(INCLUDE_PATH, "config.h")) header.add_file(os.path.join(INCLUDE_PATH, "forwards.h")) header.add_text("#endif //ifndef JSON_FORWARD_AMALGAMATED_H_INCLUDED") @@ -110,7 +112,7 @@ def amalgamate_source(source_top_dir=None, print("Amalgamating source...") source = AmalgamationFile(source_top_dir) - source.add_text("/// Json-cpp amalgamated source (http://jsoncpp.sourceforge.net/).") + source.add_text("/// Json-cpp amalgamated source (https://github.com/open-source-parsers/jsoncpp/).") source.add_text('/// It is intended to be used with #include "%s"' % header_include_path) source.add_file("LICENSE", wrap_in_comment=True) source.add_text("") diff --git a/appveyor.yml b/appveyor.yml index 0b9c8fe3f..cccce4298 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,7 @@ clone_folder: c:\projects\jsoncpp environment: + matrix: - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 CMAKE_GENERATOR: Visual Studio 14 2015 @@ -13,11 +14,15 @@ environment: build_script: - cmake --version - - cd c:\projects\jsoncpp - - cmake -G "%CMAKE_GENERATOR%" -DCMAKE_INSTALL_PREFIX:PATH=%CD:\=/%/install -DBUILD_SHARED_LIBS:BOOL=ON . + # The build script starts in root. + - set JSONCPP_FOLDER=%cd% + - set JSONCPP_BUILD_FOLDER=%JSONCPP_FOLDER%\build\release + - mkdir -p %JSONCPP_BUILD_FOLDER% + - cd %JSONCPP_BUILD_FOLDER% + - cmake -G "%CMAKE_GENERATOR%" -DCMAKE_INSTALL_PREFIX:PATH=%CD:\=/%/install -DBUILD_SHARED_LIBS:BOOL=ON %JSONCPP_FOLDER% # Use ctest to make a dashboard build: # - ctest -D Experimental(Start|Update|Configure|Build|Test|Coverage|MemCheck|Submit) - # NOTE: Testing on window is not yet finished: + # NOTE: Testing on windows is not yet finished: # - ctest -C Release -D ExperimentalStart -D ExperimentalConfigure -D ExperimentalBuild -D ExperimentalTest -D ExperimentalSubmit - ctest -C Release -D ExperimentalStart -D ExperimentalConfigure -D ExperimentalBuild -D ExperimentalSubmit # Final step is to verify that installation succeeds diff --git a/cmake/JoinPaths.cmake b/cmake/JoinPaths.cmake new file mode 100644 index 000000000..2b376b733 --- /dev/null +++ b/cmake/JoinPaths.cmake @@ -0,0 +1,23 @@ +# This module provides a function for joining paths +# known from most languages +# +# SPDX-License-Identifier: (MIT OR CC0-1.0) +# Copyright 2020 Jan Tojnar +# https://github.com/jtojnar/cmake-snips +# +# Modelled after Python’s os.path.join +# https://docs.python.org/3.7/library/os.path.html#os.path.join +# Windows not supported +function(join_paths joined_path first_path_segment) + set(temp_path "${first_path_segment}") + foreach(current_segment IN LISTS ARGN) + if(NOT ("${current_segment}" STREQUAL "")) + if(IS_ABSOLUTE "${current_segment}") + set(temp_path "${current_segment}") + else() + set(temp_path "${temp_path}/${current_segment}") + endif() + endif() + endforeach() + set(${joined_path} "${temp_path}" PARENT_SCOPE) +endfunction() diff --git a/dev.makefile b/dev.makefile index 1a4be6a91..545ff2730 100644 --- a/dev.makefile +++ b/dev.makefile @@ -1,9 +1,11 @@ # This is only for jsoncpp developers/contributors. # We use this to sign releases, generate documentation, etc. -VER?=$(shell cat version.txt) +VER?=$(shell cat version) default: @echo "VER=${VER}" +update-version: + perl get_version.pl meson.build >| version sign: jsoncpp-${VER}.tar.gz gpg --armor --detach-sign $< gpg --verify $<.asc diff --git a/devtools/antglob.py b/devtools/antglob.py index 98437658c..bd2d7aee9 100644 --- a/devtools/antglob.py +++ b/devtools/antglob.py @@ -146,7 +146,7 @@ def glob_impl(root_dir_path): entry_type = is_file and FILE_LINK or DIR_LINK else: entry_type = is_file and FILE or DIR -## print '=> type: %d' % entry_type, +## print '=> type: %d' % entry_type, if (entry_type & entry_type_filter) != 0: ## print ' => KEEP' yield os.path.join(dir_path, entry) diff --git a/devtools/batchbuild.py b/devtools/batchbuild.py index 0eb0690e8..bf8be48df 100644 --- a/devtools/batchbuild.py +++ b/devtools/batchbuild.py @@ -9,7 +9,7 @@ import string import subprocess import sys -import cgi +import html class BuildDesc: def __init__(self, prepend_envs=None, variables=None, build_type=None, generator=None): @@ -195,12 +195,12 @@ def generate_html_report(html_report_path, builds): for variable in variables: build_types = sorted(build_types_by_variable[variable]) nb_build_type = len(build_types_by_variable[variable]) - th_vars.append('%s' % (nb_build_type, cgi.escape(' '.join(variable)))) + th_vars.append('%s' % (nb_build_type, html.escape(' '.join(variable)))) for build_type in build_types: - th_build_types.append('%s' % cgi.escape(build_type)) + th_build_types.append('%s' % html.escape(build_type)) tr_builds = [] for generator in sorted(builds_by_generator): - tds = [ '%s\n' % cgi.escape(generator) ] + tds = [ '%s\n' % html.escape(generator) ] for variable in variables: build_types = sorted(build_types_by_variable[variable]) for build_type in build_types: diff --git a/devtools/fixeol.py b/devtools/fixeol.py index 45252a07d..11e1ce2a1 100644 --- a/devtools/fixeol.py +++ b/devtools/fixeol.py @@ -32,8 +32,8 @@ def fix_source_eol(path, is_dry_run = True, verbose = True, eol = '\n'): if verbose: print(is_dry_run and ' NEED FIX' or ' FIXED') return True -## -## +## +## ## ##def _do_fix(is_dry_run = True): ## from waftools import antglob diff --git a/devtools/licenseupdater.py b/devtools/licenseupdater.py index 36bdb5c09..d9b662e01 100644 --- a/devtools/licenseupdater.py +++ b/devtools/licenseupdater.py @@ -20,7 +20,7 @@ def update_license(path, dry_run, show_diff): dry_run: if True, just print the path of the file that would be updated, but don't change it. show_diff: if True, print the path of the file that would be modified, - as well as the change made to the file. + as well as the change made to the file. """ with open(path, 'rt') as fin: original_text = fin.read().replace('\r\n','\n') @@ -51,7 +51,7 @@ def update_license_in_source_directories(source_dirs, dry_run, show_diff): dry_run: if True, just print the path of the file that would be updated, but don't change it. show_diff: if True, print the path of the file that would be modified, - as well as the change made to the file. + as well as the change made to the file. """ from devtools import antglob prune_dirs = antglob.prune_dirs + 'scons-local* ./build* ./libs ./dist' diff --git a/doxybuild.py b/doxybuild.py index 862c1f43f..254ab714b 100644 --- a/doxybuild.py +++ b/doxybuild.py @@ -46,7 +46,7 @@ def do_subst_in_file(targetfile, sourcefile, dict): with open(sourcefile, 'r') as f: contents = f.read() for (k,v) in list(dict.items()): - v = v.replace('\\','\\\\') + v = v.replace('\\','\\\\') contents = re.sub(k, v, contents) with open(targetfile, 'w') as f: f.write(contents) @@ -158,7 +158,7 @@ def main(): Generates doxygen documentation in build/doxygen. Optionally makes a tarball of the documentation to dist/. - Must be started in the project top directory. + Must be started in the project top directory. """ from optparse import OptionParser parser = OptionParser(usage=usage) diff --git a/example/BUILD.bazel b/example/BUILD.bazel new file mode 100644 index 000000000..38e7dfcf7 --- /dev/null +++ b/example/BUILD.bazel @@ -0,0 +1,33 @@ +cc_binary( + name = "readFromStream_ok", + srcs = ["readFromStream/readFromStream.cpp"], + deps = ["//:jsoncpp"], + args = ["$(location :readFromStream/withComment.json)"], + data = ["readFromStream/withComment.json"], +) + +cc_binary( + name = "readFromStream_err", + srcs = ["readFromStream/readFromStream.cpp"], + deps = ["//:jsoncpp"], + args = ["$(location :readFromStream/errorFormat.json)"], + data = ["readFromStream/errorFormat.json"], +) + +cc_binary( + name = "readFromString", + srcs = ["readFromString/readFromString.cpp"], + deps = ["//:jsoncpp"], +) + +cc_binary( + name = "streamWrite", + srcs = ["streamWrite/streamWrite.cpp"], + deps = ["//:jsoncpp"], +) + +cc_binary( + name = "stringWrite", + srcs = ["stringWrite/stringWrite.cpp"], + deps = ["//:jsoncpp"], +) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 000000000..230d1bd7b --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1,27 @@ +#vim: et ts =4 sts = 4 sw = 4 tw = 0 +set(EXAMPLES + readFromString + readFromStream + stringWrite + streamWrite +) +add_definitions(-D_GLIBCXX_USE_CXX11_ABI) + +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + add_compile_options(-Wall -Wextra) +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + add_definitions( + -D_SCL_SECURE_NO_WARNINGS + -D_CRT_SECURE_NO_WARNINGS + -D_WIN32_WINNT=0x601 + -D_WINSOCK_DEPRECATED_NO_WARNINGS + ) +endif() + +foreach(example ${EXAMPLES}) + add_executable(${example} ${example}/${example}.cpp) + target_include_directories(${example} PUBLIC ${CMAKE_SOURCE_DIR}/include) + target_link_libraries(${example} jsoncpp_lib) +endforeach() + +add_custom_target(examples ALL DEPENDS ${EXAMPLES}) diff --git a/example/README.md b/example/README.md new file mode 100644 index 000000000..92b925c96 --- /dev/null +++ b/example/README.md @@ -0,0 +1,13 @@ +***NOTE*** + +If you get linker errors about undefined references to symbols that involve types in the `std::__cxx11` namespace or the tag +`[abi:cxx11]` then it probably indicates that you are trying to link together object files that were compiled with different +values for the _GLIBCXX_USE_CXX11_ABI marco. This commonly happens when linking to a third-party library that was compiled with +an older version of GCC. If the third-party library cannot be rebuilt with the new ABI, then you need to recompile your code with +the old ABI,just like: +**g++ stringWrite.cpp -ljsoncpp -std=c++11 -D_GLIBCXX_USE_CXX11_ABI=0 -o stringWrite** + +Not all of uses of the new ABI will cause changes in symbol names, for example a class with a `std::string` member variable will +have the same mangled name whether compiled with the older or new ABI. In order to detect such problems, the new types and functions +are annotated with the abi_tag attribute, allowing the compiler to warn about potential ABI incompatibilities in code using them. +Those warnings can be enabled with the `-Wabi-tag` option. diff --git a/example/readFromStream/errorFormat.json b/example/readFromStream/errorFormat.json new file mode 100644 index 000000000..2d13290b1 --- /dev/null +++ b/example/readFromStream/errorFormat.json @@ -0,0 +1,3 @@ +{ + 1: "value" +} \ No newline at end of file diff --git a/example/readFromStream/readFromStream.cpp b/example/readFromStream/readFromStream.cpp new file mode 100644 index 000000000..358d2cab0 --- /dev/null +++ b/example/readFromStream/readFromStream.cpp @@ -0,0 +1,30 @@ +#include "json/json.h" +#include +#include +/** \brief Parse from stream, collect comments and capture error info. + * Example Usage: + * $g++ readFromStream.cpp -ljsoncpp -std=c++11 -o readFromStream + * $./readFromStream + * // comment head + * { + * // comment before + * "key" : "value" + * } + * // comment after + * // comment tail + */ +int main(int argc, char* argv[]) { + Json::Value root; + std::ifstream ifs; + ifs.open(argv[1]); + + Json::CharReaderBuilder builder; + builder["collectComments"] = true; + JSONCPP_STRING errs; + if (!parseFromStream(builder, ifs, &root, &errs)) { + std::cout << errs << std::endl; + return EXIT_FAILURE; + } + std::cout << root << std::endl; + return EXIT_SUCCESS; +} diff --git a/example/readFromStream/withComment.json b/example/readFromStream/withComment.json new file mode 100644 index 000000000..7a8c82822 --- /dev/null +++ b/example/readFromStream/withComment.json @@ -0,0 +1,6 @@ +// comment head +{ + // comment before + "key" : "value" + // comment after +}// comment tail diff --git a/example/readFromString/readFromString.cpp b/example/readFromString/readFromString.cpp new file mode 100644 index 000000000..878f9eb92 --- /dev/null +++ b/example/readFromString/readFromString.cpp @@ -0,0 +1,38 @@ +#include "json/json.h" +#include +#include +/** + * \brief Parse a raw string into Value object using the CharReaderBuilder + * class, or the legacy Reader class. + * Example Usage: + * $g++ readFromString.cpp -ljsoncpp -std=c++11 -o readFromString + * $./readFromString + * colin + * 20 + */ +int main() { + const std::string rawJson = R"({"Age": 20, "Name": "colin"})"; + const auto rawJsonLength = static_cast(rawJson.length()); + constexpr bool shouldUseOldWay = false; + JSONCPP_STRING err; + Json::Value root; + + if (shouldUseOldWay) { + Json::Reader reader; + reader.parse(rawJson, root); + } else { + Json::CharReaderBuilder builder; + const std::unique_ptr reader(builder.newCharReader()); + if (!reader->parse(rawJson.c_str(), rawJson.c_str() + rawJsonLength, &root, + &err)) { + std::cout << "error: " << err << std::endl; + return EXIT_FAILURE; + } + } + const std::string name = root["Name"].asString(); + const int age = root["Age"].asInt(); + + std::cout << name << std::endl; + std::cout << age << std::endl; + return EXIT_SUCCESS; +} diff --git a/example/streamWrite/streamWrite.cpp b/example/streamWrite/streamWrite.cpp new file mode 100644 index 000000000..a72f5a52e --- /dev/null +++ b/example/streamWrite/streamWrite.cpp @@ -0,0 +1,23 @@ +#include "json/json.h" +#include +#include +/** \brief Write the Value object to a stream. + * Example Usage: + * $g++ streamWrite.cpp -ljsoncpp -std=c++11 -o streamWrite + * $./streamWrite + * { + * "Age" : 20, + * "Name" : "robin" + * } + */ +int main() { + Json::Value root; + Json::StreamWriterBuilder builder; + const std::unique_ptr writer(builder.newStreamWriter()); + + root["Name"] = "robin"; + root["Age"] = 20; + writer->write(root, &std::cout); + + return EXIT_SUCCESS; +} diff --git a/example/stringWrite/stringWrite.cpp b/example/stringWrite/stringWrite.cpp new file mode 100644 index 000000000..d501ba948 --- /dev/null +++ b/example/stringWrite/stringWrite.cpp @@ -0,0 +1,33 @@ +#include "json/json.h" +#include +/** \brief Write a Value object to a string. + * Example Usage: + * $g++ stringWrite.cpp -ljsoncpp -std=c++11 -o stringWrite + * $./stringWrite + * { + * "action" : "run", + * "data" : + * { + * "number" : 1 + * } + * } + */ +int main() { + Json::Value root; + Json::Value data; + constexpr bool shouldUseOldWay = false; + root["action"] = "run"; + data["number"] = 1; + root["data"] = data; + + if (shouldUseOldWay) { + Json::FastWriter writer; + const std::string json_file = writer.write(root); + std::cout << json_file << std::endl; + } else { + Json::StreamWriterBuilder builder; + const std::string json_file = Json::writeString(builder, root); + std::cout << json_file << std::endl; + } + return EXIT_SUCCESS; +} diff --git a/get_version.pl b/get_version.pl new file mode 100644 index 000000000..19b6a543b --- /dev/null +++ b/get_version.pl @@ -0,0 +1,5 @@ +while (<>) { + if (/version : '(.+)',/) { + print "$1"; + } +} diff --git a/include/PreventInBuildInstalls.cmake b/include/PreventInBuildInstalls.cmake new file mode 100644 index 000000000..accfea64c --- /dev/null +++ b/include/PreventInBuildInstalls.cmake @@ -0,0 +1,9 @@ +string(TOLOWER "${CMAKE_INSTALL_PREFIX}" _PREFIX) +string(TOLOWER "${ITK_BINARY_DIR}" _BUILD) +if("${_PREFIX}" STREQUAL "${_BUILD}") + message(FATAL_ERROR + "The current CMAKE_INSTALL_PREFIX points at the build tree:\n" + " ${CMAKE_INSTALL_PREFIX}\n" + "This is not supported." + ) +endif() diff --git a/include/PreventInSourceBuilds.cmake b/include/PreventInSourceBuilds.cmake new file mode 100644 index 000000000..be5d0dd41 --- /dev/null +++ b/include/PreventInSourceBuilds.cmake @@ -0,0 +1,45 @@ +# +# This function will prevent in-source builds +function(AssureOutOfSourceBuilds) + # make sure the user doesn't play dirty with symlinks + get_filename_component(srcdir "${CMAKE_CURRENT_SOURCE_DIR}" REALPATH) + get_filename_component(bindir "${CMAKE_CURRENT_BINARY_DIR}" REALPATH) + + # disallow in-source builds + if("${srcdir}" STREQUAL "${bindir}") + message("######################################################") + message("# jsoncpp should not be configured & built in the jsoncpp source directory") + message("# You must run cmake in a build directory.") + message("# For example:") + message("# mkdir jsoncpp-Sandbox ; cd jsoncpp-sandbox") + message("# git clone https://github.com/open-source-parsers/jsoncpp.git # or download & unpack the source tarball") + message("# mkdir jsoncpp-build") + message("# this will create the following directory structure") + message("#") + message("# jsoncpp-Sandbox") + message("# +--jsoncpp") + message("# +--jsoncpp-build") + message("#") + message("# Then you can proceed to configure and build") + message("# by using the following commands") + message("#") + message("# cd jsoncpp-build") + message("# cmake ../jsoncpp # or ccmake, or cmake-gui ") + message("# make") + message("#") + message("# NOTE: Given that you already tried to make an in-source build") + message("# CMake have already created several files & directories") + message("# in your source tree. run 'git status' to find them and") + message("# remove them by doing:") + message("#") + message("# cd jsoncpp-Sandbox/jsoncpp") + message("# git clean -n -d") + message("# git clean -f -d") + message("# git checkout --") + message("#") + message("######################################################") + message(FATAL_ERROR "Quitting configuration") + endif() +endfunction() + +AssureOutOfSourceBuilds() diff --git a/include/json/allocator.h b/include/json/allocator.h index d5f987e97..459c34c61 100644 --- a/include/json/allocator.h +++ b/include/json/allocator.h @@ -3,13 +3,15 @@ // recognized in your jurisdiction. // See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE -#ifndef CPPTL_JSON_ALLOCATOR_H_INCLUDED -#define CPPTL_JSON_ALLOCATOR_H_INCLUDED +#ifndef JSON_ALLOCATOR_H_INCLUDED +#define JSON_ALLOCATOR_H_INCLUDED +#include #include #include -#pragma pack(push, 8) +#pragma pack(push) +#pragma pack() namespace Json { template class SecureAllocator { @@ -35,11 +37,18 @@ template class SecureAllocator { * Release memory which was allocated for N items at pointer P. * * The memory block is filled with zeroes before being released. - * The pointer argument is tagged as "volatile" to prevent the - * compiler optimizing out this critical step. */ - void deallocate(volatile pointer p, size_type n) { - std::memset(p, 0, n * sizeof(T)); + void deallocate(pointer p, size_type n) { + // These constructs will not be removed by the compiler during optimization, + // unlike memset. +#if defined(HAVE_MEMSET_S) + memset_s(p, n * sizeof(T), 0, n * sizeof(T)); +#elif defined(_WIN32) + RtlSecureZeroMemory(p, n * sizeof(T)); +#else + std::fill_n(reinterpret_cast(p), n, 0); +#endif + // free using "global operator delete" ::operator delete(p); } @@ -69,7 +78,9 @@ template class SecureAllocator { // Boilerplate SecureAllocator() {} template SecureAllocator(const SecureAllocator&) {} - template struct rebind { using other = SecureAllocator; }; + template struct rebind { + using other = SecureAllocator; + }; }; template @@ -86,4 +97,4 @@ bool operator!=(const SecureAllocator&, const SecureAllocator&) { #pragma pack(pop) -#endif // CPPTL_JSON_ALLOCATOR_H_INCLUDED +#endif // JSON_ALLOCATOR_H_INCLUDED diff --git a/include/json/assertions.h b/include/json/assertions.h index 20716b067..666fa7f54 100644 --- a/include/json/assertions.h +++ b/include/json/assertions.h @@ -3,8 +3,8 @@ // recognized in your jurisdiction. // See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE -#ifndef CPPTL_JSON_ASSERTIONS_H_INCLUDED -#define CPPTL_JSON_ASSERTIONS_H_INCLUDED +#ifndef JSON_ASSERTIONS_H_INCLUDED +#define JSON_ASSERTIONS_H_INCLUDED #include #include @@ -21,19 +21,19 @@ // @todo <= add detail about condition in exception #define JSON_ASSERT(condition) \ - { \ + do { \ if (!(condition)) { \ Json::throwLogicError("assert json failed"); \ } \ - } + } while (0) #define JSON_FAIL_MESSAGE(message) \ - { \ + do { \ OStringStream oss; \ oss << message; \ Json::throwLogicError(oss.str()); \ abort(); \ - } + } while (0) #else // JSON_USE_EXCEPTION @@ -52,8 +52,10 @@ #endif #define JSON_ASSERT_MESSAGE(condition, message) \ - if (!(condition)) { \ - JSON_FAIL_MESSAGE(message); \ - } + do { \ + if (!(condition)) { \ + JSON_FAIL_MESSAGE(message); \ + } \ + } while (0) -#endif // CPPTL_JSON_ASSERTIONS_H_INCLUDED +#endif // JSON_ASSERTIONS_H_INCLUDED diff --git a/include/json/autolink.h b/include/json/autolink.h deleted file mode 100644 index b2c0f0024..000000000 --- a/include/json/autolink.h +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#ifndef JSON_AUTOLINK_H_INCLUDED -#define JSON_AUTOLINK_H_INCLUDED - -#include "config.h" - -#ifdef JSON_IN_CPPTL -#include -#endif - -#if !defined(JSON_NO_AUTOLINK) && !defined(JSON_DLL_BUILD) && \ - !defined(JSON_IN_CPPTL) -#define CPPTL_AUTOLINK_NAME "json" -#undef CPPTL_AUTOLINK_DLL -#ifdef JSON_DLL -#define CPPTL_AUTOLINK_DLL -#endif -#include "autolink.h" -#endif - -#endif // JSON_AUTOLINK_H_INCLUDED diff --git a/include/json/config.h b/include/json/config.h index 6426c3b29..7f6e2431b 100644 --- a/include/json/config.h +++ b/include/json/config.h @@ -14,16 +14,6 @@ #include #include -/// If defined, indicates that json library is embedded in CppTL library. -//# define JSON_IN_CPPTL 1 - -/// If defined, indicates that json may leverage CppTL library -//# define JSON_USE_CPPTL 1 -/// If defined, indicates that cpptl vector based map should be used instead of -/// std::map -/// as Value container. -//# define JSON_USE_CPPTL_SMALLMAP 1 - // If non-zero, the library uses exceptions to report bad input instead of C // assertion macros. The default is to use exceptions. #ifndef JSON_USE_EXCEPTION @@ -40,28 +30,22 @@ /// Remarks: it is automatically defined in the generated amalgamated header. // #define JSON_IS_AMALGAMATION -#ifdef JSON_IN_CPPTL -#include -#ifndef JSON_USE_CPPTL -#define JSON_USE_CPPTL 1 -#endif -#endif - -#ifdef JSON_IN_CPPTL -#define JSON_API CPPTL_API -#elif defined(JSON_DLL_BUILD) +// Export macros for DLL visibility +#if defined(JSON_DLL_BUILD) #if defined(_MSC_VER) || defined(__MINGW32__) #define JSON_API __declspec(dllexport) #define JSONCPP_DISABLE_DLL_INTERFACE_WARNING #elif defined(__GNUC__) || defined(__clang__) #define JSON_API __attribute__((visibility("default"))) #endif // if defined(_MSC_VER) + #elif defined(JSON_DLL) #if defined(_MSC_VER) || defined(__MINGW32__) #define JSON_API __declspec(dllimport) #define JSONCPP_DISABLE_DLL_INTERFACE_WARNING #endif // if defined(_MSC_VER) -#endif // ifdef JSON_IN_CPPTL +#endif // ifdef JSON_DLL_BUILD + #if !defined(JSON_API) #define JSON_API #endif @@ -74,8 +58,8 @@ #if defined(_MSC_VER) && _MSC_VER < 1900 // As recommended at // https://stackoverflow.com/questions/2915672/snprintf-and-visual-studio-2010 -extern JSON_API int -msvc_pre1900_c99_snprintf(char* outBuf, size_t size, const char* format, ...); +extern JSON_API int msvc_pre1900_c99_snprintf(char* outBuf, size_t size, + const char* format, ...); #define jsoncpp_snprintf msvc_pre1900_c99_snprintf #else #define jsoncpp_snprintf std::snprintf @@ -90,21 +74,26 @@ msvc_pre1900_c99_snprintf(char* outBuf, size_t size, const char* format, ...); // C++11 should be used directly in JSONCPP. #define JSONCPP_OVERRIDE override -#if __cplusplus >= 201103L -#define JSONCPP_NOEXCEPT noexcept -#define JSONCPP_OP_EXPLICIT explicit -#elif defined(_MSC_VER) && _MSC_VER < 1900 -#define JSONCPP_NOEXCEPT throw() -#define JSONCPP_OP_EXPLICIT explicit -#elif defined(_MSC_VER) && _MSC_VER >= 1900 -#define JSONCPP_NOEXCEPT noexcept -#define JSONCPP_OP_EXPLICIT explicit -#else -#define JSONCPP_NOEXCEPT throw() -#define JSONCPP_OP_EXPLICIT +#ifdef __clang__ +#if __has_extension(attribute_deprecated_with_message) +#define JSONCPP_DEPRECATED(message) __attribute__((deprecated(message))) #endif - -#if defined(__GNUC__) && (__GNUC__ >= 6) +#elif defined(__GNUC__) // not clang (gcc comes later since clang emulates gcc) +#if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) +#define JSONCPP_DEPRECATED(message) __attribute__((deprecated(message))) +#elif (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) +#define JSONCPP_DEPRECATED(message) __attribute__((__deprecated__)) +#endif // GNUC version +#elif defined(_MSC_VER) // MSVC (after clang because clang on Windows emulates + // MSVC) +#define JSONCPP_DEPRECATED(message) __declspec(deprecated(message)) +#endif // __clang__ || __GNUC__ || _MSC_VER + +#if !defined(JSONCPP_DEPRECATED) +#define JSONCPP_DEPRECATED(message) +#endif // if !defined(JSONCPP_DEPRECATED) + +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ >= 6)) #define JSON_USE_INT64_DOUBLE_CONVERSION 1 #endif @@ -116,37 +105,37 @@ msvc_pre1900_c99_snprintf(char* outBuf, size_t size, const char* format, ...); #endif // if !defined(JSON_IS_AMALGAMATION) namespace Json { -typedef int Int; -typedef unsigned int UInt; +using Int = int; +using UInt = unsigned int; #if defined(JSON_NO_INT64) -typedef int LargestInt; -typedef unsigned int LargestUInt; +using LargestInt = int; +using LargestUInt = unsigned int; #undef JSON_HAS_INT64 #else // if defined(JSON_NO_INT64) // For Microsoft Visual use specific types as long long is not supported #if defined(_MSC_VER) // Microsoft Visual Studio -typedef __int64 Int64; -typedef unsigned __int64 UInt64; +using Int64 = __int64; +using UInt64 = unsigned __int64; #else // if defined(_MSC_VER) // Other platforms, use long long -typedef int64_t Int64; -typedef uint64_t UInt64; +using Int64 = int64_t; +using UInt64 = uint64_t; #endif // if defined(_MSC_VER) -typedef Int64 LargestInt; -typedef UInt64 LargestUInt; +using LargestInt = Int64; +using LargestUInt = UInt64; #define JSON_HAS_INT64 #endif // if defined(JSON_NO_INT64) template -using Allocator = typename std::conditional, - std::allocator>::type; +using Allocator = + typename std::conditional, + std::allocator>::type; using String = std::basic_string, Allocator>; -using IStringStream = std::basic_istringstream; -using OStringStream = std::basic_ostringstream; +using IStringStream = + std::basic_istringstream; +using OStringStream = + std::basic_ostringstream; using IStream = std::istream; using OStream = std::ostream; } // namespace Json diff --git a/include/json/forwards.h b/include/json/forwards.h index 958b5bcc8..affe33a7f 100644 --- a/include/json/forwards.h +++ b/include/json/forwards.h @@ -25,11 +25,11 @@ class Reader; class CharReader; class CharReaderBuilder; -// features.h +// json_features.h class Features; // value.h -typedef unsigned int ArrayIndex; +using ArrayIndex = unsigned int; class StaticString; class Path; class PathArgument; diff --git a/include/json/json.h b/include/json/json.h index 19f14c266..5c776a160 100644 --- a/include/json/json.h +++ b/include/json/json.h @@ -6,8 +6,8 @@ #ifndef JSON_JSON_H_INCLUDED #define JSON_JSON_H_INCLUDED -#include "autolink.h" -#include "features.h" +#include "config.h" +#include "json_features.h" #include "reader.h" #include "value.h" #include "writer.h" diff --git a/include/json/features.h b/include/json/json_features.h similarity index 92% rename from include/json/features.h rename to include/json/json_features.h index ba25e8da6..e4a61d6f1 100644 --- a/include/json/features.h +++ b/include/json/json_features.h @@ -3,14 +3,15 @@ // recognized in your jurisdiction. // See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE -#ifndef CPPTL_JSON_FEATURES_H_INCLUDED -#define CPPTL_JSON_FEATURES_H_INCLUDED +#ifndef JSON_FEATURES_H_INCLUDED +#define JSON_FEATURES_H_INCLUDED #if !defined(JSON_IS_AMALGAMATION) #include "forwards.h" #endif // if !defined(JSON_IS_AMALGAMATION) -#pragma pack(push, 8) +#pragma pack(push) +#pragma pack() namespace Json { @@ -58,4 +59,4 @@ class JSON_API Features { #pragma pack(pop) -#endif // CPPTL_JSON_FEATURES_H_INCLUDED +#endif // JSON_FEATURES_H_INCLUDED diff --git a/include/json/reader.h b/include/json/reader.h index 947fc0035..d745378fc 100644 --- a/include/json/reader.h +++ b/include/json/reader.h @@ -3,11 +3,11 @@ // recognized in your jurisdiction. // See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE -#ifndef CPPTL_JSON_READER_H_INCLUDED -#define CPPTL_JSON_READER_H_INCLUDED +#ifndef JSON_READER_H_INCLUDED +#define JSON_READER_H_INCLUDED #if !defined(JSON_IS_AMALGAMATION) -#include "features.h" +#include "json_features.h" #include "value.h" #endif // if !defined(JSON_IS_AMALGAMATION) #include @@ -23,11 +23,8 @@ #pragma warning(disable : 4251) #endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) -#pragma pack(push, 8) - -#if defined(_MSC_VER) -#pragma warning(disable : 4996) -#endif +#pragma pack(push) +#pragma pack() namespace Json { @@ -36,10 +33,11 @@ namespace Json { * * \deprecated Use CharReader and CharReaderBuilder. */ -class [[deprecated("deprecated Use CharReader and CharReaderBuilder.")]] JSON_API Reader { + +class JSON_API Reader { public: - typedef char Char; - typedef const Char* Location; + using Char = char; + using Location = const Char*; /** \brief An error tagged with where in the JSON text it was encountered. * @@ -53,10 +51,12 @@ class [[deprecated("deprecated Use CharReader and CharReaderBuilder.")]] JSON_AP }; /** \brief Constructs a Reader allowing all features for parsing. + * \deprecated Use CharReader and CharReaderBuilder. */ Reader(); /** \brief Constructs a Reader allowing the specified feature set for parsing. + * \deprecated Use CharReader and CharReaderBuilder. */ Reader(const Features& features); @@ -74,8 +74,8 @@ class [[deprecated("deprecated Use CharReader and CharReaderBuilder.")]] JSON_AP * \return \c true if the document was successfully parsed, \c false if an * error occurred. */ - bool - parse(const std::string& document, Value& root, bool collectComments = true); + bool parse(const std::string& document, Value& root, + bool collectComments = true); /** \brief Read a Value from a JSON * document. @@ -93,9 +93,7 @@ class [[deprecated("deprecated Use CharReader and CharReaderBuilder.")]] JSON_AP * \return \c true if the document was successfully parsed, \c false if an * error occurred. */ - bool parse(const char* beginDoc, - const char* endDoc, - Value& root, + bool parse(const char* beginDoc, const char* endDoc, Value& root, bool collectComments = true); /// \brief Parse from input stream. @@ -110,7 +108,7 @@ class [[deprecated("deprecated Use CharReader and CharReaderBuilder.")]] JSON_AP * occurred during parsing. * \deprecated Use getFormattedErrorMessages() instead (typo fix). */ - [[deprecated("Use getFormattedErrorMessages() instead.")]] + JSONCPP_DEPRECATED("Use getFormattedErrorMessages() instead.") String getFormatedErrorMessages() const; /** \brief Returns a user friendly string that list errors in the parsed @@ -189,11 +187,12 @@ class [[deprecated("deprecated Use CharReader and CharReaderBuilder.")]] JSON_AP Location extra_; }; - typedef std::deque Errors; + using Errors = std::deque; bool readToken(Token& token); + bool readTokenSkippingComments(Token& token); void skipSpaces(); - bool match(Location pattern, int patternLength); + bool match(const Char* pattern, int patternLength); bool readComment(); bool readCStyleComment(); bool readCppStyleComment(); @@ -208,32 +207,26 @@ class [[deprecated("deprecated Use CharReader and CharReaderBuilder.")]] JSON_AP bool decodeString(Token& token, String& decoded); bool decodeDouble(Token& token); bool decodeDouble(Token& token, Value& decoded); - bool decodeUnicodeCodePoint(Token& token, - Location& current, - Location end, + bool decodeUnicodeCodePoint(Token& token, Location& current, Location end, unsigned int& unicode); - bool decodeUnicodeEscapeSequence(Token& token, - Location& current, - Location end, - unsigned int& unicode); + bool decodeUnicodeEscapeSequence(Token& token, Location& current, + Location end, unsigned int& unicode); bool addError(const String& message, Token& token, Location extra = nullptr); bool recoverFromError(TokenType skipUntilToken); - bool addErrorAndRecover(const String& message, - Token& token, + bool addErrorAndRecover(const String& message, Token& token, TokenType skipUntilToken); void skipUntilSpace(); Value& currentValue(); Char getNextChar(); - void - getLocationLineAndColumn(Location location, int& line, int& column) const; + void getLocationLineAndColumn(Location location, int& line, + int& column) const; String getLocationLineAndColumn(Location location) const; void addComment(Location begin, Location end, CommentPlacement placement); - void skipCommentTokens(Token& token); static bool containsNewLine(Location begin, Location end); static String normalizeEOL(Location begin, Location end); - typedef std::stack Nodes; + using Nodes = std::stack; Nodes nodes_; Errors errors_; String document_; @@ -251,6 +244,12 @@ class [[deprecated("deprecated Use CharReader and CharReaderBuilder.")]] JSON_AP */ class JSON_API CharReader { public: + struct JSON_API StructuredError { + ptrdiff_t offset_start; + ptrdiff_t offset_limit; + String message; + }; + virtual ~CharReader() = default; /** \brief Read a Value from a JSON * document. The document must be a UTF-8 encoded string containing the @@ -268,10 +267,13 @@ class JSON_API CharReader { * \return \c true if the document was successfully parsed, \c false if an * error occurred. */ - virtual bool parse(char const* beginDoc, - char const* endDoc, - Value* root, - String* errs) = 0; + virtual bool parse(char const* beginDoc, char const* endDoc, Value* root, + String* errs); + + /** \brief Returns a vector of structured errors encountered while parsing. + * Each parse call resets the stored list of errors. + */ + std::vector getStructuredErrors() const; class JSON_API Factory { public: @@ -281,7 +283,21 @@ class JSON_API CharReader { */ virtual CharReader* newCharReader() const = 0; }; // Factory -}; // CharReader + +protected: + class Impl { + public: + virtual ~Impl() = default; + virtual bool parse(char const* beginDoc, char const* endDoc, Value* root, + String* errs) = 0; + virtual std::vector getStructuredErrors() const = 0; + }; + + explicit CharReader(std::unique_ptr impl) : _impl(std::move(impl)) {} + +private: + std::unique_ptr _impl; +}; // CharReader /** \brief Build a CharReader implementation. * @@ -308,6 +324,8 @@ class JSON_API CharReaderBuilder : public CharReader::Factory { * if allowComments is false. * - `"allowComments": false or true` * - true if comments are allowed. + * - `"allowTrailingCommas": false or true` + * - true if trailing commas in objects and arrays are allowed. * - `"strictRoot": false or true` * - true if root must be either an array or an object value * - `"allowDroppedNullPlaceholders": false or true` @@ -331,6 +349,9 @@ class JSON_API CharReaderBuilder : public CharReader::Factory { * - `"allowSpecialFloats": false or true` * - If true, special float values (NaNs and infinities) are allowed and * their values are lossfree restorable. + * - `"skipBom": false or true` + * - If true, if the input starts with the Unicode byte order mark (BOM), + * it is skipped. * * You can examine 'settings_` yourself to see the defaults. You can also * write and read them just like any JSON Value. @@ -364,15 +385,19 @@ class JSON_API CharReaderBuilder : public CharReader::Factory { * \snippet src/lib_json/json_reader.cpp CharReaderBuilderStrictMode */ static void strictMode(Json::Value* settings); + /** ECMA-404 mode. + * \pre 'settings' != NULL (but Json::null is fine) + * \remark Defaults: + * \snippet src/lib_json/json_reader.cpp CharReaderBuilderECMA404Mode + */ + static void ecma404Mode(Json::Value* settings); }; /** Consume entire stream and use its begin/end. * Someday we might have a real StreamReader, but for now this * is convenient. */ -bool JSON_API parseFromStream(CharReader::Factory const&, - IStream&, - Value* root, +bool JSON_API parseFromStream(CharReader::Factory const&, IStream&, Value* root, String* errs); /** \brief Read from 'sin' into 'root'. @@ -409,4 +434,4 @@ JSON_API IStream& operator>>(IStream&, Value&); #pragma warning(pop) #endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) -#endif // CPPTL_JSON_READER_H_INCLUDED +#endif // JSON_READER_H_INCLUDED diff --git a/include/json/value.h b/include/json/value.h index e1340d62b..5f6544329 100644 --- a/include/json/value.h +++ b/include/json/value.h @@ -3,35 +3,66 @@ // recognized in your jurisdiction. // See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE -#ifndef CPPTL_JSON_H_INCLUDED -#define CPPTL_JSON_H_INCLUDED +#ifndef JSON_VALUE_H_INCLUDED +#define JSON_VALUE_H_INCLUDED #if !defined(JSON_IS_AMALGAMATION) #include "forwards.h" #endif // if !defined(JSON_IS_AMALGAMATION) + +// Conditional NORETURN attribute on the throw functions would: +// a) suppress false positives from static code analysis +// b) possibly improve optimization opportunities. +#if !defined(JSONCPP_NORETURN) +#if defined(_MSC_VER) && _MSC_VER == 1800 +#define JSONCPP_NORETURN __declspec(noreturn) +#else +#define JSONCPP_NORETURN [[noreturn]] +#endif +#endif + +// Support for '= delete' with template declarations was a late addition +// to the c++11 standard and is rejected by clang 3.8 and Apple clang 8.2 +// even though these declare themselves to be c++11 compilers. +#if !defined(JSONCPP_TEMPLATE_DELETE) +#if defined(__clang__) && defined(__apple_build_version__) +#if __apple_build_version__ <= 8000042 +#define JSONCPP_TEMPLATE_DELETE +#endif +#elif defined(__clang__) +#if __clang_major__ == 3 && __clang_minor__ <= 8 +#define JSONCPP_TEMPLATE_DELETE +#endif +#endif +#if !defined(JSONCPP_TEMPLATE_DELETE) +#define JSONCPP_TEMPLATE_DELETE = delete +#endif +#endif + +#if __cplusplus >= 201703L +#define JSONCPP_HAS_STRING_VIEW 1 +#endif + #include #include +#include #include #include #include -#ifndef JSON_USE_CPPTL_SMALLMAP -#include -#else -#include -#endif -#ifdef JSON_USE_CPPTL -#include +#ifdef JSONCPP_HAS_STRING_VIEW +#include #endif // Disable warning C4251: : needs to have dll-interface to // be used by... #if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) #pragma warning(push) -#pragma warning(disable : 4251) +#pragma warning(disable : 4251 4275) #endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) -#pragma pack(push, 8) +#pragma pack(push) +#pragma pack() /** \brief JSON (JavaScript Object Notation). */ @@ -45,8 +76,8 @@ namespace Json { class JSON_API Exception : public std::exception { public: Exception(String msg); - ~Exception() JSONCPP_NOEXCEPT override; - char const* what() const JSONCPP_NOEXCEPT override; + ~Exception() noexcept override; + char const* what() const noexcept override; protected: String msg_; @@ -76,9 +107,9 @@ class JSON_API LogicError : public Exception { #endif /// used internally -[[noreturn]] void throwRuntimeError(String const& msg); +JSONCPP_NORETURN void throwRuntimeError(String const& msg); /// used internally -[[noreturn]] void throwLogicError(String const& msg); +JSONCPP_NORETURN void throwLogicError(String const& msg); /** \brief Type of the value held by a Value object. */ @@ -108,11 +139,6 @@ enum PrecisionType { decimalPlaces ///< we set max number of digits after "." in string }; -//# ifdef JSON_USE_CPPTL -// typedef CppTL::AnyEnumerator EnumMemberNames; -// typedef CppTL::AnyEnumerator EnumValues; -//# endif - /** \brief Lightweight wrapper to tag static string. * * Value constructor and objectValue member assignment takes advantage of the @@ -177,21 +203,21 @@ class JSON_API Value { friend class ValueIteratorBase; public: - typedef std::vector Members; - typedef ValueIterator iterator; - typedef ValueConstIterator const_iterator; - typedef Json::UInt UInt; - typedef Json::Int Int; + using Members = std::vector; + using iterator = ValueIterator; + using const_iterator = ValueConstIterator; + using UInt = Json::UInt; + using Int = Json::Int; #if defined(JSON_HAS_INT64) - typedef Json::UInt64 UInt64; - typedef Json::Int64 Int64; + using UInt64 = Json::UInt64; + using Int64 = Json::Int64; #endif // defined(JSON_HAS_INT64) - typedef Json::LargestInt LargestInt; - typedef Json::LargestUInt LargestUInt; - typedef Json::ArrayIndex ArrayIndex; + using LargestInt = Json::LargestInt; + using LargestUInt = Json::LargestUInt; + using ArrayIndex = Json::ArrayIndex; // Required for boost integration, e. g. BOOST_TEST - typedef std::string value_type; + using value_type = std::string; #if JSON_USE_NULLREF // Binary compatibility kludges, do not use. @@ -203,31 +229,34 @@ class JSON_API Value { static Value const& nullSingleton(); /// Minimum signed integer value that can be stored in a Json::Value. - static const LargestInt minLargestInt; + static constexpr LargestInt minLargestInt = + LargestInt(~(LargestUInt(-1) / 2)); /// Maximum signed integer value that can be stored in a Json::Value. - static const LargestInt maxLargestInt; + static constexpr LargestInt maxLargestInt = LargestInt(LargestUInt(-1) / 2); /// Maximum unsigned integer value that can be stored in a Json::Value. - static const LargestUInt maxLargestUInt; + static constexpr LargestUInt maxLargestUInt = LargestUInt(-1); /// Minimum signed int value that can be stored in a Json::Value. - static const Int minInt; + static constexpr Int minInt = Int(~(UInt(-1) / 2)); /// Maximum signed int value that can be stored in a Json::Value. - static const Int maxInt; + static constexpr Int maxInt = Int(UInt(-1) / 2); /// Maximum unsigned int value that can be stored in a Json::Value. - static const UInt maxUInt; + static constexpr UInt maxUInt = UInt(-1); #if defined(JSON_HAS_INT64) /// Minimum signed 64 bits int value that can be stored in a Json::Value. - static const Int64 minInt64; + static constexpr Int64 minInt64 = Int64(~(UInt64(-1) / 2)); /// Maximum signed 64 bits int value that can be stored in a Json::Value. - static const Int64 maxInt64; + static constexpr Int64 maxInt64 = Int64(UInt64(-1) / 2); /// Maximum unsigned 64 bits int value that can be stored in a Json::Value. - static const UInt64 maxUInt64; + static constexpr UInt64 maxUInt64 = UInt64(-1); #endif // defined(JSON_HAS_INT64) - /// Default precision for real value for string representation. - static const UInt defaultRealPrecision; - + static constexpr UInt defaultRealPrecision = 17; + // The constant is hard-coded because some compiler have trouble + // converting Value::maxUInt64 to a double correctly (AIX/xlC). + // Assumes that UInt64 is a 64 bits integer. + static constexpr double maxUInt64AsDouble = 18446744073709551615.0; // Workaround for bug in the NVIDIAs CUDA 9.1 nvcc compiler // when using gcc and clang backend compilers. CZString // cannot be defined as private. See issue #486 @@ -243,10 +272,10 @@ class JSON_API Value { CZString(ArrayIndex index); CZString(char const* str, unsigned length, DuplicationPolicy allocate); CZString(CZString const& other); - CZString(CZString&& other); + CZString(CZString&& other) noexcept; ~CZString(); CZString& operator=(const CZString& other); - CZString& operator=(CZString&& other); + CZString& operator=(CZString&& other) noexcept; bool operator<(CZString const& other) const; bool operator==(CZString const& other) const; @@ -272,11 +301,7 @@ class JSON_API Value { }; public: -#ifndef JSON_USE_CPPTL_SMALLMAP typedef std::map ObjectValues; -#else - typedef CppTL::SmallMap ObjectValues; -#endif // ifndef JSON_USE_CPPTL_SMALLMAP #endif // ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION public: @@ -325,18 +350,19 @@ class JSON_API Value { */ Value(const StaticString& value); Value(const String& value); -#ifdef JSON_USE_CPPTL - Value(const CppTL::ConstString& value); +#ifdef JSONCPP_HAS_STRING_VIEW + Value(std::string_view value); #endif Value(bool value); + Value(std::nullptr_t ptr) = delete; Value(const Value& other); - Value(Value&& other); + Value(Value&& other) noexcept; ~Value(); /// \note Overwrite existing comments. To preserve comments, use /// #swapPayload(). Value& operator=(const Value& other); - Value& operator=(Value&& other); + Value& operator=(Value&& other) noexcept; /// Swap everything. void swap(Value& other); @@ -360,7 +386,7 @@ class JSON_API Value { int compare(const Value& other) const; const char* asCString() const; ///< Embedded zeroes could cause you trouble! -#if JSONCPP_USING_SECURE_MEMORY +#if JSONCPP_USE_SECURE_MEMORY unsigned getCStringLength() const; // Allows you to understand the length of // the CString #endif @@ -369,8 +395,11 @@ class JSON_API Value { * \return false if !string. (Seg-fault if str or end are NULL.) */ bool getString(char const** begin, char const** end) const; -#ifdef JSON_USE_CPPTL - CppTL::ConstString asConstString() const; +#ifdef JSONCPP_HAS_STRING_VIEW + /** Get string_view of string-value. + * \return false if !string. (Seg-fault if str is NULL.) + */ + bool getString(std::string_view* str) const; #endif Int asInt() const; UInt asUInt() const; @@ -397,6 +426,10 @@ class JSON_API Value { bool isArray() const; bool isObject() const; + /// The `as` and `is` member function templates and specializations. + template T as() const JSONCPP_TEMPLATE_DELETE; + template bool is() const JSONCPP_TEMPLATE_DELETE; + bool isConvertibleTo(ValueType other) const; /// Number of values in array or object @@ -407,7 +440,7 @@ class JSON_API Value { bool empty() const; /// Return !isNull() - JSONCPP_OP_EXPLICIT operator bool() const; + explicit operator bool() const; /// Remove all object members and array elements. /// \pre type() is arrayValue, objectValue, or nullValue @@ -421,7 +454,7 @@ class JSON_API Value { /// \post type() is arrayValue void resize(ArrayIndex newSize); - //@{ + ///@{ /// Access an array element (zero based index). If the array contains less /// than index element, then null value are inserted in the array so that /// its size is index+1. @@ -429,15 +462,15 @@ class JSON_API Value { /// this from the operator[] which takes a string.) Value& operator[](ArrayIndex index); Value& operator[](int index); - //@} + ///@} - //@{ + ///@{ /// Access an array element (zero based index). /// (You may need to say 'value[0u]' to get your compiler to distinguish /// this from the operator[] which takes a string.) const Value& operator[](ArrayIndex index) const; const Value& operator[](int index) const; - //@} + ///@} /// If the array contains at least index+1 elements, returns the element /// value, otherwise returns defaultValue. @@ -450,6 +483,19 @@ class JSON_API Value { Value& append(const Value& value); Value& append(Value&& value); + /// \brief Insert value in array at specific index + bool insert(ArrayIndex index, const Value& newValue); + bool insert(ArrayIndex index, Value&& newValue); + +#ifdef JSONCPP_HAS_STRING_VIEW + /// Access an object value by name, create a null member if it does not exist. + /// \param key may contain embedded nulls. + Value& operator[](std::string_view key); + /// Access an object value by name, returns null if there is no member with + /// that name. + /// \param key may contain embedded nulls. + const Value& operator[](std::string_view key) const; +#else /// Access an object value by name, create a null member if it does not exist. /// \note Because of our implementation, keys are limited to 2^30 -1 chars. /// Exceeding that will cause an exception. @@ -464,6 +510,7 @@ class JSON_API Value { /// that name. /// \param key may contain embedded nulls. const Value& operator[](const String& key) const; +#endif /** \brief Access an object value by name, create a null member if it does not * exist. * @@ -477,34 +524,54 @@ class JSON_API Value { * \endcode */ Value& operator[](const StaticString& key); -#ifdef JSON_USE_CPPTL - /// Access an object value by name, create a null member if it does not exist. - Value& operator[](const CppTL::ConstString& key); - /// Access an object value by name, returns null if there is no member with - /// that name. - const Value& operator[](const CppTL::ConstString& key) const; -#endif +#ifdef JSONCPP_HAS_STRING_VIEW /// Return the member named key if it exist, defaultValue otherwise. /// \note deep copy - Value get(const char* key, const Value& defaultValue) const; + Value get(std::string_view key, const Value& defaultValue) const; +#else /// Return the member named key if it exist, defaultValue otherwise. /// \note deep copy - /// \note key may contain embedded nulls. - Value - get(const char* begin, const char* end, const Value& defaultValue) const; + Value get(const char* key, const Value& defaultValue) const; /// Return the member named key if it exist, defaultValue otherwise. /// \note deep copy /// \param key may contain embedded nulls. Value get(const String& key, const Value& defaultValue) const; -#ifdef JSON_USE_CPPTL +#endif /// Return the member named key if it exist, defaultValue otherwise. /// \note deep copy - Value get(const CppTL::ConstString& key, const Value& defaultValue) const; -#endif + /// \note key may contain embedded nulls. + Value get(const char* begin, const char* end, + const Value& defaultValue) const; /// Most general and efficient version of isMember()const, get()const, /// and operator[]const /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30 Value const* find(char const* begin, char const* end) const; + /// Most general and efficient version of isMember()const, get()const, + /// and operator[]const + Value const* find(const String& key) const; + + /// Calls find and only returns a valid pointer if the type is found + template + Value const* findValue(const String& key) const { + Value const* found = find(key); + if (!found || !(found->*TMemFn)()) + return nullptr; + return found; + } + + Value const* findNull(const String& key) const; + Value const* findBool(const String& key) const; + Value const* findInt(const String& key) const; + Value const* findInt64(const String& key) const; + Value const* findUInt(const String& key) const; + Value const* findUInt64(const String& key) const; + Value const* findIntegral(const String& key) const; + Value const* findDouble(const String& key) const; + Value const* findNumeric(const String& key) const; + Value const* findString(const String& key) const; + Value const* findArray(const String& key) const; + Value const* findObject(const String& key) const; + /// Most general and efficient version of object-mutators. /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30 /// \return non-zero, but JSON_ASSERT if this is neither object nor nullValue. @@ -514,20 +581,28 @@ class JSON_API Value { /// Do nothing if it did not exist. /// \pre type() is objectValue or nullValue /// \post type() is unchanged +#if JSONCPP_HAS_STRING_VIEW + void removeMember(std::string_view key); +#else void removeMember(const char* key); /// Same as removeMember(const char*) /// \param key may contain embedded nulls. void removeMember(const String& key); - /// Same as removeMember(const char* begin, const char* end, Value* removed), - /// but 'key' is null-terminated. - bool removeMember(const char* key, Value* removed); +#endif /** \brief Remove the named map member. * * Update 'removed' iff removed. * \param key may contain embedded nulls. * \return true iff removed (no exceptions) */ +#if JSONCPP_HAS_STRING_VIEW + bool removeMember(std::string_view key, Value* removed); +#else bool removeMember(String const& key, Value* removed); + /// Same as removeMember(const char* begin, const char* end, Value* removed), + /// but 'key' is null-terminated. + bool removeMember(const char* key, Value* removed); +#endif /// Same as removeMember(String const& key, Value* removed) bool removeMember(const char* begin, const char* end, Value* removed); /** \brief Remove the indexed array element. @@ -538,18 +613,20 @@ class JSON_API Value { */ bool removeIndex(ArrayIndex index, Value* removed); +#ifdef JSONCPP_HAS_STRING_VIEW + /// Return true if the object has a member named key. + /// \param key may contain embedded nulls. + bool isMember(std::string_view key) const; +#else /// Return true if the object has a member named key. /// \note 'key' must be null-terminated. bool isMember(const char* key) const; /// Return true if the object has a member named key. /// \param key may contain embedded nulls. bool isMember(const String& key) const; +#endif /// Same as isMember(String const& key)const bool isMember(const char* begin, const char* end) const; -#ifdef JSON_USE_CPPTL - /// Return true if the object has a member named key. - bool isMember(const CppTL::ConstString& key) const; -#endif /// \brief Return a list of the member names. /// @@ -558,13 +635,8 @@ class JSON_API Value { /// \post if type() was nullValue, it remains nullValue Members getMemberNames() const; - //# ifdef JSON_USE_CPPTL - // EnumMemberNames enumMemberNames() const; - // EnumValues enumValues() const; - //# endif - /// \deprecated Always pass len. - [[deprecated("Use setComment(String const&) instead.")]] + JSONCPP_DEPRECATED("Use setComment(String const&) instead.") void setComment(const char* comment, CommentPlacement placement) { setComment(String(comment, strlen(comment)), placement); } @@ -586,6 +658,26 @@ class JSON_API Value { iterator begin(); iterator end(); + /// \brief Returns a reference to the first element in the `Value`. + /// Requires that this value holds an array or json object, with at least one + /// element. + const Value& front() const; + + /// \brief Returns a reference to the first element in the `Value`. + /// Requires that this value holds an array or json object, with at least one + /// element. + Value& front(); + + /// \brief Returns a reference to the last element in the `Value`. + /// Requires that value holds an array or json object, with at least one + /// element. + const Value& back() const; + + /// \brief Returns a reference to the last element in the `Value`. + /// Requires that this value holds an array or json object, with at least one + /// element. + Value& back(); + // Accessors for the [start, limit) range of bytes within the JSON text from // which this value was parsed, if any. void setOffsetStart(ptrdiff_t start); @@ -637,12 +729,12 @@ class JSON_API Value { public: Comments() = default; Comments(const Comments& that); - Comments(Comments&& that); + Comments(Comments&& that) noexcept; Comments& operator=(const Comments& that); - Comments& operator=(Comments&& that); + Comments& operator=(Comments&& that) noexcept; bool has(CommentPlacement slot) const; String get(CommentPlacement slot) const; - void set(CommentPlacement slot, String s); + void set(CommentPlacement slot, String comment); private: using Array = std::array; @@ -656,6 +748,36 @@ class JSON_API Value { ptrdiff_t limit_; }; +template <> inline bool Value::as() const { return asBool(); } +template <> inline bool Value::is() const { return isBool(); } + +template <> inline Int Value::as() const { return asInt(); } +template <> inline bool Value::is() const { return isInt(); } + +template <> inline UInt Value::as() const { return asUInt(); } +template <> inline bool Value::is() const { return isUInt(); } + +#if defined(JSON_HAS_INT64) +template <> inline Int64 Value::as() const { return asInt64(); } +template <> inline bool Value::is() const { return isInt64(); } + +template <> inline UInt64 Value::as() const { return asUInt64(); } +template <> inline bool Value::is() const { return isUInt64(); } +#endif + +template <> inline double Value::as() const { return asDouble(); } +template <> inline bool Value::is() const { return isDouble(); } + +template <> inline String Value::as() const { return asString(); } +template <> inline bool Value::is() const { return isString(); } + +/// These `as` specializations are type conversions, and do not have a +/// corresponding `is`. +template <> inline float Value::as() const { return asFloat(); } +template <> inline const char* Value::as() const { + return asCString(); +} + /** \brief Experimental and untested: represents an element of the "path" to * access a node. */ @@ -666,7 +788,7 @@ class JSON_API PathArgument { PathArgument(); PathArgument(ArrayIndex index); PathArgument(const char* key); - PathArgument(const String& key); + PathArgument(String key); private: enum Kind { kindNone = 0, kindIndex, kindKey }; @@ -688,8 +810,7 @@ class JSON_API PathArgument { */ class JSON_API Path { public: - Path(const String& path, - const PathArgument& a1 = PathArgument(), + Path(const String& path, const PathArgument& a1 = PathArgument(), const PathArgument& a2 = PathArgument(), const PathArgument& a3 = PathArgument(), const PathArgument& a4 = PathArgument(), @@ -702,14 +823,12 @@ class JSON_API Path { Value& make(Value& root) const; private: - typedef std::vector InArgs; - typedef std::vector Args; + using InArgs = std::vector; + using Args = std::vector; void makePath(const String& path, const InArgs& in); - void addPathInArg(const String& path, - const InArgs& in, - InArgs::const_iterator& itInArg, - PathArgument::Kind kind); + void addPathInArg(const String& path, const InArgs& in, + InArgs::const_iterator& itInArg, PathArgument::Kind kind); static void invalidPath(const String& path, int location); Args args_; @@ -720,10 +839,10 @@ class JSON_API Path { */ class JSON_API ValueIteratorBase { public: - typedef std::bidirectional_iterator_tag iterator_category; - typedef unsigned int size_t; - typedef int difference_type; - typedef ValueIteratorBase SelfType; + using iterator_category = std::bidirectional_iterator_tag; + using size_t = unsigned int; + using difference_type = int; + using SelfType = ValueIteratorBase; bool operator==(const SelfType& other) const { return isEqual(other); } @@ -750,7 +869,7 @@ class JSON_API ValueIteratorBase { /// objectValue. /// \deprecated This cannot be used for UTF-8 strings, since there can be /// embedded nulls. - [[deprecated("Use `key = name();` instead.")]] + JSONCPP_DEPRECATED("Use `key = name();` instead.") char const* memberName() const; /// Return the member name of the referenced Value, or NULL if it is not an /// objectValue. @@ -758,7 +877,14 @@ class JSON_API ValueIteratorBase { char const* memberName(char const** end) const; protected: - Value& deref() const; + /*! Internal utility functions to assist with implementing + * other iterator functions. The const and non-const versions + * of the "deref" protected methods expose the protected + * current_ member variable in a way that can often be + * optimized away by the compiler. + */ + const Value& deref() const; + Value& deref(); void increment(); @@ -789,12 +915,12 @@ class JSON_API ValueConstIterator : public ValueIteratorBase { friend class Value; public: - typedef const Value value_type; + using value_type = const Value; // typedef unsigned int size_t; // typedef int difference_type; - typedef const Value& reference; - typedef const Value* pointer; - typedef ValueConstIterator SelfType; + using reference = const Value&; + using pointer = const Value*; + using SelfType = ValueConstIterator; ValueConstIterator(); ValueConstIterator(ValueIterator const& other); @@ -840,12 +966,12 @@ class JSON_API ValueIterator : public ValueIteratorBase { friend class Value; public: - typedef Value value_type; - typedef unsigned int size_t; - typedef int difference_type; - typedef Value& reference; - typedef Value* pointer; - typedef ValueIterator SelfType; + using value_type = Value; + using size_t = unsigned int; + using difference_type = int; + using reference = Value&; + using pointer = Value*; + using SelfType = ValueIterator; ValueIterator(); explicit ValueIterator(const ValueConstIterator& other); @@ -881,13 +1007,25 @@ class JSON_API ValueIterator : public ValueIteratorBase { return *this; } - reference operator*() const { return deref(); } - - pointer operator->() const { return &deref(); } + /*! The return value of non-const iterators can be + * changed, so the these functions are not const + * because the returned references/pointers can be used + * to change state of the base class. + */ + reference operator*() const { return const_cast(deref()); } + pointer operator->() const { return const_cast(&deref()); } }; inline void swap(Value& a, Value& b) { a.swap(b); } +inline const Value& Value::front() const { return *begin(); } + +inline Value& Value::front() { return *begin(); } + +inline const Value& Value::back() const { return *(--end()); } + +inline Value& Value::back() { return *(--end()); } + } // namespace Json #pragma pack(pop) @@ -896,4 +1034,4 @@ inline void swap(Value& a, Value& b) { a.swap(b); } #pragma warning(pop) #endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) -#endif // CPPTL_JSON_H_INCLUDED +#endif // JSON_H_INCLUDED diff --git a/include/json/version.h b/include/json/version.h index ff94372b0..555152c8c 100644 --- a/include/json/version.h +++ b/include/json/version.h @@ -1,27 +1,27 @@ #ifndef JSON_VERSION_H_INCLUDED #define JSON_VERSION_H_INCLUDED -// Note: version must be updated in three places when doing a release. This +// Note: version must be updated in four places when doing a release. This // annoying process ensures that amalgamate, CMake, and meson all report the // correct version. // 1. /meson.build // 2. /include/json/version.h // 3. /CMakeLists.txt +// 4. /MODULE.bazel // IMPORTANT: also update the SOVERSION!! -#define JSONCPP_VERSION_STRING "1.9.2" +#define JSONCPP_VERSION_STRING "1.9.7" #define JSONCPP_VERSION_MAJOR 1 #define JSONCPP_VERSION_MINOR 9 -#define JSONCPP_VERSION_PATCH 2 +#define JSONCPP_VERSION_PATCH 7 #define JSONCPP_VERSION_QUALIFIER #define JSONCPP_VERSION_HEXA \ ((JSONCPP_VERSION_MAJOR << 24) | (JSONCPP_VERSION_MINOR << 16) | \ (JSONCPP_VERSION_PATCH << 8)) -#ifdef JSONCPP_USING_SECURE_MEMORY -#undef JSONCPP_USING_SECURE_MEMORY +#if !defined(JSONCPP_USE_SECURE_MEMORY) +#define JSONCPP_USE_SECURE_MEMORY 0 #endif -#define JSONCPP_USING_SECURE_MEMORY 0 // If non-zero, the library zeroes any memory that it has allocated before // it frees its memory. diff --git a/include/json/writer.h b/include/json/writer.h index 328014166..7c56a2107 100644 --- a/include/json/writer.h +++ b/include/json/writer.h @@ -20,25 +20,25 @@ #pragma warning(disable : 4251) #endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) -#pragma pack(push, 8) +#pragma pack(push) +#pragma pack() namespace Json { class Value; /** - -Usage: -\code - using namespace Json; - void writeToStdout(StreamWriter::Factory const& factory, Value const& value) { - std::unique_ptr const writer( - factory.newStreamWriter()); - writer->write(value, &std::cout); - std::cout << std::endl; // add lf and flush - } -\endcode -*/ + * + * Usage: + * \code + * using namespace Json; + * void writeToStdout(StreamWriter::Factory const& factory, Value const& value) + * { std::unique_ptr const writer( factory.newStreamWriter()); + * writer->write(value, &std::cout); + * std::cout << std::endl; // add lf and flush + * } + * \endcode + */ class JSON_API StreamWriter { protected: OStream* sout_; // not owned; will not delete @@ -46,10 +46,11 @@ class JSON_API StreamWriter { StreamWriter(); virtual ~StreamWriter(); /** Write Value into document as configured in sub-class. - Do not take ownership of sout, but maintain a reference during function. - \pre sout != NULL - \return zero on success (For now, we always return zero, so check the - stream instead.) \throw std::exception possibly, depending on configuration + * Do not take ownership of sout, but maintain a reference during function. + * \pre sout != NULL + * \return zero on success (For now, we always return zero, so check the + * stream instead.) \throw std::exception possibly, depending on + * configuration */ virtual int write(Value const& root, OStream* sout) = 0; @@ -63,7 +64,7 @@ class JSON_API StreamWriter { */ virtual StreamWriter* newStreamWriter() const = 0; }; // Factory -}; // StreamWriter +}; // StreamWriter /** \brief Write into stringstream, then return string, for convenience. * A StreamWriter will be created from the factory, used, and then deleted. @@ -73,49 +74,51 @@ String JSON_API writeString(StreamWriter::Factory const& factory, /** \brief Build a StreamWriter implementation. -Usage: -\code - using namespace Json; - Value value = ...; - StreamWriterBuilder builder; - builder["commentStyle"] = "None"; - builder["indentation"] = " "; // or whatever you like - std::unique_ptr writer( - builder.newStreamWriter()); - writer->write(value, &std::cout); - std::cout << std::endl; // add lf and flush -\endcode +* Usage: +* \code +* using namespace Json; +* Value value = ...; +* StreamWriterBuilder builder; +* builder["commentStyle"] = "None"; +* builder["indentation"] = " "; // or whatever you like +* std::unique_ptr writer( +* builder.newStreamWriter()); +* writer->write(value, &std::cout); +* std::cout << std::endl; // add lf and flush +* \endcode */ class JSON_API StreamWriterBuilder : public StreamWriter::Factory { public: // Note: We use a Json::Value so that we can add data-members to this class // without a major version bump. /** Configuration of this builder. - Available settings (case-sensitive): - - "commentStyle": "None" or "All" - - "indentation": "". - - Setting this to an empty string also omits newline characters. - - "enableYAMLCompatibility": false or true - - slightly change the whitespace around colons - - "dropNullPlaceholders": false or true - - Drop the "null" string from the writer's output for nullValues. - Strictly speaking, this is not valid JSON. But when the output is being - fed to a browser's JavaScript, it makes for smaller output and the - browser can handle the output just fine. - - "useSpecialFloats": false or true - - If true, outputs non-finite floating point values in the following way: - NaN values as "NaN", positive infinity as "Infinity", and negative - infinity as "-Infinity". - - "precision": int - - Number of precision digits for formatting of real values. - - "precisionType": "significant"(default) or "decimal" - - Type of precision for formatting of real values. - - You can examine 'settings_` yourself - to see the defaults. You can also write and read them just like any - JSON Value. - \sa setDefaults() - */ + * Available settings (case-sensitive): + * - "commentStyle": "None" or "All" + * - "indentation": "". + * - Setting this to an empty string also omits newline characters. + * - "enableYAMLCompatibility": false or true + * - slightly change the whitespace around colons + * - "dropNullPlaceholders": false or true + * - Drop the "null" string from the writer's output for nullValues. + * Strictly speaking, this is not valid JSON. But when the output is being + * fed to a browser's JavaScript, it makes for smaller output and the + * browser can handle the output just fine. + * - "useSpecialFloats": false or true + * - If true, outputs non-finite floating point values in the following way: + * NaN values as "NaN", positive infinity as "Infinity", and negative + * infinity as "-Infinity". + * - "precision": int + * - Number of precision digits for formatting of real values. + * - "precisionType": "significant"(default) or "decimal" + * - Type of precision for formatting of real values. + * - "emitUTF8": false or true + * - If true, outputs raw UTF8 strings instead of escaping them. + + * You can examine 'settings_` yourself + * to see the defaults. You can also write and read them just like any + * JSON Value. + * \sa setDefaults() + */ Json::Value settings_; StreamWriterBuilder(); @@ -145,7 +148,7 @@ class JSON_API StreamWriterBuilder : public StreamWriter::Factory { /** \brief Abstract class for writers. * \deprecated Use StreamWriter. (And really, this is an implementation detail.) */ -class [[deprecated("Use StreamWriter instead")]] JSON_API Writer { +class JSON_API Writer { public: virtual ~Writer(); @@ -165,8 +168,7 @@ class [[deprecated("Use StreamWriter instead")]] JSON_API Writer { #pragma warning(push) #pragma warning(disable : 4996) // Deriving from deprecated class #endif -class [[deprecated("Use StreamWriterBuilder instead")]] JSON_API FastWriter - : public Writer { +class JSON_API FastWriter : public Writer { public: FastWriter(); ~FastWriter() override = default; @@ -215,7 +217,7 @@ class [[deprecated("Use StreamWriterBuilder instead")]] JSON_API FastWriter * - otherwise, it the values do not fit on one line, or the array contains * object or non empty array, then print one value per line. * - * If the Value have comments then they are outputed according to their + * If the Value have comments then they are outputted according to their *#CommentPlacement. * * \sa Reader, Value, Value::setComment() @@ -225,8 +227,7 @@ class [[deprecated("Use StreamWriterBuilder instead")]] JSON_API FastWriter #pragma warning(push) #pragma warning(disable : 4996) // Deriving from deprecated class #endif -class [[deprecated("Use StreamWriterBuilder instead")]] JSON_API - StyledWriter : public Writer { +class JSON_API StyledWriter : public Writer { public: StyledWriter(); ~StyledWriter() override = default; @@ -252,7 +253,7 @@ class [[deprecated("Use StreamWriterBuilder instead")]] JSON_API static bool hasCommentForValue(const Value& value); static String normalizeEOL(const String& text); - typedef std::vector ChildValues; + using ChildValues = std::vector; ChildValues childValues_; String document_; @@ -284,7 +285,7 @@ class [[deprecated("Use StreamWriterBuilder instead")]] JSON_API * - otherwise, it the values do not fit on one line, or the array contains * object or non empty array, then print one value per line. * - * If the Value have comments then they are outputed according to their + * If the Value have comments then they are outputted according to their #CommentPlacement. * * \sa Reader, Value, Value::setComment() @@ -294,8 +295,7 @@ class [[deprecated("Use StreamWriterBuilder instead")]] JSON_API #pragma warning(push) #pragma warning(disable : 4996) // Deriving from deprecated class #endif -class [[deprecated("Use StreamWriterBuilder instead")]] JSON_API - StyledStreamWriter { +class JSON_API StyledStreamWriter { public: /** * \param indentation Each level will be indented by this amount extra. @@ -326,7 +326,7 @@ class [[deprecated("Use StreamWriterBuilder instead")]] JSON_API static bool hasCommentForValue(const Value& value); static String normalizeEOL(const String& text); - typedef std::vector ChildValues; + using ChildValues = std::vector; ChildValues childValues_; OStream* document_; @@ -346,12 +346,12 @@ String JSON_API valueToString(UInt value); #endif // if defined(JSON_HAS_INT64) String JSON_API valueToString(LargestInt value); String JSON_API valueToString(LargestUInt value); -String JSON_API -valueToString(double value, - unsigned int precision = Value::defaultRealPrecision, - PrecisionType precisionType = PrecisionType::significantDigits); +String JSON_API valueToString( + double value, unsigned int precision = Value::defaultRealPrecision, + PrecisionType precisionType = PrecisionType::significantDigits); String JSON_API valueToString(bool value); String JSON_API valueToQuotedString(const char* value); +String JSON_API valueToQuotedString(const char* value, size_t length); /// \brief Output using the StyledStreamWriter. /// \see Json::operator>>() diff --git a/jsoncpp-namespaced-targets.cmake b/jsoncpp-namespaced-targets.cmake new file mode 100644 index 000000000..70a79ee7f --- /dev/null +++ b/jsoncpp-namespaced-targets.cmake @@ -0,0 +1,9 @@ +if (NOT TARGET JsonCpp::JsonCpp) + if (TARGET jsoncpp_static) + add_library(JsonCpp::JsonCpp INTERFACE IMPORTED) + set_target_properties(JsonCpp::JsonCpp PROPERTIES INTERFACE_LINK_LIBRARIES "jsoncpp_static") + elseif (TARGET jsoncpp_lib) + add_library(JsonCpp::JsonCpp INTERFACE IMPORTED) + set_target_properties(JsonCpp::JsonCpp PROPERTIES INTERFACE_LINK_LIBRARIES "jsoncpp_lib") + endif () +endif () diff --git a/jsoncppConfig.cmake.in b/jsoncppConfig.cmake.in new file mode 100644 index 000000000..fdd9fea6b --- /dev/null +++ b/jsoncppConfig.cmake.in @@ -0,0 +1,11 @@ +cmake_policy(PUSH) +cmake_policy(VERSION 3.0...3.26) + +@PACKAGE_INIT@ + +include ( "${CMAKE_CURRENT_LIST_DIR}/jsoncpp-targets.cmake" ) +include ( "${CMAKE_CURRENT_LIST_DIR}/jsoncpp-namespaced-targets.cmake" ) + +check_required_components(JsonCpp) + +cmake_policy(POP) diff --git a/jsoncppConfig.cmake.meson.in b/jsoncppConfig.cmake.meson.in new file mode 100644 index 000000000..be8852d0c --- /dev/null +++ b/jsoncppConfig.cmake.meson.in @@ -0,0 +1,6 @@ +@PACKAGE_INIT@ + +@MESON_SHARED_TARGET@ +@MESON_STATIC_TARGET@ + +include ( "${CMAKE_CURRENT_LIST_DIR}/jsoncpp-namespaced-targets.cmake" ) diff --git a/makerelease.py b/makerelease.py deleted file mode 100644 index bc716cbab..000000000 --- a/makerelease.py +++ /dev/null @@ -1,390 +0,0 @@ -# Copyright 2010 Baptiste Lepilleur and The JsonCpp Authors -# Distributed under MIT license, or public domain if desired and -# recognized in your jurisdiction. -# See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -"""Tag the sandbox for release, make source and doc tarballs. - -Requires Python 2.6 - -Example of invocation (use to test the script): -python makerelease.py --platform=msvc6,msvc71,msvc80,msvc90,mingw -ublep 0.6.0 0.7.0-dev - -When testing this script: -python makerelease.py --force --retag --platform=msvc6,msvc71,msvc80,mingw -ublep test-0.6.0 test-0.6.1-dev - -Example of invocation when doing a release: -python makerelease.py 0.5.0 0.6.0-dev - -Note: This was for Subversion. Now that we are in GitHub, we do not -need to build versioned tarballs anymore, so makerelease.py is defunct. -""" - -from __future__ import print_function -import os.path -import subprocess -import sys -import doxybuild -import subprocess -import xml.etree.ElementTree as ElementTree -import shutil -import urllib2 -import tempfile -import os -import time -from devtools import antglob, fixeol, tarball -import amalgamate - -SVN_ROOT = 'https://jsoncpp.svn.sourceforge.net/svnroot/jsoncpp/' -SVN_TAG_ROOT = SVN_ROOT + 'tags/jsoncpp' -SCONS_LOCAL_URL = 'http://sourceforge.net/projects/scons/files/scons-local/1.2.0/scons-local-1.2.0.tar.gz/download' -SOURCEFORGE_PROJECT = 'jsoncpp' - -def set_version(version): - with open('version','wb') as f: - f.write(version.strip()) - -def rmdir_if_exist(dir_path): - if os.path.isdir(dir_path): - shutil.rmtree(dir_path) - -class SVNError(Exception): - pass - -def svn_command(command, *args): - cmd = ['svn', '--non-interactive', command] + list(args) - print('Running:', ' '.join(cmd)) - process = subprocess.Popen(cmd, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) - stdout = process.communicate()[0] - if process.returncode: - error = SVNError('SVN command failed:\n' + stdout) - error.returncode = process.returncode - raise error - return stdout - -def check_no_pending_commit(): - """Checks that there is no pending commit in the sandbox.""" - stdout = svn_command('status', '--xml') - etree = ElementTree.fromstring(stdout) - msg = [] - for entry in etree.getiterator('entry'): - path = entry.get('path') - status = entry.find('wc-status').get('item') - if status != 'unversioned' and path != 'version': - msg.append('File "%s" has pending change (status="%s")' % (path, status)) - if msg: - msg.insert(0, 'Pending change to commit found in sandbox. Commit them first!') - return '\n'.join(msg) - -def svn_join_url(http://github.com/base_url,%20suffix): - if not base_url.endswith('/'): - base_url += '/' - if suffix.startswith('/'): - suffix = suffix[1:] - return base_url + suffix - -def svn_check_if_tag_exist(tag_url): - """Checks if a tag exist. - Returns: True if the tag exist, False otherwise. - """ - try: - list_stdout = svn_command('list', tag_url) - except SVNError as e: - if e.returncode != 1 or not str(e).find('tag_url'): - raise e - # otherwise ignore error, meaning tag does not exist - return False - return True - -def svn_commit(message): - """Commit the sandbox, providing the specified comment. - """ - svn_command('ci', '-m', message) - -def svn_tag_sandbox(tag_url, message): - """Makes a tag based on the sandbox revisions. - """ - svn_command('copy', '-m', message, '.', tag_url) - -def svn_remove_tag(tag_url, message): - """Removes an existing tag. - """ - svn_command('delete', '-m', message, tag_url) - -def svn_export(tag_url, export_dir): - """Exports the tag_url revision to export_dir. - Target directory, including its parent is created if it does not exist. - If the directory export_dir exist, it is deleted before export proceed. - """ - rmdir_if_exist(export_dir) - svn_command('export', tag_url, export_dir) - -def fix_sources_eol(dist_dir): - """Set file EOL for tarball distribution. - """ - print('Preparing exported source file EOL for distribution...') - prune_dirs = antglob.prune_dirs + 'scons-local* ./build* ./libs ./dist' - win_sources = antglob.glob(dist_dir, - includes = '**/*.sln **/*.vcproj', - prune_dirs = prune_dirs) - unix_sources = antglob.glob(dist_dir, - includes = '''**/*.h **/*.cpp **/*.inl **/*.txt **/*.dox **/*.py **/*.html **/*.in - sconscript *.json *.expected AUTHORS LICENSE''', - excludes = antglob.default_excludes + 'scons.py sconsign.py scons-*', - prune_dirs = prune_dirs) - for path in win_sources: - fixeol.fix_source_eol(path, is_dry_run = False, verbose = True, eol = '\r\n') - for path in unix_sources: - fixeol.fix_source_eol(path, is_dry_run = False, verbose = True, eol = '\n') - -def download(url, target_path): - """Download file represented by url to target_path. - """ - f = urllib2.urlopen(url) - try: - data = f.read() - finally: - f.close() - fout = open(target_path, 'wb') - try: - fout.write(data) - finally: - fout.close() - -def check_compile(distcheck_top_dir, platform): - cmd = [sys.executable, 'scons.py', 'platform=%s' % platform, 'check'] - print('Running:', ' '.join(cmd)) - log_path = os.path.join(distcheck_top_dir, 'build-%s.log' % platform) - flog = open(log_path, 'wb') - try: - process = subprocess.Popen(cmd, - stdout=flog, - stderr=subprocess.STDOUT, - cwd=distcheck_top_dir) - stdout = process.communicate()[0] - status = (process.returncode == 0) - finally: - flog.close() - return (status, log_path) - -def write_tempfile(content, **kwargs): - fd, path = tempfile.mkstemp(**kwargs) - f = os.fdopen(fd, 'wt') - try: - f.write(content) - finally: - f.close() - return path - -class SFTPError(Exception): - pass - -def run_sftp_batch(userhost, sftp, batch, retry=0): - path = write_tempfile(batch, suffix='.sftp', text=True) - # psftp -agent -C blep,jsoncpp@web.sourceforge.net -batch -b batch.sftp -bc - cmd = [sftp, '-agent', '-C', '-batch', '-b', path, '-bc', userhost] - error = None - for retry_index in range(0, max(1,retry)): - heading = retry_index == 0 and 'Running:' or 'Retrying:' - print(heading, ' '.join(cmd)) - process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - stdout = process.communicate()[0] - if process.returncode != 0: - error = SFTPError('SFTP batch failed:\n' + stdout) - else: - break - if error: - raise error - return stdout - -def sourceforge_web_synchro(sourceforge_project, doc_dir, - user=None, sftp='sftp'): - """Notes: does not synchronize sub-directory of doc-dir. - """ - userhost = '%s,%s@web.sourceforge.net' % (user, sourceforge_project) - stdout = run_sftp_batch(userhost, sftp, """ -cd htdocs -dir -exit -""") - existing_paths = set() - collect = 0 - for line in stdout.split('\n'): - line = line.strip() - if not collect and line.endswith('> dir'): - collect = True - elif collect and line.endswith('> exit'): - break - elif collect == 1: - collect = 2 - elif collect == 2: - path = line.strip().split()[-1:] - if path and path[0] not in ('.', '..'): - existing_paths.add(path[0]) - upload_paths = set([os.path.basename(p) for p in antglob.glob(doc_dir)]) - paths_to_remove = existing_paths - upload_paths - if paths_to_remove: - print('Removing the following file from web:') - print('\n'.join(paths_to_remove)) - stdout = run_sftp_batch(userhost, sftp, """cd htdocs -rm %s -exit""" % ' '.join(paths_to_remove)) - print('Uploading %d files:' % len(upload_paths)) - batch_size = 10 - upload_paths = list(upload_paths) - start_time = time.time() - for index in range(0,len(upload_paths),batch_size): - paths = upload_paths[index:index+batch_size] - file_per_sec = (time.time() - start_time) / (index+1) - remaining_files = len(upload_paths) - index - remaining_sec = file_per_sec * remaining_files - print('%d/%d, ETA=%.1fs' % (index+1, len(upload_paths), remaining_sec)) - run_sftp_batch(userhost, sftp, """cd htdocs -lcd %s -mput %s -exit""" % (doc_dir, ' '.join(paths)), retry=3) - -def sourceforge_release_tarball(sourceforge_project, paths, user=None, sftp='sftp'): - userhost = '%s,%s@frs.sourceforge.net' % (user, sourceforge_project) - run_sftp_batch(userhost, sftp, """ -mput %s -exit -""" % (' '.join(paths),)) - - -def main(): - usage = """%prog release_version next_dev_version -Update 'version' file to release_version and commit. -Generates the document tarball. -Tags the sandbox revision with release_version. -Update 'version' file to next_dev_version and commit. - -Performs an svn export of tag release version, and build a source tarball. - -Must be started in the project top directory. - -Warning: --force should only be used when developing/testing the release script. -""" - from optparse import OptionParser - parser = OptionParser(usage=usage) - parser.allow_interspersed_args = False - parser.add_option('--dot', dest="dot_path", action='http://github.com/store', default=doxybuild.find_program('dot'), - help="""Path to GraphViz dot tool. Must be full qualified path. [Default: %default]""") - parser.add_option('--doxygen', dest="doxygen_path", action='http://github.com/store', default=doxybuild.find_program('doxygen'), - help="""Path to Doxygen tool. [Default: %default]""") - parser.add_option('--force', dest="ignore_pending_commit", action='http://github.com/store_true', default=False, - help="""Ignore pending commit. [Default: %default]""") - parser.add_option('--retag', dest="retag_release", action='http://github.com/store_true', default=False, - help="""Overwrite release existing tag if it exist. [Default: %default]""") - parser.add_option('-p', '--platforms', dest="platforms", action='http://github.com/store', default='', - help="""Comma separated list of platform passed to scons for build check.""") - parser.add_option('--no-test', dest="no_test", action='http://github.com/store_true', default=False, - help="""Skips build check.""") - parser.add_option('--no-web', dest="no_web", action='http://github.com/store_true', default=False, - help="""Do not update web site.""") - parser.add_option('-u', '--upload-user', dest="user", action='http://github.com/store', - help="""Sourceforge user for SFTP documentation upload.""") - parser.add_option('--sftp', dest='sftp', action='http://github.com/store', default=doxybuild.find_program('psftp', 'sftp'), - help="""Path of the SFTP compatible binary used to upload the documentation.""") - parser.enable_interspersed_args() - options, args = parser.parse_args() - - if len(args) != 2: - parser.error('release_version missing on command-line.') - release_version = args[0] - next_version = args[1] - - if not options.platforms and not options.no_test: - parser.error('You must specify either --platform or --no-test option.') - - if options.ignore_pending_commit: - msg = '' - else: - msg = check_no_pending_commit() - if not msg: - print('Setting version to', release_version) - set_version(release_version) - svn_commit('Release ' + release_version) - tag_url = svn_join_url(http://github.com/SVN_TAG_ROOT,%20release_version) - if svn_check_if_tag_exist(tag_url): - if options.retag_release: - svn_remove_tag(tag_url, 'Overwriting previous tag') - else: - print('Aborting, tag %s already exist. Use --retag to overwrite it!' % tag_url) - sys.exit(1) - svn_tag_sandbox(tag_url, 'Release ' + release_version) - - print('Generated doxygen document...') -## doc_dirname = r'jsoncpp-api-html-0.5.0' -## doc_tarball_path = r'e:\prg\vc\Lib\jsoncpp-trunk\dist\jsoncpp-api-html-0.5.0.tar.gz' - doc_tarball_path, doc_dirname = doxybuild.build_doc(options, make_release=True) - doc_distcheck_dir = 'dist/doccheck' - tarball.decompress(doc_tarball_path, doc_distcheck_dir) - doc_distcheck_top_dir = os.path.join(doc_distcheck_dir, doc_dirname) - - export_dir = 'dist/export' - svn_export(tag_url, export_dir) - fix_sources_eol(export_dir) - - source_dir = 'jsoncpp-src-' + release_version - source_tarball_path = 'dist/%s.tar.gz' % source_dir - print('Generating source tarball to', source_tarball_path) - tarball.make_tarball(source_tarball_path, [export_dir], export_dir, prefix_dir=source_dir) - - amalgamation_tarball_path = 'dist/%s-amalgamation.tar.gz' % source_dir - print('Generating amalgamation source tarball to', amalgamation_tarball_path) - amalgamation_dir = 'dist/amalgamation' - amalgamate.amalgamate_source(export_dir, '%s/jsoncpp.cpp' % amalgamation_dir, 'json/json.h') - amalgamation_source_dir = 'jsoncpp-src-amalgamation' + release_version - tarball.make_tarball(amalgamation_tarball_path, [amalgamation_dir], - amalgamation_dir, prefix_dir=amalgamation_source_dir) - - # Decompress source tarball, download and install scons-local - distcheck_dir = 'dist/distcheck' - distcheck_top_dir = distcheck_dir + '/' + source_dir - print('Decompressing source tarball to', distcheck_dir) - rmdir_if_exist(distcheck_dir) - tarball.decompress(source_tarball_path, distcheck_dir) - scons_local_path = 'dist/scons-local.tar.gz' - print('Downloading scons-local to', scons_local_path) - download(SCONS_LOCAL_URL, scons_local_path) - print('Decompressing scons-local to', distcheck_top_dir) - tarball.decompress(scons_local_path, distcheck_top_dir) - - # Run compilation - print('Compiling decompressed tarball') - all_build_status = True - for platform in options.platforms.split(','): - print('Testing platform:', platform) - build_status, log_path = check_compile(distcheck_top_dir, platform) - print('see build log:', log_path) - print(build_status and '=> ok' or '=> FAILED') - all_build_status = all_build_status and build_status - if not build_status: - print('Testing failed on at least one platform, aborting...') - svn_remove_tag(tag_url, 'Removing tag due to failed testing') - sys.exit(1) - if options.user: - if not options.no_web: - print('Uploading documentation using user', options.user) - sourceforge_web_synchro(SOURCEFORGE_PROJECT, doc_distcheck_top_dir, user=options.user, sftp=options.sftp) - print('Completed documentation upload') - print('Uploading source and documentation tarballs for release using user', options.user) - sourceforge_release_tarball(SOURCEFORGE_PROJECT, - [source_tarball_path, doc_tarball_path], - user=options.user, sftp=options.sftp) - print('Source and doc release tarballs uploaded') - else: - print('No upload user specified. Web site and download tarball were not uploaded.') - print('Tarball can be found at:', doc_tarball_path) - - # Set next version number and commit - set_version(next_version) - svn_commit('Released ' + release_version) - else: - sys.stderr.write(msg + '\n') - -if __name__ == '__main__': - main() diff --git a/meson.build b/meson.build index d1f49897a..2648c3071 100644 --- a/meson.build +++ b/meson.build @@ -2,34 +2,35 @@ project( 'jsoncpp', 'cpp', - # Note: version must be updated in three places when doing a release. This + # Note: version must be updated in four places when doing a release. This # annoying process ensures that amalgamate, CMake, and meson all report the # correct version. # 1. /meson.build # 2. /include/json/version.h # 3. /CMakeLists.txt + # 4. /MODULE.bazel # IMPORTANT: also update the SOVERSION!! - version : '1.9.2', + version : '1.9.7', default_options : [ 'buildtype=release', 'cpp_std=c++11', 'warning_level=1'], license : 'Public Domain', - meson_version : '>= 0.50.0') + meson_version : '>= 0.54.0') -jsoncpp_headers = [ +jsoncpp_headers = files([ 'include/json/allocator.h', 'include/json/assertions.h', - 'include/json/autolink.h', 'include/json/config.h', - 'include/json/features.h', + 'include/json/json_features.h', 'include/json/forwards.h', 'include/json/json.h', 'include/json/reader.h', 'include/json/value.h', 'include/json/version.h', - 'include/json/writer.h'] + 'include/json/writer.h', +]) jsoncpp_include_directories = include_directories('include') install_headers( @@ -45,13 +46,12 @@ else endif jsoncpp_lib = library( - 'jsoncpp', - [ jsoncpp_headers, - 'src/lib_json/json_tool.h', + 'jsoncpp', files([ 'src/lib_json/json_reader.cpp', 'src/lib_json/json_value.cpp', - 'src/lib_json/json_writer.cpp'], - soversion : 22, + 'src/lib_json/json_writer.cpp', + ]), + soversion : 27, install : true, include_directories : jsoncpp_include_directories, cpp_args: dll_export_flag) @@ -63,22 +63,62 @@ import('pkgconfig').generate( filebase : 'jsoncpp', description : 'A C++ library for interacting with JSON') +cmakeconf = configuration_data() +cmakeconf.set('MESON_LIB_DIR', get_option('libdir')) +cmakeconf.set('MESON_INCLUDE_DIR', get_option('includedir')) + +fs = import('fs') +if get_option('default_library') == 'shared' + shared_name = fs.name(jsoncpp_lib.full_path()) +endif +if get_option('default_library') == 'static' + static_name = fs.name(jsoncpp_lib.full_path()) +endif +if get_option('default_library') == 'both' + shared_name = fs.name(jsoncpp_lib.get_shared_lib().full_path()) + static_name = fs.name(jsoncpp_lib.get_static_lib().full_path()) +endif + +if get_option('default_library') == 'shared' or get_option('default_library') == 'both' + cmakeconf.set('MESON_SHARED_TARGET', ''' +add_library(jsoncpp_lib IMPORTED SHARED) +set_target_properties(jsoncpp_lib PROPERTIES + IMPORTED_LOCATION "''' + join_paths('${PACKAGE_PREFIX_DIR}', get_option('libdir'), shared_name) + '''" + INTERFACE_INCLUDE_DIRECTORIES "''' + join_paths('${PACKAGE_PREFIX_DIR}', get_option('includedir')) + '")') +endif +if get_option('default_library') == 'static' or get_option('default_library') == 'both' + cmakeconf.set('MESON_STATIC_TARGET', ''' +add_library(jsoncpp_static IMPORTED STATIC) +set_target_properties(jsoncpp_static PROPERTIES + IMPORTED_LOCATION "''' + join_paths('${PACKAGE_PREFIX_DIR}', get_option('libdir'), static_name) + '''" + INTERFACE_INCLUDE_DIRECTORIES "''' + join_paths('${PACKAGE_PREFIX_DIR}', get_option('includedir')) + '")') +endif + +import('cmake').configure_package_config_file( + name: 'jsoncpp', + input: 'jsoncppConfig.cmake.meson.in', + configuration: cmakeconf) +install_data('jsoncpp-namespaced-targets.cmake', install_dir : join_paths(get_option('libdir'), 'cmake', jsoncpp_lib.name())) + # for libraries bundling jsoncpp jsoncpp_dep = declare_dependency( include_directories : jsoncpp_include_directories, link_with : jsoncpp_lib, - version : meson.project_version(), - ) + version : meson.project_version()) # tests -python = import('python3').find_python() +if meson.is_subproject() or not get_option('tests') + subdir_done() +endif + +python = find_program('python3') jsoncpp_test = executable( - 'jsoncpp_test', - [ 'src/test_lib_json/jsontest.cpp', - 'src/test_lib_json/jsontest.h', + 'jsoncpp_test', files([ + 'src/test_lib_json/jsontest.cpp', 'src/test_lib_json/main.cpp', - 'src/test_lib_json/fuzz.cpp'], + 'src/test_lib_json/fuzz.cpp', + ]), include_directories : jsoncpp_include_directories, link_with : jsoncpp_lib, install : false, @@ -101,5 +141,17 @@ test( '-B', join_paths(meson.current_source_dir(), 'test/runjsontests.py'), jsontestrunner, - join_paths(meson.current_source_dir(), 'test/data')] + join_paths(meson.current_source_dir(), 'test/data')], + ) +test( + 'jsonchecker_jsontestrunner', + python, + is_parallel : false, + args : [ + '-B', + join_paths(meson.current_source_dir(), 'test/runjsontests.py'), + '--with-json-checker', + jsontestrunner, + join_paths(meson.current_source_dir(), 'test/data')], + workdir : join_paths(meson.current_source_dir(), 'test/data'), ) diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 000000000..9c215ae6f --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,5 @@ +option( + 'tests', + type : 'boolean', + value : true, + description : 'Enable building tests') diff --git a/pkg-config/jsoncpp.pc.in b/pkg-config/jsoncpp.pc.in index dea51f512..2a2221069 100644 --- a/pkg-config/jsoncpp.pc.in +++ b/pkg-config/jsoncpp.pc.in @@ -1,9 +1,11 @@ -libdir=@CMAKE_INSTALL_FULL_LIBDIR@ -includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=@CMAKE_INSTALL_PREFIX@ +libdir=@libdir_for_pc_file@ +includedir=@includedir_for_pc_file@ Name: jsoncpp Description: A C++ library for interacting with JSON -Version: @JSONCPP_VERSION@ +Version: @PROJECT_VERSION@ URL: https://github.com/open-source-parsers/jsoncpp Libs: -L${libdir} -ljsoncpp Cflags: -I${includedir} diff --git a/reformat.sh b/reformat.sh new file mode 100755 index 000000000..cdc03b1ea --- /dev/null +++ b/reformat.sh @@ -0,0 +1 @@ +find src -name '*.cpp' -or -name '*.h' | xargs clang-format -i diff --git a/src/jsontestrunner/CMakeLists.txt b/src/jsontestrunner/CMakeLists.txt index 023a44e64..1fc71ea87 100644 --- a/src/jsontestrunner/CMakeLists.txt +++ b/src/jsontestrunner/CMakeLists.txt @@ -1,13 +1,28 @@ -find_package(PythonInterp 2.6) +if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12.0) + # The new Python3 module is much more robust than the previous PythonInterp + find_package(Python3 COMPONENTS Interpreter) + # Set variables for backwards compatibility with cmake < 3.12.0 + set(PYTHONINTERP_FOUND ${Python3_Interpreter_FOUND}) + set(PYTHON_EXECUTABLE ${Python3_EXECUTABLE}) +else() + set(Python_ADDITIONAL_VERSIONS 3.8) + find_package(PythonInterp 3) +endif() add_executable(jsontestrunner_exe - main.cpp - ) + main.cpp +) if(BUILD_SHARED_LIBS) - add_compile_definitions( JSON_DLL ) + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12.0) + add_compile_definitions( JSON_DLL ) + else() + add_definitions(-DJSON_DLL) + endif() + target_link_libraries(jsontestrunner_exe jsoncpp_lib) +else() + target_link_libraries(jsontestrunner_exe jsoncpp_static) endif() -target_link_libraries(jsontestrunner_exe jsoncpp_lib) set_target_properties(jsontestrunner_exe PROPERTIES OUTPUT_NAME jsontestrunner_exe) @@ -19,18 +34,18 @@ if(PYTHONINTERP_FOUND) # Run unit tests in post-build # (default cmake workflow hides away the test result into a file, resulting in poor dev workflow?!?) add_custom_target(jsoncpp_readerwriter_tests - "${PYTHON_EXECUTABLE}" -B "${RUNJSONTESTS_PATH}" $ "${TEST_DIR}/data" - DEPENDS jsontestrunner_exe jsoncpp_test - ) + "${PYTHON_EXECUTABLE}" -B "${RUNJSONTESTS_PATH}" $ "${TEST_DIR}/data" + DEPENDS jsontestrunner_exe jsoncpp_test + ) add_custom_target(jsoncpp_check DEPENDS jsoncpp_readerwriter_tests) ## Create tests for dashboard submission, allows easy review of CI results https://my.cdash.org/index.php?project=jsoncpp add_test(NAME jsoncpp_readerwriter - COMMAND "${PYTHON_EXECUTABLE}" -B "${RUNJSONTESTS_PATH}" $ "${TEST_DIR}/data" - WORKING_DIRECTORY "${TEST_DIR}/data" + COMMAND "${PYTHON_EXECUTABLE}" -B "${RUNJSONTESTS_PATH}" $ "${TEST_DIR}/data" + WORKING_DIRECTORY "${TEST_DIR}/data" ) add_test(NAME jsoncpp_readerwriter_json_checker - COMMAND "${PYTHON_EXECUTABLE}" -B "${RUNJSONTESTS_PATH}" --with-json-checker $ "${TEST_DIR}/data" - WORKING_DIRECTORY "${TEST_DIR}/data" + COMMAND "${PYTHON_EXECUTABLE}" -B "${RUNJSONTESTS_PATH}" --with-json-checker $ "${TEST_DIR}/data" + WORKING_DIRECTORY "${TEST_DIR}/data" ) endif() diff --git a/src/jsontestrunner/main.cpp b/src/jsontestrunner/main.cpp index d2d41aa5f..ab6a80039 100644 --- a/src/jsontestrunner/main.cpp +++ b/src/jsontestrunner/main.cpp @@ -15,7 +15,9 @@ #include // sort #include +#include #include +#include #include struct Options { @@ -55,10 +57,10 @@ static Json::String readInputTestFile(const char* path) { if (!file) return ""; fseek(file, 0, SEEK_END); - long const size = ftell(file); - size_t const usize = static_cast(size); + auto const size = ftell(file); + auto const usize = static_cast(size); fseek(file, 0, SEEK_SET); - char* buffer = new char[size + 1]; + auto buffer = new char[size + 1]; buffer[size] = 0; Json::String text; if (fread(buffer, 1, usize, file) == usize) @@ -68,8 +70,8 @@ static Json::String readInputTestFile(const char* path) { return text; } -static void -printValueTree(FILE* fout, Json::Value& value, const Json::String& path = ".") { +static void printValueTree(FILE* fout, Json::Value& value, + const Json::String& path = ".") { if (value.hasComment(Json::commentBefore)) { fprintf(fout, "%s\n", value.getComment(Json::commentBefore).c_str()); } @@ -109,7 +111,7 @@ printValueTree(FILE* fout, Json::Value& value, const Json::String& path = ".") { Json::Value::Members members(value.getMemberNames()); std::sort(members.begin(), members.end()); Json::String suffix = *(path.end() - 1) == '.' ? "" : "."; - for (auto name : members) { + for (const auto& name : members) { printValueTree(fout, value[name], path + suffix + name); } } break; @@ -125,21 +127,46 @@ printValueTree(FILE* fout, Json::Value& value, const Json::String& path = ".") { static int parseAndSaveValueTree(const Json::String& input, const Json::String& actual, const Json::String& kind, - const Json::Features& features, - bool parseOnly, - Json::Value* root) { - Json::Reader reader(features); - bool parsingSuccessful = - reader.parse(input.data(), input.data() + input.size(), *root); - if (!parsingSuccessful) { - printf("Failed to parse %s file: \n%s\n", kind.c_str(), - reader.getFormattedErrorMessages().c_str()); - return 1; + const Json::Features& features, bool parseOnly, + Json::Value* root, bool use_legacy) { + if (!use_legacy) { + Json::CharReaderBuilder builder; + + builder.settings_["allowComments"] = features.allowComments_; + builder.settings_["strictRoot"] = features.strictRoot_; + builder.settings_["allowDroppedNullPlaceholders"] = + features.allowDroppedNullPlaceholders_; + builder.settings_["allowNumericKeys"] = features.allowNumericKeys_; + + std::unique_ptr reader(builder.newCharReader()); + Json::String errors; + const bool parsingSuccessful = + reader->parse(input.data(), input.data() + input.size(), root, &errors); + + if (!parsingSuccessful) { + std::cerr << "Failed to parse " << kind << " file: " << std::endl + << errors << std::endl; + return 1; + } + + // We may instead check the legacy implementation (to ensure it doesn't + // randomly get broken). + } else { + Json::Reader reader(features); + const bool parsingSuccessful = + reader.parse(input.data(), input.data() + input.size(), *root); + if (!parsingSuccessful) { + std::cerr << "Failed to parse " << kind << " file: " << std::endl + << reader.getFormatedErrorMessages() << std::endl; + return 1; + } } + if (!parseOnly) { FILE* factual = fopen(actual.c_str(), "wt"); if (!factual) { - printf("Failed to create %s actual file.\n", kind.c_str()); + std::cerr << "Failed to create '" << kind << "' actual file." + << std::endl; return 2; } printValueTree(factual, *root); @@ -173,7 +200,7 @@ static int rewriteValueTree(const Json::String& rewritePath, *rewrite = write(root); FILE* fout = fopen(rewritePath.c_str(), "wt"); if (!fout) { - printf("Failed to create rewrite file: %s\n", rewritePath.c_str()); + std::cerr << "Failed to create rewrite file: " << rewritePath << std::endl; return 2; } fprintf(fout, "%s\n", rewrite->c_str()); @@ -194,14 +221,15 @@ static Json::String removeSuffix(const Json::String& path, static void printConfig() { // Print the configuration used to compile JsonCpp #if defined(JSON_NO_INT64) - printf("JSON_NO_INT64=1\n"); + std::cout << "JSON_NO_INT64=1" << std::endl; #else - printf("JSON_NO_INT64=0\n"); + std::cout << "JSON_NO_INT64=0" << std::endl; #endif } static int printUsage(const char* argv[]) { - printf("Usage: %s [--strict] input-json-file", argv[0]); + std::cout << "Usage: " << argv[0] << " [--strict] input-json-file" + << std::endl; return 3; } @@ -212,11 +240,14 @@ static int parseCommandLine(int argc, const char* argv[], Options* opts) { return printUsage(argv); } int index = 1; - if (Json::String(argv[index]) == "--json-checker") { - opts->features = Json::Features::strictMode(); + if (Json::String(argv[index]) == "--parse-only") { opts->parseOnly = true; ++index; } + if (Json::String(argv[index]) == "--strict") { + opts->features = Json::Features::strictMode(); + ++index; + } if (Json::String(argv[index]) == "--json-config") { printConfig(); return 3; @@ -231,7 +262,7 @@ static int parseCommandLine(int argc, const char* argv[], Options* opts) { } else if (writerName == "BuiltStyledStreamWriter") { opts->write = &useBuiltStyledStreamWriter; } else { - printf("Unknown '--json-writer %s'\n", writerName.c_str()); + std::cerr << "Unknown '--json-writer' " << writerName << std::endl; return 4; } } @@ -241,19 +272,20 @@ static int parseCommandLine(int argc, const char* argv[], Options* opts) { opts->path = argv[index]; return 0; } -static int runTest(Options const& opts) { + +static int runTest(Options const& opts, bool use_legacy) { int exitCode = 0; Json::String input = readInputTestFile(opts.path.c_str()); if (input.empty()) { - printf("Failed to read input or empty input: %s\n", opts.path.c_str()); + std::cerr << "Invalid input file: " << opts.path << std::endl; return 3; } Json::String basePath = removeSuffix(opts.path, ".json"); if (!opts.parseOnly && basePath.empty()) { - printf("Bad input path. Path does not end with '.expected':\n%s\n", - opts.path.c_str()); + std::cerr << "Bad input path '" << opts.path + << "'. Must end with '.expected'" << std::endl; return 3; } @@ -263,36 +295,50 @@ static int runTest(Options const& opts) { Json::Value root; exitCode = parseAndSaveValueTree(input, actualPath, "input", opts.features, - opts.parseOnly, &root); + opts.parseOnly, &root, use_legacy); if (exitCode || opts.parseOnly) { return exitCode; } + Json::String rewrite; exitCode = rewriteValueTree(rewritePath, root, opts.write, &rewrite); if (exitCode) { return exitCode; } + Json::Value rewriteRoot; exitCode = parseAndSaveValueTree(rewrite, rewriteActualPath, "rewrite", - opts.features, opts.parseOnly, &rewriteRoot); - if (exitCode) { - return exitCode; - } - return 0; + opts.features, opts.parseOnly, &rewriteRoot, + use_legacy); + + return exitCode; } + int main(int argc, const char* argv[]) { Options opts; try { int exitCode = parseCommandLine(argc, argv, &opts); if (exitCode != 0) { - printf("Failed to parse command-line."); + std::cerr << "Failed to parse command-line." << std::endl; return exitCode; } - return runTest(opts); + + const int modern_return_code = runTest(opts, false); + if (modern_return_code) { + return modern_return_code; + } + + const std::string filename = + opts.path.substr(opts.path.find_last_of("\\/") + 1); + const bool should_run_legacy = (filename.rfind("legacy_", 0) == 0); + if (should_run_legacy) { + return runTest(opts, true); + } } catch (const std::exception& e) { - printf("Unhandled exception:\n%s\n", e.what()); + std::cerr << "Unhandled exception:" << std::endl << e.what() << std::endl; return 1; } + return 0; } #if defined(__GNUC__) diff --git a/src/lib_json/CMakeLists.txt b/src/lib_json/CMakeLists.txt index 0fa2f5953..3037eb020 100644 --- a/src/lib_json/CMakeLists.txt +++ b/src/lib_json/CMakeLists.txt @@ -1,13 +1,7 @@ -if( CMAKE_COMPILER_IS_GNUCXX ) - #Get compiler version. - execute_process( COMMAND ${CMAKE_CXX_COMPILER} -dumpversion - OUTPUT_VARIABLE GNUCXX_VERSION ) - +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.1.2) #-Werror=* was introduced -after- GCC 4.1.2 - if( GNUCXX_VERSION VERSION_GREATER 4.1.2 ) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror=strict-aliasing") - endif() -endif( CMAKE_COMPILER_IS_GNUCXX ) + add_compile_options("-Werror=strict-aliasing") +endif() include(CheckIncludeFileCXX) include(CheckTypeSize) @@ -17,75 +11,54 @@ include(CheckCXXSymbolExists) check_include_file_cxx(clocale HAVE_CLOCALE) check_cxx_symbol_exists(localeconv clocale HAVE_LOCALECONV) -if(CMAKE_VERSION VERSION_LESS 3.0.0) - # The "LANGUAGE CXX" parameter is not supported in CMake versions below 3, - # so the C compiler and header has to be used. - check_include_file(locale.h HAVE_LOCALE_H) - set(CMAKE_EXTRA_INCLUDE_FILES locale.h) - check_type_size("struct lconv" LCONV_SIZE) - unset(CMAKE_EXTRA_INCLUDE_FILES) - check_struct_has_member("struct lconv" decimal_point locale.h HAVE_DECIMAL_POINT) -else() - set(CMAKE_EXTRA_INCLUDE_FILES clocale) - check_type_size(lconv LCONV_SIZE LANGUAGE CXX) - unset(CMAKE_EXTRA_INCLUDE_FILES) - check_struct_has_member(lconv decimal_point clocale HAVE_DECIMAL_POINT LANGUAGE CXX) -endif() +set(CMAKE_EXTRA_INCLUDE_FILES clocale) +check_type_size(lconv LCONV_SIZE LANGUAGE CXX) +unset(CMAKE_EXTRA_INCLUDE_FILES) +check_struct_has_member(lconv decimal_point clocale HAVE_DECIMAL_POINT LANGUAGE CXX) if(NOT (HAVE_CLOCALE AND HAVE_LCONV_SIZE AND HAVE_DECIMAL_POINT AND HAVE_LOCALECONV)) message(WARNING "Locale functionality is not supported") - add_compile_definitions(JSONCPP_NO_LOCALE_SUPPORT) + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12.0) + add_compile_definitions(JSONCPP_NO_LOCALE_SUPPORT) + else() + add_definitions(-DJSONCPP_NO_LOCALE_SUPPORT) + endif() endif() -set( JSONCPP_INCLUDE_DIR ../../include ) +set(JSONCPP_INCLUDE_DIR ../../include) -set( PUBLIC_HEADERS +set(PUBLIC_HEADERS ${JSONCPP_INCLUDE_DIR}/json/config.h ${JSONCPP_INCLUDE_DIR}/json/forwards.h - ${JSONCPP_INCLUDE_DIR}/json/features.h + ${JSONCPP_INCLUDE_DIR}/json/json_features.h ${JSONCPP_INCLUDE_DIR}/json/value.h ${JSONCPP_INCLUDE_DIR}/json/reader.h ${JSONCPP_INCLUDE_DIR}/json/version.h ${JSONCPP_INCLUDE_DIR}/json/writer.h ${JSONCPP_INCLUDE_DIR}/json/assertions.h - ) +) -source_group( "Public API" FILES ${PUBLIC_HEADERS} ) +source_group("Public API" FILES ${PUBLIC_HEADERS}) -set(jsoncpp_sources - json_tool.h - json_reader.cpp - json_valueiterator.inl - json_value.cpp - json_writer.cpp) +set(JSONCPP_SOURCES + json_tool.h + json_reader.cpp + json_valueiterator.inl + json_value.cpp + json_writer.cpp +) # Install instructions for this target if(JSONCPP_WITH_CMAKE_PACKAGE) set(INSTALL_EXPORT EXPORT jsoncpp) -else(JSONCPP_WITH_CMAKE_PACKAGE) +else() set(INSTALL_EXPORT) endif() -if(BUILD_SHARED_LIBS) - add_compile_definitions( JSON_DLL_BUILD ) -endif() - - -add_library(jsoncpp_lib ${PUBLIC_HEADERS} ${jsoncpp_sources}) -set_target_properties( jsoncpp_lib PROPERTIES VERSION ${JSONCPP_VERSION} SOVERSION ${JSONCPP_SOVERSION}) -set_target_properties( jsoncpp_lib PROPERTIES OUTPUT_NAME jsoncpp - DEBUG_OUTPUT_NAME jsoncpp${DEBUG_LIBNAME_SUFFIX} ) -set_target_properties( jsoncpp_lib PROPERTIES POSITION_INDEPENDENT_CODE ON) - -# Set library's runtime search path on OSX -if(APPLE) - set_target_properties( jsoncpp_lib PROPERTIES INSTALL_RPATH "@loader_path/." ) -endif() - # Specify compiler features required when compiling a given target. # See https://cmake.org/cmake/help/v3.1/prop_gbl/CMAKE_CXX_KNOWN_FEATURES.html#prop_gbl:CMAKE_CXX_KNOWN_FEATURES # for complete list of features available -target_compile_features(jsoncpp_lib PUBLIC +list(APPEND REQUIRED_FEATURES cxx_std_11 # Compiler mode is aware of C++ 11. #MSVC 1900 cxx_alignas # Alignment control alignas, as defined in N2341. #MSVC 1900 cxx_alignof # Alignment control alignof, as defined in N2341. @@ -132,14 +105,101 @@ target_compile_features(jsoncpp_lib PUBLIC cxx_variadic_templates # Variadic templates, as defined in N2242. ) -install( TARGETS jsoncpp_lib ${INSTALL_EXPORT} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) -if(NOT CMAKE_VERSION VERSION_LESS 2.8.11) - target_include_directories( jsoncpp_lib PUBLIC - $ - $ - $) +if(BUILD_SHARED_LIBS) + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12.0) + add_compile_definitions(JSON_DLL_BUILD) + else() + add_definitions(-DJSON_DLL_BUILD) + endif() + + set(SHARED_LIB ${PROJECT_NAME}_lib) + add_library(${SHARED_LIB} SHARED ${PUBLIC_HEADERS} ${JSONCPP_SOURCES}) + set_target_properties(${SHARED_LIB} PROPERTIES + OUTPUT_NAME jsoncpp + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_SOVERSION} + POSITION_INDEPENDENT_CODE ${BUILD_SHARED_LIBS} + ) + + # Set library's runtime search path on OSX + if(APPLE) + set_target_properties(${SHARED_LIB} PROPERTIES INSTALL_RPATH "@loader_path/.") + endif() + + target_compile_features(${SHARED_LIB} PUBLIC ${REQUIRED_FEATURES}) + + target_include_directories(${SHARED_LIB} PUBLIC + $ + $ + ) + + list(APPEND CMAKE_TARGETS ${SHARED_LIB}) endif() + +if(BUILD_STATIC_LIBS) + set(STATIC_LIB ${PROJECT_NAME}_static) + add_library(${STATIC_LIB} STATIC ${PUBLIC_HEADERS} ${JSONCPP_SOURCES}) + + # avoid name clashes on windows as the shared import lib is also named jsoncpp.lib + if(NOT DEFINED STATIC_SUFFIX AND BUILD_SHARED_LIBS) + if (MSVC OR ("${CMAKE_C_SIMULATE_ID}" STREQUAL "MSVC")) + set(STATIC_SUFFIX "_static") + else() + set(STATIC_SUFFIX "") + endif() + endif() + + set_target_properties(${STATIC_LIB} PROPERTIES + OUTPUT_NAME jsoncpp${STATIC_SUFFIX} + VERSION ${PROJECT_VERSION} + ) + + # Set library's runtime search path on OSX + if(APPLE) + set_target_properties(${STATIC_LIB} PROPERTIES INSTALL_RPATH "@loader_path/.") + endif() + + target_compile_features(${STATIC_LIB} PUBLIC ${REQUIRED_FEATURES}) + + target_include_directories(${STATIC_LIB} PUBLIC + $ + $ + ) + + list(APPEND CMAKE_TARGETS ${STATIC_LIB}) +endif() + +if(BUILD_OBJECT_LIBS) + set(OBJECT_LIB ${PROJECT_NAME}_object) + add_library(${OBJECT_LIB} OBJECT ${PUBLIC_HEADERS} ${JSONCPP_SOURCES}) + + set_target_properties(${OBJECT_LIB} PROPERTIES + OUTPUT_NAME jsoncpp + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_SOVERSION} + POSITION_INDEPENDENT_CODE ${BUILD_SHARED_LIBS} + ) + + # Set library's runtime search path on OSX + if(APPLE) + set_target_properties(${OBJECT_LIB} PROPERTIES INSTALL_RPATH "@loader_path/.") + endif() + + target_compile_features(${OBJECT_LIB} PUBLIC ${REQUIRED_FEATURES}) + + target_include_directories(${OBJECT_LIB} PUBLIC + $ + $ + ) + + list(APPEND CMAKE_TARGETS ${OBJECT_LIB}) +endif() + +install(TARGETS ${CMAKE_TARGETS} ${INSTALL_EXPORT} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + OBJECTS DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + diff --git a/src/lib_json/json_reader.cpp b/src/lib_json/json_reader.cpp index 1d614a4a1..5b6299906 100644 --- a/src/lib_json/json_reader.cpp +++ b/src/lib_json/json_reader.cpp @@ -10,8 +10,11 @@ #include #include #endif // if !defined(JSON_IS_AMALGAMATION) +#include #include +#include #include +#include #include #include #include @@ -20,13 +23,6 @@ #include #include -#if __cplusplus >= 201103L - -#if !defined(sscanf) -#define sscanf std::sscanf -#endif - -#endif //__cplusplus #if defined(_MSC_VER) #if !defined(_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES) @@ -50,11 +46,7 @@ static size_t const stackLimit_g = namespace Json { -#if __cplusplus >= 201103L || (defined(_CPPLIB_VER) && _CPPLIB_VER >= 520) -typedef std::unique_ptr CharReaderPtr; -#else -typedef std::auto_ptr CharReaderPtr; -#endif +using CharReaderPtr = std::unique_ptr; // Implementation of class Features // //////////////////////////////// @@ -76,25 +68,17 @@ Features Features::strictMode() { // //////////////////////////////// bool Reader::containsNewLine(Reader::Location begin, Reader::Location end) { - for (; begin < end; ++begin) - if (*begin == '\n' || *begin == '\r') - return true; - return false; + return std::any_of(begin, end, [](char b) { return b == '\n' || b == '\r'; }); } // Class Reader // ////////////////////////////////////////////////////////////////// -Reader::Reader() - : errors_(), document_(), commentsBefore_(), features_(Features::all()) {} +Reader::Reader() : features_(Features::all()) {} -Reader::Reader(const Features& features) - : errors_(), document_(), begin_(), end_(), current_(), lastValueEnd_(), - lastValue_(), commentsBefore_(), features_(features), collectComments_() { -} +Reader::Reader(const Features& features) : features_(features) {} -bool Reader::parse(const std::string& document, - Value& root, +bool Reader::parse(const std::string& document, Value& root, bool collectComments) { document_.assign(document.begin(), document.end()); const char* begin = document_.c_str(); @@ -110,14 +94,11 @@ bool Reader::parse(std::istream& is, Value& root, bool collectComments) { // Since String is reference-counted, this at least does not // create an extra copy. - String doc; - std::getline(is, doc, (char)EOF); + String doc(std::istreambuf_iterator(is), {}); return parse(doc.data(), doc.data() + doc.size(), root, collectComments); } -bool Reader::parse(const char* beginDoc, - const char* endDoc, - Value& root, +bool Reader::parse(const char* beginDoc, const char* endDoc, Value& root, bool collectComments) { if (!features_.allowComments_) { collectComments = false; @@ -137,7 +118,7 @@ bool Reader::parse(const char* beginDoc, bool successful = readValue(); Token token; - skipCommentTokens(token); + readTokenSkippingComments(token); if (collectComments_ && !commentsBefore_.empty()) root.setComment(commentsBefore_, commentAfter); if (features_.strictRoot_) { @@ -165,7 +146,7 @@ bool Reader::readValue() { throwRuntimeError("Exceeded stackLimit in readValue()."); Token token; - skipCommentTokens(token); + readTokenSkippingComments(token); bool successful = true; if (collectComments_ && !commentsBefore_.empty()) { @@ -233,14 +214,14 @@ bool Reader::readValue() { return successful; } -void Reader::skipCommentTokens(Token& token) { +bool Reader::readTokenSkippingComments(Token& token) { + bool success = readToken(token); if (features_.allowComments_) { - do { - readToken(token); - } while (token.type_ == tokenComment); - } else { - readToken(token); + while (success && token.type_ == tokenComment) { + success = readToken(token); + } } + return success; } bool Reader::readToken(Token& token) { @@ -324,7 +305,7 @@ void Reader::skipSpaces() { } } -bool Reader::match(Location pattern, int patternLength) { +bool Reader::match(const Char* pattern, int patternLength) { if (end_ - current_ < patternLength) return false; int index = patternLength; @@ -377,8 +358,7 @@ String Reader::normalizeEOL(Reader::Location begin, Reader::Location end) { return normalized; } -void Reader::addComment(Location begin, - Location end, +void Reader::addComment(Location begin, Location end, CommentPlacement placement) { assert(collectComments_); const String& normalized = normalizeEOL(begin, end); @@ -416,7 +396,7 @@ bool Reader::readCppStyleComment() { } void Reader::readNumber() { - const char* p = current_; + Location p = current_; char c = '0'; // stopgap for already consumed character // integral part while (c >= '0' && c <= '9') @@ -455,12 +435,7 @@ bool Reader::readObject(Token& token) { Value init(objectValue); currentValue().swapPayload(init); currentValue().setOffsetStart(token.start_ - begin_); - while (readToken(tokenName)) { - bool initialTokenOk = true; - while (tokenName.type_ == tokenComment && initialTokenOk) - initialTokenOk = readToken(tokenName); - if (!initialTokenOk) - break; + while (readTokenSkippingComments(tokenName)) { if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object return true; name.clear(); @@ -471,7 +446,7 @@ bool Reader::readObject(Token& token) { Value numberName; if (!decodeNumber(tokenName, numberName)) return recoverFromError(tokenObjectEnd); - name = String(numberName.asCString()); + name = numberName.asString(); } else { break; } @@ -489,15 +464,11 @@ bool Reader::readObject(Token& token) { return recoverFromError(tokenObjectEnd); Token comma; - if (!readToken(comma) || - (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator && - comma.type_ != tokenComment)) { + if (!readTokenSkippingComments(comma) || + (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator)) { return addErrorAndRecover("Missing ',' or '}' in object declaration", comma, tokenObjectEnd); } - bool finalizeTokenOk = true; - while (comma.type_ == tokenComment && finalizeTokenOk) - finalizeTokenOk = readToken(comma); if (comma.type_ == tokenObjectEnd) return true; } @@ -527,10 +498,7 @@ bool Reader::readArray(Token& token) { Token currentToken; // Accept Comment after last item in the array. - ok = readToken(currentToken); - while (currentToken.type_ == tokenComment && ok) { - ok = readToken(currentToken); - } + ok = readTokenSkippingComments(currentToken); bool badTokenType = (currentToken.type_ != tokenArraySeparator && currentToken.type_ != tokenArrayEnd); if (!ok || badTokenType) { @@ -608,11 +576,16 @@ bool Reader::decodeDouble(Token& token) { bool Reader::decodeDouble(Token& token, Value& decoded) { double value = 0; - String buffer(token.start_, token.end_); - IStringStream is(buffer); - if (!(is >> value)) - return addError( - "'" + String(token.start_, token.end_) + "' is not a number.", token); + IStringStream is(String(token.start_, token.end_)); + if (!(is >> value)) { + if (value == std::numeric_limits::max()) + value = std::numeric_limits::infinity(); + else if (value == std::numeric_limits::lowest()) + value = -std::numeric_limits::infinity(); + else if (!std::isinf(value)) + return addError( + "'" + String(token.start_, token.end_) + "' is not a number.", token); + } decoded = value; return true; } @@ -636,7 +609,7 @@ bool Reader::decodeString(Token& token, String& decoded) { Char c = *current++; if (c == '"') break; - else if (c == '\\') { + if (c == '\\') { if (current == end) return addError("Empty escape sequence in string", token, current); Char escape = *current++; @@ -681,10 +654,8 @@ bool Reader::decodeString(Token& token, String& decoded) { return true; } -bool Reader::decodeUnicodeCodePoint(Token& token, - Location& current, - Location end, - unsigned int& unicode) { +bool Reader::decodeUnicodeCodePoint(Token& token, Location& current, + Location end, unsigned int& unicode) { if (!decodeUnicodeEscapeSequence(token, current, end, unicode)) return false; @@ -708,8 +679,7 @@ bool Reader::decodeUnicodeCodePoint(Token& token, return true; } -bool Reader::decodeUnicodeEscapeSequence(Token& token, - Location& current, +bool Reader::decodeUnicodeEscapeSequence(Token& token, Location& current, Location end, unsigned int& ret_unicode) { if (end - current < 4) @@ -757,8 +727,7 @@ bool Reader::recoverFromError(TokenType skipUntilToken) { return false; } -bool Reader::addErrorAndRecover(const String& message, - Token& token, +bool Reader::addErrorAndRecover(const String& message, Token& token, TokenType skipUntilToken) { addError(message, token); return recoverFromError(skipUntilToken); @@ -772,8 +741,7 @@ Reader::Char Reader::getNextChar() { return *current_++; } -void Reader::getLocationLineAndColumn(Location location, - int& line, +void Reader::getLocationLineAndColumn(Location location, int& line, int& column) const { Location current = begin_; Location lastLineStart = current; @@ -781,7 +749,7 @@ void Reader::getLocationLineAndColumn(Location location, while (current < location && current != end_) { Char c = *current++; if (c == '\r') { - if (*current == '\n') + if (current != end_ && *current == '\n') ++current; lastLineStart = current; ++line; @@ -849,8 +817,7 @@ bool Reader::pushError(const Value& value, const String& message) { return true; } -bool Reader::pushError(const Value& value, - const String& message, +bool Reader::pushError(const Value& value, const String& message, const Value& extra) { ptrdiff_t const length = end_ - begin_; if (value.getOffsetStart() > length || value.getOffsetLimit() > length || @@ -876,6 +843,7 @@ class OurFeatures { public: static OurFeatures all(); bool allowComments_; + bool allowTrailingCommas_; bool strictRoot_; bool allowDroppedNullPlaceholders_; bool allowNumericKeys_; @@ -883,6 +851,7 @@ class OurFeatures { bool failIfExtra_; bool rejectDupKeys_; bool allowSpecialFloats_; + bool skipBom_; size_t stackLimit_; }; // OurFeatures @@ -895,24 +864,14 @@ OurFeatures OurFeatures::all() { return {}; } // for implementing JSON reading. class OurReader { public: - typedef char Char; - typedef const Char* Location; - struct StructuredError { - ptrdiff_t offset_start; - ptrdiff_t offset_limit; - String message; - }; + using Char = char; + using Location = const Char*; - OurReader(OurFeatures const& features); - bool parse(const char* beginDoc, - const char* endDoc, - Value& root, + explicit OurReader(OurFeatures const& features); + bool parse(const char* beginDoc, const char* endDoc, Value& root, bool collectComments = true); String getFormattedErrorMessages() const; - std::vector getStructuredErrors() const; - bool pushError(const Value& value, const String& message); - bool pushError(const Value& value, const String& message, const Value& extra); - bool good() const; + std::vector getStructuredErrors() const; private: OurReader(OurReader const&); // no impl @@ -952,13 +911,15 @@ class OurReader { Location extra_; }; - typedef std::deque Errors; + using Errors = std::deque; bool readToken(Token& token); + bool readTokenSkippingComments(Token& token); void skipSpaces(); - bool match(Location pattern, int patternLength); + void skipBom(bool skipBom); + bool match(const Char* pattern, int patternLength); bool readComment(); - bool readCStyleComment(); + bool readCStyleComment(bool* containsNewLineResult); bool readCppStyleComment(); bool readString(); bool readStringSingleQuote(); @@ -972,64 +933,52 @@ class OurReader { bool decodeString(Token& token, String& decoded); bool decodeDouble(Token& token); bool decodeDouble(Token& token, Value& decoded); - bool decodeUnicodeCodePoint(Token& token, - Location& current, - Location end, + bool decodeUnicodeCodePoint(Token& token, Location& current, Location end, unsigned int& unicode); - bool decodeUnicodeEscapeSequence(Token& token, - Location& current, - Location end, - unsigned int& unicode); + bool decodeUnicodeEscapeSequence(Token& token, Location& current, + Location end, unsigned int& unicode); bool addError(const String& message, Token& token, Location extra = nullptr); bool recoverFromError(TokenType skipUntilToken); - bool addErrorAndRecover(const String& message, - Token& token, + bool addErrorAndRecover(const String& message, Token& token, TokenType skipUntilToken); void skipUntilSpace(); Value& currentValue(); Char getNextChar(); - void - getLocationLineAndColumn(Location location, int& line, int& column) const; + void getLocationLineAndColumn(Location location, int& line, + int& column) const; String getLocationLineAndColumn(Location location) const; void addComment(Location begin, Location end, CommentPlacement placement); - void skipCommentTokens(Token& token); static String normalizeEOL(Location begin, Location end); static bool containsNewLine(Location begin, Location end); - typedef std::stack Nodes; - Nodes nodes_; - Errors errors_; - String document_; - Location begin_; - Location end_; - Location current_; - Location lastValueEnd_; - Value* lastValue_; - String commentsBefore_; + using Nodes = std::stack; + + Nodes nodes_{}; + Errors errors_{}; + String document_{}; + Location begin_ = nullptr; + Location end_ = nullptr; + Location current_ = nullptr; + Location lastValueEnd_ = nullptr; + Value* lastValue_ = nullptr; + bool lastValueHasAComment_ = false; + String commentsBefore_{}; OurFeatures const features_; - bool collectComments_; + bool collectComments_ = false; }; // OurReader // complete copy of Read impl, for OurReader bool OurReader::containsNewLine(OurReader::Location begin, OurReader::Location end) { - for (; begin < end; ++begin) - if (*begin == '\n' || *begin == '\r') - return true; - return false; + return std::any_of(begin, end, [](char b) { return b == '\n' || b == '\r'; }); } -OurReader::OurReader(OurFeatures const& features) - : errors_(), document_(), begin_(), end_(), current_(), lastValueEnd_(), - lastValue_(), commentsBefore_(), features_(features), collectComments_() { -} +OurReader::OurReader(OurFeatures const& features) : features_(features) {} -bool OurReader::parse(const char* beginDoc, - const char* endDoc, - Value& root, +bool OurReader::parse(const char* beginDoc, const char* endDoc, Value& root, bool collectComments) { if (!features_.allowComments_) { collectComments = false; @@ -1047,16 +996,15 @@ bool OurReader::parse(const char* beginDoc, nodes_.pop(); nodes_.push(&root); + // skip byte order mark if it exists at the beginning of the UTF-8 text. + skipBom(features_.skipBom_); bool successful = readValue(); nodes_.pop(); Token token; - skipCommentTokens(token); - if (features_.failIfExtra_) { - if ((features_.strictRoot_ || token.type_ != tokenError) && - token.type_ != tokenEndOfStream) { - addError("Extra non-whitespace after JSON value.", token); - return false; - } + readTokenSkippingComments(token); + if (features_.failIfExtra_ && (token.type_ != tokenEndOfStream)) { + addError("Extra non-whitespace after JSON value.", token); + return false; } if (collectComments_ && !commentsBefore_.empty()) root.setComment(commentsBefore_, commentAfter); @@ -1081,7 +1029,7 @@ bool OurReader::readValue() { if (nodes_.size() > features_.stackLimit_) throwRuntimeError("Exceeded stackLimit in readValue()."); Token token; - skipCommentTokens(token); + readTokenSkippingComments(token); bool successful = true; if (collectComments_ && !commentsBefore_.empty()) { @@ -1161,20 +1109,21 @@ bool OurReader::readValue() { if (collectComments_) { lastValueEnd_ = current_; + lastValueHasAComment_ = false; lastValue_ = ¤tValue(); } return successful; } -void OurReader::skipCommentTokens(Token& token) { +bool OurReader::readTokenSkippingComments(Token& token) { + bool success = readToken(token); if (features_.allowComments_) { - do { - readToken(token); - } while (token.type_ == tokenComment); - } else { - readToken(token); + while (success && token.type_ == tokenComment) { + success = readToken(token); + } } + return success; } bool OurReader::readToken(Token& token) { @@ -1203,8 +1152,11 @@ bool OurReader::readToken(Token& token) { if (features_.allowSingleQuotes_) { token.type_ = tokenString; ok = readStringSingleQuote(); - break; - } // else fall through + } else { + // If we don't allow single quotes, this is a failure case. + ok = false; + } + break; case '/': token.type_ = tokenComment; ok = readComment(); @@ -1230,6 +1182,14 @@ bool OurReader::readToken(Token& token) { ok = features_.allowSpecialFloats_ && match("nfinity", 7); } break; + case '+': + if (readNumber(true)) { + token.type_ = tokenNumber; + } else { + token.type_ = tokenPosInf; + ok = features_.allowSpecialFloats_ && match("nfinity", 7); + } + break; case 't': token.type_ = tokenTrue; ok = match("rue", 3); @@ -1287,7 +1247,17 @@ void OurReader::skipSpaces() { } } -bool OurReader::match(Location pattern, int patternLength) { +void OurReader::skipBom(bool skipBom) { + // The default behavior is to skip BOM. + if (skipBom) { + if ((end_ - begin_) >= 3 && strncmp(begin_, "\xEF\xBB\xBF", 3) == 0) { + begin_ += 3; + current_ = begin_; + } + } +} + +bool OurReader::match(const Char* pattern, int patternLength) { if (end_ - current_ < patternLength) return false; int index = patternLength; @@ -1299,21 +1269,32 @@ bool OurReader::match(Location pattern, int patternLength) { } bool OurReader::readComment() { - Location commentBegin = current_ - 1; - Char c = getNextChar(); + const Location commentBegin = current_ - 1; + const Char c = getNextChar(); bool successful = false; - if (c == '*') - successful = readCStyleComment(); - else if (c == '/') + bool cStyleWithEmbeddedNewline = false; + + const bool isCStyleComment = (c == '*'); + const bool isCppStyleComment = (c == '/'); + if (isCStyleComment) { + successful = readCStyleComment(&cStyleWithEmbeddedNewline); + } else if (isCppStyleComment) { successful = readCppStyleComment(); + } + if (!successful) return false; if (collectComments_) { CommentPlacement placement = commentBefore; - if (lastValueEnd_ && !containsNewLine(lastValueEnd_, commentBegin)) { - if (c != '*' || !containsNewLine(commentBegin, current_)) - placement = commentAfterOnSameLine; + + if (!lastValueHasAComment_) { + if (lastValueEnd_ && !containsNewLine(lastValueEnd_, commentBegin)) { + if (isCppStyleComment || !cStyleWithEmbeddedNewline) { + placement = commentAfterOnSameLine; + lastValueHasAComment_ = true; + } + } } addComment(commentBegin, current_, placement); @@ -1341,8 +1322,7 @@ String OurReader::normalizeEOL(OurReader::Location begin, return normalized; } -void OurReader::addComment(Location begin, - Location end, +void OurReader::addComment(Location begin, Location end, CommentPlacement placement) { assert(collectComments_); const String& normalized = normalizeEOL(begin, end); @@ -1354,12 +1334,17 @@ void OurReader::addComment(Location begin, } } -bool OurReader::readCStyleComment() { +bool OurReader::readCStyleComment(bool* containsNewLineResult) { + *containsNewLineResult = false; + while ((current_ + 1) < end_) { Char c = getNextChar(); if (c == '*' && *current_ == '/') break; + if (c == '\n') + *containsNewLineResult = true; } + return getNextChar() == '/'; } @@ -1380,7 +1365,7 @@ bool OurReader::readCppStyleComment() { } bool OurReader::readNumber(bool checkInf) { - const char* p = current_; + Location p = current_; if (checkInf && p != end_ && *p == 'I') { current_ = ++p; return false; @@ -1435,13 +1420,10 @@ bool OurReader::readObject(Token& token) { Value init(objectValue); currentValue().swapPayload(init); currentValue().setOffsetStart(token.start_ - begin_); - while (readToken(tokenName)) { - bool initialTokenOk = true; - while (tokenName.type_ == tokenComment && initialTokenOk) - initialTokenOk = readToken(tokenName); - if (!initialTokenOk) - break; - if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object + while (readTokenSkippingComments(tokenName)) { + if (tokenName.type_ == tokenObjectEnd && + (name.empty() || + features_.allowTrailingCommas_)) // empty object or trailing comma return true; name.clear(); if (tokenName.type_ == tokenString) { @@ -1475,15 +1457,11 @@ bool OurReader::readObject(Token& token) { return recoverFromError(tokenObjectEnd); Token comma; - if (!readToken(comma) || - (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator && - comma.type_ != tokenComment)) { + if (!readTokenSkippingComments(comma) || + (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator)) { return addErrorAndRecover("Missing ',' or '}' in object declaration", comma, tokenObjectEnd); } - bool finalizeTokenOk = true; - while (comma.type_ == tokenComment && finalizeTokenOk) - finalizeTokenOk = readToken(comma); if (comma.type_ == tokenObjectEnd) return true; } @@ -1495,15 +1473,19 @@ bool OurReader::readArray(Token& token) { Value init(arrayValue); currentValue().swapPayload(init); currentValue().setOffsetStart(token.start_ - begin_); - skipSpaces(); - if (current_ != end_ && *current_ == ']') // empty array - { - Token endArray; - readToken(endArray); - return true; - } int index = 0; for (;;) { + skipSpaces(); + if (current_ != end_ && *current_ == ']' && + (index == 0 || + (features_.allowTrailingCommas_ && + !features_.allowDroppedNullPlaceholders_))) // empty array or trailing + // comma + { + Token endArray; + readToken(endArray); + return true; + } Value& value = currentValue()[index++]; nodes_.push(&value); bool ok = readValue(); @@ -1513,10 +1495,7 @@ bool OurReader::readArray(Token& token) { Token currentToken; // Accept Comment after last item in the array. - ok = readToken(currentToken); - while (currentToken.type_ == tokenComment && ok) { - ok = readToken(currentToken); - } + ok = readTokenSkippingComments(currentToken); bool badTokenType = (currentToken.type_ != tokenArraySeparator && currentToken.type_ != tokenArrayEnd); if (!ok || badTokenType) { @@ -1544,20 +1523,45 @@ bool OurReader::decodeNumber(Token& token, Value& decoded) { // larger than the maximum supported value of an integer then // we decode the number as a double. Location current = token.start_; - bool isNegative = *current == '-'; - if (isNegative) + const bool isNegative = *current == '-'; + if (isNegative) { ++current; + } - // TODO(issue #960): Change to constexpr - static const auto positive_threshold = Value::maxLargestUInt / 10; - static const auto positive_last_digit = Value::maxLargestUInt % 10; - static const auto negative_threshold = - Value::LargestUInt(Value::minLargestInt) / 10; - static const auto negative_last_digit = - Value::LargestUInt(Value::minLargestInt) % 10; - - const auto threshold = isNegative ? negative_threshold : positive_threshold; - const auto last_digit = + // We assume we can represent the largest and smallest integer types as + // unsigned integers with separate sign. This is only true if they can fit + // into an unsigned integer. + static_assert(Value::maxLargestInt <= Value::maxLargestUInt, + "Int must be smaller than UInt"); + + // We need to convert minLargestInt into a positive number. The easiest way + // to do this conversion is to assume our "threshold" value of minLargestInt + // divided by 10 can fit in maxLargestInt when absolute valued. This should + // be a safe assumption. + static_assert(Value::minLargestInt <= -Value::maxLargestInt, + "The absolute value of minLargestInt must be greater than or " + "equal to maxLargestInt"); + static_assert(Value::minLargestInt / 10 >= -Value::maxLargestInt, + "The absolute value of minLargestInt must be only 1 magnitude " + "larger than maxLargest Int"); + + static constexpr Value::LargestUInt positive_threshold = + Value::maxLargestUInt / 10; + static constexpr Value::UInt positive_last_digit = Value::maxLargestUInt % 10; + + // For the negative values, we have to be more careful. Since typically + // -Value::minLargestInt will cause an overflow, we first divide by 10 and + // then take the inverse. This assumes that minLargestInt is only a single + // power of 10 different in magnitude, which we check above. For the last + // digit, we take the modulus before negating for the same reason. + static constexpr auto negative_threshold = + Value::LargestUInt(-(Value::minLargestInt / 10)); + static constexpr auto negative_last_digit = + Value::UInt(-(Value::minLargestInt % 10)); + + const Value::LargestUInt threshold = + isNegative ? negative_threshold : positive_threshold; + const Value::UInt max_last_digit = isNegative ? negative_last_digit : positive_last_digit; Value::LargestUInt value = 0; @@ -1569,23 +1573,27 @@ bool OurReader::decodeNumber(Token& token, Value& decoded) { const auto digit(static_cast(c - '0')); if (value >= threshold) { // We've hit or exceeded the max value divided by 10 (rounded down). If - // a) we've only just touched the limit, meaing value == threshold, + // a) we've only just touched the limit, meaning value == threshold, // b) this is the last digit, or // c) it's small enough to fit in that rounding delta, we're okay. // Otherwise treat this number as a double to avoid overflow. - if (value > threshold || current != token.end_ || digit > last_digit) { + if (value > threshold || current != token.end_ || + digit > max_last_digit) { return decodeDouble(token, decoded); } } value = value * 10 + digit; } - if (isNegative) - decoded = -Value::LargestInt(value); - else if (value <= Value::LargestUInt(Value::maxLargestInt)) + if (isNegative) { + // We use the same magnitude assumption here, just in case. + const auto last_digit = static_cast(value % 10); + decoded = -Value::LargestInt(value / 10) * 10 - last_digit; + } else if (value <= Value::LargestUInt(Value::maxLargestInt)) { decoded = Value::LargestInt(value); - else + } else { decoded = value; + } return true; } @@ -1602,37 +1610,16 @@ bool OurReader::decodeDouble(Token& token) { bool OurReader::decodeDouble(Token& token, Value& decoded) { double value = 0; - const int bufferSize = 32; - int count; - ptrdiff_t const length = token.end_ - token.start_; - - // Sanity check to avoid buffer overflow exploits. - if (length < 0) { - return addError("Unable to parse token length", token); - } - auto const ulength = static_cast(length); - - // Avoid using a string constant for the format control string given to - // sscanf, as this can cause hard to debug crashes on OS X. See here for more - // info: - // - // http://developer.apple.com/library/mac/#DOCUMENTATION/DeveloperTools/gcc-4.0.1/gcc/Incompatibilities.html - char format[] = "%lf"; - - if (length <= bufferSize) { - Char buffer[bufferSize + 1]; - memcpy(buffer, token.start_, ulength); - buffer[length] = 0; - fixNumericLocaleInput(buffer, buffer + length); - count = sscanf(buffer, format, &value); - } else { - String buffer(token.start_, token.end_); - count = sscanf(buffer.c_str(), format, &value); + IStringStream is(String(token.start_, token.end_)); + if (!(is >> value)) { + if (value == std::numeric_limits::max()) + value = std::numeric_limits::infinity(); + else if (value == std::numeric_limits::lowest()) + value = -std::numeric_limits::infinity(); + else if (!std::isinf(value)) + return addError( + "'" + String(token.start_, token.end_) + "' is not a number.", token); } - - if (count != 1) - return addError( - "'" + String(token.start_, token.end_) + "' is not a number.", token); decoded = value; return true; } @@ -1656,7 +1643,7 @@ bool OurReader::decodeString(Token& token, String& decoded) { Char c = *current++; if (c == '"') break; - else if (c == '\\') { + if (c == '\\') { if (current == end) return addError("Empty escape sequence in string", token, current); Char escape = *current++; @@ -1701,10 +1688,8 @@ bool OurReader::decodeString(Token& token, String& decoded) { return true; } -bool OurReader::decodeUnicodeCodePoint(Token& token, - Location& current, - Location end, - unsigned int& unicode) { +bool OurReader::decodeUnicodeCodePoint(Token& token, Location& current, + Location end, unsigned int& unicode) { if (!decodeUnicodeEscapeSequence(token, current, end, unicode)) return false; @@ -1728,8 +1713,7 @@ bool OurReader::decodeUnicodeCodePoint(Token& token, return true; } -bool OurReader::decodeUnicodeEscapeSequence(Token& token, - Location& current, +bool OurReader::decodeUnicodeEscapeSequence(Token& token, Location& current, Location end, unsigned int& ret_unicode) { if (end - current < 4) @@ -1777,8 +1761,7 @@ bool OurReader::recoverFromError(TokenType skipUntilToken) { return false; } -bool OurReader::addErrorAndRecover(const String& message, - Token& token, +bool OurReader::addErrorAndRecover(const String& message, Token& token, TokenType skipUntilToken) { addError(message, token); return recoverFromError(skipUntilToken); @@ -1792,8 +1775,7 @@ OurReader::Char OurReader::getNextChar() { return *current_++; } -void OurReader::getLocationLineAndColumn(Location location, - int& line, +void OurReader::getLocationLineAndColumn(Location location, int& line, int& column) const { Location current = begin_; Location lastLineStart = current; @@ -1801,7 +1783,7 @@ void OurReader::getLocationLineAndColumn(Location location, while (current < location && current != end_) { Char c = *current++; if (c == '\r') { - if (*current == '\n') + if (current != end_ && *current == '\n') ++current; lastLineStart = current; ++line; @@ -1836,10 +1818,11 @@ String OurReader::getFormattedErrorMessages() const { return formattedMessage; } -std::vector OurReader::getStructuredErrors() const { - std::vector allErrors; +std::vector +OurReader::getStructuredErrors() const { + std::vector allErrors; for (const auto& error : errors_) { - OurReader::StructuredError structured; + CharReader::StructuredError structured; structured.offset_start = error.token_.start_ - begin_; structured.offset_limit = error.token_.end_ - begin_; structured.message = error.message_; @@ -1848,60 +1831,37 @@ std::vector OurReader::getStructuredErrors() const { return allErrors; } -bool OurReader::pushError(const Value& value, const String& message) { - ptrdiff_t length = end_ - begin_; - if (value.getOffsetStart() > length || value.getOffsetLimit() > length) - return false; - Token token; - token.type_ = tokenError; - token.start_ = begin_ + value.getOffsetStart(); - token.end_ = begin_ + value.getOffsetLimit(); - ErrorInfo info; - info.token_ = token; - info.message_ = message; - info.extra_ = nullptr; - errors_.push_back(info); - return true; -} - -bool OurReader::pushError(const Value& value, - const String& message, - const Value& extra) { - ptrdiff_t length = end_ - begin_; - if (value.getOffsetStart() > length || value.getOffsetLimit() > length || - extra.getOffsetLimit() > length) - return false; - Token token; - token.type_ = tokenError; - token.start_ = begin_ + value.getOffsetStart(); - token.end_ = begin_ + value.getOffsetLimit(); - ErrorInfo info; - info.token_ = token; - info.message_ = message; - info.extra_ = begin_ + extra.getOffsetStart(); - errors_.push_back(info); - return true; -} - -bool OurReader::good() const { return errors_.empty(); } - class OurCharReader : public CharReader { - bool const collectComments_; - OurReader reader_; public: OurCharReader(bool collectComments, OurFeatures const& features) - : collectComments_(collectComments), reader_(features) {} - bool parse(char const* beginDoc, - char const* endDoc, - Value* root, - String* errs) override { - bool ok = reader_.parse(beginDoc, endDoc, *root, collectComments_); - if (errs) { - *errs = reader_.getFormattedErrorMessages(); + : CharReader( + std::unique_ptr(new OurImpl(collectComments, features))) {} + +protected: + class OurImpl : public Impl { + public: + OurImpl(bool collectComments, OurFeatures const& features) + : collectComments_(collectComments), reader_(features) {} + + bool parse(char const* beginDoc, char const* endDoc, Value* root, + String* errs) override { + bool ok = reader_.parse(beginDoc, endDoc, *root, collectComments_); + if (errs) { + *errs = reader_.getFormattedErrorMessages(); + } + return ok; } - return ok; - } + + std::vector + getStructuredErrors() const override { + return reader_.getStructuredErrors(); + } + + private: + bool const collectComments_; + OurReader reader_; + }; }; CharReaderBuilder::CharReaderBuilder() { setDefaults(&settings_); } @@ -1910,6 +1870,7 @@ CharReader* CharReaderBuilder::newCharReader() const { bool collectComments = settings_["collectComments"].asBool(); OurFeatures features = OurFeatures::all(); features.allowComments_ = settings_["allowComments"].asBool(); + features.allowTrailingCommas_ = settings_["allowTrailingCommas"].asBool(); features.strictRoot_ = settings_["strictRoot"].asBool(); features.allowDroppedNullPlaceholders_ = settings_["allowDroppedNullPlaceholders"].asBool(); @@ -1922,38 +1883,37 @@ CharReader* CharReaderBuilder::newCharReader() const { features.failIfExtra_ = settings_["failIfExtra"].asBool(); features.rejectDupKeys_ = settings_["rejectDupKeys"].asBool(); features.allowSpecialFloats_ = settings_["allowSpecialFloats"].asBool(); + features.skipBom_ = settings_["skipBom"].asBool(); return new OurCharReader(collectComments, features); } -static void getValidReaderKeys(std::set* valid_keys) { - valid_keys->clear(); - valid_keys->insert("collectComments"); - valid_keys->insert("allowComments"); - valid_keys->insert("strictRoot"); - valid_keys->insert("allowDroppedNullPlaceholders"); - valid_keys->insert("allowNumericKeys"); - valid_keys->insert("allowSingleQuotes"); - valid_keys->insert("stackLimit"); - valid_keys->insert("failIfExtra"); - valid_keys->insert("rejectDupKeys"); - valid_keys->insert("allowSpecialFloats"); -} + bool CharReaderBuilder::validate(Json::Value* invalid) const { - Json::Value my_invalid; - if (!invalid) - invalid = &my_invalid; // so we do not need to test for NULL - Json::Value& inv = *invalid; - std::set valid_keys; - getValidReaderKeys(&valid_keys); - Value::Members keys = settings_.getMemberNames(); - size_t n = keys.size(); - for (size_t i = 0; i < n; ++i) { - String const& key = keys[i]; - if (valid_keys.find(key) == valid_keys.end()) { - inv[key] = settings_[key]; - } + static const auto& valid_keys = *new std::set{ + "collectComments", + "allowComments", + "allowTrailingCommas", + "strictRoot", + "allowDroppedNullPlaceholders", + "allowNumericKeys", + "allowSingleQuotes", + "stackLimit", + "failIfExtra", + "rejectDupKeys", + "allowSpecialFloats", + "skipBom", + }; + for (auto si = settings_.begin(); si != settings_.end(); ++si) { + auto key = si.name(); + if (valid_keys.count(key)) + continue; + if (invalid) + (*invalid)[key] = *si; + else + return false; } - return inv.empty(); + return invalid ? invalid->empty() : true; } + Value& CharReaderBuilder::operator[](const String& key) { return settings_[key]; } @@ -1961,6 +1921,7 @@ Value& CharReaderBuilder::operator[](const String& key) { void CharReaderBuilder::strictMode(Json::Value* settings) { //! [CharReaderBuilderStrictMode] (*settings)["allowComments"] = false; + (*settings)["allowTrailingCommas"] = false; (*settings)["strictRoot"] = true; (*settings)["allowDroppedNullPlaceholders"] = false; (*settings)["allowNumericKeys"] = false; @@ -1969,6 +1930,7 @@ void CharReaderBuilder::strictMode(Json::Value* settings) { (*settings)["failIfExtra"] = true; (*settings)["rejectDupKeys"] = true; (*settings)["allowSpecialFloats"] = false; + (*settings)["skipBom"] = true; //! [CharReaderBuilderStrictMode] } // static @@ -1976,6 +1938,7 @@ void CharReaderBuilder::setDefaults(Json::Value* settings) { //! [CharReaderBuilderDefaults] (*settings)["collectComments"] = true; (*settings)["allowComments"] = true; + (*settings)["allowTrailingCommas"] = true; (*settings)["strictRoot"] = false; (*settings)["allowDroppedNullPlaceholders"] = false; (*settings)["allowNumericKeys"] = false; @@ -1984,19 +1947,44 @@ void CharReaderBuilder::setDefaults(Json::Value* settings) { (*settings)["failIfExtra"] = false; (*settings)["rejectDupKeys"] = false; (*settings)["allowSpecialFloats"] = false; + (*settings)["skipBom"] = true; //! [CharReaderBuilderDefaults] } +// static +void CharReaderBuilder::ecma404Mode(Json::Value* settings) { + //! [CharReaderBuilderECMA404Mode] + (*settings)["allowComments"] = false; + (*settings)["allowTrailingCommas"] = false; + (*settings)["strictRoot"] = false; + (*settings)["allowDroppedNullPlaceholders"] = false; + (*settings)["allowNumericKeys"] = false; + (*settings)["allowSingleQuotes"] = false; + (*settings)["stackLimit"] = 1000; + (*settings)["failIfExtra"] = true; + (*settings)["rejectDupKeys"] = false; + (*settings)["allowSpecialFloats"] = false; + (*settings)["skipBom"] = false; + //! [CharReaderBuilderECMA404Mode] +} + +std::vector +CharReader::getStructuredErrors() const { + return _impl->getStructuredErrors(); +} + +bool CharReader::parse(char const* beginDoc, char const* endDoc, Value* root, + String* errs) { + return _impl->parse(beginDoc, endDoc, root, errs); +} ////////////////////////////////// // global functions -bool parseFromStream(CharReader::Factory const& fact, - IStream& sin, - Value* root, +bool parseFromStream(CharReader::Factory const& fact, IStream& sin, Value* root, String* errs) { OStringStream ssin; ssin << sin.rdbuf(); - String doc = ssin.str(); + String doc = std::move(ssin).str(); char const* begin = doc.data(); char const* end = begin + doc.size(); // Note that we do not actually need a null-terminator. diff --git a/src/lib_json/json_tool.h b/src/lib_json/json_tool.h index 5c13f1fed..b952c1916 100644 --- a/src/lib_json/json_tool.h +++ b/src/lib_json/json_tool.h @@ -71,7 +71,7 @@ enum { }; // Defines a char buffer for use with uintToString(). -typedef char UIntToStringBuffer[uintToStringBufferSize]; +using UIntToStringBuffer = char[uintToStringBufferSize]; /** Converts an unsigned integer to string. * @param value Unsigned integer to convert to string @@ -116,14 +116,18 @@ template void fixNumericLocaleInput(Iter begin, Iter end) { * Return iterator that would be the new end of the range [begin,end), if we * were to delete zeros in the end of string, but not the last zero before '.'. */ -template Iter fixZerosInTheEnd(Iter begin, Iter end) { +template +Iter fixZerosInTheEnd(Iter begin, Iter end, unsigned int precision) { for (; begin != end; --end) { if (*(end - 1) != '0') { return end; } // Don't delete the last zero before the decimal point. - if (begin != (end - 1) && *(end - 2) == '.') { - return end; + if (begin != (end - 1) && begin != (end - 2) && *(end - 2) == '.') { + if (precision) { + return end; + } + return end - 2; } } return end; diff --git a/src/lib_json/json_value.cpp b/src/lib_json/json_value.cpp index 6024212f2..a875d28b2 100644 --- a/src/lib_json/json_value.cpp +++ b/src/lib_json/json_value.cpp @@ -8,24 +8,24 @@ #include #include #endif // if !defined(JSON_IS_AMALGAMATION) +#include #include #include +#include #include +#include #include #include -#ifdef JSON_USE_CPPTL -#include + +#ifdef JSONCPP_HAS_STRING_VIEW +#include #endif -#include // min() -#include // size_t // Provide implementation equivalent of std::snprintf for older _MSC compilers #if defined(_MSC_VER) && _MSC_VER < 1900 #include -static int msvc_pre1900_c99_vsnprintf(char* outBuf, - size_t size, - const char* format, - va_list ap) { +static int msvc_pre1900_c99_vsnprintf(char* outBuf, size_t size, + const char* format, va_list ap) { int count = -1; if (size != 0) count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap); @@ -34,10 +34,8 @@ static int msvc_pre1900_c99_vsnprintf(char* outBuf, return count; } -int JSON_API msvc_pre1900_c99_snprintf(char* outBuf, - size_t size, - const char* format, - ...) { +int JSON_API msvc_pre1900_c99_snprintf(char* outBuf, size_t size, + const char* format, ...) { va_list ap; va_start(ap, format); const int count = msvc_pre1900_c99_vsnprintf(outBuf, size, format, ap); @@ -88,31 +86,13 @@ Value const& Value::null = Value::nullSingleton(); Value const& Value::nullRef = Value::nullSingleton(); #endif -const Int Value::minInt = Int(~(UInt(-1) / 2)); -const Int Value::maxInt = Int(UInt(-1) / 2); -const UInt Value::maxUInt = UInt(-1); -#if defined(JSON_HAS_INT64) -const Int64 Value::minInt64 = Int64(~(UInt64(-1) / 2)); -const Int64 Value::maxInt64 = Int64(UInt64(-1) / 2); -const UInt64 Value::maxUInt64 = UInt64(-1); -// The constant is hard-coded because some compiler have trouble -// converting Value::maxUInt64 to a double correctly (AIX/xlC). -// Assumes that UInt64 is a 64 bits integer. -static const double maxUInt64AsDouble = 18446744073709551615.0; -#endif // defined(JSON_HAS_INT64) -const LargestInt Value::minLargestInt = LargestInt(~(LargestUInt(-1) / 2)); -const LargestInt Value::maxLargestInt = LargestInt(LargestUInt(-1) / 2); -const LargestUInt Value::maxLargestUInt = LargestUInt(-1); - -const UInt Value::defaultRealPrecision = 17; - #if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) template static inline bool InRange(double d, T min, U max) { // The casts can lose precision, but we are looking only for // an approximate range. Might fail on edge cases though. ~cdunn - // return d >= static_cast(min) && d <= static_cast(max); - return d >= min && d <= max; + return d >= static_cast(min) && d <= static_cast(max) && + !(static_cast(d) == min && d != static_cast(min)); } #else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) static inline double integerToDouble(Json::UInt64 value) { @@ -126,7 +106,8 @@ template static inline double integerToDouble(T value) { template static inline bool InRange(double d, T min, U max) { - return d >= integerToDouble(min) && d <= integerToDouble(max); + return d >= integerToDouble(min) && d <= integerToDouble(max) && + !(static_cast(d) == min && d != integerToDouble(min)); } #endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) @@ -143,7 +124,7 @@ static inline char* duplicateStringValue(const char* value, size_t length) { if (length >= static_cast(Value::maxInt)) length = Value::maxInt - 1; - char* newString = static_cast(malloc(length + 1)); + auto newString = static_cast(malloc(length + 1)); if (newString == nullptr) { throwRuntimeError("in Json::Value::duplicateStringValue(): " "Failed to allocate string value buffer"); @@ -163,8 +144,8 @@ static inline char* duplicateAndPrefixStringValue(const char* value, sizeof(unsigned) - 1U, "in Json::Value::duplicateAndPrefixStringValue(): " "length too big for prefixing"); - unsigned actualLength = length + static_cast(sizeof(unsigned)) + 1U; - char* newString = static_cast(malloc(actualLength)); + size_t actualLength = sizeof(length) + length + 1; + auto newString = static_cast(malloc(actualLength)); if (newString == nullptr) { throwRuntimeError("in Json::Value::duplicateAndPrefixStringValue(): " "Failed to allocate string value buffer"); @@ -175,10 +156,8 @@ static inline char* duplicateAndPrefixStringValue(const char* value, 0; // to avoid buffer over-run accidents by users later return newString; } -inline static void decodePrefixedString(bool isPrefixed, - char const* prefixed, - unsigned* length, - char const** value) { +inline static void decodePrefixedString(bool isPrefixed, char const* prefixed, + unsigned* length, char const** value) { if (!isPrefixed) { *length = static_cast(strlen(prefixed)); *value = prefixed; @@ -190,7 +169,7 @@ inline static void decodePrefixedString(bool isPrefixed, /** Free the string duplicated by * duplicateStringValue()/duplicateAndPrefixStringValue(). */ -#if JSONCPP_USING_SECURE_MEMORY +#if JSONCPP_USE_SECURE_MEMORY static inline void releasePrefixedStringValue(char* value) { unsigned length = 0; char const* valueDecoded; @@ -205,10 +184,10 @@ static inline void releaseStringValue(char* value, unsigned length) { memset(value, 0, size); free(value); } -#else // !JSONCPP_USING_SECURE_MEMORY +#else // !JSONCPP_USE_SECURE_MEMORY static inline void releasePrefixedStringValue(char* value) { free(value); } static inline void releaseStringValue(char* value, unsigned) { free(value); } -#endif // JSONCPP_USING_SECURE_MEMORY +#endif // JSONCPP_USE_SECURE_MEMORY } // namespace Json @@ -228,17 +207,25 @@ namespace Json { #if JSON_USE_EXCEPTION Exception::Exception(String msg) : msg_(std::move(msg)) {} -Exception::~Exception() JSONCPP_NOEXCEPT {} -char const* Exception::what() const JSONCPP_NOEXCEPT { return msg_.c_str(); } +Exception::~Exception() noexcept = default; +char const* Exception::what() const noexcept { return msg_.c_str(); } RuntimeError::RuntimeError(String const& msg) : Exception(msg) {} LogicError::LogicError(String const& msg) : Exception(msg) {} -[[noreturn]] void throwRuntimeError(String const& msg) { +JSONCPP_NORETURN void throwRuntimeError(String const& msg) { throw RuntimeError(msg); } -[[noreturn]] void throwLogicError(String const& msg) { throw LogicError(msg); } +JSONCPP_NORETURN void throwLogicError(String const& msg) { + throw LogicError(msg); +} #else // !JSON_USE_EXCEPTION -[[noreturn]] void throwRuntimeError(String const& msg) { abort(); } -[[noreturn]] void throwLogicError(String const& msg) { abort(); } +JSONCPP_NORETURN void throwRuntimeError(String const& msg) { + std::cerr << msg << std::endl; + abort(); +} +JSONCPP_NORETURN void throwLogicError(String const& msg) { + std::cerr << msg << std::endl; + abort(); +} #endif // ////////////////////////////////////////////////////////////////// @@ -254,8 +241,7 @@ LogicError::LogicError(String const& msg) : Exception(msg) {} Value::CZString::CZString(ArrayIndex index) : cstr_(nullptr), index_(index) {} -Value::CZString::CZString(char const* str, - unsigned length, +Value::CZString::CZString(char const* str, unsigned length, DuplicationPolicy allocate) : cstr_(str) { // allocate != duplicate @@ -279,7 +265,7 @@ Value::CZString::CZString(const CZString& other) { storage_.length_ = other.storage_.length_; } -Value::CZString::CZString(CZString&& other) +Value::CZString::CZString(CZString&& other) noexcept : cstr_(other.cstr_), index_(other.index_) { other.cstr_ = nullptr; } @@ -287,7 +273,7 @@ Value::CZString::CZString(CZString&& other) Value::CZString::~CZString() { if (cstr_ && storage_.policy_ == duplicate) { releaseStringValue(const_cast(cstr_), - storage_.length_ + 1u); // +1 for null terminating + storage_.length_ + 1U); // +1 for null terminating // character for sake of // completeness but not actually // necessary @@ -305,7 +291,7 @@ Value::CZString& Value::CZString::operator=(const CZString& other) { return *this; } -Value::CZString& Value::CZString::operator=(CZString&& other) { +Value::CZString& Value::CZString::operator=(CZString&& other) noexcept { cstr_ = other.cstr_; index_ = other.index_; other.cstr_ = nullptr; @@ -438,19 +424,19 @@ Value::Value(const String& value) { value.data(), static_cast(value.length())); } -Value::Value(const StaticString& value) { - initBasic(stringValue); - value_.string_ = const_cast(value.c_str()); -} - -#ifdef JSON_USE_CPPTL -Value::Value(const CppTL::ConstString& value) { +#ifdef JSONCPP_HAS_STRING_VIEW +Value::Value(std::string_view value) { initBasic(stringValue, true); value_.string_ = duplicateAndPrefixStringValue( - value, static_cast(value.length())); + value.data(), static_cast(value.length())); } #endif +Value::Value(const StaticString& value) { + initBasic(stringValue); + value_.string_ = const_cast(value.c_str()); +} + Value::Value(bool value) { initBasic(booleanValue); value_.bool_ = value; @@ -461,7 +447,7 @@ Value::Value(const Value& other) { dupMeta(other); } -Value::Value(Value&& other) { +Value::Value(Value&& other) noexcept { initBasic(nullValue); swap(other); } @@ -476,7 +462,7 @@ Value& Value::operator=(const Value& other) { return *this; } -Value& Value::operator=(Value&& other) { +Value& Value::operator=(Value&& other) noexcept { other.swap(*this); return *this; } @@ -518,7 +504,7 @@ int Value::compare(const Value& other) const { bool Value::operator<(const Value& other) const { int typeDelta = type() - other.type(); if (typeDelta) - return typeDelta < 0 ? true : false; + return typeDelta < 0; switch (type()) { case nullValue: return false; @@ -532,10 +518,7 @@ bool Value::operator<(const Value& other) const { return value_.bool_ < other.value_.bool_; case stringValue: { if ((value_.string_ == nullptr) || (other.value_.string_ == nullptr)) { - if (other.value_.string_) - return true; - else - return false; + return other.value_.string_ != nullptr; } unsigned this_len; unsigned other_len; @@ -556,9 +539,10 @@ bool Value::operator<(const Value& other) const { } case arrayValue: case objectValue: { - int delta = int(value_.map_->size() - other.value_.map_->size()); - if (delta) - return delta < 0; + auto thisSize = value_.map_->size(); + auto otherSize = other.value_.map_->size(); + if (thisSize != otherSize) + return thisSize < otherSize; return (*value_.map_) < (*other.value_.map_); } default: @@ -629,7 +613,7 @@ const char* Value::asCString() const { return this_str; } -#if JSONCPP_USING_SECURE_MEMORY +#if JSONCPP_USE_SECURE_MEMORY unsigned Value::getCStringLength() const { JSON_ASSERT_MESSAGE(type() == stringValue, "in Json::Value::asCString(): requires stringValue"); @@ -655,6 +639,21 @@ bool Value::getString(char const** begin, char const** end) const { return true; } +#ifdef JSONCPP_HAS_STRING_VIEW +bool Value::getString(std::string_view* str) const { + if (type() != stringValue) + return false; + if (value_.string_ == nullptr) + return false; + const char* begin; + unsigned length; + decodePrefixedString(this->isAllocated(), this->value_.string_, &length, + &begin); + *str = std::string_view(begin, length); + return true; +} +#endif + String Value::asString() const { switch (type()) { case nullValue: @@ -681,15 +680,6 @@ String Value::asString() const { } } -#ifdef JSON_USE_CPPTL -CppTL::ConstString Value::asConstString() const { - unsigned len; - char const* str; - decodePrefixedString(isAllocated(), value_.string_, &len, &str); - return CppTL::ConstString(str, len); -} -#endif - Value::Int Value::asInt() const { switch (type()) { case intValue: @@ -721,7 +711,7 @@ Value::UInt Value::asUInt() const { JSON_ASSERT_MESSAGE(isUInt(), "LargestUInt out of UInt range"); return UInt(value_.uint_); case realValue: - JSON_ASSERT_MESSAGE(InRange(value_.real_, 0, maxUInt), + JSON_ASSERT_MESSAGE(InRange(value_.real_, 0u, maxUInt), "double out of UInt range"); return UInt(value_.real_); case nullValue: @@ -744,6 +734,11 @@ Value::Int64 Value::asInt64() const { JSON_ASSERT_MESSAGE(isInt64(), "LargestUInt out of Int64 range"); return Int64(value_.uint_); case realValue: + // If the double value is in proximity to minInt64, it will be rounded to + // minInt64. The correct value in this scenario is indeterminable + JSON_ASSERT_MESSAGE( + value_.real_ != minInt64, + "Double value is minInt64, precise value cannot be determined"); JSON_ASSERT_MESSAGE(InRange(value_.real_, minInt64, maxInt64), "double out of Int64 range"); return Int64(value_.real_); @@ -765,7 +760,7 @@ Value::UInt64 Value::asUInt64() const { case uintValue: return UInt64(value_.uint_); case realValue: - JSON_ASSERT_MESSAGE(InRange(value_.real_, 0, maxUInt64), + JSON_ASSERT_MESSAGE(InRange(value_.real_, 0u, maxUInt64), "double out of UInt64 range"); return UInt64(value_.real_); case nullValue: @@ -833,7 +828,7 @@ float Value::asFloat() const { case nullValue: return 0.0; case booleanValue: - return value_.bool_ ? 1.0f : 0.0f; + return value_.bool_ ? 1.0F : 0.0F; default: break; } @@ -847,9 +842,9 @@ bool Value::asBool() const { case nullValue: return false; case intValue: - return value_.int_ ? true : false; + return value_.int_ != 0; case uintValue: - return value_.uint_ ? true : false; + return value_.uint_ != 0; case realValue: { // According to JavaScript language zero or NaN is regarded as false const auto value_classification = std::fpclassify(value_.real_); @@ -865,7 +860,7 @@ bool Value::isConvertibleTo(ValueType other) const { switch (other) { case nullValue: return (isNumeric() && asDouble() == 0.0) || - (type() == booleanValue && value_.bool_ == false) || + (type() == booleanValue && !value_.bool_) || (type() == stringValue && asString().empty()) || (type() == arrayValue && value_.map_->empty()) || (type() == objectValue && value_.map_->empty()) || @@ -876,7 +871,7 @@ bool Value::isConvertibleTo(ValueType other) const { type() == booleanValue || type() == nullValue; case uintValue: return isUInt() || - (type() == realValue && InRange(value_.real_, 0, maxUInt)) || + (type() == realValue && InRange(value_.real_, 0u, maxUInt)) || type() == booleanValue || type() == nullValue; case realValue: return isNumeric() || type() == booleanValue || type() == nullValue; @@ -920,9 +915,8 @@ ArrayIndex Value::size() const { bool Value::empty() const { if (isNull() || isArray() || isObject()) - return size() == 0u; - else - return false; + return size() == 0U; + return false; } Value::operator bool() const { return !isNull(); } @@ -952,7 +946,8 @@ void Value::resize(ArrayIndex newSize) { if (newSize == 0) clear(); else if (newSize > oldSize) - this->operator[](newSize - 1); + for (ArrayIndex i = oldSize; i < newSize; ++i) + (*this)[i]; else { for (ArrayIndex index = newSize; index < oldSize; ++index) { value_.map_->erase(index); @@ -1131,12 +1126,64 @@ Value const* Value::find(char const* begin, char const* end) const { return nullptr; return &(*it).second; } +Value const* Value::find(const String& key) const { + return find(key.data(), key.data() + key.length()); +} + +Value const* Value::findNull(const String& key) const { + return findValue(key); +} +Value const* Value::findBool(const String& key) const { + return findValue(key); +} +Value const* Value::findInt(const String& key) const { + return findValue(key); +} +Value const* Value::findInt64(const String& key) const { + return findValue(key); +} +Value const* Value::findUInt(const String& key) const { + return findValue(key); +} +Value const* Value::findUInt64(const String& key) const { + return findValue(key); +} +Value const* Value::findIntegral(const String& key) const { + return findValue(key); +} +Value const* Value::findDouble(const String& key) const { + return findValue(key); +} +Value const* Value::findNumeric(const String& key) const { + return findValue(key); +} +Value const* Value::findString(const String& key) const { + return findValue(key); +} +Value const* Value::findArray(const String& key) const { + return findValue(key); +} +Value const* Value::findObject(const String& key) const { + return findValue(key); +} + Value* Value::demand(char const* begin, char const* end) { JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, "in Json::Value::demand(begin, end): requires " "objectValue or nullValue"); return &resolveReference(begin, end); } +#ifdef JSONCPP_HAS_STRING_VIEW +const Value& Value::operator[](std::string_view key) const { + Value const* found = find(key.data(), key.data() + key.length()); + if (!found) + return nullSingleton(); + return *found; +} +Value& Value::operator[](std::string_view key) { + return resolveReference(key.data(), key.data() + key.length()); +} +#else const Value& Value::operator[](const char* key) const { Value const* found = find(key, key + strlen(key)); if (!found) @@ -1144,7 +1191,7 @@ const Value& Value::operator[](const char* key) const { return *found; } Value const& Value::operator[](const String& key) const { - Value const* found = find(key.data(), key.data() + key.length()); + Value const* found = find(key); if (!found) return nullSingleton(); return *found; @@ -1157,41 +1204,58 @@ Value& Value::operator[](const char* key) { Value& Value::operator[](const String& key) { return resolveReference(key.data(), key.data() + key.length()); } +#endif Value& Value::operator[](const StaticString& key) { return resolveReference(key.c_str()); } -#ifdef JSON_USE_CPPTL -Value& Value::operator[](const CppTL::ConstString& key) { - return resolveReference(key.c_str(), key.end_c_str()); -} -Value const& Value::operator[](CppTL::ConstString const& key) const { - Value const* found = find(key.c_str(), key.end_c_str()); - if (!found) - return nullSingleton(); - return *found; +Value& Value::append(const Value& value) { return append(Value(value)); } + +Value& Value::append(Value&& value) { + JSON_ASSERT_MESSAGE(type() == nullValue || type() == arrayValue, + "in Json::Value::append: requires arrayValue"); + if (type() == nullValue) { + *this = Value(arrayValue); + } + return this->value_.map_->emplace(size(), std::move(value)).first->second; } -#endif -Value& Value::append(const Value& value) { return (*this)[size()] = value; } +bool Value::insert(ArrayIndex index, const Value& newValue) { + return insert(index, Value(newValue)); +} -Value& Value::append(Value&& value) { - return (*this)[size()] = std::move(value); +bool Value::insert(ArrayIndex index, Value&& newValue) { + JSON_ASSERT_MESSAGE(type() == nullValue || type() == arrayValue, + "in Json::Value::insert: requires arrayValue"); + ArrayIndex length = size(); + if (index > length) { + return false; + } + for (ArrayIndex i = length; i > index; i--) { + (*this)[i] = std::move((*this)[i - 1]); + } + (*this)[index] = std::move(newValue); + return true; } -Value Value::get(char const* begin, - char const* end, +Value Value::get(char const* begin, char const* end, Value const& defaultValue) const { Value const* found = find(begin, end); return !found ? defaultValue : *found; } +#ifdef JSONCPP_HAS_STRING_VIEW +Value Value::get(std::string_view key, const Value& defaultValue) const { + return get(key.data(), key.data() + key.length(), defaultValue); +} +#else Value Value::get(char const* key, Value const& defaultValue) const { return get(key, key + strlen(key), defaultValue); } Value Value::get(String const& key, Value const& defaultValue) const { return get(key.data(), key.data() + key.length(), defaultValue); } +#endif bool Value::removeMember(const char* begin, const char* end, Value* removed) { if (type() != objectValue) { @@ -1207,12 +1271,31 @@ bool Value::removeMember(const char* begin, const char* end, Value* removed) { value_.map_->erase(it); return true; } +#ifdef JSONCPP_HAS_STRING_VIEW +bool Value::removeMember(std::string_view key, Value* removed) { + return removeMember(key.data(), key.data() + key.length(), removed); +} +#else bool Value::removeMember(const char* key, Value* removed) { return removeMember(key, key + strlen(key), removed); } bool Value::removeMember(String const& key, Value* removed) { return removeMember(key.data(), key.data() + key.length(), removed); } +#endif + +#ifdef JSONCPP_HAS_STRING_VIEW +void Value::removeMember(std::string_view key) { + JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, + "in Json::Value::removeMember(): requires objectValue"); + if (type() == nullValue) + return; + + CZString actualKey(key.data(), unsigned(key.length()), + CZString::noDuplication); + value_.map_->erase(actualKey); +} +#else void Value::removeMember(const char* key) { JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, "in Json::Value::removeMember(): requires objectValue"); @@ -1223,6 +1306,7 @@ void Value::removeMember(const char* key) { value_.map_->erase(actualKey); } void Value::removeMember(const String& key) { removeMember(key.c_str()); } +#endif bool Value::removeIndex(ArrayIndex index, Value* removed) { if (type() != arrayValue) { @@ -1234,7 +1318,7 @@ bool Value::removeIndex(ArrayIndex index, Value* removed) { return false; } if (removed) - *removed = it->second; + *removed = std::move(it->second); ArrayIndex oldSize = size(); // shift left all items left, into the place of the "removed" for (ArrayIndex i = index; i < (oldSize - 1); ++i) { @@ -1248,28 +1332,21 @@ bool Value::removeIndex(ArrayIndex index, Value* removed) { return true; } -#ifdef JSON_USE_CPPTL -Value Value::get(const CppTL::ConstString& key, - const Value& defaultValue) const { - return get(key.c_str(), key.end_c_str(), defaultValue); -} -#endif - bool Value::isMember(char const* begin, char const* end) const { Value const* value = find(begin, end); return nullptr != value; } +#ifdef JSONCPP_HAS_STRING_VIEW +bool Value::isMember(std::string_view key) const { + return isMember(key.data(), key.data() + key.length()); +} +#else bool Value::isMember(char const* key) const { return isMember(key, key + strlen(key)); } bool Value::isMember(String const& key) const { return isMember(key.data(), key.data() + key.length()); } - -#ifdef JSON_USE_CPPTL -bool Value::isMember(const CppTL::ConstString& key) const { - return isMember(key.c_str(), key.end_c_str()); -} #endif Value::Members Value::getMemberNames() const { @@ -1287,31 +1364,6 @@ Value::Members Value::getMemberNames() const { } return members; } -// -//# ifdef JSON_USE_CPPTL -// EnumMemberNames -// Value::enumMemberNames() const -//{ -// if ( type() == objectValue ) -// { -// return CppTL::Enum::any( CppTL::Enum::transform( -// CppTL::Enum::keys( *(value_.map_), CppTL::Type() ), -// MemberNamesTransform() ) ); -// } -// return EnumMemberNames(); -//} -// -// -// EnumValues -// Value::enumValues() const -//{ -// if ( type() == objectValue || type() == arrayValue ) -// return CppTL::Enum::anyValues( *(value_.map_), -// CppTL::Type() ); -// return EnumValues(); -//} -// -//# endif static bool IsIntegral(double d) { double integral_part; @@ -1375,8 +1427,12 @@ bool Value::isInt64() const { // Note that maxInt64 (= 2^63 - 1) is not exactly representable as a // double, so double(maxInt64) will be rounded up to 2^63. Therefore we // require the value to be strictly less than the limit. - return value_.real_ >= double(minInt64) && - value_.real_ < double(maxInt64) && IsIntegral(value_.real_); + // minInt64 is -2^63 which can be represented as a double, but since double + // values in its proximity are also rounded to -2^63, we require the value + // to be strictly greater than the limit to avoid returning 'true' for + // values that are not in the range + return value_.real_ > double(minInt64) && value_.real_ < double(maxInt64) && + IsIntegral(value_.real_); default: break; } @@ -1414,7 +1470,11 @@ bool Value::isIntegral() const { // Note that maxUInt64 (= 2^64 - 1) is not exactly representable as a // double, so double(maxUInt64) will be rounded up to 2^64. Therefore we // require the value to be strictly less than the limit. - return value_.real_ >= double(minInt64) && + // minInt64 is -2^63 which can be represented as a double, but since double + // values in its proximity are also rounded to -2^63, we require the value + // to be strictly greater than the limit to avoid returning 'true' for + // values that are not in the range + return value_.real_ > double(minInt64) && value_.real_ < maxUInt64AsDouble && IsIntegral(value_.real_); #else return value_.real_ >= minInt && value_.real_ <= maxUInt && @@ -1441,14 +1501,15 @@ bool Value::isObject() const { return type() == objectValue; } Value::Comments::Comments(const Comments& that) : ptr_{cloneUnique(that.ptr_)} {} -Value::Comments::Comments(Comments&& that) : ptr_{std::move(that.ptr_)} {} +Value::Comments::Comments(Comments&& that) noexcept + : ptr_{std::move(that.ptr_)} {} Value::Comments& Value::Comments::operator=(const Comments& that) { ptr_ = cloneUnique(that.ptr_); return *this; } -Value::Comments& Value::Comments::operator=(Comments&& that) { +Value::Comments& Value::Comments::operator=(Comments&& that) noexcept { ptr_ = std::move(that.ptr_); return *this; } @@ -1464,9 +1525,10 @@ String Value::Comments::get(CommentPlacement slot) const { } void Value::Comments::set(CommentPlacement slot, String comment) { - if (!ptr_) { + if (slot >= CommentPlacement::numberOfCommentPlacement) + return; + if (!ptr_) ptr_ = std::unique_ptr(new Array()); - } (*ptr_)[slot] = std::move(comment); } @@ -1475,9 +1537,8 @@ void Value::setComment(String comment, CommentPlacement placement) { // Always discard trailing newline, to aid indentation. comment.pop_back(); } - JSON_ASSERT(!comment.empty()); JSON_ASSERT_MESSAGE( - comment[0] == '\0' || comment[0] == '/', + comment.empty() || comment[0] == '/', "in Json::Value::setComment(): Comments must start with /"); comments_.set(placement, std::move(comment)); } @@ -1563,25 +1624,20 @@ Value::iterator Value::end() { // class PathArgument // ////////////////////////////////////////////////////////////////// -PathArgument::PathArgument() : key_() {} +PathArgument::PathArgument() = default; PathArgument::PathArgument(ArrayIndex index) - : key_(), index_(index), kind_(kindIndex) {} + : index_(index), kind_(kindIndex) {} -PathArgument::PathArgument(const char* key) - : key_(key), index_(), kind_(kindKey) {} +PathArgument::PathArgument(const char* key) : key_(key), kind_(kindKey) {} -PathArgument::PathArgument(const String& key) - : key_(key.c_str()), index_(), kind_(kindKey) {} +PathArgument::PathArgument(String key) : key_(std::move(key)), kind_(kindKey) {} // class Path // ////////////////////////////////////////////////////////////////// -Path::Path(const String& path, - const PathArgument& a1, - const PathArgument& a2, - const PathArgument& a3, - const PathArgument& a4, +Path::Path(const String& path, const PathArgument& a1, const PathArgument& a2, + const PathArgument& a3, const PathArgument& a4, const PathArgument& a5) { InArgs in; in.reserve(5); @@ -1624,8 +1680,7 @@ void Path::makePath(const String& path, const InArgs& in) { } } -void Path::addPathInArg(const String& /*path*/, - const InArgs& in, +void Path::addPathInArg(const String& /*path*/, const InArgs& in, InArgs::const_iterator& itInArg, PathArgument::Kind kind) { if (itInArg == in.end()) { diff --git a/src/lib_json/json_valueiterator.inl b/src/lib_json/json_valueiterator.inl index 4a3d210de..d6128b8ed 100644 --- a/src/lib_json/json_valueiterator.inl +++ b/src/lib_json/json_valueiterator.inl @@ -21,7 +21,8 @@ ValueIteratorBase::ValueIteratorBase( const Value::ObjectValues::iterator& current) : current_(current), isNull_(false) {} -Value& ValueIteratorBase::deref() const { return current_->second; } +Value& ValueIteratorBase::deref() { return current_->second; } +const Value& ValueIteratorBase::deref() const { return current_->second; } void ValueIteratorBase::increment() { ++current_; } @@ -29,9 +30,6 @@ void ValueIteratorBase::decrement() { --current_; } ValueIteratorBase::difference_type ValueIteratorBase::computeDistance(const SelfType& other) const { -#ifdef JSON_USE_CPPTL_SMALLMAP - return other.current_ - current_; -#else // Iterator for null value are initialized using the default // constructor, which initialize current_ to the default // std::map::iterator. As begin() and end() are two instance @@ -52,7 +50,6 @@ ValueIteratorBase::computeDistance(const SelfType& other) const { ++myDistance; } return myDistance; -#endif } bool ValueIteratorBase::isEqual(const SelfType& other) const { diff --git a/src/lib_json/json_writer.cpp b/src/lib_json/json_writer.cpp index 41dfd8cf6..ac14eb11f 100644 --- a/src/lib_json/json_writer.cpp +++ b/src/lib_json/json_writer.cpp @@ -7,7 +7,11 @@ #include "json_tool.h" #include #endif // if !defined(JSON_IS_AMALGAMATION) +#include #include +#include +#include +#include #include #include #include @@ -15,67 +19,6 @@ #include #include -#if __cplusplus >= 201103L -#include -#include - -#if !defined(isnan) -#define isnan std::isnan -#endif - -#if !defined(isfinite) -#define isfinite std::isfinite -#endif - -#else -#include -#include - -#if defined(_MSC_VER) -#if !defined(isnan) -#include -#define isnan _isnan -#endif - -#if !defined(isfinite) -#include -#define isfinite _finite -#endif - -#if !defined(_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES) -#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1 -#endif //_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES - -#endif //_MSC_VER - -#if defined(__sun) && defined(__SVR4) // Solaris -#if !defined(isfinite) -#include -#define isfinite finite -#endif -#endif - -#if defined(__hpux) -#if !defined(isfinite) -#if defined(__ia64) && !defined(finite) -#define isfinite(x) \ - ((sizeof(x) == sizeof(float) ? _Isfinitef(x) : _IsFinite(x))) -#endif -#endif -#endif - -#if !defined(isnan) -// IEEE standard states that NaN values will not compare to themselves -#define isnan(x) (x != x) -#endif - -#if !defined(__APPLE__) -#if !defined(isfinite) -#define isfinite finite -#endif -#endif -#endif - #if defined(_MSC_VER) // Disable warning about strdup being deprecated. #pragma warning(disable : 4996) @@ -83,11 +26,7 @@ namespace Json { -#if __cplusplus >= 201103L || (defined(_CPPLIB_VER) && _CPPLIB_VER >= 520) -typedef std::unique_ptr StreamWriterPtr; -#else -typedef std::auto_ptr StreamWriterPtr; -#endif +using StreamWriterPtr = std::unique_ptr; String valueToString(LargestInt value) { UIntToStringBuffer buffer; @@ -122,18 +61,17 @@ String valueToString(UInt value) { return valueToString(LargestUInt(value)); } #endif // # if defined(JSON_HAS_INT64) namespace { -String valueToString(double value, - bool useSpecialFloats, - unsigned int precision, - PrecisionType precisionType) { +String valueToString(double value, bool useSpecialFloats, + unsigned int precision, PrecisionType precisionType) { // Print into the buffer. We need not request the alternative representation // that always has a decimal point because JSON doesn't distinguish the // concepts of reals and integers. - if (!isfinite(value)) { - static const char* const reps[2][3] = {{"NaN", "-Infinity", "Infinity"}, - {"null", "-1e+9999", "1e+9999"}}; - return reps[useSpecialFloats ? 0 : 1] - [isnan(value) ? 0 : (value < 0) ? 1 : 2]; + if (!std::isfinite(value)) { + if (std::isnan(value)) + return useSpecialFloats ? "NaN" : "null"; + if (value < 0) + return useSpecialFloats ? "-Infinity" : "-1e+9999"; + return useSpecialFloats ? "Infinity" : "1e+9999"; } String buffer(size_t(36), '\0'); @@ -154,38 +92,35 @@ String valueToString(double value, buffer.erase(fixNumericLocale(buffer.begin(), buffer.end()), buffer.end()); - // strip the zero padding from the right - if (precisionType == PrecisionType::decimalPlaces) { - buffer.erase(fixZerosInTheEnd(buffer.begin(), buffer.end()), buffer.end()); - } - // try to ensure we preserve the fact that this was given to us as a double on // input if (buffer.find('.') == buffer.npos && buffer.find('e') == buffer.npos) { buffer += ".0"; } + + // strip the zero padding from the right + if (precisionType == PrecisionType::decimalPlaces) { + buffer.erase(fixZerosInTheEnd(buffer.begin(), buffer.end(), precision), + buffer.end()); + } + return buffer; } } // namespace -String valueToString(double value, - unsigned int precision, +String valueToString(double value, unsigned int precision, PrecisionType precisionType) { return valueToString(value, false, precision, precisionType); } String valueToString(bool value) { return value ? "true" : "false"; } -static bool isAnyCharRequiredQuoting(char const* s, size_t n) { +static bool doesAnyCharRequireEscaping(char const* s, size_t n) { assert(s || !n); - char const* const end = s + n; - for (char const* cur = s; cur < end; ++cur) { - if (*cur == '\\' || *cur == '\"' || *cur < ' ' || - static_cast(*cur) < 0x80) - return true; - } - return false; + return std::any_of(s, s + n, [](unsigned char c) { + return c == '\\' || c == '"' || c < 0x20 || c > 0x7F; + }); } static unsigned int utf8ToCodepoint(const char*& s, const char* e) { @@ -267,11 +202,20 @@ static String toHex16Bit(unsigned int x) { return result; } -static String valueToQuotedStringN(const char* value, unsigned length) { +static void appendRaw(String& result, unsigned ch) { + result += static_cast(ch); +} + +static void appendHex(String& result, unsigned ch) { + result.append("\\u").append(toHex16Bit(ch)); +} + +static String valueToQuotedStringN(const char* value, size_t length, + bool emitUTF8 = false) { if (value == nullptr) return ""; - if (!isAnyCharRequiredQuoting(value, length)) + if (!doesAnyCharRequireEscaping(value, length)) return String("\"") + value + "\""; // We have to walk value and escape any special characters. // Appending to String is not efficient, but this should be rare. @@ -313,21 +257,28 @@ static String valueToQuotedStringN(const char* value, unsigned length) { // Should add a flag to allow this compatibility mode and prevent this // sequence from occurring. default: { - unsigned int cp = utf8ToCodepoint(c, end); - // don't escape non-control characters - // (short escape sequence are applied above) - if (cp < 0x80 && cp >= 0x20) - result += static_cast(cp); - else if (cp < 0x10000) { // codepoint is in Basic Multilingual Plane - result += "\\u"; - result += toHex16Bit(cp); - } else { // codepoint is not in Basic Multilingual Plane - // convert to surrogate pair first - cp -= 0x10000; - result += "\\u"; - result += toHex16Bit((cp >> 10) + 0xD800); - result += "\\u"; - result += toHex16Bit((cp & 0x3FF) + 0xDC00); + if (emitUTF8) { + unsigned codepoint = static_cast(*c); + if (codepoint < 0x20) { + appendHex(result, codepoint); + } else { + appendRaw(result, codepoint); + } + } else { + unsigned codepoint = utf8ToCodepoint(c, end); // modifies `c` + if (codepoint < 0x20) { + appendHex(result, codepoint); + } else if (codepoint < 0x80) { + appendRaw(result, codepoint); + } else if (codepoint < 0x10000) { + // Basic Multilingual Plane + appendHex(result, codepoint); + } else { + // Extended Unicode. Encode 20 bits as a surrogate pair. + codepoint -= 0x10000; + appendHex(result, 0xd800 + ((codepoint >> 10) & 0x3ff)); + appendHex(result, 0xdc00 + (codepoint & 0x3ff)); + } } } break; } @@ -337,7 +288,11 @@ static String valueToQuotedStringN(const char* value, unsigned length) { } String valueToQuotedString(const char* value) { - return valueToQuotedStringN(value, static_cast(strlen(value))); + return valueToQuotedStringN(value, strlen(value)); +} + +String valueToQuotedString(const char* value, size_t length) { + return valueToQuotedStringN(value, length); } // Class Writer @@ -386,7 +341,7 @@ void FastWriter::writeValue(const Value& value) { char const* end; bool ok = value.getString(&str, &end); if (ok) - document_ += valueToQuotedStringN(str, static_cast(end - str)); + document_ += valueToQuotedStringN(str, static_cast(end - str)); break; } case booleanValue: @@ -409,8 +364,7 @@ void FastWriter::writeValue(const Value& value) { const String& name = *it; if (it != members.begin()) document_ += ','; - document_ += valueToQuotedStringN(name.data(), - static_cast(name.length())); + document_ += valueToQuotedStringN(name.data(), name.length()); document_ += yamlCompatibilityEnabled_ ? ": " : ":"; writeValue(value[name]); } @@ -455,7 +409,7 @@ void StyledWriter::writeValue(const Value& value) { char const* end; bool ok = value.getString(&str, &end); if (ok) - pushValue(valueToQuotedStringN(str, static_cast(end - str))); + pushValue(valueToQuotedStringN(str, static_cast(end - str))); else pushValue(""); break; @@ -478,7 +432,7 @@ void StyledWriter::writeValue(const Value& value) { const String& name = *it; const Value& childValue = value[name]; writeCommentBeforeValue(childValue); - writeWithIndent(valueToQuotedString(name.c_str())); + writeWithIndent(valueToQuotedString(name.c_str(), name.size())); document_ += " : "; writeValue(childValue); if (++it == members.end()) { @@ -496,7 +450,7 @@ void StyledWriter::writeValue(const Value& value) { } void StyledWriter::writeArrayValue(const Value& value) { - unsigned size = value.size(); + size_t size = value.size(); if (size == 0) pushValue("[]"); else { @@ -505,7 +459,7 @@ void StyledWriter::writeArrayValue(const Value& value) { writeWithIndent("["); indent(); bool hasChildValue = !childValues_.empty(); - unsigned index = 0; + ArrayIndex index = 0; for (;;) { const Value& childValue = value[index]; writeCommentBeforeValue(childValue); @@ -528,7 +482,7 @@ void StyledWriter::writeArrayValue(const Value& value) { { assert(childValues_.size() == size); document_ += "[ "; - for (unsigned index = 0; index < size; ++index) { + for (size_t index = 0; index < size; ++index) { if (index > 0) document_ += ", "; document_ += childValues_[index]; @@ -673,7 +627,7 @@ void StyledStreamWriter::writeValue(const Value& value) { char const* end; bool ok = value.getString(&str, &end); if (ok) - pushValue(valueToQuotedStringN(str, static_cast(end - str))); + pushValue(valueToQuotedStringN(str, static_cast(end - str))); else pushValue(""); break; @@ -696,7 +650,7 @@ void StyledStreamWriter::writeValue(const Value& value) { const String& name = *it; const Value& childValue = value[name]; writeCommentBeforeValue(childValue); - writeWithIndent(valueToQuotedString(name.c_str())); + writeWithIndent(valueToQuotedString(name.c_str(), name.size())); *document_ << " : "; writeValue(childValue); if (++it == members.end()) { @@ -864,13 +818,10 @@ struct CommentStyle { }; struct BuiltStyledStreamWriter : public StreamWriter { - BuiltStyledStreamWriter(String indentation, - CommentStyle::Enum cs, - String colonSymbol, - String nullSymbol, - String endingLineFeedSymbol, - bool useSpecialFloats, - unsigned int precision, + BuiltStyledStreamWriter(String indentation, CommentStyle::Enum cs, + String colonSymbol, String nullSymbol, + String endingLineFeedSymbol, bool useSpecialFloats, + bool emitUTF8, unsigned int precision, PrecisionType precisionType); int write(Value const& root, OStream* sout) override; @@ -887,7 +838,7 @@ struct BuiltStyledStreamWriter : public StreamWriter { void writeCommentAfterValueOnSameLine(Value const& root); static bool hasCommentForValue(const Value& value); - typedef std::vector ChildValues; + using ChildValues = std::vector; ChildValues childValues_; String indentString_; @@ -900,23 +851,20 @@ struct BuiltStyledStreamWriter : public StreamWriter { bool addChildValues_ : 1; bool indented_ : 1; bool useSpecialFloats_ : 1; + bool emitUTF8_ : 1; unsigned int precision_; PrecisionType precisionType_; }; -BuiltStyledStreamWriter::BuiltStyledStreamWriter(String indentation, - CommentStyle::Enum cs, - String colonSymbol, - String nullSymbol, - String endingLineFeedSymbol, - bool useSpecialFloats, - unsigned int precision, - PrecisionType precisionType) +BuiltStyledStreamWriter::BuiltStyledStreamWriter( + String indentation, CommentStyle::Enum cs, String colonSymbol, + String nullSymbol, String endingLineFeedSymbol, bool useSpecialFloats, + bool emitUTF8, unsigned int precision, PrecisionType precisionType) : rightMargin_(74), indentation_(std::move(indentation)), cs_(cs), colonSymbol_(std::move(colonSymbol)), nullSymbol_(std::move(nullSymbol)), endingLineFeedSymbol_(std::move(endingLineFeedSymbol)), addChildValues_(false), indented_(false), - useSpecialFloats_(useSpecialFloats), precision_(precision), - precisionType_(precisionType) {} + useSpecialFloats_(useSpecialFloats), emitUTF8_(emitUTF8), + precision_(precision), precisionType_(precisionType) {} int BuiltStyledStreamWriter::write(Value const& root, OStream* sout) { sout_ = sout; addChildValues_ = false; @@ -953,7 +901,8 @@ void BuiltStyledStreamWriter::writeValue(Value const& value) { char const* end; bool ok = value.getString(&str, &end); if (ok) - pushValue(valueToQuotedStringN(str, static_cast(end - str))); + pushValue( + valueToQuotedStringN(str, static_cast(end - str), emitUTF8_)); else pushValue(""); break; @@ -976,8 +925,8 @@ void BuiltStyledStreamWriter::writeValue(Value const& value) { String const& name = *it; Value const& childValue = value[name]; writeCommentBeforeValue(childValue); - writeWithIndent(valueToQuotedStringN( - name.data(), static_cast(name.length()))); + writeWithIndent( + valueToQuotedStringN(name.data(), name.length(), emitUTF8_)); *sout_ << colonSymbol_; writeValue(childValue); if (++it == members.end()) { @@ -1153,12 +1102,13 @@ StreamWriter::Factory::~Factory() = default; StreamWriterBuilder::StreamWriterBuilder() { setDefaults(&settings_); } StreamWriterBuilder::~StreamWriterBuilder() = default; StreamWriter* StreamWriterBuilder::newStreamWriter() const { - String indentation = settings_["indentation"].asString(); - String cs_str = settings_["commentStyle"].asString(); - String pt_str = settings_["precisionType"].asString(); - bool eyc = settings_["enableYAMLCompatibility"].asBool(); - bool dnp = settings_["dropNullPlaceholders"].asBool(); - bool usf = settings_["useSpecialFloats"].asBool(); + const String indentation = settings_["indentation"].asString(); + const String cs_str = settings_["commentStyle"].asString(); + const String pt_str = settings_["precisionType"].asString(); + const bool eyc = settings_["enableYAMLCompatibility"].asBool(); + const bool dnp = settings_["dropNullPlaceholders"].asBool(); + const bool usf = settings_["useSpecialFloats"].asBool(); + const bool emitUTF8 = settings_["emitUTF8"].asBool(); unsigned int pre = settings_["precision"].asUInt(); CommentStyle::Enum cs = CommentStyle::All; if (cs_str == "All") { @@ -1190,36 +1140,33 @@ StreamWriter* StreamWriterBuilder::newStreamWriter() const { pre = 17; String endingLineFeedSymbol; return new BuiltStyledStreamWriter(indentation, cs, colonSymbol, nullSymbol, - endingLineFeedSymbol, usf, pre, + endingLineFeedSymbol, usf, emitUTF8, pre, precisionType); } -static void getValidWriterKeys(std::set* valid_keys) { - valid_keys->clear(); - valid_keys->insert("indentation"); - valid_keys->insert("commentStyle"); - valid_keys->insert("enableYAMLCompatibility"); - valid_keys->insert("dropNullPlaceholders"); - valid_keys->insert("useSpecialFloats"); - valid_keys->insert("precision"); - valid_keys->insert("precisionType"); -} + bool StreamWriterBuilder::validate(Json::Value* invalid) const { - Json::Value my_invalid; - if (!invalid) - invalid = &my_invalid; // so we do not need to test for NULL - Json::Value& inv = *invalid; - std::set valid_keys; - getValidWriterKeys(&valid_keys); - Value::Members keys = settings_.getMemberNames(); - size_t n = keys.size(); - for (size_t i = 0; i < n; ++i) { - String const& key = keys[i]; - if (valid_keys.find(key) == valid_keys.end()) { - inv[key] = settings_[key]; - } + static const auto& valid_keys = *new std::set{ + "indentation", + "commentStyle", + "enableYAMLCompatibility", + "dropNullPlaceholders", + "useSpecialFloats", + "emitUTF8", + "precision", + "precisionType", + }; + for (auto si = settings_.begin(); si != settings_.end(); ++si) { + auto key = si.name(); + if (valid_keys.count(key)) + continue; + if (invalid) + (*invalid)[key] = *si; + else + return false; } - return inv.empty(); + return invalid ? invalid->empty() : true; } + Value& StreamWriterBuilder::operator[](const String& key) { return settings_[key]; } @@ -1231,6 +1178,7 @@ void StreamWriterBuilder::setDefaults(Json::Value* settings) { (*settings)["enableYAMLCompatibility"] = false; (*settings)["dropNullPlaceholders"] = false; (*settings)["useSpecialFloats"] = false; + (*settings)["emitUTF8"] = false; (*settings)["precision"] = 17; (*settings)["precisionType"] = "significant"; //! [StreamWriterBuilderDefaults] @@ -1240,7 +1188,7 @@ String writeString(StreamWriter::Factory const& factory, Value const& root) { OStringStream sout; StreamWriterPtr const writer(factory.newStreamWriter()); writer->write(root, &sout); - return sout.str(); + return std::move(sout).str(); } OStream& operator<<(OStream& sout, Value const& root) { diff --git a/src/test_lib_json/CMakeLists.txt b/src/test_lib_json/CMakeLists.txt index abb181361..1c3fce913 100644 --- a/src/test_lib_json/CMakeLists.txt +++ b/src/test_lib_json/CMakeLists.txt @@ -1,42 +1,39 @@ # vim: et ts=4 sts=4 sw=4 tw=0 -add_executable( jsoncpp_test - jsontest.cpp - jsontest.h - fuzz.cpp - fuzz.h - main.cpp - ) +add_executable(jsoncpp_test + jsontest.cpp + jsontest.h + fuzz.cpp + fuzz.h + main.cpp +) if(BUILD_SHARED_LIBS) - add_compile_definitions( JSON_DLL ) + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12.0) + add_compile_definitions( JSON_DLL ) + else() + add_definitions( -DJSON_DLL ) + endif() + target_link_libraries(jsoncpp_test jsoncpp_lib) +else() + target_link_libraries(jsoncpp_test jsoncpp_static) endif() -target_link_libraries(jsoncpp_test jsoncpp_lib) # another way to solve issue #90 #set_target_properties(jsoncpp_test PROPERTIES COMPILE_FLAGS -ffloat-store) +## Create tests for dashboard submission, allows easy review of CI results https://my.cdash.org/index.php?project=jsoncpp +add_test(NAME jsoncpp_test + COMMAND ${CMAKE_CROSSCOMPILING_EMULATOR} $ +) +set_target_properties(jsoncpp_test PROPERTIES OUTPUT_NAME jsoncpp_test) + # Run unit tests in post-build # (default cmake workflow hides away the test result into a file, resulting in poor dev workflow?!?) if(JSONCPP_WITH_POST_BUILD_UNITTEST) - if(BUILD_SHARED_LIBS) - # First, copy the shared lib, for Microsoft. - # Then, run the test executable. - add_custom_command( TARGET jsoncpp_test - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ - COMMAND ${CMAKE_CROSSCOMPILING_EMULATOR} $) - else(BUILD_SHARED_LIBS) - # Just run the test executable. - add_custom_command( TARGET jsoncpp_test - POST_BUILD - COMMAND ${CMAKE_CROSSCOMPILING_EMULATOR} $) - endif() - ## Create tests for dashboard submission, allows easy review of CI results https://my.cdash.org/index.php?project=jsoncpp - add_test(NAME jsoncpp_test - COMMAND ${CMAKE_CROSSCOMPILING_EMULATOR} $ + add_custom_command(TARGET jsoncpp_test + POST_BUILD + COMMAND ${CMAKE_CROSSCOMPILING_EMULATOR} $ ) endif() - -set_target_properties(jsoncpp_test PROPERTIES OUTPUT_NAME jsoncpp_test) diff --git a/src/test_lib_json/fuzz.cpp b/src/test_lib_json/fuzz.cpp index f79f19ffe..5b75c22e6 100644 --- a/src/test_lib_json/fuzz.cpp +++ b/src/test_lib_json/fuzz.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include namespace Json { @@ -23,8 +22,12 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { return 0; } - uint32_t hash_settings = *(const uint32_t*)data; + const uint32_t hash_settings = static_cast(data[0]) | + (static_cast(data[1]) << 8) | + (static_cast(data[2]) << 16) | + (static_cast(data[3]) << 24); data += sizeof(uint32_t); + size -= sizeof(uint32_t); builder.settings_["failIfExtra"] = hash_settings & (1 << 0); builder.settings_["allowComments_"] = hash_settings & (1 << 1); @@ -35,11 +38,13 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { builder.settings_["failIfExtra_"] = hash_settings & (1 << 6); builder.settings_["rejectDupKeys_"] = hash_settings & (1 << 7); builder.settings_["allowSpecialFloats_"] = hash_settings & (1 << 8); + builder.settings_["collectComments"] = hash_settings & (1 << 9); + builder.settings_["allowTrailingCommas_"] = hash_settings & (1 << 10); std::unique_ptr reader(builder.newCharReader()); Json::Value root; - const char* data_str = reinterpret_cast(data); + const auto data_str = reinterpret_cast(data); try { reader->parse(data_str, data_str + size, &root, nullptr); } catch (Json::Exception const&) { diff --git a/src/test_lib_json/fuzz.dict b/src/test_lib_json/fuzz.dict new file mode 100644 index 000000000..725423d2f --- /dev/null +++ b/src/test_lib_json/fuzz.dict @@ -0,0 +1,54 @@ +# +# AFL dictionary for JSON +# ----------------------- +# +# Just the very basics. +# +# Inspired by a dictionary by Jakub Wilk +# +# https://github.com/rc0r/afl-fuzz/blob/master/dictionaries/json.dict +# + +"0" +",0" +":0" +"0:" +"-1.2e+3" + +"true" +"false" +"null" + +"\"\"" +",\"\"" +":\"\"" +"\"\":" + +"{}" +",{}" +":{}" +"{\"\":0}" +"{{}}" + +"[]" +",[]" +":[]" +"[0]" +"[[]]" + +"''" +"\\" +"\\b" +"\\f" +"\\n" +"\\r" +"\\t" +"\\u0000" +"\\x00" +"\\0" +"\\uD800\\uDC00" +"\\uDBFF\\uDFFF" + +"\"\":0" +"//" +"/**/" diff --git a/src/test_lib_json/jsontest.cpp b/src/test_lib_json/jsontest.cpp index 4c59d07b5..508067a77 100644 --- a/src/test_lib_json/jsontest.cpp +++ b/src/test_lib_json/jsontest.cpp @@ -82,8 +82,8 @@ TestResult::TestResult() { void TestResult::setTestName(const Json::String& name) { name_ = name; } -TestResult& -TestResult::addFailure(const char* file, unsigned int line, const char* expr) { +TestResult& TestResult::addFailure(const char* file, unsigned int line, + const char* expr) { /// Walks the PredicateContext stack adding them to failures_ if not already /// added. unsigned int nestingLevel = 0; @@ -107,10 +107,8 @@ TestResult::addFailure(const char* file, unsigned int line, const char* expr) { return *this; } -void TestResult::addFailureInfo(const char* file, - unsigned int line, - const char* expr, - unsigned int nestingLevel) { +void TestResult::addFailureInfo(const char* file, unsigned int line, + const char* expr, unsigned int nestingLevel) { Failure failure; failure.file_ = file; failure.line_ = line; @@ -269,19 +267,18 @@ bool Runner::runAllTest(bool printSummary) const { printf("All %zu tests passed\n", count); } return true; - } else { - for (auto& result : failures) { - result.printFailure(count > 1); - } + } + for (auto& result : failures) { + result.printFailure(count > 1); + } - if (printSummary) { - size_t const failedCount = failures.size(); - size_t const passedCount = count - failedCount; - printf("%zu/%zu tests passed (%zu failure(s))\n", passedCount, count, - failedCount); - } - return false; + if (printSummary) { + size_t const failedCount = failures.size(); + size_t const passedCount = count - failedCount; + printf("%zu/%zu tests passed (%zu failure(s))\n", passedCount, count, + failedCount); } + return false; } bool Runner::testIndex(const Json::String& testName, size_t& indexOut) const { @@ -310,7 +307,8 @@ int Runner::runCommandLine(int argc, const char* argv[]) const { if (opt == "--list-tests") { listTests(); return 0; - } else if (opt == "--test-auto") { + } + if (opt == "--test-auto") { preventDialogOnCrash(); } else if (opt == "--test") { ++index; @@ -342,8 +340,8 @@ int Runner::runCommandLine(int argc, const char* argv[]) const { #if defined(_MSC_VER) && defined(_DEBUG) // Hook MSVCRT assertions to prevent dialog from appearing -static int -msvcrtSilentReportHook(int reportType, char* message, int* /*returnValue*/) { +static int msvcrtSilentReportHook(int reportType, char* message, + int* /*returnValue*/) { // The default CRT handling of error and assertion is to display // an error dialog to the user. // Instead, when an error or an assertion occurs, we force the @@ -378,8 +376,8 @@ void Runner::preventDialogOnCrash() { _CrtSetReportHook(&msvcrtSilentReportHook); #endif // if defined(_MSC_VER) -// @todo investigate this handler (for buffer overflow) -// _set_security_error_handler + // @todo investigate this handler (for buffer overflow) + // _set_security_error_handler #if defined(_WIN32) // Prevents the system from popping a dialog for debugging if the @@ -412,18 +410,15 @@ Json::String ToJsonString(const char* toConvert) { Json::String ToJsonString(Json::String in) { return in; } -#if JSONCPP_USING_SECURE_MEMORY +#if JSONCPP_USE_SECURE_MEMORY Json::String ToJsonString(std::string in) { return Json::String(in.data(), in.data() + in.length()); } #endif -TestResult& checkStringEqual(TestResult& result, - const Json::String& expected, - const Json::String& actual, - const char* file, - unsigned int line, - const char* expr) { +TestResult& checkStringEqual(TestResult& result, const Json::String& expected, + const Json::String& actual, const char* file, + unsigned int line, const char* expr) { if (expected != actual) { result.addFailure(file, line, expr); result << "Expected: '" << expected << "'\n"; diff --git a/src/test_lib_json/jsontest.h b/src/test_lib_json/jsontest.h index e9c11a470..69e3264b9 100644 --- a/src/test_lib_json/jsontest.h +++ b/src/test_lib_json/jsontest.h @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -41,7 +42,7 @@ class Failure { /// Must be a POD to allow inline initialisation without stepping /// into the debugger. struct PredicateContext { - typedef unsigned int Id; + using Id = unsigned int; Id id_; const char* file_; unsigned int line_; @@ -68,12 +69,12 @@ class TestResult { void setTestName(const Json::String& name); /// Adds an assertion failure. - TestResult& - addFailure(const char* file, unsigned int line, const char* expr = nullptr); + TestResult& addFailure(const char* file, unsigned int line, + const char* expr = nullptr); /// Removes the last PredicateContext added to the predicate stack /// chained list. - /// Next messages will be targed at the PredicateContext that was removed. + /// Next messages will be targeted at the PredicateContext that was removed. TestResult& popPredicateContext(); bool failed() const; @@ -83,9 +84,7 @@ class TestResult { // Generic operator that will work with anything ostream can deal with. template TestResult& operator<<(const T& value) { Json::OStringStream oss; - oss.precision(16); - oss.setf(std::ios_base::floatfield); - oss << value; + oss << std::setprecision(16) << std::hexfloat << value; return addToLastFailure(oss.str()); } @@ -98,14 +97,12 @@ class TestResult { private: TestResult& addToLastFailure(const Json::String& message); /// Adds a failure or a predicate context - void addFailureInfo(const char* file, - unsigned int line, - const char* expr, + void addFailureInfo(const char* file, unsigned int line, const char* expr, unsigned int nestingLevel); static Json::String indentText(const Json::String& text, const Json::String& indent); - typedef std::deque Failures; + using Failures = std::deque; Failures failures_; Json::String name_; PredicateContext rootPredicateNode_; @@ -132,7 +129,7 @@ class TestCase { }; /// Function pointer type for TestCase factory -typedef TestCase* (*TestCaseFactory)(); +using TestCaseFactory = TestCase* (*)(); class Runner { public: @@ -171,17 +168,13 @@ class Runner { static void preventDialogOnCrash(); private: - typedef std::deque Factories; + using Factories = std::deque; Factories tests_; }; template -TestResult& checkEqual(TestResult& result, - T expected, - U actual, - const char* file, - unsigned int line, - const char* expr) { +TestResult& checkEqual(TestResult& result, T expected, U actual, + const char* file, unsigned int line, const char* expr) { if (static_cast(expected) != actual) { result.addFailure(file, line, expr); result << "Expected: " << static_cast(expected) << "\n"; @@ -192,16 +185,13 @@ TestResult& checkEqual(TestResult& result, Json::String ToJsonString(const char* toConvert); Json::String ToJsonString(Json::String in); -#if JSONCPP_USING_SECURE_MEMORY +#if JSONCPP_USE_SECURE_MEMORY Json::String ToJsonString(std::string in); #endif -TestResult& checkStringEqual(TestResult& result, - const Json::String& expected, - const Json::String& actual, - const char* file, - unsigned int line, - const char* expr); +TestResult& checkStringEqual(TestResult& result, const Json::String& expected, + const Json::String& actual, const char* file, + unsigned int line, const char* expr); } // namespace JsonTest @@ -217,7 +207,7 @@ TestResult& checkStringEqual(TestResult& result, /// The predicate may do other assertions and be a member function of the /// fixture. #define JSONTEST_ASSERT_PRED(expr) \ - { \ + do { \ JsonTest::PredicateContext _minitest_Context = { \ result_->predicateId_, __FILE__, __LINE__, #expr, NULL, NULL}; \ result_->predicateStackTail_->next_ = &_minitest_Context; \ @@ -225,7 +215,7 @@ TestResult& checkStringEqual(TestResult& result, result_->predicateStackTail_ = &_minitest_Context; \ (expr); \ result_->popPredicateContext(); \ - } + } while (0) /// \brief Asserts that two values are equals. #define JSONTEST_ASSERT_EQUAL(expected, actual) \ @@ -240,7 +230,7 @@ TestResult& checkStringEqual(TestResult& result, /// \brief Asserts that a given expression throws an exception #define JSONTEST_ASSERT_THROWS(expr) \ - { \ + do { \ bool _threw = false; \ try { \ expr; \ @@ -250,7 +240,7 @@ TestResult& checkStringEqual(TestResult& result, if (!_threw) \ result_->addFailure(__FILE__, __LINE__, \ "expected exception thrown: " #expr); \ - } + } while (0) /// \brief Begin a fixture test case. #define JSONTEST_FIXTURE(FixtureType, name) \ @@ -273,4 +263,26 @@ TestResult& checkStringEqual(TestResult& result, #define JSONTEST_REGISTER_FIXTURE(runner, FixtureType, name) \ (runner).add(JSONTEST_FIXTURE_FACTORY(FixtureType, name)) +/// \brief Begin a fixture test case. +#define JSONTEST_FIXTURE_V2(FixtureType, name, collections) \ + class Test##FixtureType##name : public FixtureType { \ + public: \ + static JsonTest::TestCase* factory() { \ + return new Test##FixtureType##name(); \ + } \ + static bool collect() { \ + (collections).push_back(JSONTEST_FIXTURE_FACTORY(FixtureType, name)); \ + return true; \ + } \ + \ + public: /* overridden from TestCase */ \ + const char* testName() const override { return #FixtureType "/" #name; } \ + void runTestCase() override; \ + }; \ + \ + static bool test##FixtureType##name##collect = \ + Test##FixtureType##name::collect(); \ + \ + void Test##FixtureType##name::runTestCase() + #endif // ifndef JSONTEST_H_INCLUDED diff --git a/src/test_lib_json/main.cpp b/src/test_lib_json/main.cpp index 7de849198..60f149d5e 100644 --- a/src/test_lib_json/main.cpp +++ b/src/test_lib_json/main.cpp @@ -12,15 +12,22 @@ #include "fuzz.h" #include "jsontest.h" +#include #include #include +#include #include #include +#include #include #include #include +#include #include #include +#include + +using CharReaderPtr = std::unique_ptr; // Make numeric limits more convenient to talk about. // Assumes int type in 32 bits. @@ -53,31 +60,61 @@ static inline double uint64ToDouble(Json::UInt64 value) { } #endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) +// local_ is the collection for the testcases in this code file. +static std::deque local_; +#define JSONTEST_FIXTURE_LOCAL(FixtureType, name) \ + JSONTEST_FIXTURE_V2(FixtureType, name, local_) + struct ValueTest : JsonTest::TestCase { Json::Value null_; - Json::Value emptyArray_; - Json::Value emptyObject_; - Json::Value integer_; - Json::Value unsignedInteger_; - Json::Value smallUnsignedInteger_; - Json::Value real_; - Json::Value float_; + Json::Value emptyArray_{Json::arrayValue}; + Json::Value emptyObject_{Json::objectValue}; + Json::Value integer_{123456789}; + Json::Value unsignedInteger_{34567890}; + Json::Value smallUnsignedInteger_{Json::Value::UInt(Json::Value::maxInt)}; + Json::Value real_{1234.56789}; + Json::Value float_{0.00390625f}; Json::Value array1_; Json::Value object1_; - Json::Value emptyString_; - Json::Value string1_; - Json::Value string_; - Json::Value true_; - Json::Value false_; - - ValueTest() - : emptyArray_(Json::arrayValue), emptyObject_(Json::objectValue), - integer_(123456789), unsignedInteger_(34567890u), - smallUnsignedInteger_(Json::Value::UInt(Json::Value::maxInt)), - real_(1234.56789), float_(0.00390625f), emptyString_(""), string1_("a"), - string_("sometext with space"), true_(true), false_(false) { + Json::Value object2_; + Json::Value object3_; + Json::Value emptyString_{""}; + Json::Value string1_{"a"}; + Json::Value string_{"sometext with space"}; + Json::Value true_{true}; + Json::Value false_{false}; + + ValueTest() { array1_.append(1234); object1_["id"] = 1234; + + // object2 with matching values + object2_["null"] = Json::nullValue; + object2_["bool"] = true; + object2_["int"] = Json::Int{Json::Value::maxInt}; + object2_["int64"] = Json::Int64{Json::Value::maxInt64}; + object2_["uint"] = Json::UInt{Json::Value::maxUInt}; + object2_["uint64"] = Json::UInt64{Json::Value::maxUInt64}; + object2_["integral"] = 1234; + object2_["double"] = 1234.56789; + object2_["numeric"] = 0.12345f; + object2_["string"] = "string"; + object2_["array"] = Json::arrayValue; + object2_["object"] = Json::objectValue; + + // object3 with not matching values + object3_["object"] = Json::nullValue; + object3_["null"] = true; + object3_["bool"] = Json::Int{Json::Value::maxInt}; + object3_["int"] = "not_an_int"; + object3_["int64"] = "not_an_int64"; + object3_["uint"] = "not_an_uint"; + object3_["uin64"] = "not_an_uint64"; + object3_["integral"] = 1234.56789; + object3_["double"] = false; + object3_["numeric"] = "string"; + object3_["string"] = Json::arrayValue; + object3_["array"] = Json::objectValue; } struct IsCheck { @@ -116,56 +153,48 @@ struct ValueTest : JsonTest::TestCase { }; Json::String ValueTest::normalizeFloatingPointStr(const Json::String& s) { - Json::String::size_type index = s.find_last_of("eE"); - if (index != Json::String::npos) { - Json::String::size_type hasSign = - (s[index + 1] == '+' || s[index + 1] == '-') ? 1 : 0; - Json::String::size_type exponentStartIndex = index + 1 + hasSign; - Json::String normalized = s.substr(0, exponentStartIndex); - Json::String::size_type indexDigit = - s.find_first_not_of('0', exponentStartIndex); - Json::String exponent = "0"; - if (indexDigit != Json::String::npos) // There is an exponent different - // from 0 - { - exponent = s.substr(indexDigit); - } - return normalized + exponent; - } - return s; -} - -JSONTEST_FIXTURE(ValueTest, checkNormalizeFloatingPointStr) { - JSONTEST_ASSERT_STRING_EQUAL("0.0", normalizeFloatingPointStr("0.0")); - JSONTEST_ASSERT_STRING_EQUAL("0e0", normalizeFloatingPointStr("0e0")); - JSONTEST_ASSERT_STRING_EQUAL("1234.0", normalizeFloatingPointStr("1234.0")); - JSONTEST_ASSERT_STRING_EQUAL("1234.0e0", - normalizeFloatingPointStr("1234.0e0")); - JSONTEST_ASSERT_STRING_EQUAL("1234.0e-1", - normalizeFloatingPointStr("1234.0e-1")); - JSONTEST_ASSERT_STRING_EQUAL("1234.0e+0", - normalizeFloatingPointStr("1234.0e+0")); - JSONTEST_ASSERT_STRING_EQUAL("1234.0e+1", - normalizeFloatingPointStr("1234.0e+001")); - JSONTEST_ASSERT_STRING_EQUAL("1234e-1", normalizeFloatingPointStr("1234e-1")); - JSONTEST_ASSERT_STRING_EQUAL("1234e+0", - normalizeFloatingPointStr("1234e+000")); - JSONTEST_ASSERT_STRING_EQUAL("1234e+1", - normalizeFloatingPointStr("1234e+001")); - JSONTEST_ASSERT_STRING_EQUAL("1234e10", normalizeFloatingPointStr("1234e10")); - JSONTEST_ASSERT_STRING_EQUAL("1234e10", - normalizeFloatingPointStr("1234e010")); - JSONTEST_ASSERT_STRING_EQUAL("1234e+10", - normalizeFloatingPointStr("1234e+010")); - JSONTEST_ASSERT_STRING_EQUAL("1234e-10", - normalizeFloatingPointStr("1234e-010")); - JSONTEST_ASSERT_STRING_EQUAL("1234e+100", - normalizeFloatingPointStr("1234e+100")); - JSONTEST_ASSERT_STRING_EQUAL("1234e-100", - normalizeFloatingPointStr("1234e-100")); -} - -JSONTEST_FIXTURE(ValueTest, memberCount) { + auto index = s.find_last_of("eE"); + if (index == s.npos) + return s; + std::size_t signWidth = (s[index + 1] == '+' || s[index + 1] == '-') ? 1 : 0; + auto exponentStartIndex = index + 1 + signWidth; + Json::String normalized = s.substr(0, exponentStartIndex); + auto indexDigit = s.find_first_not_of('0', exponentStartIndex); + Json::String exponent = "0"; + if (indexDigit != s.npos) { // nonzero exponent + exponent = s.substr(indexDigit); + } + return normalized + exponent; +} + +JSONTEST_FIXTURE_LOCAL(ValueTest, checkNormalizeFloatingPointStr) { + struct TestData { + std::string in; + std::string out; + } const testData[] = { + {"0.0", "0.0"}, + {"0e0", "0e0"}, + {"1234.0", "1234.0"}, + {"1234.0e0", "1234.0e0"}, + {"1234.0e-1", "1234.0e-1"}, + {"1234.0e+0", "1234.0e+0"}, + {"1234.0e+001", "1234.0e+1"}, + {"1234e-1", "1234e-1"}, + {"1234e+000", "1234e+0"}, + {"1234e+001", "1234e+1"}, + {"1234e10", "1234e10"}, + {"1234e010", "1234e10"}, + {"1234e+010", "1234e+10"}, + {"1234e-010", "1234e-10"}, + {"1234e+100", "1234e+100"}, + {"1234e-100", "1234e-100"}, + }; + for (const auto& td : testData) { + JSONTEST_ASSERT_STRING_EQUAL(normalizeFloatingPointStr(td.in), td.out); + } +} + +JSONTEST_FIXTURE_LOCAL(ValueTest, memberCount) { JSONTEST_ASSERT_PRED(checkMemberCount(emptyArray_, 0)); JSONTEST_ASSERT_PRED(checkMemberCount(emptyObject_, 0)); JSONTEST_ASSERT_PRED(checkMemberCount(array1_, 1)); @@ -183,7 +212,7 @@ JSONTEST_FIXTURE(ValueTest, memberCount) { JSONTEST_ASSERT_PRED(checkMemberCount(float_, 0)); } -JSONTEST_FIXTURE(ValueTest, objects) { +JSONTEST_FIXTURE_LOCAL(ValueTest, objects) { // Types IsCheck checks; checks.isObject_ = true; @@ -221,11 +250,76 @@ JSONTEST_FIXTURE(ValueTest, objects) { JSONTEST_ASSERT(foundId != nullptr); JSONTEST_ASSERT_EQUAL(Json::Value(1234), *foundId); + const std::string stringIdKey = "id"; + const Json::Value* stringFoundId = object1_.find(stringIdKey); + JSONTEST_ASSERT(stringFoundId != nullptr); + JSONTEST_ASSERT_EQUAL(Json::Value(1234), *stringFoundId); + const char unknownIdKey[] = "unknown id"; const Json::Value* foundUnknownId = object1_.find(unknownIdKey, unknownIdKey + strlen(unknownIdKey)); JSONTEST_ASSERT_EQUAL(nullptr, foundUnknownId); + const std::string stringUnknownIdKey = "unknown id"; + const Json::Value* stringFoundUnknownId = object1_.find(stringUnknownIdKey); + JSONTEST_ASSERT_EQUAL(nullptr, stringFoundUnknownId); + + // Access through find() + const Json::Value* nullFound = object2_.findNull("null"); + JSONTEST_ASSERT(nullFound != nullptr); + JSONTEST_ASSERT_EQUAL(Json::nullValue, *nullFound); + JSONTEST_ASSERT(object3_.findNull("null") == nullptr); + + const Json::Value* boolFound = object2_.findBool("bool"); + JSONTEST_ASSERT(boolFound != nullptr); + JSONTEST_ASSERT_EQUAL(true, *boolFound); + JSONTEST_ASSERT(object3_.findBool("bool") == nullptr); + + const Json::Value* intFound = object2_.findInt("int"); + JSONTEST_ASSERT(intFound != nullptr); + JSONTEST_ASSERT_EQUAL(Json::Int{Json::Value::maxInt}, *intFound); + JSONTEST_ASSERT(object3_.findInt("int") == nullptr); + + const Json::Value* int64Found = object2_.findInt64("int64"); + JSONTEST_ASSERT(int64Found != nullptr); + JSONTEST_ASSERT_EQUAL(Json::Int64{Json::Value::maxInt64}, *int64Found); + JSONTEST_ASSERT(object3_.findInt64("int64") == nullptr); + + const Json::Value* uintFound = object2_.findUInt("uint"); + JSONTEST_ASSERT(uintFound != nullptr); + JSONTEST_ASSERT_EQUAL(Json::UInt{Json::Value::maxUInt}, *uintFound); + JSONTEST_ASSERT(object3_.findUInt("uint") == nullptr); + + const Json::Value* uint64Found = object2_.findUInt64("uint64"); + JSONTEST_ASSERT(uint64Found != nullptr); + JSONTEST_ASSERT_EQUAL(Json::UInt64{Json::Value::maxUInt64}, *uint64Found); + JSONTEST_ASSERT(object3_.findUInt64("uint64") == nullptr); + + const Json::Value* integralFound = object2_.findIntegral("integral"); + JSONTEST_ASSERT(integralFound != nullptr); + JSONTEST_ASSERT_EQUAL(1234, *integralFound); + JSONTEST_ASSERT(object3_.findIntegral("integral") == nullptr); + + const Json::Value* doubleFound = object2_.findDouble("double"); + JSONTEST_ASSERT(doubleFound != nullptr); + JSONTEST_ASSERT_EQUAL(1234.56789, *doubleFound); + JSONTEST_ASSERT(object3_.findDouble("double") == nullptr); + + const Json::Value* numericFound = object2_.findNumeric("numeric"); + JSONTEST_ASSERT(numericFound != nullptr); + JSONTEST_ASSERT_EQUAL(0.12345f, *numericFound); + JSONTEST_ASSERT(object3_.findNumeric("numeric") == nullptr); + + const Json::Value* stringFound = object2_.findString("string"); + JSONTEST_ASSERT(stringFound != nullptr); + JSONTEST_ASSERT_EQUAL(std::string{"string"}, *stringFound); + JSONTEST_ASSERT(object3_.findString("string") == nullptr); + + const Json::Value* arrayFound = object2_.findArray("array"); + JSONTEST_ASSERT(arrayFound != nullptr); + JSONTEST_ASSERT_EQUAL(Json::arrayValue, *arrayFound); + JSONTEST_ASSERT(object3_.findArray("array") == nullptr); + // Access through demand() const char yetAnotherIdKey[] = "yet another id"; const Json::Value* foundYetAnotherId = @@ -262,9 +356,24 @@ JSONTEST_FIXTURE(ValueTest, objects) { did = object1_.removeMember("some other id", gotPtr); JSONTEST_ASSERT_EQUAL(nullptr, gotPtr); JSONTEST_ASSERT_EQUAL(true, did); + + // Using other removeMember interfaces, the test idea is the same as above. + object1_["some other id"] = "foo"; + const Json::String key("some other id"); + did = object1_.removeMember(key, &got); + JSONTEST_ASSERT_EQUAL(Json::Value("foo"), got); + JSONTEST_ASSERT_EQUAL(true, did); + got = Json::Value("bar"); + did = object1_.removeMember(key, &got); + JSONTEST_ASSERT_EQUAL(Json::Value("bar"), got); + JSONTEST_ASSERT_EQUAL(false, did); + + object1_["some other id"] = "foo"; + object1_.removeMember(key); + JSONTEST_ASSERT_EQUAL(Json::nullValue, object1_[key]); } -JSONTEST_FIXTURE(ValueTest, arrays) { +JSONTEST_FIXTURE_LOCAL(ValueTest, arrays) { const unsigned int index0 = 0; // Types @@ -296,10 +405,14 @@ JSONTEST_FIXTURE(ValueTest, arrays) { const Json::Value& constArray = array1_; JSONTEST_ASSERT_EQUAL(Json::Value(1234), constArray[index0]); JSONTEST_ASSERT_EQUAL(Json::Value(1234), constArray[0]); + JSONTEST_ASSERT_EQUAL(Json::Value(1234), constArray.front()); + JSONTEST_ASSERT_EQUAL(Json::Value(1234), constArray.back()); // Access through non-const reference JSONTEST_ASSERT_EQUAL(Json::Value(1234), array1_[index0]); JSONTEST_ASSERT_EQUAL(Json::Value(1234), array1_[0]); + JSONTEST_ASSERT_EQUAL(Json::Value(1234), array1_.front()); + JSONTEST_ASSERT_EQUAL(Json::Value(1234), array1_.back()); array1_[2] = Json::Value(17); JSONTEST_ASSERT_EQUAL(Json::Value(), array1_[1]); @@ -309,7 +422,64 @@ JSONTEST_FIXTURE(ValueTest, arrays) { JSONTEST_ASSERT_EQUAL(Json::Value(17), got); JSONTEST_ASSERT_EQUAL(false, array1_.removeIndex(2, &got)); // gone now } -JSONTEST_FIXTURE(ValueTest, arrayIssue252) { +JSONTEST_FIXTURE_LOCAL(ValueTest, resizeArray) { + Json::Value array; + { + for (Json::ArrayIndex i = 0; i < 10; i++) + array[i] = i; + JSONTEST_ASSERT_EQUAL(array.size(), 10); + // The length set is greater than the length of the array. + array.resize(15); + JSONTEST_ASSERT_EQUAL(array.size(), 15); + + // The length set is less than the length of the array. + array.resize(5); + JSONTEST_ASSERT_EQUAL(array.size(), 5); + + // The length of the array is set to 0. + array.resize(0); + JSONTEST_ASSERT_EQUAL(array.size(), 0); + } + { + for (Json::ArrayIndex i = 0; i < 10; i++) + array[i] = i; + JSONTEST_ASSERT_EQUAL(array.size(), 10); + array.clear(); + JSONTEST_ASSERT_EQUAL(array.size(), 0); + } +} + +JSONTEST_FIXTURE_LOCAL(ValueTest, resizePopulatesAllMissingElements) { + Json::ArrayIndex n = 10; + Json::Value v; + v.resize(n); + JSONTEST_ASSERT_EQUAL(n, v.size()); + JSONTEST_ASSERT_EQUAL(n, std::distance(v.begin(), v.end())); + JSONTEST_ASSERT_EQUAL(v.front(), Json::Value{}); + JSONTEST_ASSERT_EQUAL(v.back(), Json::Value{}); + for (const Json::Value& e : v) + JSONTEST_ASSERT_EQUAL(e, Json::Value{}); +} + +JSONTEST_FIXTURE_LOCAL(ValueTest, getArrayValue) { + Json::Value array; + for (Json::ArrayIndex i = 0; i < 5; i++) + array[i] = i; + + JSONTEST_ASSERT_EQUAL(array.size(), 5); + const Json::Value defaultValue(10); + Json::ArrayIndex index = 0; + for (; index <= 4; index++) + JSONTEST_ASSERT_EQUAL(index, array.get(index, defaultValue).asInt()); + + index = 4; + JSONTEST_ASSERT_EQUAL(array.isValidIndex(index), true); + index = 5; + JSONTEST_ASSERT_EQUAL(array.isValidIndex(index), false); + JSONTEST_ASSERT_EQUAL(defaultValue, array.get(index, defaultValue)); + JSONTEST_ASSERT_EQUAL(array.isValidIndex(index), false); +} +JSONTEST_FIXTURE_LOCAL(ValueTest, arrayIssue252) { int count = 5; Json::Value root; Json::Value item; @@ -322,7 +492,71 @@ JSONTEST_FIXTURE(ValueTest, arrayIssue252) { // JSONTEST_ASSERT_EQUAL(5, root["array"].size()); } -JSONTEST_FIXTURE(ValueTest, null) { +JSONTEST_FIXTURE_LOCAL(ValueTest, arrayInsertAtRandomIndex) { + Json::Value array; + const Json::Value str0("index2"); + const Json::Value str1("index3"); + array.append("index0"); // append rvalue + array.append("index1"); + array.append(str0); // append lvalue + + std::vector vec; // storage value address for checking + for (Json::ArrayIndex i = 0; i < 3; i++) { + vec.push_back(&array[i]); + } + JSONTEST_ASSERT_EQUAL(Json::Value("index0"), array[0]); // check append + JSONTEST_ASSERT_EQUAL(Json::Value("index1"), array[1]); + JSONTEST_ASSERT_EQUAL(Json::Value("index2"), array[2]); + JSONTEST_ASSERT_EQUAL(Json::Value("index0"), array.front()); + JSONTEST_ASSERT_EQUAL(Json::Value("index2"), array.back()); + + // insert lvalue at the head + JSONTEST_ASSERT(array.insert(0, str1)); + JSONTEST_ASSERT_EQUAL(Json::Value("index3"), array[0]); + JSONTEST_ASSERT_EQUAL(Json::Value("index0"), array[1]); + JSONTEST_ASSERT_EQUAL(Json::Value("index1"), array[2]); + JSONTEST_ASSERT_EQUAL(Json::Value("index2"), array[3]); + JSONTEST_ASSERT_EQUAL(Json::Value("index3"), array.front()); + JSONTEST_ASSERT_EQUAL(Json::Value("index2"), array.back()); + // checking address + for (Json::ArrayIndex i = 0; i < 3; i++) { + JSONTEST_ASSERT_EQUAL(vec[i], &array[i]); + } + vec.push_back(&array[3]); + // insert rvalue at middle + JSONTEST_ASSERT(array.insert(2, "index4")); + JSONTEST_ASSERT_EQUAL(Json::Value("index3"), array[0]); + JSONTEST_ASSERT_EQUAL(Json::Value("index0"), array[1]); + JSONTEST_ASSERT_EQUAL(Json::Value("index4"), array[2]); + JSONTEST_ASSERT_EQUAL(Json::Value("index1"), array[3]); + JSONTEST_ASSERT_EQUAL(Json::Value("index2"), array[4]); + JSONTEST_ASSERT_EQUAL(Json::Value("index3"), array.front()); + JSONTEST_ASSERT_EQUAL(Json::Value("index2"), array.back()); + // checking address + for (Json::ArrayIndex i = 0; i < 4; i++) { + JSONTEST_ASSERT_EQUAL(vec[i], &array[i]); + } + vec.push_back(&array[4]); + // insert rvalue at the tail + JSONTEST_ASSERT(array.insert(5, "index5")); + JSONTEST_ASSERT_EQUAL(Json::Value("index3"), array[0]); + JSONTEST_ASSERT_EQUAL(Json::Value("index0"), array[1]); + JSONTEST_ASSERT_EQUAL(Json::Value("index4"), array[2]); + JSONTEST_ASSERT_EQUAL(Json::Value("index1"), array[3]); + JSONTEST_ASSERT_EQUAL(Json::Value("index2"), array[4]); + JSONTEST_ASSERT_EQUAL(Json::Value("index5"), array[5]); + JSONTEST_ASSERT_EQUAL(Json::Value("index3"), array.front()); + JSONTEST_ASSERT_EQUAL(Json::Value("index5"), array.back()); + // checking address + for (Json::ArrayIndex i = 0; i < 5; i++) { + JSONTEST_ASSERT_EQUAL(vec[i], &array[i]); + } + vec.push_back(&array[5]); + // beyond max array size, it should not be allowed to insert into its tail + JSONTEST_ASSERT(!array.insert(10, "index10")); +} + +JSONTEST_FIXTURE_LOCAL(ValueTest, null) { JSONTEST_ASSERT_EQUAL(Json::nullValue, null_.type()); IsCheck checks; @@ -355,7 +589,7 @@ JSONTEST_FIXTURE(ValueTest, null) { JSONTEST_ASSERT_EQUAL(!object1_, false); } -JSONTEST_FIXTURE(ValueTest, strings) { +JSONTEST_FIXTURE_LOCAL(ValueTest, strings) { JSONTEST_ASSERT_EQUAL(Json::stringValue, string1_.type()); IsCheck checks; @@ -384,7 +618,7 @@ JSONTEST_FIXTURE(ValueTest, strings) { JSONTEST_ASSERT_STRING_EQUAL("a", string1_.asCString()); } -JSONTEST_FIXTURE(ValueTest, bools) { +JSONTEST_FIXTURE_LOCAL(ValueTest, bools) { JSONTEST_ASSERT_EQUAL(Json::booleanValue, false_.type()); IsCheck checks; @@ -426,7 +660,7 @@ JSONTEST_FIXTURE(ValueTest, bools) { JSONTEST_ASSERT_EQUAL(0.0, false_.asFloat()); } -JSONTEST_FIXTURE(ValueTest, integers) { +JSONTEST_FIXTURE_LOCAL(ValueTest, integers) { IsCheck checks; Json::Value val; @@ -1043,15 +1277,13 @@ JSONTEST_FIXTURE(ValueTest, integers) { JSONTEST_ASSERT_EQUAL(true, val.asBool()); JSONTEST_ASSERT_STRING_EQUAL("-9223372036854775808", val.asString()); - // int64 min (floating point constructor). Note that kint64min *is* exactly - // representable as a double. + // int64 min (floating point constructor). Since double values in proximity of + // kint64min are rounded to kint64min, we don't check for conversion to int64. val = Json::Value(double(kint64min)); JSONTEST_ASSERT_EQUAL(Json::realValue, val.type()); checks = IsCheck(); - checks.isInt64_ = true; - checks.isIntegral_ = true; checks.isDouble_ = true; checks.isNumeric_ = true; JSONTEST_ASSERT_PRED(checkIs(val, checks)); @@ -1060,8 +1292,6 @@ JSONTEST_FIXTURE(ValueTest, integers) { JSONTEST_ASSERT(!val.isConvertibleTo(Json::intValue)); JSONTEST_ASSERT(!val.isConvertibleTo(Json::uintValue)); - JSONTEST_ASSERT_EQUAL(kint64min, val.asInt64()); - JSONTEST_ASSERT_EQUAL(kint64min, val.asLargestInt()); JSONTEST_ASSERT_EQUAL(-9223372036854775808.0, val.asDouble()); JSONTEST_ASSERT_EQUAL(-9223372036854775808.0, val.asFloat()); JSONTEST_ASSERT_EQUAL(true, val.asBool()); @@ -1164,7 +1394,7 @@ JSONTEST_FIXTURE(ValueTest, integers) { #endif } -JSONTEST_FIXTURE(ValueTest, nonIntegers) { +JSONTEST_FIXTURE_LOCAL(ValueTest, nonIntegers) { IsCheck checks; Json::Value val; @@ -1364,9 +1594,7 @@ void ValueTest::checkMemberCount(Json::Value& value, JSONTEST_ASSERT_PRED(checkConstMemberCount(value, expectedCount)); } -ValueTest::IsCheck::IsCheck() - - = default; +ValueTest::IsCheck::IsCheck() = default; void ValueTest::checkIs(const Json::Value& value, const IsCheck& check) { JSONTEST_ASSERT_EQUAL(check.isObject_, value.isObject()); @@ -1389,31 +1617,35 @@ void ValueTest::checkIs(const Json::Value& value, const IsCheck& check) { #endif } -JSONTEST_FIXTURE(ValueTest, compareNull) { +JSONTEST_FIXTURE_LOCAL(ValueTest, compareNull) { JSONTEST_ASSERT_PRED(checkIsEqual(Json::Value(), Json::Value())); + JSONTEST_ASSERT_PRED( + checkIsEqual(Json::Value::nullSingleton(), Json::Value())); + JSONTEST_ASSERT_PRED( + checkIsEqual(Json::Value::nullSingleton(), Json::Value::nullSingleton())); } -JSONTEST_FIXTURE(ValueTest, compareInt) { +JSONTEST_FIXTURE_LOCAL(ValueTest, compareInt) { JSONTEST_ASSERT_PRED(checkIsLess(0, 10)); JSONTEST_ASSERT_PRED(checkIsEqual(10, 10)); JSONTEST_ASSERT_PRED(checkIsEqual(-10, -10)); JSONTEST_ASSERT_PRED(checkIsLess(-10, 0)); } -JSONTEST_FIXTURE(ValueTest, compareUInt) { +JSONTEST_FIXTURE_LOCAL(ValueTest, compareUInt) { JSONTEST_ASSERT_PRED(checkIsLess(0u, 10u)); JSONTEST_ASSERT_PRED(checkIsLess(0u, Json::Value::maxUInt)); JSONTEST_ASSERT_PRED(checkIsEqual(10u, 10u)); } -JSONTEST_FIXTURE(ValueTest, compareDouble) { +JSONTEST_FIXTURE_LOCAL(ValueTest, compareDouble) { JSONTEST_ASSERT_PRED(checkIsLess(0.0, 10.0)); JSONTEST_ASSERT_PRED(checkIsEqual(10.0, 10.0)); JSONTEST_ASSERT_PRED(checkIsEqual(-10.0, -10.0)); JSONTEST_ASSERT_PRED(checkIsLess(-10.0, 0.0)); } -JSONTEST_FIXTURE(ValueTest, compareString) { +JSONTEST_FIXTURE_LOCAL(ValueTest, compareString) { JSONTEST_ASSERT_PRED(checkIsLess("", " ")); JSONTEST_ASSERT_PRED(checkIsLess("", "a")); JSONTEST_ASSERT_PRED(checkIsLess("abcd", "zyui")); @@ -1424,13 +1656,13 @@ JSONTEST_FIXTURE(ValueTest, compareString) { JSONTEST_ASSERT_PRED(checkIsEqual("ABCD", "ABCD")); } -JSONTEST_FIXTURE(ValueTest, compareBoolean) { +JSONTEST_FIXTURE_LOCAL(ValueTest, compareBoolean) { JSONTEST_ASSERT_PRED(checkIsLess(false, true)); JSONTEST_ASSERT_PRED(checkIsEqual(false, false)); JSONTEST_ASSERT_PRED(checkIsEqual(true, true)); } -JSONTEST_FIXTURE(ValueTest, compareArray) { +JSONTEST_FIXTURE_LOCAL(ValueTest, compareArray) { // array compare size then content Json::Value emptyArray(Json::arrayValue); Json::Value l1aArray; @@ -1445,32 +1677,60 @@ JSONTEST_FIXTURE(ValueTest, compareArray) { l2bArray.append(10); JSONTEST_ASSERT_PRED(checkIsLess(emptyArray, l1aArray)); JSONTEST_ASSERT_PRED(checkIsLess(emptyArray, l2aArray)); - JSONTEST_ASSERT_PRED(checkIsLess(l1aArray, l2aArray)); + JSONTEST_ASSERT_PRED(checkIsLess(l1aArray, l1bArray)); + JSONTEST_ASSERT_PRED(checkIsLess(l1bArray, l2aArray)); JSONTEST_ASSERT_PRED(checkIsLess(l2aArray, l2bArray)); JSONTEST_ASSERT_PRED(checkIsEqual(emptyArray, Json::Value(emptyArray))); JSONTEST_ASSERT_PRED(checkIsEqual(l1aArray, Json::Value(l1aArray))); + JSONTEST_ASSERT_PRED(checkIsEqual(l1bArray, Json::Value(l1bArray))); + JSONTEST_ASSERT_PRED(checkIsEqual(l2aArray, Json::Value(l2aArray))); JSONTEST_ASSERT_PRED(checkIsEqual(l2bArray, Json::Value(l2bArray))); } -JSONTEST_FIXTURE(ValueTest, compareObject) { +JSONTEST_FIXTURE_LOCAL(ValueTest, compareObject) { // object compare size then content Json::Value emptyObject(Json::objectValue); Json::Value l1aObject; l1aObject["key1"] = 0; Json::Value l1bObject; - l1aObject["key1"] = 10; + l1bObject["key1"] = 10; Json::Value l2aObject; l2aObject["key1"] = 0; l2aObject["key2"] = 0; + Json::Value l2bObject; + l2bObject["key1"] = 10; + l2bObject["key2"] = 0; JSONTEST_ASSERT_PRED(checkIsLess(emptyObject, l1aObject)); - JSONTEST_ASSERT_PRED(checkIsLess(emptyObject, l2aObject)); - JSONTEST_ASSERT_PRED(checkIsLess(l1aObject, l2aObject)); + JSONTEST_ASSERT_PRED(checkIsLess(l1aObject, l1bObject)); + JSONTEST_ASSERT_PRED(checkIsLess(l1bObject, l2aObject)); + JSONTEST_ASSERT_PRED(checkIsLess(l2aObject, l2bObject)); JSONTEST_ASSERT_PRED(checkIsEqual(emptyObject, Json::Value(emptyObject))); JSONTEST_ASSERT_PRED(checkIsEqual(l1aObject, Json::Value(l1aObject))); + JSONTEST_ASSERT_PRED(checkIsEqual(l1bObject, Json::Value(l1bObject))); JSONTEST_ASSERT_PRED(checkIsEqual(l2aObject, Json::Value(l2aObject))); + JSONTEST_ASSERT_PRED(checkIsEqual(l2bObject, Json::Value(l2bObject))); + { + Json::Value aObject; + aObject["a"] = 10; + Json::Value bObject; + bObject["b"] = 0; + Json::Value cObject; + cObject["c"] = 20; + cObject["f"] = 15; + Json::Value dObject; + dObject["d"] = -2; + dObject["e"] = 10; + JSONTEST_ASSERT_PRED(checkIsLess(aObject, bObject)); + JSONTEST_ASSERT_PRED(checkIsLess(bObject, cObject)); + JSONTEST_ASSERT_PRED(checkIsLess(cObject, dObject)); + JSONTEST_ASSERT_PRED(checkIsEqual(aObject, Json::Value(aObject))); + JSONTEST_ASSERT_PRED(checkIsEqual(bObject, Json::Value(bObject))); + JSONTEST_ASSERT_PRED(checkIsEqual(cObject, Json::Value(cObject))); + JSONTEST_ASSERT_PRED(checkIsEqual(dObject, Json::Value(dObject))); + } } -JSONTEST_FIXTURE(ValueTest, compareType) { +JSONTEST_FIXTURE_LOCAL(ValueTest, compareType) { // object of different type are ordered according to their type JSONTEST_ASSERT_PRED(checkIsLess(Json::Value(), Json::Value(1))); JSONTEST_ASSERT_PRED(checkIsLess(Json::Value(1), Json::Value(1u))); @@ -1483,7 +1743,7 @@ JSONTEST_FIXTURE(ValueTest, compareType) { Json::Value(Json::objectValue))); } -JSONTEST_FIXTURE(ValueTest, CopyObject) { +JSONTEST_FIXTURE_LOCAL(ValueTest, CopyObject) { Json::Value arrayVal; arrayVal.append("val1"); arrayVal.append("val2"); @@ -1515,6 +1775,24 @@ JSONTEST_FIXTURE(ValueTest, CopyObject) { JSONTEST_ASSERT(copy1 == "string value"); copy2.copy(arrayVal); JSONTEST_ASSERT(copy2.size() == 5); + { + Json::Value srcObject, objectCopy, otherObject; + srcObject["key0"] = 10; + objectCopy.copy(srcObject); + JSONTEST_ASSERT(srcObject["key0"] == 10); + JSONTEST_ASSERT(objectCopy["key0"] == 10); + JSONTEST_ASSERT(srcObject.getMemberNames().size() == 1); + JSONTEST_ASSERT(objectCopy.getMemberNames().size() == 1); + otherObject["key1"] = 15; + otherObject["key2"] = 16; + JSONTEST_ASSERT(otherObject.getMemberNames().size() == 2); + objectCopy.copy(otherObject); + JSONTEST_ASSERT(objectCopy["key1"] == 15); + JSONTEST_ASSERT(objectCopy["key2"] == 16); + JSONTEST_ASSERT(objectCopy.getMemberNames().size() == 2); + otherObject["key1"] = 20; + JSONTEST_ASSERT(objectCopy["key1"] == 15); + } } void ValueTest::checkIsLess(const Json::Value& x, const Json::Value& y) { @@ -1547,7 +1825,7 @@ void ValueTest::checkIsEqual(const Json::Value& x, const Json::Value& y) { JSONTEST_ASSERT(y.compare(x) == 0); } -JSONTEST_FIXTURE(ValueTest, typeChecksThrowExceptions) { +JSONTEST_FIXTURE_LOCAL(ValueTest, typeChecksThrowExceptions) { #if JSON_USE_EXCEPTION Json::Value intVal(1); @@ -1613,7 +1891,7 @@ JSONTEST_FIXTURE(ValueTest, typeChecksThrowExceptions) { #endif } -JSONTEST_FIXTURE(ValueTest, offsetAccessors) { +JSONTEST_FIXTURE_LOCAL(ValueTest, offsetAccessors) { Json::Value x; JSONTEST_ASSERT(x.getOffsetStart() == 0); JSONTEST_ASSERT(x.getOffsetLimit() == 0); @@ -1632,7 +1910,7 @@ JSONTEST_FIXTURE(ValueTest, offsetAccessors) { JSONTEST_ASSERT(y.getOffsetLimit() == 0); } -JSONTEST_FIXTURE(ValueTest, StaticString) { +JSONTEST_FIXTURE_LOCAL(ValueTest, StaticString) { char mutant[] = "hello"; Json::StaticString ss(mutant); Json::String regular(mutant); @@ -1655,9 +1933,9 @@ JSONTEST_FIXTURE(ValueTest, StaticString) { } } -JSONTEST_FIXTURE(ValueTest, WideString) { +JSONTEST_FIXTURE_LOCAL(ValueTest, WideString) { // https://github.com/open-source-parsers/jsoncpp/issues/756 - const std::string uni = u8"式,进"; // "\u5f0f\uff0c\u8fdb" + const std::string uni = u8"\u5f0f\uff0c\u8fdb"; // "式,进" std::string styled; { Json::Value v; @@ -1677,7 +1955,7 @@ JSONTEST_FIXTURE(ValueTest, WideString) { JSONTEST_ASSERT_STRING_EQUAL(root["abc"].asString(), uni); } -JSONTEST_FIXTURE(ValueTest, CommentBefore) { +JSONTEST_FIXTURE_LOCAL(ValueTest, CommentBefore) { Json::Value val; // fill val val.setComment(Json::String("// this comment should appear before"), Json::commentBefore); @@ -1714,14 +1992,14 @@ JSONTEST_FIXTURE(ValueTest, CommentBefore) { Json::String result = Json::writeString(wbuilder, val); JSONTEST_ASSERT_STRING_EQUAL(expected, result); Json::String res2 = val.toStyledString(); - Json::String exp2 = ""; + Json::String exp2; exp2 += expected; exp2 += "\n"; JSONTEST_ASSERT_STRING_EQUAL(exp2, res2); } } -JSONTEST_FIXTURE(ValueTest, zeroes) { +JSONTEST_FIXTURE_LOCAL(ValueTest, zeroes) { char const cstr[] = "h\0i"; Json::String binary(cstr, sizeof(cstr)); // include trailing 0 JSONTEST_ASSERT_EQUAL(4U, binary.length()); @@ -1747,7 +2025,7 @@ JSONTEST_FIXTURE(ValueTest, zeroes) { } } -JSONTEST_FIXTURE(ValueTest, zeroesInKeys) { +JSONTEST_FIXTURE_LOCAL(ValueTest, zeroesInKeys) { char const cstr[] = "h\0i"; Json::String binary(cstr, sizeof(cstr)); // include trailing 0 JSONTEST_ASSERT_EQUAL(4U, binary.length()); @@ -1775,7 +2053,7 @@ JSONTEST_FIXTURE(ValueTest, zeroesInKeys) { } } -JSONTEST_FIXTURE(ValueTest, specialFloats) { +JSONTEST_FIXTURE_LOCAL(ValueTest, specialFloats) { Json::StreamWriterBuilder b; b.settings_["useSpecialFloats"] = true; @@ -1795,7 +2073,7 @@ JSONTEST_FIXTURE(ValueTest, specialFloats) { JSONTEST_ASSERT_STRING_EQUAL(expected, result); } -JSONTEST_FIXTURE(ValueTest, precision) { +JSONTEST_FIXTURE_LOCAL(ValueTest, precision) { Json::StreamWriterBuilder b; b.settings_["precision"] = 5; @@ -1845,6 +2123,34 @@ JSONTEST_FIXTURE(ValueTest, precision) { result = Json::writeString(b, v); JSONTEST_ASSERT_STRING_EQUAL(expected, result); + b.settings_["precision"] = 0; + b.settings_["precisionType"] = "decimal"; + v = 123.56345694873740545068; + expected = "124"; + result = Json::writeString(b, v); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); + + b.settings_["precision"] = 1; + b.settings_["precisionType"] = "decimal"; + v = 1230.001; + expected = "1230.0"; + result = Json::writeString(b, v); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); + + b.settings_["precision"] = 0; + b.settings_["precisionType"] = "decimal"; + v = 1230.001; + expected = "1230"; + result = Json::writeString(b, v); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); + + b.settings_["precision"] = 0; + b.settings_["precisionType"] = "decimal"; + v = 1231.5; + expected = "1232"; + result = Json::writeString(b, v); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); + b.settings_["precision"] = 10; b.settings_["precisionType"] = "decimal"; v = 0.23300000; @@ -1852,10 +2158,92 @@ JSONTEST_FIXTURE(ValueTest, precision) { result = Json::writeString(b, v); JSONTEST_ASSERT_STRING_EQUAL(expected, result); } +JSONTEST_FIXTURE_LOCAL(ValueTest, searchValueByPath) { + Json::Value root, subroot; + root["property1"][0] = 0; + root["property1"][1] = 1; + subroot["object"] = "object"; + root["property2"] = subroot; + + const Json::Value defaultValue("error"); + Json::FastWriter writer; -struct WriterTest : JsonTest::TestCase {}; + { + const Json::String expected("{" + "\"property1\":[0,1]," + "\"property2\":{\"object\":\"object\"}" + "}\n"); + Json::String outcome = writer.write(root); + JSONTEST_ASSERT_STRING_EQUAL(expected, outcome); + + // Array member exists. + const Json::Path path1(".property1.[%]", 1); + Json::Value result = path1.resolve(root); + JSONTEST_ASSERT_EQUAL(Json::Value(1), result); + result = path1.resolve(root, defaultValue); + JSONTEST_ASSERT_EQUAL(Json::Value(1), result); + + // Array member does not exist. + const Json::Path path2(".property1.[2]"); + result = path2.resolve(root); + JSONTEST_ASSERT_EQUAL(Json::nullValue, result); + result = path2.resolve(root, defaultValue); + JSONTEST_ASSERT_EQUAL(defaultValue, result); + + // Access array path form error + const Json::Path path3(".property1.0"); + result = path3.resolve(root); + JSONTEST_ASSERT_EQUAL(Json::nullValue, result); + result = path3.resolve(root, defaultValue); + JSONTEST_ASSERT_EQUAL(defaultValue, result); + + // Object member exists. + const Json::Path path4(".property2.%", "object"); + result = path4.resolve(root); + JSONTEST_ASSERT_EQUAL(Json::Value("object"), result); + result = path4.resolve(root, defaultValue); + JSONTEST_ASSERT_EQUAL(Json::Value("object"), result); + + // Object member does not exist. + const Json::Path path5(".property2.hello"); + result = path5.resolve(root); + JSONTEST_ASSERT_EQUAL(Json::nullValue, result); + result = path5.resolve(root, defaultValue); + JSONTEST_ASSERT_EQUAL(defaultValue, result); + + // Access object path form error + const Json::Path path6(".property2.[0]"); + result = path5.resolve(root); + JSONTEST_ASSERT_EQUAL(Json::nullValue, result); + result = path6.resolve(root, defaultValue); + JSONTEST_ASSERT_EQUAL(defaultValue, result); + + // resolve will not change the value + outcome = writer.write(root); + JSONTEST_ASSERT_STRING_EQUAL(expected, outcome); + } + { + const Json::String expected("{" + "\"property1\":[0,1,null]," + "\"property2\":{" + "\"hello\":null," + "\"object\":\"object\"}}\n"); + Json::Path path1(".property1.[%]", 2); + Json::Value& value1 = path1.make(root); + JSONTEST_ASSERT_EQUAL(Json::nullValue, value1); + + Json::Path path2(".property2.%", "hello"); + Json::Value& value2 = path2.make(root); + JSONTEST_ASSERT_EQUAL(Json::nullValue, value2); + + // make will change the value + const Json::String outcome = writer.write(root); + JSONTEST_ASSERT_STRING_EQUAL(expected, outcome); + } +} +struct FastWriterTest : JsonTest::TestCase {}; -JSONTEST_FIXTURE(WriterTest, dropNullPlaceholders) { +JSONTEST_FIXTURE_LOCAL(FastWriterTest, dropNullPlaceholders) { Json::FastWriter writer; Json::Value nullValue; JSONTEST_ASSERT(writer.write(nullValue) == "null\n"); @@ -1864,306 +2252,1185 @@ JSONTEST_FIXTURE(WriterTest, dropNullPlaceholders) { JSONTEST_ASSERT(writer.write(nullValue) == "\n"); } -struct StreamWriterTest : JsonTest::TestCase {}; +JSONTEST_FIXTURE_LOCAL(FastWriterTest, enableYAMLCompatibility) { + Json::FastWriter writer; + Json::Value root; + root["hello"] = "world"; -JSONTEST_FIXTURE(StreamWriterTest, dropNullPlaceholders) { - Json::StreamWriterBuilder b; - Json::Value nullValue; - b.settings_["dropNullPlaceholders"] = false; - JSONTEST_ASSERT(Json::writeString(b, nullValue) == "null"); - b.settings_["dropNullPlaceholders"] = true; - JSONTEST_ASSERT(Json::writeString(b, nullValue).empty()); -} + JSONTEST_ASSERT(writer.write(root) == "{\"hello\":\"world\"}\n"); -JSONTEST_FIXTURE(StreamWriterTest, writeZeroes) { - Json::String binary("hi", 3); // include trailing 0 - JSONTEST_ASSERT_EQUAL(3, binary.length()); - Json::String expected("\"hi\\u0000\""); // unicoded zero - Json::StreamWriterBuilder b; - { - Json::Value root; - root = binary; - JSONTEST_ASSERT_STRING_EQUAL(binary, root.asString()); - Json::String out = Json::writeString(b, root); - JSONTEST_ASSERT_EQUAL(expected.size(), out.size()); - JSONTEST_ASSERT_STRING_EQUAL(expected, out); - } - { - Json::Value root; - root["top"] = binary; - JSONTEST_ASSERT_STRING_EQUAL(binary, root["top"].asString()); - Json::String out = Json::writeString(b, root["top"]); - JSONTEST_ASSERT_STRING_EQUAL(expected, out); - } + writer.enableYAMLCompatibility(); + JSONTEST_ASSERT(writer.write(root) == "{\"hello\": \"world\"}\n"); } -struct ReaderTest : JsonTest::TestCase {}; +JSONTEST_FIXTURE_LOCAL(FastWriterTest, omitEndingLineFeed) { + Json::FastWriter writer; + Json::Value nullValue; -JSONTEST_FIXTURE(ReaderTest, parseWithNoErrors) { - Json::Reader reader; - Json::Value root; - bool ok = reader.parse("{ \"property\" : \"value\" }", root); - JSONTEST_ASSERT(ok); - JSONTEST_ASSERT(reader.getFormattedErrorMessages().empty()); - JSONTEST_ASSERT(reader.getStructuredErrors().empty()); -} + JSONTEST_ASSERT(writer.write(nullValue) == "null\n"); -JSONTEST_FIXTURE(ReaderTest, parseWithNoErrorsTestingOffsets) { - Json::Reader reader; - Json::Value root; - bool ok = reader.parse("{ \"property\" : [\"value\", \"value2\"], \"obj\" : " - "{ \"nested\" : 123, \"bool\" : true}, \"null\" : " - "null, \"false\" : false }", - root); - JSONTEST_ASSERT(ok); - JSONTEST_ASSERT(reader.getFormattedErrorMessages().empty()); - JSONTEST_ASSERT(reader.getStructuredErrors().empty()); - JSONTEST_ASSERT(root["property"].getOffsetStart() == 15); - JSONTEST_ASSERT(root["property"].getOffsetLimit() == 34); - JSONTEST_ASSERT(root["property"][0].getOffsetStart() == 16); - JSONTEST_ASSERT(root["property"][0].getOffsetLimit() == 23); - JSONTEST_ASSERT(root["property"][1].getOffsetStart() == 25); - JSONTEST_ASSERT(root["property"][1].getOffsetLimit() == 33); - JSONTEST_ASSERT(root["obj"].getOffsetStart() == 44); - JSONTEST_ASSERT(root["obj"].getOffsetLimit() == 76); - JSONTEST_ASSERT(root["obj"]["nested"].getOffsetStart() == 57); - JSONTEST_ASSERT(root["obj"]["nested"].getOffsetLimit() == 60); - JSONTEST_ASSERT(root["obj"]["bool"].getOffsetStart() == 71); - JSONTEST_ASSERT(root["obj"]["bool"].getOffsetLimit() == 75); - JSONTEST_ASSERT(root["null"].getOffsetStart() == 87); - JSONTEST_ASSERT(root["null"].getOffsetLimit() == 91); - JSONTEST_ASSERT(root["false"].getOffsetStart() == 103); - JSONTEST_ASSERT(root["false"].getOffsetLimit() == 108); - JSONTEST_ASSERT(root.getOffsetStart() == 0); - JSONTEST_ASSERT(root.getOffsetLimit() == 110); -} - -JSONTEST_FIXTURE(ReaderTest, parseWithOneError) { - Json::Reader reader; - Json::Value root; - bool ok = reader.parse("{ \"property\" :: \"value\" }", root); - JSONTEST_ASSERT(!ok); - JSONTEST_ASSERT(reader.getFormattedErrorMessages() == - "* Line 1, Column 15\n Syntax error: value, object or array " - "expected.\n"); - std::vector errors = - reader.getStructuredErrors(); - JSONTEST_ASSERT(errors.size() == 1); - JSONTEST_ASSERT(errors.at(0).offset_start == 14); - JSONTEST_ASSERT(errors.at(0).offset_limit == 15); - JSONTEST_ASSERT(errors.at(0).message == - "Syntax error: value, object or array expected."); + writer.omitEndingLineFeed(); + JSONTEST_ASSERT(writer.write(nullValue) == "null"); } -JSONTEST_FIXTURE(ReaderTest, parseChineseWithOneError) { - Json::Reader reader; +JSONTEST_FIXTURE_LOCAL(FastWriterTest, writeNumericValue) { + Json::FastWriter writer; + const Json::String expected("{" + "\"emptyValue\":null," + "\"false\":false," + "\"null\":\"null\"," + "\"number\":-6200000000000000.0," + "\"real\":1.256," + "\"uintValue\":17" + "}\n"); Json::Value root; - bool ok = reader.parse("{ \"pr佐藤erty\" :: \"value\" }", root); - JSONTEST_ASSERT(!ok); - JSONTEST_ASSERT(reader.getFormattedErrorMessages() == - "* Line 1, Column 19\n Syntax error: value, object or array " - "expected.\n"); - std::vector errors = - reader.getStructuredErrors(); - JSONTEST_ASSERT(errors.size() == 1); - JSONTEST_ASSERT(errors.at(0).offset_start == 18); - JSONTEST_ASSERT(errors.at(0).offset_limit == 19); - JSONTEST_ASSERT(errors.at(0).message == - "Syntax error: value, object or array expected."); + root["emptyValue"] = Json::nullValue; + root["false"] = false; + root["null"] = "null"; + root["number"] = -6.2e+15; + root["real"] = 1.256; + root["uintValue"] = Json::Value(17U); + + const Json::String result = writer.write(root); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); } -JSONTEST_FIXTURE(ReaderTest, parseWithDetailError) { - Json::Reader reader; +JSONTEST_FIXTURE_LOCAL(FastWriterTest, writeArrays) { + Json::FastWriter writer; + const Json::String expected("{" + "\"property1\":[\"value1\",\"value2\"]," + "\"property2\":[]" + "}\n"); Json::Value root; - bool ok = reader.parse("{ \"property\" : \"v\\alue\" }", root); - JSONTEST_ASSERT(!ok); - JSONTEST_ASSERT(reader.getFormattedErrorMessages() == - "* Line 1, Column 16\n Bad escape sequence in string\nSee " - "Line 1, Column 20 for detail.\n"); - std::vector errors = - reader.getStructuredErrors(); - JSONTEST_ASSERT(errors.size() == 1); - JSONTEST_ASSERT(errors.at(0).offset_start == 15); - JSONTEST_ASSERT(errors.at(0).offset_limit == 23); - JSONTEST_ASSERT(errors.at(0).message == "Bad escape sequence in string"); -} + root["property1"][0] = "value1"; + root["property1"][1] = "value2"; + root["property2"] = Json::arrayValue; -struct CharReaderTest : JsonTest::TestCase {}; + const Json::String result = writer.write(root); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); +} -JSONTEST_FIXTURE(CharReaderTest, parseWithNoErrors) { - Json::CharReaderBuilder b; - Json::CharReader* reader(b.newCharReader()); - Json::String errs; - Json::Value root; - char const doc[] = "{ \"property\" : \"value\" }"; - bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); - JSONTEST_ASSERT(ok); - JSONTEST_ASSERT(errs.empty()); - delete reader; +JSONTEST_FIXTURE_LOCAL(FastWriterTest, writeNestedObjects) { + Json::FastWriter writer; + const Json::String expected("{" + "\"object1\":{" + "\"bool\":true," + "\"nested\":123" + "}," + "\"object2\":{}" + "}\n"); + Json::Value root, child; + child["nested"] = 123; + child["bool"] = true; + root["object1"] = child; + root["object2"] = Json::objectValue; + + const Json::String result = writer.write(root); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); } -JSONTEST_FIXTURE(CharReaderTest, parseWithNoErrorsTestingOffsets) { - Json::CharReaderBuilder b; - Json::CharReader* reader(b.newCharReader()); - Json::String errs; +struct StyledWriterTest : JsonTest::TestCase {}; + +JSONTEST_FIXTURE_LOCAL(StyledWriterTest, writeNumericValue) { + Json::StyledWriter writer; + const Json::String expected("{\n" + " \"emptyValue\" : null,\n" + " \"false\" : false,\n" + " \"null\" : \"null\",\n" + " \"number\" : -6200000000000000.0,\n" + " \"real\" : 1.256,\n" + " \"uintValue\" : 17\n" + "}\n"); Json::Value root; - char const doc[] = "{ \"property\" : [\"value\", \"value2\"], \"obj\" : " - "{ \"nested\" : 123, \"bool\" : true}, \"null\" : " - "null, \"false\" : false }"; - bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); - JSONTEST_ASSERT(ok); - JSONTEST_ASSERT(errs.empty()); - delete reader; + root["emptyValue"] = Json::nullValue; + root["false"] = false; + root["null"] = "null"; + root["number"] = -6.2e+15; + root["real"] = 1.256; + root["uintValue"] = Json::Value(17U); + + const Json::String result = writer.write(root); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); } -JSONTEST_FIXTURE(CharReaderTest, parseWithOneError) { - Json::CharReaderBuilder b; - Json::CharReader* reader(b.newCharReader()); - Json::String errs; +JSONTEST_FIXTURE_LOCAL(StyledWriterTest, writeArrays) { + Json::StyledWriter writer; + const Json::String expected("{\n" + " \"property1\" : [ \"value1\", \"value2\" ],\n" + " \"property2\" : []\n" + "}\n"); Json::Value root; - char const doc[] = "{ \"property\" :: \"value\" }"; - bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); - JSONTEST_ASSERT(!ok); - JSONTEST_ASSERT(errs == - "* Line 1, Column 15\n Syntax error: value, object or array " - "expected.\n"); - delete reader; + root["property1"][0] = "value1"; + root["property1"][1] = "value2"; + root["property2"] = Json::arrayValue; + + const Json::String result = writer.write(root); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); } -JSONTEST_FIXTURE(CharReaderTest, parseChineseWithOneError) { - Json::CharReaderBuilder b; - Json::CharReader* reader(b.newCharReader()); - Json::String errs; - Json::Value root; - char const doc[] = "{ \"pr佐藤erty\" :: \"value\" }"; - bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); - JSONTEST_ASSERT(!ok); - JSONTEST_ASSERT(errs == - "* Line 1, Column 19\n Syntax error: value, object or array " - "expected.\n"); - delete reader; +JSONTEST_FIXTURE_LOCAL(StyledWriterTest, writeNestedObjects) { + Json::StyledWriter writer; + const Json::String expected("{\n" + " \"object1\" : {\n" + " \"bool\" : true,\n" + " \"nested\" : 123\n" + " },\n" + " \"object2\" : {}\n" + "}\n"); + Json::Value root, child; + child["nested"] = 123; + child["bool"] = true; + root["object1"] = child; + root["object2"] = Json::objectValue; + + const Json::String result = writer.write(root); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); } -JSONTEST_FIXTURE(CharReaderTest, parseWithDetailError) { - Json::CharReaderBuilder b; - Json::CharReader* reader(b.newCharReader()); - Json::String errs; - Json::Value root; - char const doc[] = "{ \"property\" : \"v\\alue\" }"; - bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); - JSONTEST_ASSERT(!ok); - JSONTEST_ASSERT(errs == - "* Line 1, Column 16\n Bad escape sequence in string\nSee " - "Line 1, Column 20 for detail.\n"); - delete reader; +JSONTEST_FIXTURE_LOCAL(StyledWriterTest, multiLineArray) { + Json::StyledWriter writer; + { + // Array member has more than 20 print effect rendering lines + const Json::String expected("[\n " + "0,\n 1,\n 2,\n " + "3,\n 4,\n 5,\n " + "6,\n 7,\n 8,\n " + "9,\n 10,\n 11,\n " + "12,\n 13,\n 14,\n " + "15,\n 16,\n 17,\n " + "18,\n 19,\n 20\n]\n"); + Json::Value root; + for (Json::ArrayIndex i = 0; i < 21; i++) + root[i] = i; + const Json::String result = writer.write(root); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); + } + { + // Array members do not exceed 21 print effects to render a single line + const Json::String expected("[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]\n"); + Json::Value root; + for (Json::ArrayIndex i = 0; i < 10; i++) + root[i] = i; + const Json::String result = writer.write(root); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); + } } -JSONTEST_FIXTURE(CharReaderTest, parseWithStackLimit) { - Json::CharReaderBuilder b; - Json::Value root; - char const doc[] = "{ \"property\" : \"value\" }"; +JSONTEST_FIXTURE_LOCAL(StyledWriterTest, writeValueWithComment) { + Json::StyledWriter writer; { - b.settings_["stackLimit"] = 2; - Json::CharReader* reader(b.newCharReader()); - Json::String errs; - bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); - JSONTEST_ASSERT(ok); - JSONTEST_ASSERT(errs.empty()); - JSONTEST_ASSERT_EQUAL("value", root["property"]); - delete reader; + const Json::String expected("\n//commentBeforeValue\n\"hello\"\n"); + Json::Value root = "hello"; + root.setComment(Json::String("//commentBeforeValue"), Json::commentBefore); + const Json::String result = writer.write(root); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); } { - b.settings_["stackLimit"] = 1; - Json::CharReader* reader(b.newCharReader()); - Json::String errs; - JSONTEST_ASSERT_THROWS( - reader->parse(doc, doc + std::strlen(doc), &root, &errs)); - delete reader; + const Json::String expected("\"hello\" //commentAfterValueOnSameLine\n"); + Json::Value root = "hello"; + root.setComment(Json::String("//commentAfterValueOnSameLine"), + Json::commentAfterOnSameLine); + const Json::String result = writer.write(root); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); + } + { + const Json::String expected("\"hello\"\n//commentAfter\n\n"); + Json::Value root = "hello"; + root.setComment(Json::String("//commentAfter"), Json::commentAfter); + const Json::String result = writer.write(root); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); } } -struct CharReaderStrictModeTest : JsonTest::TestCase {}; +struct StyledStreamWriterTest : JsonTest::TestCase {}; + +JSONTEST_FIXTURE_LOCAL(StyledStreamWriterTest, writeNumericValue) { + Json::StyledStreamWriter writer; + const Json::String expected("{\n" + "\t\"emptyValue\" : null,\n" + "\t\"false\" : false,\n" + "\t\"null\" : \"null\",\n" + "\t\"number\" : -6200000000000000.0,\n" + "\t\"real\" : 1.256,\n" + "\t\"uintValue\" : 17\n" + "}\n"); -JSONTEST_FIXTURE(CharReaderStrictModeTest, dupKeys) { - Json::CharReaderBuilder b; Json::Value root; - char const doc[] = - "{ \"property\" : \"value\", \"key\" : \"val1\", \"key\" : \"val2\" }"; - { - b.strictMode(&b.settings_); - Json::CharReader* reader(b.newCharReader()); - Json::String errs; - bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); - JSONTEST_ASSERT(!ok); - JSONTEST_ASSERT_STRING_EQUAL("* Line 1, Column 41\n" - " Duplicate key: 'key'\n", - errs); - JSONTEST_ASSERT_EQUAL("val1", root["key"]); // so far - delete reader; - } + root["emptyValue"] = Json::nullValue; + root["false"] = false; + root["null"] = "null"; + root["number"] = -6.2e+15; // big float number + root["real"] = 1.256; // float number + root["uintValue"] = Json::Value(17U); + + Json::OStringStream sout; + writer.write(sout, root); + const Json::String result = sout.str(); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); } -struct CharReaderFailIfExtraTest : JsonTest::TestCase {}; -JSONTEST_FIXTURE(CharReaderFailIfExtraTest, issue164) { - // This is interpreted as a string value followed by a colon. - Json::CharReaderBuilder b; +JSONTEST_FIXTURE_LOCAL(StyledStreamWriterTest, writeArrays) { + Json::StyledStreamWriter writer; + const Json::String expected("{\n" + "\t\"property1\" : [ \"value1\", \"value2\" ],\n" + "\t\"property2\" : []\n" + "}\n"); Json::Value root; - char const doc[] = " \"property\" : \"value\" }"; + root["property1"][0] = "value1"; + root["property1"][1] = "value2"; + root["property2"] = Json::arrayValue; + + Json::OStringStream sout; + writer.write(sout, root); + const Json::String result = sout.str(); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); +} + +JSONTEST_FIXTURE_LOCAL(StyledStreamWriterTest, writeNestedObjects) { + Json::StyledStreamWriter writer; + const Json::String expected("{\n" + "\t\"object1\" : \n" + "\t" + "{\n" + "\t\t\"bool\" : true,\n" + "\t\t\"nested\" : 123\n" + "\t},\n" + "\t\"object2\" : {}\n" + "}\n"); + Json::Value root, child; + child["nested"] = 123; + child["bool"] = true; + root["object1"] = child; + root["object2"] = Json::objectValue; + + Json::OStringStream sout; + writer.write(sout, root); + const Json::String result = sout.str(); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); +} + +JSONTEST_FIXTURE_LOCAL(StyledStreamWriterTest, multiLineArray) { { - b.settings_["failIfExtra"] = false; - Json::CharReader* reader(b.newCharReader()); - Json::String errs; - bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); - JSONTEST_ASSERT(ok); - JSONTEST_ASSERT(errs.empty()); - JSONTEST_ASSERT_EQUAL("property", root); - delete reader; + // Array member has more than 20 print effect rendering lines + const Json::String expected("[\n\t0," + "\n\t1," + "\n\t2," + "\n\t3," + "\n\t4," + "\n\t5," + "\n\t6," + "\n\t7," + "\n\t8," + "\n\t9," + "\n\t10," + "\n\t11," + "\n\t12," + "\n\t13," + "\n\t14," + "\n\t15," + "\n\t16," + "\n\t17," + "\n\t18," + "\n\t19," + "\n\t20\n]\n"); + Json::StyledStreamWriter writer; + Json::Value root; + for (Json::ArrayIndex i = 0; i < 21; i++) + root[i] = i; + Json::OStringStream sout; + writer.write(sout, root); + const Json::String result = sout.str(); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); } { - b.settings_["failIfExtra"] = true; - Json::CharReader* reader(b.newCharReader()); - Json::String errs; - bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); - JSONTEST_ASSERT(!ok); - JSONTEST_ASSERT_STRING_EQUAL("* Line 1, Column 13\n" - " Extra non-whitespace after JSON value.\n", - errs); - JSONTEST_ASSERT_EQUAL("property", root); - delete reader; + Json::StyledStreamWriter writer; + // Array members do not exceed 21 print effects to render a single line + const Json::String expected("[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]\n"); + Json::Value root; + for (Json::ArrayIndex i = 0; i < 10; i++) + root[i] = i; + Json::OStringStream sout; + writer.write(sout, root); + const Json::String result = sout.str(); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); } +} + +JSONTEST_FIXTURE_LOCAL(StyledStreamWriterTest, writeValueWithComment) { + Json::StyledStreamWriter writer("\t"); { - b.strictMode(&b.settings_); - Json::CharReader* reader(b.newCharReader()); - Json::String errs; - bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); - JSONTEST_ASSERT(!ok); - JSONTEST_ASSERT_STRING_EQUAL("* Line 1, Column 13\n" - " Extra non-whitespace after JSON value.\n", - errs); - JSONTEST_ASSERT_EQUAL("property", root); - delete reader; + const Json::String expected("//commentBeforeValue\n\"hello\"\n"); + Json::Value root = "hello"; + Json::OStringStream sout; + root.setComment(Json::String("//commentBeforeValue"), Json::commentBefore); + writer.write(sout, root); + const Json::String result = sout.str(); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); } + { + const Json::String expected("\"hello\" //commentAfterValueOnSameLine\n"); + Json::Value root = "hello"; + Json::OStringStream sout; + root.setComment(Json::String("//commentAfterValueOnSameLine"), + Json::commentAfterOnSameLine); + writer.write(sout, root); + const Json::String result = sout.str(); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); + } + { + const Json::String expected("\"hello\"\n//commentAfter\n"); + Json::Value root = "hello"; + Json::OStringStream sout; + root.setComment(Json::String("//commentAfter"), Json::commentAfter); + writer.write(sout, root); + const Json::String result = sout.str(); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); + } +} + +struct StreamWriterTest : JsonTest::TestCase {}; + +JSONTEST_FIXTURE_LOCAL(StreamWriterTest, writeNumericValue) { + Json::StreamWriterBuilder writer; + const Json::String expected("{\n" + "\t\"emptyValue\" : null,\n" + "\t\"false\" : false,\n" + "\t\"null\" : \"null\",\n" + "\t\"number\" : -6200000000000000.0,\n" + "\t\"real\" : 1.256,\n" + "\t\"uintValue\" : 17\n" + "}"); + Json::Value root; + root["emptyValue"] = Json::nullValue; + root["false"] = false; + root["null"] = "null"; + root["number"] = -6.2e+15; + root["real"] = 1.256; + root["uintValue"] = Json::Value(17U); + + const Json::String result = Json::writeString(writer, root); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); +} + +JSONTEST_FIXTURE_LOCAL(StreamWriterTest, writeArrays) { + Json::StreamWriterBuilder writer; + const Json::String expected("{\n" + "\t\"property1\" : \n" + "\t[\n" + "\t\t\"value1\",\n" + "\t\t\"value2\"\n" + "\t],\n" + "\t\"property2\" : []\n" + "}"); + + Json::Value root; + root["property1"][0] = "value1"; + root["property1"][1] = "value2"; + root["property2"] = Json::arrayValue; + + const Json::String result = Json::writeString(writer, root); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); +} + +JSONTEST_FIXTURE_LOCAL(StreamWriterTest, writeNestedObjects) { + Json::StreamWriterBuilder writer; + const Json::String expected("{\n" + "\t\"object1\" : \n" + "\t{\n" + "\t\t\"bool\" : true,\n" + "\t\t\"nested\" : 123\n" + "\t},\n" + "\t\"object2\" : {}\n" + "}"); + + Json::Value root, child; + child["nested"] = 123; + child["bool"] = true; + root["object1"] = child; + root["object2"] = Json::objectValue; + + const Json::String result = Json::writeString(writer, root); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); +} + +JSONTEST_FIXTURE_LOCAL(StreamWriterTest, multiLineArray) { + Json::StreamWriterBuilder wb; + wb.settings_["commentStyle"] = "None"; + { + // When wb.settings_["commentStyle"] = "None", the effect of + // printing multiple lines will be displayed when there are + // more than 20 array members. + const Json::String expected("[\n\t0," + "\n\t1," + "\n\t2," + "\n\t3," + "\n\t4," + "\n\t5," + "\n\t6," + "\n\t7," + "\n\t8," + "\n\t9," + "\n\t10," + "\n\t11," + "\n\t12," + "\n\t13," + "\n\t14," + "\n\t15," + "\n\t16," + "\n\t17," + "\n\t18," + "\n\t19," + "\n\t20\n]"); + Json::Value root; + for (Json::ArrayIndex i = 0; i < 21; i++) + root[i] = i; + const Json::String result = Json::writeString(wb, root); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); + } + { + // Array members do not exceed 21 print effects to render a single line + const Json::String expected("[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]"); + Json::Value root; + for (Json::ArrayIndex i = 0; i < 10; i++) + root[i] = i; + const Json::String result = Json::writeString(wb, root); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); + } +} + +JSONTEST_FIXTURE_LOCAL(StreamWriterTest, dropNullPlaceholders) { + Json::StreamWriterBuilder b; + Json::Value nullValue; + b.settings_["dropNullPlaceholders"] = false; + JSONTEST_ASSERT(Json::writeString(b, nullValue) == "null"); + b.settings_["dropNullPlaceholders"] = true; + JSONTEST_ASSERT(Json::writeString(b, nullValue).empty()); +} + +JSONTEST_FIXTURE_LOCAL(StreamWriterTest, enableYAMLCompatibility) { + Json::StreamWriterBuilder b; + Json::Value root; + root["hello"] = "world"; + + b.settings_["indentation"] = ""; + JSONTEST_ASSERT(Json::writeString(b, root) == "{\"hello\":\"world\"}"); + + b.settings_["enableYAMLCompatibility"] = true; + JSONTEST_ASSERT(Json::writeString(b, root) == "{\"hello\": \"world\"}"); + + b.settings_["enableYAMLCompatibility"] = false; + JSONTEST_ASSERT(Json::writeString(b, root) == "{\"hello\":\"world\"}"); +} + +JSONTEST_FIXTURE_LOCAL(StreamWriterTest, indentation) { + Json::StreamWriterBuilder b; + Json::Value root; + root["hello"] = "world"; + + b.settings_["indentation"] = ""; + JSONTEST_ASSERT(Json::writeString(b, root) == "{\"hello\":\"world\"}"); + + b.settings_["indentation"] = "\t"; + JSONTEST_ASSERT(Json::writeString(b, root) == + "{\n\t\"hello\" : \"world\"\n}"); +} + +JSONTEST_FIXTURE_LOCAL(StreamWriterTest, writeZeroes) { + Json::String binary("hi", 3); // include trailing 0 + JSONTEST_ASSERT_EQUAL(3, binary.length()); + Json::String expected(R"("hi\u0000")"); // unicoded zero + Json::StreamWriterBuilder b; + { + Json::Value root; + root = binary; + JSONTEST_ASSERT_STRING_EQUAL(binary, root.asString()); + Json::String out = Json::writeString(b, root); + JSONTEST_ASSERT_EQUAL(expected.size(), out.size()); + JSONTEST_ASSERT_STRING_EQUAL(expected, out); + } + { + Json::Value root; + root["top"] = binary; + JSONTEST_ASSERT_STRING_EQUAL(binary, root["top"].asString()); + Json::String out = Json::writeString(b, root["top"]); + JSONTEST_ASSERT_STRING_EQUAL(expected, out); + } +} + +JSONTEST_FIXTURE_LOCAL(StreamWriterTest, unicode) { + // Create a Json value containing UTF-8 string with some chars that need + // escape (tab,newline). + Json::Value root; + root["test"] = "\t\n\xF0\x91\xA2\xA1\x3D\xC4\xB3\xF0\x9B\x84\x9B\xEF\xBD\xA7"; + + Json::StreamWriterBuilder b; + + // Default settings - should be unicode escaped. + JSONTEST_ASSERT(Json::writeString(b, root) == + "{\n\t\"test\" : " + "\"\\t\\n\\ud806\\udca1=\\u0133\\ud82c\\udd1b\\uff67\"\n}"); + + b.settings_["emitUTF8"] = true; + + // Should not be unicode escaped. + JSONTEST_ASSERT( + Json::writeString(b, root) == + "{\n\t\"test\" : " + "\"\\t\\n\xF0\x91\xA2\xA1=\xC4\xB3\xF0\x9B\x84\x9B\xEF\xBD\xA7\"\n}"); + + b.settings_["emitUTF8"] = false; + + // Should be unicode escaped. + JSONTEST_ASSERT(Json::writeString(b, root) == + "{\n\t\"test\" : " + "\"\\t\\n\\ud806\\udca1=\\u0133\\ud82c\\udd1b\\uff67\"\n}"); +} + +// Control chars should be escaped regardless of UTF-8 input encoding. +JSONTEST_FIXTURE_LOCAL(StreamWriterTest, escapeControlCharacters) { + auto uEscape = [](unsigned ch) { + static const char h[] = "0123456789abcdef"; + std::string r = "\\u"; + r += h[(ch >> (3 * 4)) & 0xf]; + r += h[(ch >> (2 * 4)) & 0xf]; + r += h[(ch >> (1 * 4)) & 0xf]; + r += h[(ch >> (0 * 4)) & 0xf]; + return r; + }; + auto shortEscape = [](unsigned ch) -> const char* { + switch (ch) { + case '\"': + return "\\\""; + case '\\': + return "\\\\"; + case '\b': + return "\\b"; + case '\f': + return "\\f"; + case '\n': + return "\\n"; + case '\r': + return "\\r"; + case '\t': + return "\\t"; + default: + return nullptr; + } + }; + + Json::StreamWriterBuilder b; + + for (bool emitUTF8 : {true, false}) { + b.settings_["emitUTF8"] = emitUTF8; + + for (unsigned i = 0; i != 0x100; ++i) { + if (!emitUTF8 && i >= 0x80) + break; // The algorithm would try to parse UTF-8, so stop here. + + std::string raw({static_cast(i)}); + std::string esc = raw; + if (i < 0x20) + esc = uEscape(i); + if (const char* shEsc = shortEscape(i)) + esc = shEsc; + + // std::cout << "emit=" << emitUTF8 << ", i=" << std::hex << i << std::dec + // << std::endl; + + Json::Value root; + root["test"] = raw; + JSONTEST_ASSERT_STRING_EQUAL( + std::string("{\n\t\"test\" : \"").append(esc).append("\"\n}"), + Json::writeString(b, root)) + << ", emit=" << emitUTF8 << ", i=" << i << ", raw=\"" << raw << "\"" + << ", esc=\"" << esc << "\""; + } + } +} + +#ifdef _WIN32 +JSONTEST_FIXTURE_LOCAL(StreamWriterTest, escapeTabCharacterWindows) { + // Get the current locale before changing it + std::string currentLocale = setlocale(LC_ALL, NULL); + setlocale(LC_ALL, "English_United States.1252"); + + Json::Value root; + root["test"] = "\tTabTesting\t"; + + Json::StreamWriterBuilder b; + + JSONTEST_ASSERT(Json::writeString(b, root) == "{\n\t\"test\" : " + "\"\\tTabTesting\\t\"\n}"); + + b.settings_["emitUTF8"] = true; + JSONTEST_ASSERT(Json::writeString(b, root) == "{\n\t\"test\" : " + "\"\\tTabTesting\\t\"\n}"); + + b.settings_["emitUTF8"] = false; + JSONTEST_ASSERT(Json::writeString(b, root) == "{\n\t\"test\" : " + "\"\\tTabTesting\\t\"\n}"); + + // Restore the locale + if (!currentLocale.empty()) + setlocale(LC_ALL, currentLocale.c_str()); +} +#endif + +struct ReaderTest : JsonTest::TestCase { + void setStrictMode() { + reader = std::unique_ptr( + new Json::Reader(Json::Features{}.strictMode())); + } + + void setFeatures(Json::Features& features) { + reader = std::unique_ptr(new Json::Reader(features)); + } + + void checkStructuredErrors( + const std::vector& actual, + const std::vector& expected) { + JSONTEST_ASSERT_EQUAL(expected.size(), actual.size()); + for (size_t i = 0; i < actual.size(); ++i) { + const auto& a = actual[i]; + const auto& e = expected[i]; + JSONTEST_ASSERT_EQUAL(e.offset_start, a.offset_start) << i; + JSONTEST_ASSERT_EQUAL(e.offset_limit, a.offset_limit) << i; + JSONTEST_ASSERT_EQUAL(e.message, a.message) << i; + } + } + + template void checkParse(Input&& input) { + JSONTEST_ASSERT(reader->parse(input, root)); + } + + template + void + checkParse(Input&& input, + const std::vector& structured) { + JSONTEST_ASSERT(!reader->parse(input, root)); + checkStructuredErrors(reader->getStructuredErrors(), structured); + } + + template + void checkParse(Input&& input, + const std::vector& structured, + const std::string& formatted) { + checkParse(input, structured); + JSONTEST_ASSERT_EQUAL(formatted, reader->getFormattedErrorMessages()); + } + + std::unique_ptr reader{new Json::Reader()}; + Json::Value root; +}; + +JSONTEST_FIXTURE_LOCAL(ReaderTest, parseWithNoErrors) { + checkParse(R"({ "property" : "value" })"); +} + +JSONTEST_FIXTURE_LOCAL(ReaderTest, parseObject) { + checkParse(R"({"property"})", + {{11, 12, "Missing ':' after object member name"}}, + "* Line 1, Column 12\n Missing ':' after object member name\n"); + checkParse( + R"({"property" : "value" )", + {{22, 22, "Missing ',' or '}' in object declaration"}}, + "* Line 1, Column 23\n Missing ',' or '}' in object declaration\n"); + checkParse(R"({"property" : "value", )", + {{23, 23, "Missing '}' or object member name"}}, + "* Line 1, Column 24\n Missing '}' or object member name\n"); +} + +JSONTEST_FIXTURE_LOCAL(ReaderTest, parseArray) { + checkParse( + R"([ "value" )", {{10, 10, "Missing ',' or ']' in array declaration"}}, + "* Line 1, Column 11\n Missing ',' or ']' in array declaration\n"); + checkParse( + R"([ "value1" "value2" ] )", + {{11, 19, "Missing ',' or ']' in array declaration"}}, + "* Line 1, Column 12\n Missing ',' or ']' in array declaration\n"); +} + +JSONTEST_FIXTURE_LOCAL(ReaderTest, parseString) { + checkParse(R"([ "\u8a2a" ])"); + checkParse( + R"([ "\ud801" ])", + {{2, 10, + "additional six characters expected to parse unicode surrogate " + "pair."}}, + "* Line 1, Column 3\n" + " additional six characters expected to parse unicode surrogate pair.\n" + "See Line 1, Column 10 for detail.\n"); + checkParse(R"([ "\ud801\d1234" ])", + {{2, 16, + "expecting another \\u token to begin the " + "second half of a unicode surrogate pair"}}, + "* Line 1, Column 3\n" + " expecting another \\u token to begin the " + "second half of a unicode surrogate pair\n" + "See Line 1, Column 12 for detail.\n"); + checkParse(R"([ "\ua3t@" ])", + {{2, 10, + "Bad unicode escape sequence in string: " + "hexadecimal digit expected."}}, + "* Line 1, Column 3\n" + " Bad unicode escape sequence in string: " + "hexadecimal digit expected.\n" + "See Line 1, Column 9 for detail.\n"); + checkParse( + R"([ "\ua3t" ])", + {{2, 9, "Bad unicode escape sequence in string: four digits expected."}}, + "* Line 1, Column 3\n" + " Bad unicode escape sequence in string: four digits expected.\n" + "See Line 1, Column 6 for detail.\n"); +} + +JSONTEST_FIXTURE_LOCAL(ReaderTest, parseComment) { + checkParse( + R"({ /*commentBeforeValue*/ "property" : "value" }//commentAfterValue)" + "\n"); + checkParse(" true //comment1\n//comment2\r//comment3\r\n"); +} + +JSONTEST_FIXTURE_LOCAL(ReaderTest, streamParseWithNoErrors) { + std::string styled = R"({ "property" : "value" })"; + std::istringstream iss(styled); + checkParse(iss); +} + +JSONTEST_FIXTURE_LOCAL(ReaderTest, parseWithNoErrorsTestingOffsets) { + checkParse(R"({)" + R"( "property" : ["value", "value2"],)" + R"( "obj" : { "nested" : -6.2e+15, "bool" : true},)" + R"( "null" : null,)" + R"( "false" : false)" + R"( })"); + auto checkOffsets = [&](const Json::Value& v, int start, int limit) { + JSONTEST_ASSERT_EQUAL(start, v.getOffsetStart()); + JSONTEST_ASSERT_EQUAL(limit, v.getOffsetLimit()); + }; + checkOffsets(root, 0, 115); + checkOffsets(root["property"], 15, 34); + checkOffsets(root["property"][0], 16, 23); + checkOffsets(root["property"][1], 25, 33); + checkOffsets(root["obj"], 44, 81); + checkOffsets(root["obj"]["nested"], 57, 65); + checkOffsets(root["obj"]["bool"], 76, 80); + checkOffsets(root["null"], 92, 96); + checkOffsets(root["false"], 108, 113); +} + +JSONTEST_FIXTURE_LOCAL(ReaderTest, parseWithOneError) { + checkParse(R"({ "property" :: "value" })", + {{14, 15, "Syntax error: value, object or array expected."}}, + "* Line 1, Column 15\n Syntax error: value, object or array " + "expected.\n"); + checkParse("s", {{0, 1, "Syntax error: value, object or array expected."}}, + "* Line 1, Column 1\n Syntax error: value, object or array " + "expected.\n"); +} + +JSONTEST_FIXTURE_LOCAL(ReaderTest, parseSpecialFloat) { + checkParse(R"({ "a" : Infi })", + {{8, 9, "Syntax error: value, object or array expected."}}, + "* Line 1, Column 9\n Syntax error: value, object or array " + "expected.\n"); + checkParse(R"({ "a" : Infiniaa })", + {{8, 9, "Syntax error: value, object or array expected."}}, + "* Line 1, Column 9\n Syntax error: value, object or array " + "expected.\n"); +} + +JSONTEST_FIXTURE_LOCAL(ReaderTest, strictModeParseNumber) { + setStrictMode(); + checkParse( + "123", + {{0, 3, + "A valid JSON document must be either an array or an object value."}}, + "* Line 1, Column 1\n" + " A valid JSON document must be either an array or an object value.\n"); +} + +JSONTEST_FIXTURE_LOCAL(ReaderTest, parseChineseWithOneError) { + checkParse(R"({ "pr)" + u8"\u4f50\u85e4" // 佐藤 + R"(erty" :: "value" })", + {{18, 19, "Syntax error: value, object or array expected."}}, + "* Line 1, Column 19\n Syntax error: value, object or array " + "expected.\n"); +} + +JSONTEST_FIXTURE_LOCAL(ReaderTest, parseWithDetailError) { + checkParse(R"({ "property" : "v\alue" })", + {{15, 23, "Bad escape sequence in string"}}, + "* Line 1, Column 16\n" + " Bad escape sequence in string\n" + "See Line 1, Column 20 for detail.\n"); +} + +JSONTEST_FIXTURE_LOCAL(ReaderTest, pushErrorTest) { + checkParse(R"({ "AUTHOR" : 123 })"); + if (!root["AUTHOR"].isString()) { + JSONTEST_ASSERT( + reader->pushError(root["AUTHOR"], "AUTHOR must be a string")); + } + JSONTEST_ASSERT_STRING_EQUAL(reader->getFormattedErrorMessages(), + "* Line 1, Column 14\n" + " AUTHOR must be a string\n"); + + checkParse(R"({ "AUTHOR" : 123 })"); + if (!root["AUTHOR"].isString()) { + JSONTEST_ASSERT(reader->pushError(root["AUTHOR"], "AUTHOR must be a string", + root["AUTHOR"])); + } + JSONTEST_ASSERT_STRING_EQUAL(reader->getFormattedErrorMessages(), + "* Line 1, Column 14\n" + " AUTHOR must be a string\n" + "See Line 1, Column 14 for detail.\n"); +} + +JSONTEST_FIXTURE_LOCAL(ReaderTest, allowNumericKeysTest) { + Json::Features features; + features.allowNumericKeys_ = true; + setFeatures(features); + checkParse(R"({ 123 : "abc" })"); +} + +struct CharReaderTest : JsonTest::TestCase {}; + +JSONTEST_FIXTURE_LOCAL(CharReaderTest, parseWithNoErrors) { + Json::CharReaderBuilder b; + CharReaderPtr reader(b.newCharReader()); + Json::String errs; + Json::Value root; + char const doc[] = R"({ "property" : "value" })"; + bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); + JSONTEST_ASSERT(ok); + JSONTEST_ASSERT(errs.empty()); +} + +JSONTEST_FIXTURE_LOCAL(CharReaderTest, parseWithNoErrorsTestingOffsets) { + Json::CharReaderBuilder b; + CharReaderPtr reader(b.newCharReader()); + Json::String errs; + Json::Value root; + char const doc[] = "{ \"property\" : [\"value\", \"value2\"], \"obj\" : " + "{ \"nested\" : -6.2e+15, \"num\" : +123, \"bool\" : " + "true}, \"null\" : null, \"false\" : false }"; + bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); + JSONTEST_ASSERT(ok); + JSONTEST_ASSERT(errs.empty()); +} + +JSONTEST_FIXTURE_LOCAL(CharReaderTest, parseNumber) { + Json::CharReaderBuilder b; + CharReaderPtr reader(b.newCharReader()); + Json::String errs; + Json::Value root; + { + // if intvalue > threshold, treat the number as a double. + // 21 digits + char const doc[] = "[111111111111111111111]"; + bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); + JSONTEST_ASSERT(ok); + JSONTEST_ASSERT(errs.empty()); + JSONTEST_ASSERT_EQUAL(1.1111111111111111e+020, root[0]); + } +} + +JSONTEST_FIXTURE_LOCAL(CharReaderTest, parseString) { + Json::CharReaderBuilder b; + CharReaderPtr reader(b.newCharReader()); + Json::Value root; + Json::String errs; + { + char const doc[] = "[\"\"]"; + bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); + JSONTEST_ASSERT(ok); + JSONTEST_ASSERT(errs.empty()); + JSONTEST_ASSERT_EQUAL("", root[0]); + } + { + char const doc[] = R"(["\u8A2a"])"; + bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); + JSONTEST_ASSERT(ok); + JSONTEST_ASSERT(errs.empty()); + JSONTEST_ASSERT_EQUAL(u8"\u8A2a", root[0].asString()); // "訪" + } + { + char const doc[] = R"([ "\uD801" ])"; + bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); + JSONTEST_ASSERT(!ok); + JSONTEST_ASSERT(errs == "* Line 1, Column 3\n" + " additional six characters expected to " + "parse unicode surrogate pair.\n" + "See Line 1, Column 10 for detail.\n"); + } + { + char const doc[] = R"([ "\uD801\d1234" ])"; + bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); + JSONTEST_ASSERT(!ok); + JSONTEST_ASSERT(errs == "* Line 1, Column 3\n" + " expecting another \\u token to begin the " + "second half of a unicode surrogate pair\n" + "See Line 1, Column 12 for detail.\n"); + } + { + char const doc[] = R"([ "\ua3t@" ])"; + bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); + JSONTEST_ASSERT(!ok); + JSONTEST_ASSERT(errs == "* Line 1, Column 3\n" + " Bad unicode escape sequence in string: " + "hexadecimal digit expected.\n" + "See Line 1, Column 9 for detail.\n"); + } + { + char const doc[] = R"([ "\ua3t" ])"; + bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); + JSONTEST_ASSERT(!ok); + JSONTEST_ASSERT( + errs == + "* Line 1, Column 3\n" + " Bad unicode escape sequence in string: four digits expected.\n" + "See Line 1, Column 6 for detail.\n"); + } + { + b.settings_["allowSingleQuotes"] = true; + CharReaderPtr charreader(b.newCharReader()); + char const doc[] = R"({'a': 'x\ty', "b":'x\\y'})"; + bool ok = charreader->parse(doc, doc + std::strlen(doc), &root, &errs); + JSONTEST_ASSERT(ok); + JSONTEST_ASSERT_STRING_EQUAL("", errs); + JSONTEST_ASSERT_EQUAL(2u, root.size()); + JSONTEST_ASSERT_STRING_EQUAL("x\ty", root["a"].asString()); + JSONTEST_ASSERT_STRING_EQUAL("x\\y", root["b"].asString()); + } +} + +JSONTEST_FIXTURE_LOCAL(CharReaderTest, parseComment) { + Json::CharReaderBuilder b; + CharReaderPtr reader(b.newCharReader()); + Json::Value root; + Json::String errs; + { + char const doc[] = "//comment1\n { //comment2\n \"property\" :" + " \"value\" //comment3\n } //comment4\n"; + bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); + JSONTEST_ASSERT(ok); + JSONTEST_ASSERT(errs.empty()); + JSONTEST_ASSERT_EQUAL("value", root["property"]); + } + { + char const doc[] = "{ \"property\" //comment\n : \"value\" }"; + bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); + JSONTEST_ASSERT(!ok); + JSONTEST_ASSERT(errs == "* Line 1, Column 14\n" + " Missing ':' after object member name\n"); + } + { + char const doc[] = "//comment1\n [ //comment2\n \"value\" //comment3\n," + " //comment4\n true //comment5\n ] //comment6\n"; + bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); + JSONTEST_ASSERT(ok); + JSONTEST_ASSERT(errs.empty()); + JSONTEST_ASSERT_EQUAL("value", root[0]); + JSONTEST_ASSERT_EQUAL(true, root[1]); + } +} + +JSONTEST_FIXTURE_LOCAL(CharReaderTest, parseObjectWithErrors) { + Json::CharReaderBuilder b; + CharReaderPtr reader(b.newCharReader()); + Json::Value root; + Json::String errs; + { + char const doc[] = R"({ "property" : "value" )"; + bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); + JSONTEST_ASSERT(!ok); + JSONTEST_ASSERT(errs == "* Line 1, Column 24\n" + " Missing ',' or '}' in object declaration\n"); + JSONTEST_ASSERT_EQUAL("value", root["property"]); + } + { + char const doc[] = R"({ "property" : "value" ,)"; + bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); + JSONTEST_ASSERT(!ok); + JSONTEST_ASSERT(errs == "* Line 1, Column 25\n" + " Missing '}' or object member name\n"); + JSONTEST_ASSERT_EQUAL("value", root["property"]); + } +} + +JSONTEST_FIXTURE_LOCAL(CharReaderTest, parseArrayWithErrors) { + Json::CharReaderBuilder b; + CharReaderPtr reader(b.newCharReader()); + Json::Value root; + Json::String errs; + { + char const doc[] = "[ \"value\" "; + bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); + JSONTEST_ASSERT(!ok); + JSONTEST_ASSERT(errs == "* Line 1, Column 11\n" + " Missing ',' or ']' in array declaration\n"); + JSONTEST_ASSERT_EQUAL("value", root[0]); + } + { + char const doc[] = R"([ "value1" "value2" ])"; + bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); + JSONTEST_ASSERT(!ok); + JSONTEST_ASSERT(errs == "* Line 1, Column 12\n" + " Missing ',' or ']' in array declaration\n"); + JSONTEST_ASSERT_EQUAL("value1", root[0]); + } +} + +JSONTEST_FIXTURE_LOCAL(CharReaderTest, parseWithOneError) { + Json::CharReaderBuilder b; + CharReaderPtr reader(b.newCharReader()); + Json::String errs; + Json::Value root; + char const doc[] = R"({ "property" :: "value" })"; + bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); + JSONTEST_ASSERT(!ok); + JSONTEST_ASSERT(errs == + "* Line 1, Column 15\n Syntax error: value, object or array " + "expected.\n"); +} + +JSONTEST_FIXTURE_LOCAL(CharReaderTest, parseChineseWithOneError) { + Json::CharReaderBuilder b; + CharReaderPtr reader(b.newCharReader()); + Json::String errs; + Json::Value root; + char const doc[] = "{ \"pr佐藤erty\" :: \"value\" }"; + bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); + JSONTEST_ASSERT(!ok); + JSONTEST_ASSERT(errs == + "* Line 1, Column 19\n Syntax error: value, object or array " + "expected.\n"); +} + +JSONTEST_FIXTURE_LOCAL(CharReaderTest, parseWithDetailError) { + Json::CharReaderBuilder b; + CharReaderPtr reader(b.newCharReader()); + Json::String errs; + Json::Value root; + char const doc[] = R"({ "property" : "v\alue" })"; + bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); + JSONTEST_ASSERT(!ok); + JSONTEST_ASSERT(errs == + "* Line 1, Column 16\n Bad escape sequence in string\nSee " + "Line 1, Column 20 for detail.\n"); +} + +JSONTEST_FIXTURE_LOCAL(CharReaderTest, parseWithStackLimit) { + Json::CharReaderBuilder b; + Json::Value root; + char const doc[] = R"({ "property" : "value" })"; + { + b.settings_["stackLimit"] = 2; + CharReaderPtr reader(b.newCharReader()); + Json::String errs; + bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); + JSONTEST_ASSERT(ok); + JSONTEST_ASSERT(errs.empty()); + JSONTEST_ASSERT_EQUAL("value", root["property"]); + } + { + b.settings_["stackLimit"] = 1; + CharReaderPtr reader(b.newCharReader()); + Json::String errs; + JSONTEST_ASSERT_THROWS( + reader->parse(doc, doc + std::strlen(doc), &root, &errs)); + } +} + +JSONTEST_FIXTURE_LOCAL(CharReaderTest, testOperator) { + const std::string styled = R"({ "property" : "value" })"; + std::istringstream iss(styled); + Json::Value root; + iss >> root; + JSONTEST_ASSERT_EQUAL("value", root["property"]); +} + +struct CharReaderStrictModeTest : JsonTest::TestCase {}; + +JSONTEST_FIXTURE_LOCAL(CharReaderStrictModeTest, dupKeys) { + Json::CharReaderBuilder b; + Json::Value root; + char const doc[] = + R"({ "property" : "value", "key" : "val1", "key" : "val2" })"; { b.strictMode(&b.settings_); + CharReaderPtr reader(b.newCharReader()); + Json::String errs; + bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); + JSONTEST_ASSERT(!ok); + JSONTEST_ASSERT_STRING_EQUAL("* Line 1, Column 41\n" + " Duplicate key: 'key'\n", + errs); + JSONTEST_ASSERT_EQUAL("val1", root["key"]); // so far + } +} +struct CharReaderFailIfExtraTest : JsonTest::TestCase {}; + +JSONTEST_FIXTURE_LOCAL(CharReaderFailIfExtraTest, issue164) { + // This is interpreted as a string value followed by a colon. + Json::CharReaderBuilder b; + Json::Value root; + char const doc[] = R"( "property" : "value" })"; + { b.settings_["failIfExtra"] = false; - Json::CharReader* reader(b.newCharReader()); + CharReaderPtr reader(b.newCharReader()); + Json::String errs; + bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); + JSONTEST_ASSERT(ok); + JSONTEST_ASSERT(errs.empty()); + JSONTEST_ASSERT_EQUAL("property", root); + } + { + b.settings_["failIfExtra"] = true; + CharReaderPtr reader(b.newCharReader()); + Json::String errs; + bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); + JSONTEST_ASSERT(!ok); + JSONTEST_ASSERT_STRING_EQUAL("* Line 1, Column 13\n" + " Extra non-whitespace after JSON value.\n", + errs); + JSONTEST_ASSERT_EQUAL("property", root); + } + { + b.strictMode(&b.settings_); + CharReaderPtr reader(b.newCharReader()); Json::String errs; bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); JSONTEST_ASSERT(!ok); - JSONTEST_ASSERT_STRING_EQUAL("* Line 1, Column 1\n" - " A valid JSON document must be either an array or an object value.\n", + JSONTEST_ASSERT_STRING_EQUAL("* Line 1, Column 13\n" + " Extra non-whitespace after JSON value.\n", errs); JSONTEST_ASSERT_EQUAL("property", root); - delete reader; + } + { + b.strictMode(&b.settings_); + b.settings_["failIfExtra"] = false; + CharReaderPtr reader(b.newCharReader()); + Json::String errs; + bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); + JSONTEST_ASSERT(!ok); + JSONTEST_ASSERT_STRING_EQUAL( + "* Line 1, Column 1\n" + " A valid JSON document must be either an array or an object value.\n", + errs); + JSONTEST_ASSERT_EQUAL("property", root); } } -JSONTEST_FIXTURE(CharReaderFailIfExtraTest, issue107) { + +JSONTEST_FIXTURE_LOCAL(CharReaderFailIfExtraTest, issue107) { // This is interpreted as an int value followed by a colon. Json::CharReaderBuilder b; Json::Value root; char const doc[] = "1:2:3"; b.settings_["failIfExtra"] = true; - Json::CharReader* reader(b.newCharReader()); + CharReaderPtr reader(b.newCharReader()); Json::String errs; bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); JSONTEST_ASSERT(!ok); @@ -2171,179 +3438,167 @@ JSONTEST_FIXTURE(CharReaderFailIfExtraTest, issue107) { " Extra non-whitespace after JSON value.\n", errs); JSONTEST_ASSERT_EQUAL(1, root.asInt()); - delete reader; } -JSONTEST_FIXTURE(CharReaderFailIfExtraTest, commentAfterObject) { +JSONTEST_FIXTURE_LOCAL(CharReaderFailIfExtraTest, commentAfterObject) { Json::CharReaderBuilder b; Json::Value root; { char const doc[] = "{ \"property\" : \"value\" } //trailing\n//comment\n"; b.settings_["failIfExtra"] = true; - Json::CharReader* reader(b.newCharReader()); + CharReaderPtr reader(b.newCharReader()); Json::String errs; bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); JSONTEST_ASSERT(ok); JSONTEST_ASSERT_STRING_EQUAL("", errs); JSONTEST_ASSERT_EQUAL("value", root["property"]); - delete reader; } } -JSONTEST_FIXTURE(CharReaderFailIfExtraTest, commentAfterArray) { +JSONTEST_FIXTURE_LOCAL(CharReaderFailIfExtraTest, commentAfterArray) { Json::CharReaderBuilder b; Json::Value root; char const doc[] = "[ \"property\" , \"value\" ] //trailing\n//comment\n"; b.settings_["failIfExtra"] = true; - Json::CharReader* reader(b.newCharReader()); + CharReaderPtr reader(b.newCharReader()); Json::String errs; bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); JSONTEST_ASSERT(ok); JSONTEST_ASSERT_STRING_EQUAL("", errs); JSONTEST_ASSERT_EQUAL("value", root[1u]); - delete reader; } -JSONTEST_FIXTURE(CharReaderFailIfExtraTest, commentAfterBool) { +JSONTEST_FIXTURE_LOCAL(CharReaderFailIfExtraTest, commentAfterBool) { Json::CharReaderBuilder b; Json::Value root; char const doc[] = " true /*trailing\ncomment*/"; b.settings_["failIfExtra"] = true; - Json::CharReader* reader(b.newCharReader()); + CharReaderPtr reader(b.newCharReader()); Json::String errs; bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); JSONTEST_ASSERT(ok); JSONTEST_ASSERT_STRING_EQUAL("", errs); JSONTEST_ASSERT_EQUAL(true, root.asBool()); - delete reader; } -struct CharReaderAllowDropNullTest : JsonTest::TestCase {}; -JSONTEST_FIXTURE(CharReaderAllowDropNullTest, issue178) { +JSONTEST_FIXTURE_LOCAL(CharReaderFailIfExtraTest, parseComment) { Json::CharReaderBuilder b; - b.settings_["allowDroppedNullPlaceholders"] = true; + b.settings_["failIfExtra"] = true; + CharReaderPtr reader(b.newCharReader()); Json::Value root; Json::String errs; - Json::CharReader* reader(b.newCharReader()); - { - char const doc[] = "{\"a\":,\"b\":true}"; - bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); - JSONTEST_ASSERT(ok); - JSONTEST_ASSERT_STRING_EQUAL("", errs); - JSONTEST_ASSERT_EQUAL(2u, root.size()); - JSONTEST_ASSERT_EQUAL(Json::nullValue, root.get("a", true)); - } - { - char const doc[] = "{\"a\":}"; - bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); - JSONTEST_ASSERT(ok); - JSONTEST_ASSERT_STRING_EQUAL("", errs); - JSONTEST_ASSERT_EQUAL(1u, root.size()); - JSONTEST_ASSERT_EQUAL(Json::nullValue, root.get("a", true)); - } - { - char const doc[] = "[]"; - bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); - JSONTEST_ASSERT(ok); - JSONTEST_ASSERT(errs.empty()); - JSONTEST_ASSERT_EQUAL(0u, root.size()); - JSONTEST_ASSERT_EQUAL(Json::arrayValue, root); - } - { - char const doc[] = "[null]"; - bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); - JSONTEST_ASSERT(ok); - JSONTEST_ASSERT(errs.empty()); - JSONTEST_ASSERT_EQUAL(1u, root.size()); - } - { - char const doc[] = "[,]"; - bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); - JSONTEST_ASSERT(ok); - JSONTEST_ASSERT_STRING_EQUAL("", errs); - JSONTEST_ASSERT_EQUAL(2u, root.size()); - } - { - char const doc[] = "[,,,]"; - bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); - JSONTEST_ASSERT(ok); - JSONTEST_ASSERT_STRING_EQUAL("", errs); - JSONTEST_ASSERT_EQUAL(4u, root.size()); - } { - char const doc[] = "[null,]"; + char const doc[] = " true //comment1\n//comment2\r//comment3\r\n"; bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); JSONTEST_ASSERT(ok); JSONTEST_ASSERT_STRING_EQUAL("", errs); - JSONTEST_ASSERT_EQUAL(2u, root.size()); - } - { - char const doc[] = "[,null]"; - bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); - JSONTEST_ASSERT(ok); - JSONTEST_ASSERT(errs.empty()); - JSONTEST_ASSERT_EQUAL(2u, root.size()); - } - { - char const doc[] = "[,,]"; - bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); - JSONTEST_ASSERT(ok); - JSONTEST_ASSERT_STRING_EQUAL("", errs); - JSONTEST_ASSERT_EQUAL(3u, root.size()); + JSONTEST_ASSERT_EQUAL(true, root.asBool()); } { - char const doc[] = "[null,,]"; + char const doc[] = " true //com\rment"; bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); - JSONTEST_ASSERT(ok); - JSONTEST_ASSERT_STRING_EQUAL("", errs); - JSONTEST_ASSERT_EQUAL(3u, root.size()); + JSONTEST_ASSERT(!ok); + JSONTEST_ASSERT_STRING_EQUAL("* Line 2, Column 1\n" + " Extra non-whitespace after JSON value.\n", + errs); + JSONTEST_ASSERT_EQUAL(true, root.asBool()); } { - char const doc[] = "[,null,]"; + char const doc[] = " true //com\nment"; bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); - JSONTEST_ASSERT(ok); - JSONTEST_ASSERT_STRING_EQUAL("", errs); - JSONTEST_ASSERT_EQUAL(3u, root.size()); + JSONTEST_ASSERT(!ok); + JSONTEST_ASSERT_STRING_EQUAL("* Line 2, Column 1\n" + " Extra non-whitespace after JSON value.\n", + errs); + JSONTEST_ASSERT_EQUAL(true, root.asBool()); } - { - char const doc[] = "[,,null]"; - bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); - JSONTEST_ASSERT(ok); - JSONTEST_ASSERT(errs.empty()); - JSONTEST_ASSERT_EQUAL(3u, root.size()); +} + +struct CharReaderAllowDropNullTest : JsonTest::TestCase { + using Value = Json::Value; + using ValueCheck = std::function; + + Value nullValue = Value{Json::nullValue}; + Value emptyArray = Value{Json::arrayValue}; + + ValueCheck checkEq(const Value& v) { + return [=](const Value& root) { JSONTEST_ASSERT_EQUAL(root, v); }; } - { - char const doc[] = "[[],,,]"; - bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); - JSONTEST_ASSERT(ok); - JSONTEST_ASSERT_STRING_EQUAL("", errs); - JSONTEST_ASSERT_EQUAL(4u, root.size()); - JSONTEST_ASSERT_EQUAL(Json::arrayValue, root[0u]); + + static ValueCheck objGetAnd(std::string idx, ValueCheck f) { + return [=](const Value& root) { f(root.get(idx, true)); }; } - { - char const doc[] = "[,[],,]"; - bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); - JSONTEST_ASSERT(ok); - JSONTEST_ASSERT_STRING_EQUAL("", errs); - JSONTEST_ASSERT_EQUAL(4u, root.size()); - JSONTEST_ASSERT_EQUAL(Json::arrayValue, root[1u]); + + static ValueCheck arrGetAnd(int idx, ValueCheck f) { + return [=](const Value& root) { f(root[idx]); }; } - { - char const doc[] = "[,,,[]]"; - bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); +}; + +JSONTEST_FIXTURE_LOCAL(CharReaderAllowDropNullTest, issue178) { + struct TestSpec { + int line; + std::string doc; + size_t rootSize; + ValueCheck onRoot; + }; + const TestSpec specs[] = { + {__LINE__, R"({"a":,"b":true})", 2, objGetAnd("a", checkEq(nullValue))}, + {__LINE__, R"({"a":,"b":true})", 2, objGetAnd("a", checkEq(nullValue))}, + {__LINE__, R"({"a":})", 1, objGetAnd("a", checkEq(nullValue))}, + {__LINE__, "[]", 0, checkEq(emptyArray)}, + {__LINE__, "[null]", 1, nullptr}, + {__LINE__, "[,]", 2, nullptr}, + {__LINE__, "[,,,]", 4, nullptr}, + {__LINE__, "[null,]", 2, nullptr}, + {__LINE__, "[,null]", 2, nullptr}, + {__LINE__, "[,,]", 3, nullptr}, + {__LINE__, "[null,,]", 3, nullptr}, + {__LINE__, "[,null,]", 3, nullptr}, + {__LINE__, "[,,null]", 3, nullptr}, + {__LINE__, "[[],,,]", 4, arrGetAnd(0, checkEq(emptyArray))}, + {__LINE__, "[,[],,]", 4, arrGetAnd(1, checkEq(emptyArray))}, + {__LINE__, "[,,,[]]", 4, arrGetAnd(3, checkEq(emptyArray))}, + }; + for (const auto& spec : specs) { + Json::CharReaderBuilder b; + b.settings_["allowDroppedNullPlaceholders"] = true; + std::unique_ptr reader(b.newCharReader()); + + Json::Value root; + Json::String errs; + bool ok = reader->parse(spec.doc.data(), spec.doc.data() + spec.doc.size(), + &root, &errs); JSONTEST_ASSERT(ok); - JSONTEST_ASSERT(errs.empty()); - JSONTEST_ASSERT_EQUAL(4u, root.size()); - JSONTEST_ASSERT_EQUAL(Json::arrayValue, root[3u]); + JSONTEST_ASSERT_STRING_EQUAL(errs, ""); + if (spec.onRoot) { + spec.onRoot(root); + } } - delete reader; +} + +struct CharReaderAllowNumericKeysTest : JsonTest::TestCase {}; + +JSONTEST_FIXTURE_LOCAL(CharReaderAllowNumericKeysTest, allowNumericKeys) { + Json::CharReaderBuilder b; + b.settings_["allowNumericKeys"] = true; + Json::Value root; + Json::String errs; + CharReaderPtr reader(b.newCharReader()); + char const doc[] = "{15:true,-16:true,12.01:true}"; + bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); + JSONTEST_ASSERT(ok); + JSONTEST_ASSERT_STRING_EQUAL("", errs); + JSONTEST_ASSERT_EQUAL(3u, root.size()); + JSONTEST_ASSERT_EQUAL(true, root.get("15", false)); + JSONTEST_ASSERT_EQUAL(true, root.get("-16", false)); + JSONTEST_ASSERT_EQUAL(true, root.get("12.01", false)); } struct CharReaderAllowSingleQuotesTest : JsonTest::TestCase {}; -JSONTEST_FIXTURE(CharReaderAllowSingleQuotesTest, issue182) { +JSONTEST_FIXTURE_LOCAL(CharReaderAllowSingleQuotesTest, issue182) { Json::CharReaderBuilder b; b.settings_["allowSingleQuotes"] = true; Json::Value root; Json::String errs; - Json::CharReader* reader(b.newCharReader()); + CharReaderPtr reader(b.newCharReader()); { char const doc[] = "{'a':true,\"b\":true}"; bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); @@ -2362,17 +3617,16 @@ JSONTEST_FIXTURE(CharReaderAllowSingleQuotesTest, issue182) { JSONTEST_ASSERT_STRING_EQUAL("x", root["a"].asString()); JSONTEST_ASSERT_STRING_EQUAL("y", root["b"].asString()); } - delete reader; } struct CharReaderAllowZeroesTest : JsonTest::TestCase {}; -JSONTEST_FIXTURE(CharReaderAllowZeroesTest, issue176) { +JSONTEST_FIXTURE_LOCAL(CharReaderAllowZeroesTest, issue176) { Json::CharReaderBuilder b; b.settings_["allowSingleQuotes"] = true; Json::Value root; Json::String errs; - Json::CharReader* reader(b.newCharReader()); + CharReaderPtr reader(b.newCharReader()); { char const doc[] = "{'a':true,\"b\":true}"; bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); @@ -2391,29 +3645,55 @@ JSONTEST_FIXTURE(CharReaderAllowZeroesTest, issue176) { JSONTEST_ASSERT_STRING_EQUAL("x", root["a"].asString()); JSONTEST_ASSERT_STRING_EQUAL("y", root["b"].asString()); } - delete reader; } struct CharReaderAllowSpecialFloatsTest : JsonTest::TestCase {}; -JSONTEST_FIXTURE(CharReaderAllowSpecialFloatsTest, issue209) { +JSONTEST_FIXTURE_LOCAL(CharReaderAllowSpecialFloatsTest, specialFloat) { + Json::CharReaderBuilder b; + CharReaderPtr reader(b.newCharReader()); + Json::Value root; + Json::String errs; + { + char const doc[] = "{\"a\": NaN}"; + bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); + JSONTEST_ASSERT(!ok); + JSONTEST_ASSERT_STRING_EQUAL( + "* Line 1, Column 7\n" + " Syntax error: value, object or array expected.\n", + errs); + } + { + char const doc[] = "{\"a\": Infinity}"; + bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); + JSONTEST_ASSERT(!ok); + JSONTEST_ASSERT_STRING_EQUAL( + "* Line 1, Column 7\n" + " Syntax error: value, object or array expected.\n", + errs); + } +} + +JSONTEST_FIXTURE_LOCAL(CharReaderAllowSpecialFloatsTest, issue209) { Json::CharReaderBuilder b; b.settings_["allowSpecialFloats"] = true; Json::Value root; Json::String errs; - Json::CharReader* reader(b.newCharReader()); + CharReaderPtr reader(b.newCharReader()); { - char const doc[] = "{\"a\":NaN,\"b\":Infinity,\"c\":-Infinity}"; + char const doc[] = R"({"a":NaN,"b":Infinity,"c":-Infinity,"d":+Infinity})"; bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); JSONTEST_ASSERT(ok); JSONTEST_ASSERT_STRING_EQUAL("", errs); - JSONTEST_ASSERT_EQUAL(3u, root.size()); + JSONTEST_ASSERT_EQUAL(4u, root.size()); double n = root["a"].asDouble(); JSONTEST_ASSERT(std::isnan(n)); JSONTEST_ASSERT_EQUAL(std::numeric_limits::infinity(), root.get("b", 0.0)); JSONTEST_ASSERT_EQUAL(-std::numeric_limits::infinity(), root.get("c", 0.0)); + JSONTEST_ASSERT_EQUAL(std::numeric_limits::infinity(), + root.get("d", 0.0)); } struct TestData { @@ -2437,21 +3717,22 @@ JSONTEST_FIXTURE(CharReaderAllowSpecialFloatsTest, issue209) { {__LINE__, false, "{\"a\":.Infinity}"}, // {__LINE__, false, "{\"a\":_Infinity}"}, // {__LINE__, false, "{\"a\":_nfinity}"}, // - {__LINE__, true, "{\"a\":-Infinity}"} // + {__LINE__, true, "{\"a\":-Infinity}"}, // + {__LINE__, true, "{\"a\":+Infinity}"} // }; for (const auto& td : test_data) { bool ok = reader->parse(&*td.in.begin(), &*td.in.begin() + td.in.size(), &root, &errs); - JSONTEST_ASSERT(td.ok == ok) << "line:" << td.line << "\n" - << " expected: {" - << "ok:" << td.ok << ", in:\'" << td.in << "\'" - << "}\n" - << " actual: {" - << "ok:" << ok << "}\n"; + // clang-format off + JSONTEST_ASSERT(td.ok == ok) << + "line:" << td.line << "\n " << + "expected: {ok:" << td.ok << ", in:\'" << td.in << "\'}\n " << + "actual: {ok:" << ok << "}\n"; + // clang-format on } { - char const doc[] = "{\"posInf\": Infinity, \"NegInf\": -Infinity}"; + char const doc[] = R"({"posInf": +Infinity, "NegInf": -Infinity})"; bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); JSONTEST_ASSERT(ok); JSONTEST_ASSERT_STRING_EQUAL("", errs); @@ -2461,12 +3742,57 @@ JSONTEST_FIXTURE(CharReaderAllowSpecialFloatsTest, issue209) { JSONTEST_ASSERT_EQUAL(-std::numeric_limits::infinity(), root["NegInf"].asDouble()); } - delete reader; +} + +struct EscapeSequenceTest : JsonTest::TestCase {}; + +JSONTEST_FIXTURE_LOCAL(EscapeSequenceTest, readerParseEscapeSequence) { + Json::Reader reader; + Json::Value root; + bool ok = reader.parse("[\"\\\"\",\"\\/\",\"\\\\\",\"\\b\"," + "\"\\f\",\"\\n\",\"\\r\",\"\\t\"," + "\"\\u0278\",\"\\ud852\\udf62\"]\n", + root); + JSONTEST_ASSERT(ok); + JSONTEST_ASSERT(reader.getFormattedErrorMessages().empty()); + JSONTEST_ASSERT(reader.getStructuredErrors().empty()); +} + +JSONTEST_FIXTURE_LOCAL(EscapeSequenceTest, charReaderParseEscapeSequence) { + Json::CharReaderBuilder b; + CharReaderPtr reader(b.newCharReader()); + Json::Value root; + Json::String errs; + char const doc[] = "[\"\\\"\",\"\\/\",\"\\\\\",\"\\b\"," + "\"\\f\",\"\\n\",\"\\r\",\"\\t\"," + "\"\\u0278\",\"\\ud852\\udf62\"]"; + bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); + JSONTEST_ASSERT(ok); + JSONTEST_ASSERT(errs.empty()); +} + +JSONTEST_FIXTURE_LOCAL(EscapeSequenceTest, writeEscapeSequence) { + Json::FastWriter writer; + const Json::String expected("[\"\\\"\",\"\\\\\",\"\\b\"," + "\"\\f\",\"\\n\",\"\\r\",\"\\t\"," + "\"\\u0278\",\"\\ud852\\udf62\"]\n"); + Json::Value root; + root[0] = "\""; + root[1] = "\\"; + root[2] = "\b"; + root[3] = "\f"; + root[4] = "\n"; + root[5] = "\r"; + root[6] = "\t"; + root[7] = "ɸ"; + root[8] = "𤭢"; + const Json::String result = writer.write(root); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); } struct BuilderTest : JsonTest::TestCase {}; -JSONTEST_FIXTURE(BuilderTest, settings) { +JSONTEST_FIXTURE_LOCAL(BuilderTest, settings) { { Json::Value errs; Json::CharReaderBuilder rb; @@ -2487,23 +3813,116 @@ JSONTEST_FIXTURE(BuilderTest, settings) { } } +struct BomTest : JsonTest::TestCase {}; + +JSONTEST_FIXTURE_LOCAL(BomTest, skipBom) { + const std::string with_bom = "\xEF\xBB\xBF{\"key\" : \"value\"}"; + Json::Value root; + JSONCPP_STRING errs; + std::istringstream iss(with_bom); + bool ok = parseFromStream(Json::CharReaderBuilder(), iss, &root, &errs); + // The default behavior is to skip the BOM, so we can parse it normally. + JSONTEST_ASSERT(ok); + JSONTEST_ASSERT(errs.empty()); + JSONTEST_ASSERT_STRING_EQUAL(root["key"].asString(), "value"); +} +JSONTEST_FIXTURE_LOCAL(BomTest, notSkipBom) { + const std::string with_bom = "\xEF\xBB\xBF{\"key\" : \"value\"}"; + Json::Value root; + JSONCPP_STRING errs; + std::istringstream iss(with_bom); + Json::CharReaderBuilder b; + b.settings_["skipBom"] = false; + bool ok = parseFromStream(b, iss, &root, &errs); + // Detect the BOM, and failed on it. + JSONTEST_ASSERT(!ok); + JSONTEST_ASSERT(!errs.empty()); +} + struct IteratorTest : JsonTest::TestCase {}; -JSONTEST_FIXTURE(IteratorTest, distance) { +JSONTEST_FIXTURE_LOCAL(IteratorTest, convert) { + Json::Value j; + const Json::Value& cj = j; + auto it = j.begin(); + Json::Value::const_iterator cit; + cit = it; + JSONTEST_ASSERT(cit == cj.begin()); +} + +JSONTEST_FIXTURE_LOCAL(IteratorTest, decrement) { + Json::Value json; + json["k1"] = "a"; + json["k2"] = "b"; + std::vector values; + for (auto it = json.end(); it != json.begin();) { + --it; + values.push_back(it->asString()); + } + JSONTEST_ASSERT((values == std::vector{"b", "a"})); +} + +JSONTEST_FIXTURE_LOCAL(IteratorTest, reverseIterator) { Json::Value json; json["k1"] = "a"; json["k2"] = "b"; - int dist = 0; - Json::String str; - for (Json::ValueIterator it = json.begin(); it != json.end(); ++it) { - dist = it - json.begin(); - str = it->asString().c_str(); + std::vector values; + using Iter = decltype(json.begin()); + auto re = std::reverse_iterator(json.begin()); + for (auto it = std::reverse_iterator(json.end()); it != re; ++it) { + values.push_back(it->asString()); + } + JSONTEST_ASSERT((values == std::vector{"b", "a"})); +} + +JSONTEST_FIXTURE_LOCAL(IteratorTest, distance) { + { + Json::Value json; + json["k1"] = "a"; + json["k2"] = "b"; + int i = 0; + auto it = json.begin(); + for (;; ++it, ++i) { + auto dist = it - json.begin(); + JSONTEST_ASSERT_EQUAL(i, dist); + if (it == json.end()) + break; + } + } + { + Json::Value empty; + JSONTEST_ASSERT_EQUAL(0, empty.end() - empty.end()); + JSONTEST_ASSERT_EQUAL(0, empty.end() - empty.begin()); + } +} + +JSONTEST_FIXTURE_LOCAL(IteratorTest, nullValues) { + { + Json::Value json; + auto end = json.end(); + auto endCopy = end; + JSONTEST_ASSERT(endCopy == end); + endCopy = end; + JSONTEST_ASSERT(endCopy == end); + } + { + // Same test, now with const Value. + const Json::Value json; + auto end = json.end(); + auto endCopy = end; + JSONTEST_ASSERT(endCopy == end); + endCopy = end; + JSONTEST_ASSERT(endCopy == end); } - JSONTEST_ASSERT_EQUAL(1, dist); - JSONTEST_ASSERT_STRING_EQUAL("b", str); } -JSONTEST_FIXTURE(IteratorTest, names) { +JSONTEST_FIXTURE_LOCAL(IteratorTest, staticStringKey) { + Json::Value json; + json[Json::StaticString("k1")] = "a"; + JSONTEST_ASSERT_EQUAL(Json::Value("k1"), json.begin().key()); +} + +JSONTEST_FIXTURE_LOCAL(IteratorTest, names) { Json::Value json; json["k1"] = "a"; json["k2"] = "b"; @@ -2511,17 +3930,19 @@ JSONTEST_FIXTURE(IteratorTest, names) { JSONTEST_ASSERT(it != json.end()); JSONTEST_ASSERT_EQUAL(Json::Value("k1"), it.key()); JSONTEST_ASSERT_STRING_EQUAL("k1", it.name()); + JSONTEST_ASSERT_STRING_EQUAL("k1", it.memberName()); JSONTEST_ASSERT_EQUAL(-1, it.index()); ++it; JSONTEST_ASSERT(it != json.end()); JSONTEST_ASSERT_EQUAL(Json::Value("k2"), it.key()); JSONTEST_ASSERT_STRING_EQUAL("k2", it.name()); + JSONTEST_ASSERT_STRING_EQUAL("k2", it.memberName()); JSONTEST_ASSERT_EQUAL(-1, it.index()); ++it; JSONTEST_ASSERT(it == json.end()); } -JSONTEST_FIXTURE(IteratorTest, indexes) { +JSONTEST_FIXTURE_LOCAL(IteratorTest, indexes) { Json::Value json; json[0] = "a"; json[1] = "b"; @@ -2539,11 +3960,10 @@ JSONTEST_FIXTURE(IteratorTest, indexes) { JSONTEST_ASSERT(it == json.end()); } -JSONTEST_FIXTURE(IteratorTest, const) { +JSONTEST_FIXTURE_LOCAL(IteratorTest, constness) { Json::Value const v; JSONTEST_ASSERT_THROWS( - Json::Value::iterator it(v.begin()) // Compile, but throw. - ); + Json::Value::iterator it(v.begin())); // Compile, but throw. Json::Value value; @@ -2560,13 +3980,13 @@ JSONTEST_FIXTURE(IteratorTest, const) { for (; iter != value.end(); ++iter) { out << *iter << ','; } - Json::String expected = "\" 9\",\"10\",\"11\","; + Json::String expected = R"(" 9","10","11",)"; JSONTEST_ASSERT_STRING_EQUAL(expected, out.str()); } struct RValueTest : JsonTest::TestCase {}; -JSONTEST_FIXTURE(RValueTest, moveConstruction) { +JSONTEST_FIXTURE_LOCAL(RValueTest, moveConstruction) { Json::Value json; json["key"] = "value"; Json::Value moved = std::move(json); @@ -2580,7 +4000,7 @@ struct FuzzTest : JsonTest::TestCase {}; // Build and run the fuzz test without any fuzzer, so that it's guaranteed not // go out of date, even if it's never run as an actual fuzz test. -JSONTEST_FIXTURE(FuzzTest, fuzzDoesntCrash) { +JSONTEST_FIXTURE_LOCAL(FuzzTest, fuzzDoesntCrash) { const std::string example = "{}"; JSONTEST_ASSERT_EQUAL( 0, @@ -2588,91 +4008,99 @@ JSONTEST_FIXTURE(FuzzTest, fuzzDoesntCrash) { example.size())); } +struct ParseWithStructuredErrorsTest : JsonTest::TestCase { + void testErrors( + const std::string& doc, bool success, + const std::vector& expectedErrors) { + Json::CharReaderBuilder b; + CharReaderPtr reader(b.newCharReader()); + Json::Value root; + JSONTEST_ASSERT_EQUAL( + reader->parse(doc.data(), doc.data() + doc.length(), &root, nullptr), + success); + auto actualErrors = reader->getStructuredErrors(); + JSONTEST_ASSERT_EQUAL(expectedErrors.size(), actualErrors.size()); + for (std::size_t i = 0; i < actualErrors.size(); i++) { + const auto& a = actualErrors[i]; + const auto& e = expectedErrors[i]; + JSONTEST_ASSERT_EQUAL(a.offset_start, e.offset_start); + JSONTEST_ASSERT_EQUAL(a.offset_limit, e.offset_limit); + JSONTEST_ASSERT_STRING_EQUAL(a.message, e.message); + } + } +}; + +JSONTEST_FIXTURE_LOCAL(ParseWithStructuredErrorsTest, success) { + testErrors("{}", true, {}); +} + +JSONTEST_FIXTURE_LOCAL(ParseWithStructuredErrorsTest, singleError) { + testErrors("{ 1 : 2 }", false, {{2, 3, "Missing '}' or object member name"}}); +} + int main(int argc, const char* argv[]) { JsonTest::Runner runner; - JSONTEST_REGISTER_FIXTURE(runner, ValueTest, checkNormalizeFloatingPointStr); - JSONTEST_REGISTER_FIXTURE(runner, ValueTest, memberCount); - JSONTEST_REGISTER_FIXTURE(runner, ValueTest, objects); - JSONTEST_REGISTER_FIXTURE(runner, ValueTest, arrays); - JSONTEST_REGISTER_FIXTURE(runner, ValueTest, arrayIssue252); - JSONTEST_REGISTER_FIXTURE(runner, ValueTest, null); - JSONTEST_REGISTER_FIXTURE(runner, ValueTest, strings); - JSONTEST_REGISTER_FIXTURE(runner, ValueTest, bools); - JSONTEST_REGISTER_FIXTURE(runner, ValueTest, integers); - JSONTEST_REGISTER_FIXTURE(runner, ValueTest, nonIntegers); - JSONTEST_REGISTER_FIXTURE(runner, ValueTest, compareNull); - JSONTEST_REGISTER_FIXTURE(runner, ValueTest, compareInt); - JSONTEST_REGISTER_FIXTURE(runner, ValueTest, compareUInt); - JSONTEST_REGISTER_FIXTURE(runner, ValueTest, compareDouble); - JSONTEST_REGISTER_FIXTURE(runner, ValueTest, compareString); - JSONTEST_REGISTER_FIXTURE(runner, ValueTest, compareBoolean); - JSONTEST_REGISTER_FIXTURE(runner, ValueTest, compareArray); - JSONTEST_REGISTER_FIXTURE(runner, ValueTest, compareObject); - JSONTEST_REGISTER_FIXTURE(runner, ValueTest, compareType); - JSONTEST_REGISTER_FIXTURE(runner, ValueTest, CopyObject); - JSONTEST_REGISTER_FIXTURE(runner, ValueTest, offsetAccessors); - JSONTEST_REGISTER_FIXTURE(runner, ValueTest, typeChecksThrowExceptions); - JSONTEST_REGISTER_FIXTURE(runner, ValueTest, StaticString); - JSONTEST_REGISTER_FIXTURE(runner, ValueTest, WideString); - JSONTEST_REGISTER_FIXTURE(runner, ValueTest, CommentBefore); - // JSONTEST_REGISTER_FIXTURE(runner, ValueTest, nulls); - JSONTEST_REGISTER_FIXTURE(runner, ValueTest, zeroes); - JSONTEST_REGISTER_FIXTURE(runner, ValueTest, zeroesInKeys); - JSONTEST_REGISTER_FIXTURE(runner, ValueTest, specialFloats); - JSONTEST_REGISTER_FIXTURE(runner, ValueTest, precision); - - JSONTEST_REGISTER_FIXTURE(runner, WriterTest, dropNullPlaceholders); - JSONTEST_REGISTER_FIXTURE(runner, StreamWriterTest, dropNullPlaceholders); - JSONTEST_REGISTER_FIXTURE(runner, StreamWriterTest, writeZeroes); - - JSONTEST_REGISTER_FIXTURE(runner, ReaderTest, parseWithNoErrors); - JSONTEST_REGISTER_FIXTURE(runner, ReaderTest, - parseWithNoErrorsTestingOffsets); - JSONTEST_REGISTER_FIXTURE(runner, ReaderTest, parseWithOneError); - JSONTEST_REGISTER_FIXTURE(runner, ReaderTest, parseChineseWithOneError); - JSONTEST_REGISTER_FIXTURE(runner, ReaderTest, parseWithDetailError); - - JSONTEST_REGISTER_FIXTURE(runner, CharReaderTest, parseWithNoErrors); - JSONTEST_REGISTER_FIXTURE(runner, CharReaderTest, - parseWithNoErrorsTestingOffsets); - JSONTEST_REGISTER_FIXTURE(runner, CharReaderTest, parseWithOneError); - JSONTEST_REGISTER_FIXTURE(runner, CharReaderTest, parseChineseWithOneError); - JSONTEST_REGISTER_FIXTURE(runner, CharReaderTest, parseWithDetailError); - JSONTEST_REGISTER_FIXTURE(runner, CharReaderTest, parseWithStackLimit); - - JSONTEST_REGISTER_FIXTURE(runner, CharReaderStrictModeTest, dupKeys); - - JSONTEST_REGISTER_FIXTURE(runner, CharReaderFailIfExtraTest, issue164); - JSONTEST_REGISTER_FIXTURE(runner, CharReaderFailIfExtraTest, issue107); - JSONTEST_REGISTER_FIXTURE(runner, CharReaderFailIfExtraTest, - commentAfterObject); - JSONTEST_REGISTER_FIXTURE(runner, CharReaderFailIfExtraTest, - commentAfterArray); - JSONTEST_REGISTER_FIXTURE(runner, CharReaderFailIfExtraTest, - commentAfterBool); - - JSONTEST_REGISTER_FIXTURE(runner, CharReaderAllowDropNullTest, issue178); - - JSONTEST_REGISTER_FIXTURE(runner, CharReaderAllowSingleQuotesTest, issue182); - - JSONTEST_REGISTER_FIXTURE(runner, CharReaderAllowZeroesTest, issue176); - - JSONTEST_REGISTER_FIXTURE(runner, CharReaderAllowSpecialFloatsTest, issue209); - - JSONTEST_REGISTER_FIXTURE(runner, BuilderTest, settings); - - JSONTEST_REGISTER_FIXTURE(runner, IteratorTest, distance); - JSONTEST_REGISTER_FIXTURE(runner, IteratorTest, names); - JSONTEST_REGISTER_FIXTURE(runner, IteratorTest, indexes); - JSONTEST_REGISTER_FIXTURE(runner, IteratorTest, const); - - JSONTEST_REGISTER_FIXTURE(runner, RValueTest, moveConstruction); - - JSONTEST_REGISTER_FIXTURE(runner, FuzzTest, fuzzDoesntCrash); + + for (auto& local : local_) { + runner.add(local); + } return runner.runCommandLine(argc, argv); } +struct MemberTemplateAs : JsonTest::TestCase { + template + JsonTest::TestResult& EqEval(T v, F f) const { + const Json::Value j = v; + return JSONTEST_ASSERT_EQUAL(j.as(), f(j)); + } +}; + +JSONTEST_FIXTURE_LOCAL(MemberTemplateAs, BehavesSameAsNamedAs) { + const Json::Value jstr = "hello world"; + JSONTEST_ASSERT_STRING_EQUAL(jstr.as(), jstr.asCString()); + JSONTEST_ASSERT_STRING_EQUAL(jstr.as(), jstr.asString()); + EqEval(Json::Int(64), [](const Json::Value& j) { return j.asInt(); }); + EqEval(Json::UInt(64), [](const Json::Value& j) { return j.asUInt(); }); +#if defined(JSON_HAS_INT64) + EqEval(Json::Int64(64), [](const Json::Value& j) { return j.asInt64(); }); + EqEval(Json::UInt64(64), [](const Json::Value& j) { return j.asUInt64(); }); +#endif // if defined(JSON_HAS_INT64) + EqEval(Json::LargestInt(64), + [](const Json::Value& j) { return j.asLargestInt(); }); + EqEval(Json::LargestUInt(64), + [](const Json::Value& j) { return j.asLargestUInt(); }); + + EqEval(69.69f, [](const Json::Value& j) { return j.asFloat(); }); + EqEval(69.69, [](const Json::Value& j) { return j.asDouble(); }); + EqEval(false, [](const Json::Value& j) { return j.asBool(); }); + EqEval(true, [](const Json::Value& j) { return j.asBool(); }); +} + +class MemberTemplateIs : public JsonTest::TestCase {}; + +JSONTEST_FIXTURE_LOCAL(MemberTemplateIs, BehavesSameAsNamedIs) { + const Json::Value values[] = {true, 142, 40.63, "hello world"}; + for (const Json::Value& j : values) { + JSONTEST_ASSERT_EQUAL(j.is(), j.isBool()); + JSONTEST_ASSERT_EQUAL(j.is(), j.isInt()); + JSONTEST_ASSERT_EQUAL(j.is(), j.isInt64()); + JSONTEST_ASSERT_EQUAL(j.is(), j.isUInt()); + JSONTEST_ASSERT_EQUAL(j.is(), j.isUInt64()); + JSONTEST_ASSERT_EQUAL(j.is(), j.isDouble()); + JSONTEST_ASSERT_EQUAL(j.is(), j.isString()); + } +} + +class VersionTest : public JsonTest::TestCase {}; + +JSONTEST_FIXTURE_LOCAL(VersionTest, VersionNumbersMatch) { + std::ostringstream vstr; + vstr << JSONCPP_VERSION_MAJOR << '.' << JSONCPP_VERSION_MINOR << '.' + << JSONCPP_VERSION_PATCH; + JSONTEST_ASSERT_EQUAL(vstr.str(), std::string(JSONCPP_VERSION_STRING)); +} + #if defined(__GNUC__) #pragma GCC diagnostic pop #endif diff --git a/test/data/fail_invalid_quote.json b/test/data/fail_invalid_quote.json new file mode 100644 index 000000000..dae27f53f --- /dev/null +++ b/test/data/fail_invalid_quote.json @@ -0,0 +1 @@ +{'//this is bad JSON.'} \ No newline at end of file diff --git a/test/data/fail_strict_comment_01.json b/test/data/fail_strict_comment_01.json new file mode 100644 index 000000000..b7e0a5e75 --- /dev/null +++ b/test/data/fail_strict_comment_01.json @@ -0,0 +1,4 @@ +{ + "a": "aaa", + "b": "bbb" // comments not allowed in strict mode +} diff --git a/test/data/fail_strict_comment_02.json b/test/data/fail_strict_comment_02.json new file mode 100644 index 000000000..699a7f731 --- /dev/null +++ b/test/data/fail_strict_comment_02.json @@ -0,0 +1,4 @@ +{ + "a": "aaa", // comments not allowed in strict mode + "b": "bbb" +} diff --git a/test/data/fail_strict_comment_03.json b/test/data/fail_strict_comment_03.json new file mode 100644 index 000000000..5f0fabf1f --- /dev/null +++ b/test/data/fail_strict_comment_03.json @@ -0,0 +1,3 @@ +{ + "array" : [1, 2, 3 /* comments not allowed in strict mode */] +} diff --git a/test/data/fail_test_array_02.json b/test/data/fail_test_array_02.json new file mode 100644 index 000000000..222a1b47c --- /dev/null +++ b/test/data/fail_test_array_02.json @@ -0,0 +1 @@ +[1,,] diff --git a/test/data/fail_test_object_01.json b/test/data/fail_test_object_01.json new file mode 100644 index 000000000..46fd39ab3 --- /dev/null +++ b/test/data/fail_test_object_01.json @@ -0,0 +1 @@ +{ "count" : 1234,, } diff --git a/test/data/fail_test_object_02.json b/test/data/fail_test_object_02.json new file mode 100644 index 000000000..afe62c5b5 --- /dev/null +++ b/test/data/fail_test_object_02.json @@ -0,0 +1 @@ +{"one": 1 /* } */ { "two" : 2 } diff --git a/test/data/test_array_01.expected b/test/data/legacy_test_array_01.expected similarity index 100% rename from test/data/test_array_01.expected rename to test/data/legacy_test_array_01.expected diff --git a/test/data/test_array_01.json b/test/data/legacy_test_array_01.json similarity index 100% rename from test/data/test_array_01.json rename to test/data/legacy_test_array_01.json diff --git a/test/data/test_array_02.expected b/test/data/legacy_test_array_02.expected similarity index 100% rename from test/data/test_array_02.expected rename to test/data/legacy_test_array_02.expected diff --git a/test/data/test_array_02.json b/test/data/legacy_test_array_02.json similarity index 100% rename from test/data/test_array_02.json rename to test/data/legacy_test_array_02.json diff --git a/test/data/test_array_03.expected b/test/data/legacy_test_array_03.expected similarity index 100% rename from test/data/test_array_03.expected rename to test/data/legacy_test_array_03.expected diff --git a/test/data/test_array_03.json b/test/data/legacy_test_array_03.json similarity index 100% rename from test/data/test_array_03.json rename to test/data/legacy_test_array_03.json diff --git a/test/data/test_array_04.expected b/test/data/legacy_test_array_04.expected similarity index 100% rename from test/data/test_array_04.expected rename to test/data/legacy_test_array_04.expected diff --git a/test/data/test_array_04.json b/test/data/legacy_test_array_04.json similarity index 100% rename from test/data/test_array_04.json rename to test/data/legacy_test_array_04.json diff --git a/test/data/test_array_05.expected b/test/data/legacy_test_array_05.expected similarity index 100% rename from test/data/test_array_05.expected rename to test/data/legacy_test_array_05.expected diff --git a/test/data/test_array_05.json b/test/data/legacy_test_array_05.json similarity index 100% rename from test/data/test_array_05.json rename to test/data/legacy_test_array_05.json diff --git a/test/data/test_array_06.expected b/test/data/legacy_test_array_06.expected similarity index 100% rename from test/data/test_array_06.expected rename to test/data/legacy_test_array_06.expected diff --git a/test/data/test_array_06.json b/test/data/legacy_test_array_06.json similarity index 73% rename from test/data/test_array_06.json rename to test/data/legacy_test_array_06.json index 7f6c516af..1fda03bb1 100644 --- a/test/data/test_array_06.json +++ b/test/data/legacy_test_array_06.json @@ -1,4 +1,4 @@ -[ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", +[ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "ccccccccccccccccccccccc", "dddddddddddddddddddddddddddddddddddddddddddddddddddd" ] \ No newline at end of file diff --git a/test/data/test_array_07.expected b/test/data/legacy_test_array_07.expected similarity index 100% rename from test/data/test_array_07.expected rename to test/data/legacy_test_array_07.expected diff --git a/test/data/test_array_07.json b/test/data/legacy_test_array_07.json similarity index 100% rename from test/data/test_array_07.json rename to test/data/legacy_test_array_07.json diff --git a/test/data/test_basic_01.expected b/test/data/legacy_test_basic_01.expected similarity index 100% rename from test/data/test_basic_01.expected rename to test/data/legacy_test_basic_01.expected diff --git a/test/data/test_basic_01.json b/test/data/legacy_test_basic_01.json similarity index 100% rename from test/data/test_basic_01.json rename to test/data/legacy_test_basic_01.json diff --git a/test/data/test_basic_02.expected b/test/data/legacy_test_basic_02.expected similarity index 100% rename from test/data/test_basic_02.expected rename to test/data/legacy_test_basic_02.expected diff --git a/test/data/test_basic_02.json b/test/data/legacy_test_basic_02.json similarity index 100% rename from test/data/test_basic_02.json rename to test/data/legacy_test_basic_02.json diff --git a/test/data/test_basic_03.expected b/test/data/legacy_test_basic_03.expected similarity index 100% rename from test/data/test_basic_03.expected rename to test/data/legacy_test_basic_03.expected diff --git a/test/data/test_basic_03.json b/test/data/legacy_test_basic_03.json similarity index 100% rename from test/data/test_basic_03.json rename to test/data/legacy_test_basic_03.json diff --git a/test/data/test_basic_04.expected b/test/data/legacy_test_basic_04.expected similarity index 100% rename from test/data/test_basic_04.expected rename to test/data/legacy_test_basic_04.expected diff --git a/test/data/test_basic_04.json b/test/data/legacy_test_basic_04.json similarity index 100% rename from test/data/test_basic_04.json rename to test/data/legacy_test_basic_04.json diff --git a/test/data/test_basic_05.expected b/test/data/legacy_test_basic_05.expected similarity index 100% rename from test/data/test_basic_05.expected rename to test/data/legacy_test_basic_05.expected diff --git a/test/data/test_basic_05.json b/test/data/legacy_test_basic_05.json similarity index 100% rename from test/data/test_basic_05.json rename to test/data/legacy_test_basic_05.json diff --git a/test/data/test_basic_06.expected b/test/data/legacy_test_basic_06.expected similarity index 100% rename from test/data/test_basic_06.expected rename to test/data/legacy_test_basic_06.expected diff --git a/test/data/test_basic_06.json b/test/data/legacy_test_basic_06.json similarity index 100% rename from test/data/test_basic_06.json rename to test/data/legacy_test_basic_06.json diff --git a/test/data/test_basic_07.expected b/test/data/legacy_test_basic_07.expected similarity index 100% rename from test/data/test_basic_07.expected rename to test/data/legacy_test_basic_07.expected diff --git a/test/data/test_basic_07.json b/test/data/legacy_test_basic_07.json similarity index 100% rename from test/data/test_basic_07.json rename to test/data/legacy_test_basic_07.json diff --git a/test/data/test_basic_08.expected b/test/data/legacy_test_basic_08.expected similarity index 100% rename from test/data/test_basic_08.expected rename to test/data/legacy_test_basic_08.expected diff --git a/test/data/test_basic_08.json b/test/data/legacy_test_basic_08.json similarity index 100% rename from test/data/test_basic_08.json rename to test/data/legacy_test_basic_08.json diff --git a/test/data/test_basic_09.expected b/test/data/legacy_test_basic_09.expected similarity index 100% rename from test/data/test_basic_09.expected rename to test/data/legacy_test_basic_09.expected diff --git a/test/data/test_basic_09.json b/test/data/legacy_test_basic_09.json similarity index 100% rename from test/data/test_basic_09.json rename to test/data/legacy_test_basic_09.json diff --git a/test/data/test_comment_00.expected b/test/data/legacy_test_comment_00.expected similarity index 100% rename from test/data/test_comment_00.expected rename to test/data/legacy_test_comment_00.expected diff --git a/test/data/test_comment_00.json b/test/data/legacy_test_comment_00.json similarity index 100% rename from test/data/test_comment_00.json rename to test/data/legacy_test_comment_00.json diff --git a/test/data/test_comment_01.expected b/test/data/legacy_test_comment_01.expected similarity index 100% rename from test/data/test_comment_01.expected rename to test/data/legacy_test_comment_01.expected diff --git a/test/data/test_comment_01.json b/test/data/legacy_test_comment_01.json similarity index 100% rename from test/data/test_comment_01.json rename to test/data/legacy_test_comment_01.json diff --git a/test/data/test_comment_02.expected b/test/data/legacy_test_comment_02.expected similarity index 100% rename from test/data/test_comment_02.expected rename to test/data/legacy_test_comment_02.expected diff --git a/test/data/test_comment_02.json b/test/data/legacy_test_comment_02.json similarity index 100% rename from test/data/test_comment_02.json rename to test/data/legacy_test_comment_02.json diff --git a/test/data/test_complex_01.expected b/test/data/legacy_test_complex_01.expected similarity index 100% rename from test/data/test_complex_01.expected rename to test/data/legacy_test_complex_01.expected diff --git a/test/data/legacy_test_complex_01.json b/test/data/legacy_test_complex_01.json new file mode 100644 index 000000000..2c4a869ab --- /dev/null +++ b/test/data/legacy_test_complex_01.json @@ -0,0 +1,17 @@ +{ + "count" : 1234, + "name" : { "aka" : "T.E.S.T.", "id" : 123987 }, + "attribute" : [ + "random", + "short", + "bold", + 12, + { "height" : 7, "width" : 64 } + ], + "test": { "1" : + { "2" : + { "3" : { "coord" : [ 1,2] } + } + } + } +} diff --git a/test/data/test_integer_01.expected b/test/data/legacy_test_integer_01.expected similarity index 100% rename from test/data/test_integer_01.expected rename to test/data/legacy_test_integer_01.expected diff --git a/test/data/test_integer_01.json b/test/data/legacy_test_integer_01.json similarity index 100% rename from test/data/test_integer_01.json rename to test/data/legacy_test_integer_01.json diff --git a/test/data/test_integer_02.expected b/test/data/legacy_test_integer_02.expected similarity index 100% rename from test/data/test_integer_02.expected rename to test/data/legacy_test_integer_02.expected diff --git a/test/data/test_integer_02.json b/test/data/legacy_test_integer_02.json similarity index 100% rename from test/data/test_integer_02.json rename to test/data/legacy_test_integer_02.json diff --git a/test/data/test_integer_03.expected b/test/data/legacy_test_integer_03.expected similarity index 100% rename from test/data/test_integer_03.expected rename to test/data/legacy_test_integer_03.expected diff --git a/test/data/test_integer_03.json b/test/data/legacy_test_integer_03.json similarity index 100% rename from test/data/test_integer_03.json rename to test/data/legacy_test_integer_03.json diff --git a/test/data/test_integer_04.expected b/test/data/legacy_test_integer_04.expected similarity index 100% rename from test/data/test_integer_04.expected rename to test/data/legacy_test_integer_04.expected diff --git a/test/data/test_integer_04.json b/test/data/legacy_test_integer_04.json similarity index 100% rename from test/data/test_integer_04.json rename to test/data/legacy_test_integer_04.json diff --git a/test/data/test_integer_05.expected b/test/data/legacy_test_integer_05.expected similarity index 100% rename from test/data/test_integer_05.expected rename to test/data/legacy_test_integer_05.expected diff --git a/test/data/test_integer_05.json b/test/data/legacy_test_integer_05.json similarity index 100% rename from test/data/test_integer_05.json rename to test/data/legacy_test_integer_05.json diff --git a/test/data/test_integer_06_64bits.expected b/test/data/legacy_test_integer_06_64bits.expected similarity index 100% rename from test/data/test_integer_06_64bits.expected rename to test/data/legacy_test_integer_06_64bits.expected diff --git a/test/data/test_integer_06_64bits.json b/test/data/legacy_test_integer_06_64bits.json similarity index 100% rename from test/data/test_integer_06_64bits.json rename to test/data/legacy_test_integer_06_64bits.json diff --git a/test/data/test_integer_07_64bits.expected b/test/data/legacy_test_integer_07_64bits.expected similarity index 100% rename from test/data/test_integer_07_64bits.expected rename to test/data/legacy_test_integer_07_64bits.expected diff --git a/test/data/test_integer_07_64bits.json b/test/data/legacy_test_integer_07_64bits.json similarity index 100% rename from test/data/test_integer_07_64bits.json rename to test/data/legacy_test_integer_07_64bits.json diff --git a/test/data/test_integer_08_64bits.expected b/test/data/legacy_test_integer_08_64bits.expected similarity index 100% rename from test/data/test_integer_08_64bits.expected rename to test/data/legacy_test_integer_08_64bits.expected diff --git a/test/data/test_integer_08_64bits.json b/test/data/legacy_test_integer_08_64bits.json similarity index 100% rename from test/data/test_integer_08_64bits.json rename to test/data/legacy_test_integer_08_64bits.json diff --git a/test/data/test_large_01.expected b/test/data/legacy_test_large_01.expected similarity index 100% rename from test/data/test_large_01.expected rename to test/data/legacy_test_large_01.expected diff --git a/test/data/test_large_01.json b/test/data/legacy_test_large_01.json similarity index 100% rename from test/data/test_large_01.json rename to test/data/legacy_test_large_01.json diff --git a/test/data/test_object_01.expected b/test/data/legacy_test_object_01.expected similarity index 100% rename from test/data/test_object_01.expected rename to test/data/legacy_test_object_01.expected diff --git a/test/data/test_object_01.json b/test/data/legacy_test_object_01.json similarity index 100% rename from test/data/test_object_01.json rename to test/data/legacy_test_object_01.json diff --git a/test/data/test_object_02.expected b/test/data/legacy_test_object_02.expected similarity index 100% rename from test/data/test_object_02.expected rename to test/data/legacy_test_object_02.expected diff --git a/test/data/test_object_02.json b/test/data/legacy_test_object_02.json similarity index 100% rename from test/data/test_object_02.json rename to test/data/legacy_test_object_02.json diff --git a/test/data/test_object_03.expected b/test/data/legacy_test_object_03.expected similarity index 100% rename from test/data/test_object_03.expected rename to test/data/legacy_test_object_03.expected diff --git a/test/data/test_object_03.json b/test/data/legacy_test_object_03.json similarity index 95% rename from test/data/test_object_03.json rename to test/data/legacy_test_object_03.json index 4fcd4d821..90dba2af8 100644 --- a/test/data/test_object_03.json +++ b/test/data/legacy_test_object_03.json @@ -1,4 +1,4 @@ -{ +{ "count" : 1234, "name" : "test", "attribute" : "random" diff --git a/test/data/test_object_04.expected b/test/data/legacy_test_object_04.expected similarity index 100% rename from test/data/test_object_04.expected rename to test/data/legacy_test_object_04.expected diff --git a/test/data/test_object_04.json b/test/data/legacy_test_object_04.json similarity index 81% rename from test/data/test_object_04.json rename to test/data/legacy_test_object_04.json index 450762d71..9e43ff89b 100644 --- a/test/data/test_object_04.json +++ b/test/data/legacy_test_object_04.json @@ -1,3 +1,3 @@ -{ +{ "" : 1234 } diff --git a/test/data/test_preserve_comment_01.expected b/test/data/legacy_test_preserve_comment_01.expected similarity index 88% rename from test/data/test_preserve_comment_01.expected rename to test/data/legacy_test_preserve_comment_01.expected index 2797aa7d6..d6c11b4c9 100644 --- a/test/data/test_preserve_comment_01.expected +++ b/test/data/legacy_test_preserve_comment_01.expected @@ -6,6 +6,6 @@ /* Comment before 'second' */ .second=2 -/* A comment at +/* A comment at the end of the file. */ diff --git a/test/data/test_preserve_comment_01.json b/test/data/legacy_test_preserve_comment_01.json similarity index 91% rename from test/data/test_preserve_comment_01.json rename to test/data/legacy_test_preserve_comment_01.json index fabd55dd9..21b5ea7fa 100644 --- a/test/data/test_preserve_comment_01.json +++ b/test/data/legacy_test_preserve_comment_01.json @@ -9,6 +9,6 @@ "second" : 2 } -/* A comment at +/* A comment at the end of the file. */ diff --git a/test/data/test_real_01.expected b/test/data/legacy_test_real_01.expected similarity index 100% rename from test/data/test_real_01.expected rename to test/data/legacy_test_real_01.expected diff --git a/test/data/test_real_01.json b/test/data/legacy_test_real_01.json similarity index 100% rename from test/data/test_real_01.json rename to test/data/legacy_test_real_01.json diff --git a/test/data/test_real_02.expected b/test/data/legacy_test_real_02.expected similarity index 100% rename from test/data/test_real_02.expected rename to test/data/legacy_test_real_02.expected diff --git a/test/data/test_real_02.json b/test/data/legacy_test_real_02.json similarity index 100% rename from test/data/test_real_02.json rename to test/data/legacy_test_real_02.json diff --git a/test/data/test_real_03.expected b/test/data/legacy_test_real_03.expected similarity index 100% rename from test/data/test_real_03.expected rename to test/data/legacy_test_real_03.expected diff --git a/test/data/test_real_03.json b/test/data/legacy_test_real_03.json similarity index 100% rename from test/data/test_real_03.json rename to test/data/legacy_test_real_03.json diff --git a/test/data/test_real_04.expected b/test/data/legacy_test_real_04.expected similarity index 100% rename from test/data/test_real_04.expected rename to test/data/legacy_test_real_04.expected diff --git a/test/data/test_real_04.json b/test/data/legacy_test_real_04.json similarity index 100% rename from test/data/test_real_04.json rename to test/data/legacy_test_real_04.json diff --git a/test/data/test_real_05.expected b/test/data/legacy_test_real_05.expected similarity index 100% rename from test/data/test_real_05.expected rename to test/data/legacy_test_real_05.expected diff --git a/test/data/test_real_05.json b/test/data/legacy_test_real_05.json similarity index 100% rename from test/data/test_real_05.json rename to test/data/legacy_test_real_05.json diff --git a/test/data/test_real_06.expected b/test/data/legacy_test_real_06.expected similarity index 100% rename from test/data/test_real_06.expected rename to test/data/legacy_test_real_06.expected diff --git a/test/data/test_real_06.json b/test/data/legacy_test_real_06.json similarity index 100% rename from test/data/test_real_06.json rename to test/data/legacy_test_real_06.json diff --git a/test/data/test_real_07.expected b/test/data/legacy_test_real_07.expected similarity index 100% rename from test/data/test_real_07.expected rename to test/data/legacy_test_real_07.expected diff --git a/test/data/test_real_07.json b/test/data/legacy_test_real_07.json similarity index 100% rename from test/data/test_real_07.json rename to test/data/legacy_test_real_07.json diff --git a/test/data/test_real_08.expected b/test/data/legacy_test_real_08.expected similarity index 100% rename from test/data/test_real_08.expected rename to test/data/legacy_test_real_08.expected diff --git a/test/data/test_real_08.json b/test/data/legacy_test_real_08.json similarity index 100% rename from test/data/test_real_08.json rename to test/data/legacy_test_real_08.json diff --git a/test/data/test_real_09.expected b/test/data/legacy_test_real_09.expected similarity index 100% rename from test/data/test_real_09.expected rename to test/data/legacy_test_real_09.expected diff --git a/test/data/test_real_09.json b/test/data/legacy_test_real_09.json similarity index 100% rename from test/data/test_real_09.json rename to test/data/legacy_test_real_09.json diff --git a/test/data/test_real_10.expected b/test/data/legacy_test_real_10.expected similarity index 100% rename from test/data/test_real_10.expected rename to test/data/legacy_test_real_10.expected diff --git a/test/data/test_real_10.json b/test/data/legacy_test_real_10.json similarity index 100% rename from test/data/test_real_10.json rename to test/data/legacy_test_real_10.json diff --git a/test/data/test_real_11.expected b/test/data/legacy_test_real_11.expected similarity index 100% rename from test/data/test_real_11.expected rename to test/data/legacy_test_real_11.expected diff --git a/test/data/test_real_11.json b/test/data/legacy_test_real_11.json similarity index 100% rename from test/data/test_real_11.json rename to test/data/legacy_test_real_11.json diff --git a/test/data/test_real_12.expected b/test/data/legacy_test_real_12.expected similarity index 100% rename from test/data/test_real_12.expected rename to test/data/legacy_test_real_12.expected diff --git a/test/data/test_real_12.json b/test/data/legacy_test_real_12.json similarity index 100% rename from test/data/test_real_12.json rename to test/data/legacy_test_real_12.json diff --git a/test/data/legacy_test_real_13.expected b/test/data/legacy_test_real_13.expected new file mode 100644 index 000000000..8d3f03faa --- /dev/null +++ b/test/data/legacy_test_real_13.expected @@ -0,0 +1,3 @@ +.=[] +.[0]=-inf +.[1]=inf diff --git a/test/data/legacy_test_real_13.json b/test/data/legacy_test_real_13.json new file mode 100644 index 000000000..287258a81 --- /dev/null +++ b/test/data/legacy_test_real_13.json @@ -0,0 +1 @@ +[-1e+9999, 1e+9999] diff --git a/test/data/test_string_01.expected b/test/data/legacy_test_string_01.expected similarity index 100% rename from test/data/test_string_01.expected rename to test/data/legacy_test_string_01.expected diff --git a/test/data/test_string_01.json b/test/data/legacy_test_string_01.json similarity index 100% rename from test/data/test_string_01.json rename to test/data/legacy_test_string_01.json diff --git a/test/data/test_string_02.expected b/test/data/legacy_test_string_02.expected similarity index 100% rename from test/data/test_string_02.expected rename to test/data/legacy_test_string_02.expected diff --git a/test/data/test_string_02.json b/test/data/legacy_test_string_02.json similarity index 100% rename from test/data/test_string_02.json rename to test/data/legacy_test_string_02.json diff --git a/test/data/test_string_03.expected b/test/data/legacy_test_string_03.expected similarity index 100% rename from test/data/test_string_03.expected rename to test/data/legacy_test_string_03.expected diff --git a/test/data/test_string_03.json b/test/data/legacy_test_string_03.json similarity index 100% rename from test/data/test_string_03.json rename to test/data/legacy_test_string_03.json diff --git a/test/data/test_string_04.expected b/test/data/legacy_test_string_04.expected similarity index 100% rename from test/data/test_string_04.expected rename to test/data/legacy_test_string_04.expected diff --git a/test/data/test_string_04.json b/test/data/legacy_test_string_04.json similarity index 100% rename from test/data/test_string_04.json rename to test/data/legacy_test_string_04.json diff --git a/test/data/test_string_05.expected b/test/data/legacy_test_string_05.expected similarity index 100% rename from test/data/test_string_05.expected rename to test/data/legacy_test_string_05.expected diff --git a/test/data/test_string_05.json b/test/data/legacy_test_string_05.json similarity index 100% rename from test/data/test_string_05.json rename to test/data/legacy_test_string_05.json diff --git a/test/data/test_string_unicode_01.expected b/test/data/legacy_test_string_unicode_01.expected similarity index 100% rename from test/data/test_string_unicode_01.expected rename to test/data/legacy_test_string_unicode_01.expected diff --git a/test/data/test_string_unicode_01.json b/test/data/legacy_test_string_unicode_01.json similarity index 100% rename from test/data/test_string_unicode_01.json rename to test/data/legacy_test_string_unicode_01.json diff --git a/test/data/test_string_unicode_02.expected b/test/data/legacy_test_string_unicode_02.expected similarity index 100% rename from test/data/test_string_unicode_02.expected rename to test/data/legacy_test_string_unicode_02.expected diff --git a/test/data/test_string_unicode_02.json b/test/data/legacy_test_string_unicode_02.json similarity index 100% rename from test/data/test_string_unicode_02.json rename to test/data/legacy_test_string_unicode_02.json diff --git a/test/data/test_string_unicode_03.expected b/test/data/legacy_test_string_unicode_03.expected similarity index 100% rename from test/data/test_string_unicode_03.expected rename to test/data/legacy_test_string_unicode_03.expected diff --git a/test/data/test_string_unicode_03.json b/test/data/legacy_test_string_unicode_03.json similarity index 100% rename from test/data/test_string_unicode_03.json rename to test/data/legacy_test_string_unicode_03.json diff --git a/test/data/test_string_unicode_04.expected b/test/data/legacy_test_string_unicode_04.expected similarity index 100% rename from test/data/test_string_unicode_04.expected rename to test/data/legacy_test_string_unicode_04.expected diff --git a/test/data/test_string_unicode_04.json b/test/data/legacy_test_string_unicode_04.json similarity index 100% rename from test/data/test_string_unicode_04.json rename to test/data/legacy_test_string_unicode_04.json diff --git a/test/data/test_string_unicode_05.expected b/test/data/legacy_test_string_unicode_05.expected similarity index 100% rename from test/data/test_string_unicode_05.expected rename to test/data/legacy_test_string_unicode_05.expected diff --git a/test/data/test_string_unicode_05.json b/test/data/legacy_test_string_unicode_05.json similarity index 100% rename from test/data/test_string_unicode_05.json rename to test/data/legacy_test_string_unicode_05.json diff --git a/test/data/test_array_08.expected b/test/data/test_array_08.expected new file mode 100644 index 000000000..ef1f2623d --- /dev/null +++ b/test/data/test_array_08.expected @@ -0,0 +1,2 @@ +.=[] +.[0]=1 diff --git a/test/data/test_array_08.json b/test/data/test_array_08.json new file mode 100644 index 000000000..e8b1a170f --- /dev/null +++ b/test/data/test_array_08.json @@ -0,0 +1 @@ +[1,] diff --git a/test/data/test_complex_01.json b/test/data/test_complex_01.json deleted file mode 100644 index cc0f30f5c..000000000 --- a/test/data/test_complex_01.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "count" : 1234, - "name" : { "aka" : "T.E.S.T.", "id" : 123987 }, - "attribute" : [ - "random", - "short", - "bold", - 12, - { "height" : 7, "width" : 64 } - ], - "test": { "1" : - { "2" : - { "3" : { "coord" : [ 1,2] } - } - } - } -} diff --git a/test/data/test_object_05.expected b/test/data/test_object_05.expected new file mode 100644 index 000000000..79391c2a6 --- /dev/null +++ b/test/data/test_object_05.expected @@ -0,0 +1,2 @@ +.={} +.count=1234 diff --git a/test/data/test_object_05.json b/test/data/test_object_05.json new file mode 100644 index 000000000..c4344b17f --- /dev/null +++ b/test/data/test_object_05.json @@ -0,0 +1 @@ +{ "count" : 1234, } diff --git a/test/pyjsontestrunner.py b/test/pyjsontestrunner.py index bd749b530..8acdbd2de 100644 --- a/test/pyjsontestrunner.py +++ b/test/pyjsontestrunner.py @@ -15,7 +15,7 @@ if len(sys.argv) != 2: print("Usage: %s input-json-file", sys.argv[0]) sys.exit(3) - + input_path = sys.argv[1] base_path = os.path.splitext(input_path)[0] actual_path = base_path + '.actual' @@ -23,7 +23,7 @@ rewrite_actual_path = base_path + '.actual-rewrite' def valueTreeToString(fout, value, path = '.'): - ty = type(value) + ty = type(value) if ty is types.DictType: fout.write('%s={}\n' % path) suffix = path[-1] != '.' and '.' or '' @@ -49,7 +49,7 @@ def valueTreeToString(fout, value, path = '.'): fout.write('%s=null\n' % path) else: assert False and "Unexpected value type" - + def parseAndSaveValueTree(input, actual_path): root = json.loads(input) fout = file(actual_path, 'wt') @@ -62,7 +62,7 @@ def rewriteValueTree(value, rewrite_path): #rewrite = rewrite[1:-1] # Somehow the string is quoted ! jsonpy bug ? file(rewrite_path, 'wt').write(rewrite + '\n') return rewrite - + input = file(input_path, 'rt').read() root = parseAndSaveValueTree(input, actual_path) rewrite = rewriteValueTree(json.write(root), rewrite_path) diff --git a/test/runjsontests.py b/test/runjsontests.py index dfdeca3ea..49cc7a960 100644 --- a/test/runjsontests.py +++ b/test/runjsontests.py @@ -62,6 +62,10 @@ def safeReadFile(path): except IOError as e: return '' % (path,e) +class FailError(Exception): + def __init__(self, msg): + super(Exception, self).__init__(msg) + def runAllTests(jsontest_executable_path, input_dir = None, use_valgrind=False, with_json_checker=False, writerClass='StyledWriter'): @@ -69,57 +73,41 @@ def runAllTests(jsontest_executable_path, input_dir = None, input_dir = os.path.join(os.getcwd(), 'data') tests = glob(os.path.join(input_dir, '*.json')) if with_json_checker: - all_test_jsonchecker = glob(os.path.join(input_dir, '../jsonchecker', '*.json')) - # These tests fail with strict json support, but pass with jsoncpp extra lieniency - """ - Failure details: - * Test ../jsonchecker/fail25.json - Parsing should have failed: - [" tab character in string "] - - * Test ../jsonchecker/fail13.json - Parsing should have failed: - {"Numbers cannot have leading zeroes": 013} - - * Test ../jsonchecker/fail18.json - Parsing should have failed: - [[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]] - - * Test ../jsonchecker/fail8.json - Parsing should have failed: - ["Extra close"]] - - * Test ../jsonchecker/fail7.json - Parsing should have failed: - ["Comma after the close"], - - * Test ../jsonchecker/fail10.json - Parsing should have failed: - {"Extra value after close": true} "misplaced quoted value" - - * Test ../jsonchecker/fail27.json - Parsing should have failed: - ["line - break"] - """ - known_differences_withjsonchecker = [ "fail25.json", "fail13.json", "fail18.json", "fail8.json", - "fail7.json", "fail10.json", "fail27.json" ] - test_jsonchecker = [ test for test in all_test_jsonchecker if os.path.basename(test) not in known_differences_withjsonchecker ] + all_tests = glob(os.path.join(input_dir, '../jsonchecker', '*.json')) + # These tests fail with strict json support, but pass with JsonCPP's + # extra leniency features. When adding a new exclusion to this list, + # remember to add the test's number and reasoning here: + known = ["fail{}.json".format(n) for n in [ + 4, 9, # fail because we allow trailing commas + 7, # fails because we allow commas after close + 8, # fails because we allow extra close + 10, # fails because we allow extra values after close + 13, # fails because we allow leading zeroes in numbers + 18, # fails because we allow deeply nested values + 25, # fails because we allow tab characters in strings + 27, # fails because we allow string line breaks + ]] + test_jsonchecker = [ test for test in all_tests + if os.path.basename(test) not in known] else: test_jsonchecker = [] + failed_tests = [] valgrind_path = use_valgrind and VALGRIND_CMD or '' for input_path in tests + test_jsonchecker: expect_failure = os.path.basename(input_path).startswith('fail') - is_json_checker_test = (input_path in test_jsonchecker) or expect_failure + is_json_checker_test = input_path in test_jsonchecker + is_parse_only = is_json_checker_test or expect_failure + is_strict_test = ('_strict_' in os.path.basename(input_path)) or is_json_checker_test print('TESTING:', input_path, end=' ') - options = is_json_checker_test and '--json-checker' or '' + options = is_parse_only and '--parse-only' or '' + options += is_strict_test and ' --strict' or '' options += ' --json-writer %s'%writerClass cmd = '%s%s %s "%s"' % ( valgrind_path, jsontest_executable_path, options, input_path) status, process_output = getStatusOutput(cmd) - if is_json_checker_test: + if is_parse_only: if expect_failure: if not status: print('FAILED') @@ -161,10 +149,9 @@ def runAllTests(jsontest_executable_path, input_dir = None, print() print('Test results: %d passed, %d failed.' % (len(tests)-len(failed_tests), len(failed_tests))) - return 1 + raise FailError(repr(failed_tests)) else: print('All %d tests passed.' % len(tests)) - return 0 def main(): from optparse import OptionParser @@ -187,24 +174,21 @@ def main(): input_path = os.path.normpath(os.path.abspath(args[1])) else: input_path = None - status = runAllTests(jsontest_executable_path, input_path, + runAllTests(jsontest_executable_path, input_path, use_valgrind=options.valgrind, with_json_checker=options.with_json_checker, writerClass='StyledWriter') - if status: - sys.exit(status) - status = runAllTests(jsontest_executable_path, input_path, + runAllTests(jsontest_executable_path, input_path, use_valgrind=options.valgrind, with_json_checker=options.with_json_checker, writerClass='StyledStreamWriter') - if status: - sys.exit(status) - status = runAllTests(jsontest_executable_path, input_path, + runAllTests(jsontest_executable_path, input_path, use_valgrind=options.valgrind, with_json_checker=options.with_json_checker, writerClass='BuiltStyledStreamWriter') - if status: - sys.exit(status) if __name__ == '__main__': - main() + try: + main() + except FailError: + sys.exit(1)