diff --git a/.clang-format b/.clang-format index dd51247d5..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: false -Standard: Cpp03 -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/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..35477097a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,26 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Meson version + - Ninja version + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..bbcbbe7d6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. 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 4b721f028..69868f413 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /build/ +/build-*/ *.pyc *.swp *.actual @@ -6,12 +7,10 @@ *.process-output *.rewrite /bin/ -/buildscons/ /libs/ /doc/doxyfile /dist/ -#/version -#/include/json/version.h +/.cache/ # MSVC project files: *.sln @@ -30,9 +29,9 @@ # CMake-generated files: CMakeFiles/ -*.cmake -pkg-config/jsoncpp.pc +/pkg-config/jsoncpp.pc jsoncpp_lib_static.dir/ +compile_commands.json # In case someone runs cmake in the root-dir: /CMakeCache.txt @@ -50,3 +49,13 @@ jsoncpp_lib_static.dir/ .project .cproject /.settings/ + +# 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 b5ac60a0e..000000000 --- a/.travis.yml +++ /dev/null @@ -1,47 +0,0 @@ -# Build matrix / environment variable are explained on: -# http://about.travis-ci.org/docs/user/build-configuration/ -# This file can be validated on: -# http://lint.travis-ci.org/ -# See also -# http://stackoverflow.com/questions/22111549/travis-ci-with-clang-3-4-and-c11/30925448#30925448 -# to allow C++11, though we are not yet building with -std=c++11 - -install: -# /usr/bin/gcc is 4.6 always, but gcc-X.Y is available. -- if [ "$CXX" = "g++" ]; then export CXX="g++-4.9" CC="gcc-4.9"; fi -# /usr/bin/clang has a conflict with gcc, so use clang-X.Y. -- if [ "$CXX" = "clang++" ]; then export CXX="clang++-3.5" CC="clang-3.5"; fi -- echo ${PATH} -- ls /usr/local -- ls /usr/local/bin -- export PATH=/usr/local/bin:/usr/bin:${PATH} -- echo ${CXX} -- ${CXX} --version -- which valgrind -addons: - apt: - sources: - - ubuntu-toolchain-r-test - - llvm-toolchain-precise-3.5 - - george-edison55-precise-backports # cmake 3.2.3 - packages: - - cmake - - cmake-data - - gcc-4.9 - - g++-4.9 - - clang-3.5 - - valgrind -os: - - linux -language: cpp -compiler: - - gcc - - clang -script: ./travis.sh -env: - matrix: - - SHARED_LIB=ON STATIC_LIB=ON CMAKE_PKG=ON BUILD_TYPE=release VERBOSE_MAKE=false - - SHARED_LIB=OFF STATIC_LIB=ON CMAKE_PKG=OFF BUILD_TYPE=debug VERBOSE_MAKE=true VERBOSE -notifications: - email: false -sudo: false diff --git a/AUTHORS b/AUTHORS index 5747e61c1..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 @@ -37,6 +38,7 @@ datadiode David Seifert David West dawesc +Devin Jeanpierre Dmitry Marakasov dominicpezzuto Don Milham @@ -57,6 +59,7 @@ Iñaki Baz Castillo Jacco Jean-Christophe Fillion-Robin Jonas Platte +Jordan Bayles Jörg Krause Keith Lea Kevin Grant @@ -95,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 7c38e27ed..5ab9c52a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,159 +1,214 @@ # vim: et ts=4 sts=4 sw=4 tw=0 -CMAKE_MINIMUM_REQUIRED(VERSION 3.1) -PROJECT(jsoncpp) -ENABLE_TESTING() - -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) -OPTION(JSONCPP_WITH_WARNING_AS_ERROR "Force compilation to fail if a warning occurs" OFF) -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" OFF) -OPTION(BUILD_SHARED_LIBS "Build jsoncpp_lib as a shared library." OFF) -OPTION(BUILD_STATIC_LIBS "Build jsoncpp_lib static library." ON) - -# Ensures that CMAKE_BUILD_TYPE is visible in cmake-gui on Unix -IF(NOT WIN32) - IF(NOT CMAKE_BUILD_TYPE) - SET(CMAKE_BUILD_TYPE Release CACHE STRING - "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel Coverage." - FORCE) - ENDIF() -ENDIF() - -# Enable runtime search path support for dynamic libraries on OSX -IF(APPLE) - SET(CMAKE_MACOSX_RPATH 1) -ENDIF() +# ==== Define cmake build policies that affect compilation and linkage default behaviors +# +# Set the JSONCPP_NEWEST_VALIDATED_POLICIES_VERSION string to the newest cmake version +# 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 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 +# continue to generate policy warnings "CMake Warning (dev)...Policy CMP0XXX is not set:" +# +set(JSONCPP_OLDEST_VALIDATED_POLICIES_VERSION "3.8.0") +set(JSONCPP_NEWEST_VALIDATED_POLICIES_VERSION "3.13.2") +cmake_minimum_required(VERSION ${JSONCPP_OLDEST_VALIDATED_POLICIES_VERSION}) +if("${CMAKE_VERSION}" VERSION_LESS "${JSONCPP_NEWEST_VALIDATED_POLICIES_VERSION}") + #Set and use the newest available cmake policies that are validated to work + set(JSONCPP_CMAKE_POLICY_VERSION "${CMAKE_VERSION}") +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 +# +foreach(pnew "") # Currently Empty + if(POLICY ${pnew}) + cmake_policy(SET ${pnew} NEW) + endif() +endforeach() +foreach(pold "") # Currently Empty + if(POLICY ${pold}) + cmake_policy(SET ${pold} OLD) + endif() +endforeach() + +# 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) + +# 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() + +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 + # 4. ./MODULE.bazel + # IMPORTANT: also update the PROJECT_SOVERSION!! + VERSION 1.9.7 # [.[.[.]]] + LANGUAGES CXX) + +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) +option(JSONCPP_WITH_WARNING_AS_ERROR "Force compilation to fail if a warning occurs" OFF) +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(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") - -# Set variable named ${VAR_NAME} to value ${VALUE} -FUNCTION(set_using_dynamic_name VAR_NAME VALUE) - SET( "${VAR_NAME}" "${VALUE}" PARENT_SCOPE) -ENDFUNCTION() - -# Extract major, minor, patch from version text -# Parse a version string "X.Y.Z" and outputs -# version parts in ${OUPUT_PREFIX}_MAJOR, _MINOR, _PATCH. -# If parse succeeds then ${OUPUT_PREFIX}_FOUND is TRUE. -MACRO(jsoncpp_parse_version VERSION_TEXT OUPUT_PREFIX) - SET(VERSION_REGEX "[0-9]+\\.[0-9]+\\.[0-9]+(-[a-zA-Z0-9_]+)?") - IF( ${VERSION_TEXT} MATCHES ${VERSION_REGEX} ) - STRING(REGEX MATCHALL "[0-9]+|-([A-Za-z0-9_]+)" VERSION_PARTS ${VERSION_TEXT}) - LIST(GET VERSION_PARTS 0 ${OUPUT_PREFIX}_MAJOR) - LIST(GET VERSION_PARTS 1 ${OUPUT_PREFIX}_MINOR) - LIST(GET VERSION_PARTS 2 ${OUPUT_PREFIX}_PATCH) - set_using_dynamic_name( "${OUPUT_PREFIX}_FOUND" TRUE ) - ELSE( ${VERSION_TEXT} MATCHES ${VERSION_REGEX} ) - set_using_dynamic_name( "${OUPUT_PREFIX}_FOUND" FALSE ) - ENDIF() -ENDMACRO() - -# Read out version from "version" file -#FILE(STRINGS "version" JSONCPP_VERSION) -#SET( JSONCPP_VERSION_MAJOR X ) -#SET( JSONCPP_VERSION_MINOR Y ) -#SET( JSONCPP_VERSION_PATCH Z ) -SET( JSONCPP_VERSION 1.8.0 ) -jsoncpp_parse_version( ${JSONCPP_VERSION} JSONCPP_VERSION ) -#IF(NOT JSONCPP_VERSION_FOUND) -# MESSAGE(FATAL_ERROR "Failed to parse version string properly. Expect X.Y.Z") -#ENDIF(NOT JSONCPP_VERSION_FOUND) -SET( JSONCPP_SOVERSION 11 ) -SET( JSONCPP_USE_SECURE_MEMORY "0" CACHE STRING "-D...=1 to use memory-wiping allocator for STL" ) - -MESSAGE(STATUS "JsonCpp Version: ${JSONCPP_VERSION_MAJOR}.${JSONCPP_VERSION_MINOR}.${JSONCPP_VERSION_PATCH}") -# File version.h is only regenerated on CMake configure step -CONFIGURE_FILE( "${PROJECT_SOURCE_DIR}/src/lib_json/version.h.in" - "${PROJECT_SOURCE_DIR}/include/json/version.h" - NEWLINE_STYLE UNIX ) -CONFIGURE_FILE( "${PROJECT_SOURCE_DIR}/version.in" - "${PROJECT_SOURCE_DIR}/version" - NEWLINE_STYLE UNIX ) - -MACRO(UseCompilationWarningAsError) - IF(MSVC) +include(GNUInstallDirs) + +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() + +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) + +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. - SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /WX ") - ELSEIF(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror") - IF(JSONCPP_WITH_STRICT_ISO) - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic-errors") - ENDIF() - ENDIF() -ENDMACRO() + add_compile_options($<$:/WX>) + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + add_compile_options(-Werror) + if(JSONCPP_WITH_STRICT_ISO) + add_compile_options(-pedantic-errors) + endif() + endif() +endmacro() # Include our configuration header -INCLUDE_DIRECTORIES( ${jsoncpp_SOURCE_DIR}/include ) +include_directories(${jsoncpp_SOURCE_DIR}/include) -IF(MSVC) +if(MSVC) # Only enabled in debug because some old versions of VS STL generate # unreachable code warning when compiled in release configuration. - SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /W4 ") -ENDIF() + add_compile_options($<$:/W4>) + if (JSONCPP_STATIC_WINDOWS_RUNTIME) + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + endif() +endif() -# Require C++11 support, prefer ISO C++ over GNU variants, -# as relying solely on ISO C++ is more portable. -SET(CMAKE_CXX_STANDARD 11) -SET(CMAKE_CXX_STANDARD_REQUIRED ON) -SET(CMAKE_CXX_EXTENSIONS OFF) - -IF(CMAKE_CXX_COMPILER_ID MATCHES "Clang") +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") # using regular Clang or AppleClang - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wconversion -Wshadow -Werror=conversion -Werror=sign-compare") -ELSEIF(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + 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 - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wconversion -Wshadow -Wextra") + add_compile_options(-Wall -Wconversion -Wshadow -Wextra) # not yet ready for -Wsign-conversion - IF(JSONCPP_WITH_STRICT_ISO) - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic") - ENDIF() - IF(JSONCPP_WITH_WARNING_AS_ERROR) - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror=conversion") - ENDIF() -ELSEIF(CMAKE_CXX_COMPILER_ID STREQUAL "Intel") + if(JSONCPP_WITH_STRICT_ISO) + 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 - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wconversion -Wshadow -Wextra -Werror=conversion") + add_compile_options(-Wall -Wconversion -Wshadow -Wextra) + + if(JSONCPP_WITH_WARNING_AS_ERROR) + add_compile_options(-Werror=conversion) + elseif(JSONCPP_WITH_STRICT_ISO) + add_compile_options(-Wpedantic) + endif() +endif() - IF(JSONCPP_WITH_STRICT_ISO AND NOT JSONCPP_WITH_WARNING_AS_ERROR) - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic") - ENDIF() -ENDIF() +if(JSONCPP_WITH_WARNING_AS_ERROR) + use_compilation_warning_as_error() +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_PKGCONFIG_SUPPORT) + include(JoinPaths) -IF(JSONCPP_WITH_WARNING_AS_ERROR) - UseCompilationWarningAsError() -ENDIF() + join_paths(libdir_for_pc_file "\${exec_prefix}" "${CMAKE_INSTALL_LIBDIR}") + join_paths(includedir_for_pc_file "\${prefix}" "${CMAKE_INSTALL_INCLUDEDIR}") -IF(JSONCPP_WITH_PKGCONFIG_SUPPORT) - CONFIGURE_FILE( + configure_file( "pkg-config/jsoncpp.pc.in" "pkg-config/jsoncpp.pc" @ONLY) - INSTALL(FILES "${CMAKE_CURRENT_BINARY_DIR}/pkg-config/jsoncpp.pc" + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/pkg-config/jsoncpp.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") -ENDIF() - -IF(JSONCPP_WITH_CMAKE_PACKAGE) - INSTALL(EXPORT jsoncpp - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/jsoncpp - FILE jsoncppConfig.cmake) -ENDIF() +endif() + +if(JSONCPP_WITH_CMAKE_PACKAGE) + 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) +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 new file mode 100644 index 000000000..5f5c032a8 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,152 @@ +# Contributing to JsonCpp + +## Building + +Both CMake and Meson tools are capable of generating a variety of build environments for you preferred development environment. +Using cmake or meson you can generate an XCode, Visual Studio, Unix Makefile, Ninja, or other environment that fits your needs. + +An example of a common Meson/Ninja environment is described next. + +## Building and testing with Meson/Ninja +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 +things like version strings might break. + +First, install both meson (which requires Python3) and ninja. +If you wish to install to a directory other than /usr/local, set an environment variable called DESTDIR with the desired path: + DESTDIR=/path/to/install/dir + +Then, +```sh + cd jsoncpp/ + BUILD_TYPE=debug + #BUILD_TYPE=release + LIB_TYPE=shared + #LIB_TYPE=static + meson --buildtype ${BUILD_TYPE} --default-library ${LIB_TYPE} . build-${LIB_TYPE} + ninja -v -C build-${LIB_TYPE} + + 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 + +## Running the tests manually + +You need to run tests manually only if you are troubleshooting an issue. + +In the instructions below, replace `path/to/jsontest` with the path of the +`jsontest` executable that was compiled on your platform. + + cd test + # This will run the Reader/Writer tests + python runjsontests.py path/to/jsontest + + # This will run the Reader/Writer tests, using JSONChecker test suite + # (http://www.json.org/JSON_checker/). + # Notes: not all tests pass: JsonCpp is too lenient (for example, + # it allows an integer to start with '0'). The goal is to improve + # strict mode parsing to get all tests to pass. + python runjsontests.py --with-json-checker path/to/jsontest + + # This will run the unit tests (mostly Value) + python rununittests.py path/to/test_lib_json + + # You can run the tests using valgrind: + python rununittests.py --valgrind path/to/test_lib_json + +## Building the documentation + +Run the Python script `doxybuild.py` from the top directory: + + python doxybuild.py --doxygen=$(which doxygen) --open --with-dot + +See `doxybuild.py --help` for options. + +## Adding a reader/writer test + +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 flattened representation of the + input document. + +The `TESTNAME.expected` file format is as follows: + +* Each line represents a JSON element of the element tree represented by the + input document. +* Each line has two parts: the path to access the element separated from the + element value by `=`. Array and object values are always empty (i.e. + represented by either `[]` or `{}`). +* Element path `.` represents the root element, and is used to separate object + members. `[N]` is used to specify the value of an array element at index `N`. + +See the examples `test_complex_01.json` and `test_complex_01.expected` to better understand element paths. + +## Understanding reader/writer test output + +When a test is run, output files are generated beside the input test files. Below is a short description of the content of each file: + +* `test_complex_01.json`: input JSON document. +* `test_complex_01.expected`: flattened JSON element tree used to check if + parsing was corrected. +* `test_complex_01.actual`: flattened JSON element tree produced by `jsontest` + from reading `test_complex_01.json`. +* `test_complex_01.rewrite`: JSON document written by `jsontest` using the + `Json::Value` parsed from `test_complex_01.json` and serialized using + `Json::StyledWritter`. +* `test_complex_01.actual-rewrite`: flattened JSON element tree produced by + `jsontest` from reading `test_complex_01.rewrite`. +* `test_complex_01.process-output`: `jsontest` output, typically useful for + understanding parsing errors. + +## Versioning rules + +Consumers of this library require a strict approach to incrementing versioning of the JsonCpp library. Currently, we follow the below set of rules: + +* Any new public symbols require a minor version bump. +* Any alteration or removal of public symbols requires a major version bump, including changing the size of a class. This is necessary for +consumers to do dependency injection properly. + +## Preparing code for submission + +Generally, JsonCpp's style guide has been pretty relaxed, with the following common themes: + +* Variables and function names use lower camel case (E.g. parseValue or collectComments). +* Class use camel case (e.g. OurReader) +* Member variables have a trailing underscore +* Prefer `nullptr` over `NULL`. +* Passing by non-const reference is allowed. +* Single statement if blocks may omit brackets. +* Generally prefer less space over more space. + +For an example: + +```c++ +bool Reader::decodeNumber(Token& token) { + Value decoded; + if (!decodeNumber(token, decoded)) + return false; + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} +``` + +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/CTestConfig.cmake b/CTestConfig.cmake new file mode 100644 index 000000000..b8fc6d55b --- /dev/null +++ b/CTestConfig.cmake @@ -0,0 +1,15 @@ +## This file should be placed in the root directory of your project. +## Then modify the CMakeLists.txt file in the root directory of your +## project to incorporate the testing dashboard. +## +## # The following are required to submit to the CDash dashboard: +## ENABLE_TESTING() +## INCLUDE(CTest) + +set(CTEST_PROJECT_NAME "jsoncpp") +set(CTEST_NIGHTLY_START_TIME "01:23:45 UTC") + +set(CTEST_DROP_METHOD "https") +set(CTEST_DROP_SITE "my.cdash.org") +set(CTEST_DROP_LOCATION "/submit.php?project=jsoncpp") +set(CTEST_DROP_SITE_CDASH TRUE) diff --git a/LICENSE b/LICENSE index 55a3b2db9..c41a1d1c7 100644 --- a/LICENSE +++ b/LICENSE @@ -1,29 +1,29 @@ -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... -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 The JsonCpp Authors, and is -released under the terms of the MIT License (see below). +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: ======================================================================== -Copyright (c) 2007-2010 The JsonCpp Authors +Copyright (c) 2007-2010 Baptiste Lepilleur and The JsonCpp Authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 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/NEWS.txt b/NEWS.txt deleted file mode 100644 index 5733fcd5e..000000000 --- a/NEWS.txt +++ /dev/null @@ -1,175 +0,0 @@ -New in SVN ----------- - - * Updated the type system's behavior, in order to better support backwards - compatibility with code that was written before 64-bit integer support was - introduced. Here's how it works now: - - * isInt, isInt64, isUInt, and isUInt64 return true if and only if the - value can be exactly represented as that type. In particular, a value - constructed with a double like 17.0 will now return true for all of - these methods. - - * isDouble and isFloat now return true for all numeric values, since all - numeric values can be converted to a double or float without - truncation. Note however that the conversion may not be exact -- for - example, doubles cannot exactly represent all integers above 2^53 + 1. - - * isBool, isNull, isString, isArray, and isObject now return true if and - only if the value is of that type. - - * isConvertibleTo(fooValue) indicates that it is safe to call asFoo. - (For each type foo, isFoo always implies isConvertibleTo(fooValue).) - asFoo returns an approximate or exact representation as appropriate. - For example, a double value may be truncated when asInt is called. - - * For backwards compatibility with old code, isConvertibleTo(intValue) - may return false even if type() == intValue. This is because the value - may have been constructed with a 64-bit integer larger than maxInt, - and calling asInt() would cause an exception. If you're writing new - code, use isInt64 to find out whether the value is exactly - representable using an Int64, or asDouble() combined with minInt64 and - maxInt64 to figure out whether it is approximately representable. - -* Value - - Patch #10: BOOST_FOREACH compatibility. Made Json::iterator more - standard compliant, added missing iterator_category and value_type - typedefs (contribued by Robert A. Iannucci). - -* Compilation - - - New CMake based build system. Based in part on contribution from - Igor Okulist and Damien Buhl (Patch #14). - - - New header json/version.h now contains version number macros - (JSONCPP_VERSION_MAJOR, JSONCPP_VERSION_MINOR, JSONCPP_VERSION_PATCH - and JSONCPP_VERSION_HEXA). - - - Patch #11: added missing JSON_API on some classes causing link issues - when building as a dynamic library on Windows - (contributed by Francis Bolduc). - - - Visual Studio DLL: suppressed warning "C4251: : - needs to have dll-interface to be used by..." via pragma push/pop - in json-cpp headers. - - - Added Travis CI intregration: https://travis-ci.org/blep/jsoncpp-mirror - -* Bug fixes - - Patch #15: Copy constructor does not initialize allocated_ for stringValue - (contributed by rmongia). - - - Patch #16: Missing field copy in Json::Value::iterator causing infinite - loop when using experimental internal map (#define JSON_VALUE_USE_INTERNAL_MAP) - (contributed by Ming-Lin Kao). - - - New in JsonCpp 0.6.0: - --------------------- - -* Compilation - - - LD_LIBRARY_PATH and LIBRARY_PATH environment variables are now - propagated to the build environment as this is required for some - compiler installation. - - - Added support for Microsoft Visual Studio 2008 (bug #2930462): - The platform "msvc90" has been added. - - Notes: you need to setup the environment by running vcvars32.bat - (e.g. MSVC 2008 command prompt in start menu) before running scons. - - - Added support for amalgamated source and header generation (a la sqlite). - Refer to README.md section "Generating amalgamated source and header" - for detail. - -* Value - - - Removed experimental ValueAllocator, it caused static - initialization/destruction order issues (bug #2934500). - The DefaultValueAllocator has been inlined in code. - - - Added support for 64 bits integer: - - Types Json::Int64 and Json::UInt64 have been added. They are aliased - to 64 bits integers on system that support them (based on __int64 on - Microsoft Visual Studio platform, and long long on other platforms). - - Types Json::LargestInt and Json::LargestUInt have been added. They are - aliased to the largest integer type supported: - either Json::Int/Json::UInt or Json::Int64/Json::UInt64 respectively. - - Json::Value::asInt() and Json::Value::asUInt() still returns plain - "int" based types, but asserts if an attempt is made to retrieve - a 64 bits value that can not represented as the return type. - - Json::Value::asInt64() and Json::Value::asUInt64() have been added - to obtain the 64 bits integer value. - - Json::Value::asLargestInt() and Json::Value::asLargestUInt() returns - the integer as a LargestInt/LargestUInt respectively. Those functions - functions are typically used when implementing writer. - - The reader attempts to read number as 64 bits integer, and fall back - to reading a double if the number is not in the range of 64 bits - integer. - - Warning: Json::Value::asInt() and Json::Value::asUInt() now returns - long long. This changes break code that was passing the return value - to *printf() function. - - Support for 64 bits integer can be disabled by defining the macro - JSON_NO_INT64 (uncomment it in json/config.h for example), though - it should have no impact on existing usage. - - - The type Json::ArrayIndex is used for indexes of a JSON value array. It - is an unsigned int (typically 32 bits). - - - Array index can be passed as int to operator[], allowing use of literal: - Json::Value array; - array.append( 1234 ); - int value = array[0].asInt(); // did not compile previously - - - Added float Json::Value::asFloat() to obtain a floating point value as a - float (avoid lost of precision warning caused by used of asDouble() - to initialize a float). - -* Reader - - - Renamed Reader::getFormatedErrorMessages() to getFormattedErrorMessages. - Bug #3023708 (Formatted has 2 't'). The old member function is deprecated - but still present for backward compatibility. - -* Tests - - - Added test to ensure that the escape sequence "\/" is corrected handled - by the parser. - -* Bug fixes - - - Bug #3139677: JSON [1 2 3] was incorrectly parsed as [1, 3]. Error is now - correctly detected. - - - Bug #3139678: stack buffer overflow when parsing a double with a - length of 32 characters. - - - Fixed Value::operator <= implementation (had the semantic of operator >=). - Found when adding unit tests for comparison operators. - - - Value::compare() is now const and has an actual implementation with - unit tests. - - - Bug #2407932: strpbrk() can fail for NULL pointer. - - - Bug #3306345: Fixed minor typo in Path::resolve(). - - - Bug #3314841/#3306896: errors in amalgamate.py - - - Fixed some Coverity warnings and line-endings. - -* License - - - See file LICENSE for details. Basically JsonCpp is now licensed under - MIT license, or public domain if desired and recognized in your jurisdiction. - Thanks to Stephan G. Beal [http://wanderinghorse.net/home/stephan/]) who - helped figuring out the solution to the public domain issue. diff --git a/README.md b/README.md index fbc8ef692..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)](http://www.conan.io/source/jsoncpp/1.8.0/theirix/ci) +[![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,237 +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 recommended approach to integrating JsonCpp in your project is to include -the [amalgamated source](#generating-amalgamated-source-and-header) (a single -`.cpp` file and two `.h` files) in your project, and compile and build as you -would any other source file. This ensures consistency of compilation flags and -ABI compatibility, issues which arise when building shared or static -libraries. See the next section for instructions. - -The `include/` should be added to your compiler include path. JsonCpp headers -should be included as follow: - - #include - -If JsonCpp was built as a dynamic library on Windows, then your project needs to define the macro `JSON_DLL`. - -### Generating amalgamated source and header - -JsonCpp is provided with a script to generate a single header and a single -source file to ease inclusion into an existing project. The amalgamated source -can be generated at any time by running the following command from the -top-directory (this requires Python 2.6): - - python amalgamate.py - -It is possible to specify header name. See the `-h` option for detail. - -By default, the following files are generated: - -* `dist/jsoncpp.cpp`: source file that needs to be added to your project. -* `dist/json/json.h`: corresponding header file for use in your project. It is - equivalent to including `json/json.h` in non-amalgamated source. This header - only depends on standard headers. -* `dist/json/json-forwards.h`: header that provides forward declaration of all - JsonCpp types. - -The amalgamated sources are generated by concatenating JsonCpp source in the -correct order and defining the macro `JSON_IS_AMALGAMATION` to prevent inclusion of other headers. - - -## Contributing to JsonCpp - -### Building and testing with Conan - -[Conan](https://www.conan.io/#/) is an open source package manager intended for C/C++ projects. -It is cross platform and build system agnostic. - -Conan requires Python for running, and can be installed using pip: - - pip install conan - - Detailed instructions can be found on [conan docs](http://docs.conan.io/en/latest/). - -For build jsoncpp with conan, you need to create a [conanfile.txt](http://docs.conan.io/en/latest/reference/conanfile_txt.html) or a [conanfile.py](http://docs.conan.io/en/latest/reference/conanfile.html). The first is simpler, but the second is more flexible. - -This is a sample conanfile.txt: - -``` -[requires] -jsoncpp/1.8.0@theirix/ci - -[generators] -cmake -``` - -**Note**: cmake is not required, you can use other [integrations](http://docs.conan.io/en/latest/integrations.html). Or you can set the appropriate environment variables, using [virtualenv generators](http://docs.conan.io/en/latest/mastering/virtualenv.html). - -Then run the following command from the conanfile directory: - - conan install --build missing - -This will try to download the appropriate package for your settings (OS, compiler, architecture) from the [recipe packages](https://www.conan.io/source/jsoncpp/1.8.0/theirix/ci). If it is not found, the package will be built. - -**Note**: you do not need to install cmake to build jsoncpp using conan, because the recipe will download it automatically. - -If you need, you can customize the jsoncpp recipe. Just clone/fork [it from github](https://github.com/theirix/conan-jsoncpp/). - -See [integrations instructions](http://docs.conan.io/en/latest/integrations.html) for how to use your build system with conan. - -### Building and testing with CMake - -[CMake][] is a C++ Makefiles/Solution generator. It is usually available on most Linux system as package. On Ubuntu: - - sudo apt-get install cmake - -[CMake]: http://www.cmake.org - -Note that Python is also required to run the JSON reader/writer tests. If -missing, the build will skip running those tests. - -When running CMake, a few parameters are required: - -* A build directory where the makefiles/solution are generated. It is also used - to store objects, libraries and executables files. -* The generator to use: makefiles or Visual Studio solution? What version or - Visual Studio, 32 or 64 bits solution? - -Steps for generating solution/makefiles using `cmake-gui`: - -* Make "source code" point to the source directory. -* Make "where to build the binary" point to the directory to use for the build. -* Click on the "Grouped" check box. -* Review JsonCpp build options (tick `BUILD_SHARED_LIBS` to build as a - dynamic library). -* Click the configure button at the bottom, then the generate button. -* The generated solution/makefiles can be found in the binary directory. - -Alternatively, from the command-line on Unix in the source directory: - - mkdir -p build/debug - cd build/debug - cmake -DCMAKE_BUILD_TYPE=debug -DBUILD_STATIC_LIBS=ON -DBUILD_SHARED_LIBS=OFF -DARCHIVE_INSTALL_DIR=. -G "Unix Makefiles" ../.. - make - -For a good pkg-config file, add: - - -DCMAKE_INSTALL_INCLUDEDIR=include/jsoncpp - -Running `cmake -h` will display the list of available generators (passed using -the `-G` option). - -By default CMake hides compilation commands. This can be modified by specifying -`-DCMAKE_VERBOSE_MAKEFILE=true` when generating makefiles. - -### Building and testing with SCons - -**Note:** The SCons-based build system is deprecated. Please use CMake (see the -section above). - -JsonCpp can use [Scons][] as a build system. Note that SCons requires Python to -be installed. - -[SCons]: http://www.scons.org/ - -Invoke SCons as follows: - - scons platform=$PLATFORM [TARGET] - -where `$PLATFORM` may be one of: - -* `suncc`: Sun C++ (Solaris) -* `vacpp`: Visual Age C++ (AIX) -* `mingw` -* `msvc6`: Microsoft Visual Studio 6 service pack 5-6 -* `msvc70`: Microsoft Visual Studio 2002 -* `msvc71`: Microsoft Visual Studio 2003 -* `msvc80`: Microsoft Visual Studio 2005 -* `msvc90`: Microsoft Visual Studio 2008 -* `linux-gcc`: Gnu C++ (linux, also reported to work for Mac OS X) - -If you are building with Microsoft Visual Studio 2008, you need to set up the -environment by running `vcvars32.bat` (e.g. MSVC 2008 command prompt) before -running SCons. - -### Running the tests manually - -You need to run tests manually only if you are troubleshooting an issue. - -In the instructions below, replace `path/to/jsontest` with the path of the -`jsontest` executable that was compiled on your platform. - - cd test - # This will run the Reader/Writer tests - python runjsontests.py path/to/jsontest - - # This will run the Reader/Writer tests, using JSONChecker test suite - # (http://www.json.org/JSON_checker/). - # Notes: not all tests pass: JsonCpp is too lenient (for example, - # it allows an integer to start with '0'). The goal is to improve - # strict mode parsing to get all tests to pass. - python runjsontests.py --with-json-checker path/to/jsontest - - # This will run the unit tests (mostly Value) - python rununittests.py path/to/test_lib_json - - # You can run the tests using valgrind: - python rununittests.py --valgrind path/to/test_lib_json - -### Running the tests using SCons - -Note that tests can be run using SCons using the `check` target: - - scons platform=$PLATFORM check - -### Building the documentation - -Run the Python script `doxybuild.py` from the top directory: - - python doxybuild.py --doxygen=$(which doxygen) --open --with-dot - -See `doxybuild.py --help` for options. - -### Adding a reader/writer test - -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 - input document. - -The `TESTNAME.expected` file format is as follows: +### The vcpkg dependency manager +You can download and install JsonCpp using the [vcpkg](https://github.com/Microsoft/vcpkg/) dependency manager: -* Each line represents a JSON element of the element tree represented by the - input document. -* Each line has two parts: the path to access the element separated from the - element value by `=`. Array and object values are always empty (i.e. - represented by either `[]` or `{}`). -* Element path `.` represents the root element, and is used to separate object - members. `[N]` is used to specify the value of an array element at index `N`. + git clone https://github.com/Microsoft/vcpkg.git + cd vcpkg + ./bootstrap-vcpkg.sh + ./vcpkg integrate install + ./vcpkg install jsoncpp -See the examples `test_complex_01.json` and `test_complex_01.expected` to better understand element paths. +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. -### Understanding reader/writer test output +### Amalgamated source +https://github.com/open-source-parsers/jsoncpp/wiki/Amalgamated-(Possibly-outdated) -When a test is run, output files are generated beside the input test files. Below is a short description of the content of each file: +### 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`. -* `test_complex_01.json`: input JSON document. -* `test_complex_01.expected`: flattened JSON element tree used to check if - parsing was corrected. -* `test_complex_01.actual`: flattened JSON element tree produced by `jsontest` - from reading `test_complex_01.json`. -* `test_complex_01.rewrite`: JSON document written by `jsontest` using the - `Json::Value` parsed from `test_complex_01.json` and serialized using - `Json::StyledWritter`. -* `test_complex_01.actual-rewrite`: flattened JSON element tree produced by - `jsontest` from reading `test_complex_01.rewrite`. -* `test_complex_01.process-output`: `jsontest` output, typically useful for - understanding parsing errors. +### Other ways +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/SConstruct b/SConstruct deleted file mode 100644 index f3a73f773..000000000 --- a/SConstruct +++ /dev/null @@ -1,248 +0,0 @@ -""" -Notes: -- shared library support is buggy: it assumes that a static and dynamic library can be build from the same object files. This is not true on many platforms. For this reason it is only enabled on linux-gcc at the current time. - -To add a platform: -- add its name in options allowed_values below -- add tool initialization for this platform. Search for "if platform == 'suncc'" as an example. -""" - -import os -import os.path -import sys - -JSONCPP_VERSION = open(File('#version').abspath,'rt').read().strip() -DIST_DIR = '#dist' - -options = Variables() -options.Add( EnumVariable('platform', - 'Platform (compiler/stl) used to build the project', - 'msvc71', - allowed_values='suncc vacpp mingw msvc6 msvc7 msvc71 msvc80 msvc90 linux-gcc'.split(), - ignorecase=2) ) - -try: - platform = ARGUMENTS['platform'] - if platform == 'linux-gcc': - CXX = 'g++' # not quite right, but env is not yet available. - import commands - version = commands.getoutput('%s -dumpversion' %CXX) - platform = 'linux-gcc-%s' %version - print "Using platform '%s'" %platform - LD_LIBRARY_PATH = os.environ.get('LD_LIBRARY_PATH', '') - LD_LIBRARY_PATH = "%s:libs/%s" %(LD_LIBRARY_PATH, platform) - os.environ['LD_LIBRARY_PATH'] = LD_LIBRARY_PATH - print "LD_LIBRARY_PATH =", LD_LIBRARY_PATH -except KeyError: - print 'You must specify a "platform"' - sys.exit(2) - -print "Building using PLATFORM =", platform - -rootbuild_dir = Dir('#buildscons') -build_dir = os.path.join( '#buildscons', platform ) -bin_dir = os.path.join( '#bin', platform ) -lib_dir = os.path.join( '#libs', platform ) -sconsign_dir_path = Dir(build_dir).abspath -sconsign_path = os.path.join( sconsign_dir_path, '.sconsign.dbm' ) - -# Ensure build directory exist (SConsignFile fail otherwise!) -if not os.path.exists( sconsign_dir_path ): - os.makedirs( sconsign_dir_path ) - -# Store all dependencies signature in a database -SConsignFile( sconsign_path ) - -def make_environ_vars(): - """Returns a dictionnary with environment variable to use when compiling.""" - # PATH is required to find the compiler - # TEMP is required for at least mingw - # LD_LIBRARY_PATH & co is required on some system for the compiler - vars = {} - for name in ('PATH', 'TEMP', 'TMP', 'LD_LIBRARY_PATH', 'LIBRARY_PATH'): - if name in os.environ: - vars[name] = os.environ[name] - return vars - - -env = Environment( ENV = make_environ_vars(), - toolpath = ['scons-tools'], - tools=[] ) #, tools=['default'] ) - -if platform == 'suncc': - env.Tool( 'sunc++' ) - env.Tool( 'sunlink' ) - env.Tool( 'sunar' ) - env.Append( CCFLAGS = ['-mt'] ) -elif platform == 'vacpp': - env.Tool( 'default' ) - env.Tool( 'aixcc' ) - env['CXX'] = 'xlC_r' #scons does not pick-up the correct one ! - # using xlC_r ensure multi-threading is enabled: - # http://publib.boulder.ibm.com/infocenter/pseries/index.jsp?topic=/com.ibm.vacpp7a.doc/compiler/ref/cuselect.htm - env.Append( CCFLAGS = '-qrtti=all', - LINKFLAGS='-bh:5' ) # -bh:5 remove duplicate symbol warning -elif platform == 'msvc6': - env['MSVS_VERSION']='6.0' - for tool in ['msvc', 'msvs', 'mslink', 'masm', 'mslib']: - env.Tool( tool ) - env['CXXFLAGS']='-GR -GX /nologo /MT' -elif platform == 'msvc70': - env['MSVS_VERSION']='7.0' - for tool in ['msvc', 'msvs', 'mslink', 'masm', 'mslib']: - env.Tool( tool ) - env['CXXFLAGS']='-GR -GX /nologo /MT' -elif platform == 'msvc71': - env['MSVS_VERSION']='7.1' - for tool in ['msvc', 'msvs', 'mslink', 'masm', 'mslib']: - env.Tool( tool ) - env['CXXFLAGS']='-GR -GX /nologo /MT' -elif platform == 'msvc80': - env['MSVS_VERSION']='8.0' - for tool in ['msvc', 'msvs', 'mslink', 'masm', 'mslib']: - env.Tool( tool ) - env['CXXFLAGS']='-GR -EHsc /nologo /MT' -elif platform == 'msvc90': - env['MSVS_VERSION']='9.0' - # Scons 1.2 fails to detect the correct location of the platform SDK. - # So we propagate those from the environment. This requires that the - # user run vcvars32.bat before compiling. - if 'INCLUDE' in os.environ: - env['ENV']['INCLUDE'] = os.environ['INCLUDE'] - if 'LIB' in os.environ: - env['ENV']['LIB'] = os.environ['LIB'] - for tool in ['msvc', 'msvs', 'mslink', 'masm', 'mslib']: - env.Tool( tool ) - env['CXXFLAGS']='-GR -EHsc /nologo /MT' -elif platform == 'mingw': - env.Tool( 'mingw' ) - env.Append( CPPDEFINES=[ "WIN32", "NDEBUG", "_MT" ] ) -elif platform.startswith('linux-gcc'): - env.Tool( 'default' ) - env.Append( LIBS = ['pthread'], CCFLAGS = os.environ.get("CXXFLAGS", "-Wall"), LINKFLAGS=os.environ.get("LDFLAGS", "") ) - env['SHARED_LIB_ENABLED'] = True -else: - print "UNSUPPORTED PLATFORM." - env.Exit(1) - -env.Tool('targz') -env.Tool('srcdist') -env.Tool('globtool') - -env.Append( CPPPATH = ['#include'], - LIBPATH = lib_dir ) -short_platform = platform -if short_platform.startswith('msvc'): - short_platform = short_platform[2:] -# Notes: on Windows you need to rebuild the source for each variant -# Build script does not support that yet so we only build static libraries. -# This also fails on AIX because both dynamic and static library ends with -# extension .a. -env['SHARED_LIB_ENABLED'] = env.get('SHARED_LIB_ENABLED', False) -env['LIB_PLATFORM'] = short_platform -env['LIB_LINK_TYPE'] = 'lib' # static -env['LIB_CRUNTIME'] = 'mt' -env['LIB_NAME_SUFFIX'] = '${LIB_PLATFORM}_${LIB_LINK_TYPE}${LIB_CRUNTIME}' # must match autolink naming convention -env['JSONCPP_VERSION'] = JSONCPP_VERSION -env['BUILD_DIR'] = env.Dir(build_dir) -env['ROOTBUILD_DIR'] = env.Dir(rootbuild_dir) -env['DIST_DIR'] = DIST_DIR -if 'TarGz' in env['BUILDERS']: - class SrcDistAdder: - def __init__( self, env ): - self.env = env - def __call__( self, *args, **kw ): - apply( self.env.SrcDist, (self.env['SRCDIST_TARGET'],) + args, kw ) - env['SRCDIST_BUILDER'] = env.TarGz -else: # If tarfile module is missing - class SrcDistAdder: - def __init__( self, env ): - pass - def __call__( self, *args, **kw ): - pass -env['SRCDIST_ADD'] = SrcDistAdder( env ) -env['SRCDIST_TARGET'] = os.path.join( DIST_DIR, 'jsoncpp-src-%s.tar.gz' % env['JSONCPP_VERSION'] ) - -env_testing = env.Clone( ) -env_testing.Append( LIBS = ['json_${LIB_NAME_SUFFIX}'] ) - -def buildJSONExample( env, target_sources, target_name ): - env = env.Clone() - env.Append( CPPPATH = ['#'] ) - exe = env.Program( target=target_name, - source=target_sources ) - env['SRCDIST_ADD']( source=[target_sources] ) - global bin_dir - return env.Install( bin_dir, exe ) - -def buildJSONTests( env, target_sources, target_name ): - jsontests_node = buildJSONExample( env, target_sources, target_name ) - check_alias_target = env.Alias( 'check', jsontests_node, RunJSONTests( jsontests_node, jsontests_node ) ) - env.AlwaysBuild( check_alias_target ) - -def buildUnitTests( env, target_sources, target_name ): - jsontests_node = buildJSONExample( env, target_sources, target_name ) - check_alias_target = env.Alias( 'check', jsontests_node, - RunUnitTests( jsontests_node, jsontests_node ) ) - env.AlwaysBuild( check_alias_target ) - -def buildLibrary( env, target_sources, target_name ): - static_lib = env.StaticLibrary( target=target_name + '_${LIB_NAME_SUFFIX}', - source=target_sources ) - global lib_dir - env.Install( lib_dir, static_lib ) - if env['SHARED_LIB_ENABLED']: - shared_lib = env.SharedLibrary( target=target_name + '_${LIB_NAME_SUFFIX}', - source=target_sources ) - env.Install( lib_dir, shared_lib ) - env['SRCDIST_ADD']( source=[target_sources] ) - -Export( 'env env_testing buildJSONExample buildLibrary buildJSONTests buildUnitTests' ) - -def buildProjectInDirectory( target_directory ): - global build_dir - target_build_dir = os.path.join( build_dir, target_directory ) - target = os.path.join( target_directory, 'sconscript' ) - SConscript( target, build_dir=target_build_dir, duplicate=0 ) - env['SRCDIST_ADD']( source=[target] ) - - -def runJSONTests_action( target, source = None, env = None ): - # Add test scripts to python path - jsontest_path = Dir( '#test' ).abspath - sys.path.insert( 0, jsontest_path ) - data_path = os.path.join( jsontest_path, 'data' ) - import runjsontests - return runjsontests.runAllTests( os.path.abspath(source[0].path), data_path ) - -def runJSONTests_string( target, source = None, env = None ): - return 'RunJSONTests("%s")' % source[0] - -import SCons.Action -ActionFactory = SCons.Action.ActionFactory -RunJSONTests = ActionFactory(runJSONTests_action, runJSONTests_string ) - -def runUnitTests_action( target, source = None, env = None ): - # Add test scripts to python path - jsontest_path = Dir( '#test' ).abspath - sys.path.insert( 0, jsontest_path ) - import rununittests - return rununittests.runAllTests( os.path.abspath(source[0].path) ) - -def runUnitTests_string( target, source = None, env = None ): - return 'RunUnitTests("%s")' % source[0] - -RunUnitTests = ActionFactory(runUnitTests_action, runUnitTests_string ) - -env.Alias( 'check' ) - -srcdist_cmd = env['SRCDIST_ADD']( source = """ - AUTHORS README.md SConstruct - """.split() ) -env.Alias( 'src-dist', srcdist_cmd ) - -buildProjectInDirectory( 'src/jsontestrunner' ) -buildProjectInDirectory( 'src/lib_json' ) -buildProjectInDirectory( 'src/test_lib_json' ) -#print env.Dump() - 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 old mode 100644 new mode 100755 index 9cb2d08cc..1d1e48810 --- a/amalgamate.py +++ b/amalgamate.py @@ -1,14 +1,19 @@ -"""Amalgate json-cpp library sources into a single source and header file. +#!/usr/bin/env python + +"""Amalgamate json-cpp library sources into a single source and header file. Works with python2.6+ and python3.4+. Example of invocation (must be invoked from json-cpp top directory): -python amalgate.py +python amalgamate.py """ import os import os.path import sys +INCLUDE_PATH = "include/json" +SRC_PATH = "src/lib_json" + class AmalgamationFile: def __init__(self, top_dir): self.top_dir = top_dir @@ -50,62 +55,64 @@ def write_to(self, output_path): def amalgamate_source(source_top_dir=None, target_source_path=None, header_include_path=None): - """Produces amalgated source. + """Produces amalgamated source. Parameters: source_top_dir: top-directory target_source_path: output .cpp path header_include_path: generated header path relative to target_source_path. """ - print("Amalgating header...") + print("Amalgamating header...") header = AmalgamationFile(source_top_dir) - header.add_text("/// Json-cpp amalgated 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_AMALGATED_H_INCLUDED") - header.add_text("# define JSON_AMALGATED_H_INCLUDED") - header.add_text("/// If defined, indicates that the source file is amalgated") + header.add_text("#ifndef JSON_AMALGAMATED_H_INCLUDED") + header.add_text("# define JSON_AMALGAMATED_H_INCLUDED") + 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("include/json/version.h") - #header.add_file("include/json/allocator.h") # Not available here. - header.add_file("include/json/config.h") - header.add_file("include/json/forwards.h") - header.add_file("include/json/features.h") - header.add_file("include/json/value.h") - header.add_file("include/json/reader.h") - header.add_file("include/json/writer.h") - header.add_file("include/json/assertions.h") - header.add_text("#endif //ifndef JSON_AMALGATED_H_INCLUDED") + 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_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")) + header.add_file(os.path.join(INCLUDE_PATH, "assertions.h")) + header.add_text("#endif //ifndef JSON_AMALGAMATED_H_INCLUDED") target_header_path = os.path.join(os.path.dirname(target_source_path), header_include_path) - print("Writing amalgated header to %r" % target_header_path) + print("Writing amalgamated header to %r" % target_header_path) header.write_to(target_header_path) base, ext = os.path.splitext(header_include_path) forward_header_include_path = base + "-forwards" + ext - print("Amalgating forward header...") + print("Amalgamating forward header...") header = AmalgamationFile(source_top_dir) - header.add_text("/// Json-cpp amalgated 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) - header.add_text("#ifndef JSON_FORWARD_AMALGATED_H_INCLUDED") - header.add_text("# define JSON_FORWARD_AMALGATED_H_INCLUDED") - header.add_text("/// If defined, indicates that the source file is amalgated") + header.add_text("#ifndef JSON_FORWARD_AMALGAMATED_H_INCLUDED") + header.add_text("# define JSON_FORWARD_AMALGAMATED_H_INCLUDED") + 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("include/json/config.h") - header.add_file("include/json/forwards.h") - header.add_text("#endif //ifndef JSON_FORWARD_AMALGATED_H_INCLUDED") + 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") target_forward_header_path = os.path.join(os.path.dirname(target_source_path), forward_header_include_path) - print("Writing amalgated forward header to %r" % target_forward_header_path) + print("Writing amalgamated forward header to %r" % target_forward_header_path) header.write_to(target_forward_header_path) - print("Amalgating source...") + print("Amalgamating source...") source = AmalgamationFile(source_top_dir) - source.add_text("/// Json-cpp amalgated 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("") @@ -116,19 +123,18 @@ def amalgamate_source(source_top_dir=None, #endif """) source.add_text("") - lib_json = "src/lib_json" - source.add_file(os.path.join(lib_json, "json_tool.h")) - source.add_file(os.path.join(lib_json, "json_reader.cpp")) - source.add_file(os.path.join(lib_json, "json_valueiterator.inl")) - source.add_file(os.path.join(lib_json, "json_value.cpp")) - source.add_file(os.path.join(lib_json, "json_writer.cpp")) - - print("Writing amalgated source to %r" % target_source_path) + source.add_file(os.path.join(SRC_PATH, "json_tool.h")) + source.add_file(os.path.join(SRC_PATH, "json_reader.cpp")) + source.add_file(os.path.join(SRC_PATH, "json_valueiterator.inl")) + source.add_file(os.path.join(SRC_PATH, "json_value.cpp")) + source.add_file(os.path.join(SRC_PATH, "json_writer.cpp")) + + print("Writing amalgamated source to %r" % target_source_path) source.write_to(target_source_path) def main(): usage = """%prog [options] -Generate a single amalgated source and header file from the sources. +Generate a single amalgamated source and header file from the sources. """ from optparse import OptionParser parser = OptionParser(usage=usage) @@ -136,7 +142,7 @@ def main(): parser.add_option("-s", "--source", dest="target_source_path", action="http://github.com/store", default="dist/jsoncpp.cpp", help="""Output .cpp source path. [Default: %default]""") parser.add_option("-i", "--include", dest="header_include_path", action="http://github.com/store", default="json/json.h", - help="""Header include path. Used to include the header from the amalgated source file. [Default: %default]""") + help="""Header include path. Used to include the header from the amalgamated source file. [Default: %default]""") parser.add_option("-t", "--top-dir", dest="top_dir", action="http://github.com/store", default=os.getcwd(), help="""Source top-directory. [Default: %default]""") parser.enable_interspersed_args() @@ -149,7 +155,7 @@ def main(): sys.stderr.write(msg + "\n") sys.exit(1) else: - print("Source succesfully amalagated") + print("Source successfully amalgamated") if __name__ == "__main__": main() diff --git a/appveyor.yml b/appveyor.yml index f4966a34d..cccce4298 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,30 +1,32 @@ -# This is a comment. - -version: build.{build} - -os: Windows Server 2012 R2 - clone_folder: c:\projects\jsoncpp -platform: - - Win32 - - x64 - -configuration: - - Debug - - Release - -# scripts to run before build -before_build: - - echo "Running cmake..." - - cd c:\projects\jsoncpp - - cmake --version - - set PATH=C:\Program Files (x86)\MSBuild\14.0\Bin;%PATH% - - if %PLATFORM% == Win32 cmake . - - if %PLATFORM% == x64 cmake -G "Visual Studio 12 2013 Win64" . - -build: - project: jsoncpp.sln # path to Visual Studio solution or project +environment: + + matrix: + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + CMAKE_GENERATOR: Visual Studio 14 2015 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + CMAKE_GENERATOR: Visual Studio 14 2015 Win64 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + CMAKE_GENERATOR: Visual Studio 15 2017 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + CMAKE_GENERATOR: Visual Studio 15 2017 Win64 + +build_script: + - cmake --version + # 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 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 + - cmake --build . --config Release --target install deploy: provider: GitHub 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 d288b1665..545ff2730 100644 --- a/dev.makefile +++ b/dev.makefile @@ -4,6 +4,8 @@ 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 @@ -12,7 +14,7 @@ jsoncpp-%.tar.gz: curl https://github.com/open-source-parsers/jsoncpp/archive/$*.tar.gz -o $@ dox: python doxybuild.py --doxygen=$$(which doxygen) --in doc/web_doxyfile.in - rsync -va --delete dist/doxygen/jsoncpp-api-html-${VER}/ ../jsoncpp-docs/doxygen/ + rsync -va -c --delete dist/doxygen/jsoncpp-api-html-${VER}/ ../jsoncpp-docs/doxygen/ # Then 'git add -A' and 'git push' in jsoncpp-docs. build: mkdir -p build/debug diff --git a/devtools/__init__.py b/devtools/__init__.py index 90b4fd7fb..4a51e6514 100644 --- a/devtools/__init__.py +++ b/devtools/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2010 The JsonCpp Authors +# 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 diff --git a/devtools/antglob.py b/devtools/antglob.py index 75615ed41..bd2d7aee9 100644 --- a/devtools/antglob.py +++ b/devtools/antglob.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # encoding: utf-8 -# Copyright 2009 The JsonCpp Authors +# Copyright 2009 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 @@ -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 e089f9f93..11e1ce2a1 100644 --- a/devtools/fixeol.py +++ b/devtools/fixeol.py @@ -1,4 +1,4 @@ -# Copyright 2010 The JsonCpp Authors +# 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 @@ -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 43ddb4f46..d9b662e01 100644 --- a/devtools/licenseupdater.py +++ b/devtools/licenseupdater.py @@ -6,7 +6,7 @@ # and ends with the first blank line. LICENSE_BEGIN = "// Copyright " -BRIEF_LICENSE = LICENSE_BEGIN + """2007-2010 The JsonCpp Authors +BRIEF_LICENSE = LICENSE_BEGIN + """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 @@ -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/devtools/tarball.py b/devtools/tarball.py index ce3659444..3c0ba65e7 100644 --- a/devtools/tarball.py +++ b/devtools/tarball.py @@ -1,4 +1,4 @@ -# Copyright 2010 The JsonCpp Authors +# 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 diff --git a/doc/doxyfile.in b/doc/doxyfile.in index 57c61c27e..dcf514ea3 100644 --- a/doc/doxyfile.in +++ b/doc/doxyfile.in @@ -271,7 +271,7 @@ OPTIMIZE_OUTPUT_VHDL = NO # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and -# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, # C#, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL. For instance to make # doxygen treat .inc files as Fortran files (default is PHP), and .f files as C # (default is Fortran), use: inc=Fortran f=C. @@ -984,7 +984,8 @@ VERBATIM_HEADERS = YES # classes, structs, unions or interfaces. # The default value is: YES. -ALPHABETICAL_INDEX = NO +ALPHABETICAL_INDEX = YES +TOC_INCLUDE_HEADINGS = 2 # The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in # which the alphabetical index list will be split. @@ -1071,7 +1072,7 @@ HTML_STYLESHEET = # defined cascading style sheet that is included after the standard style sheets # created by doxygen. Using this option one can overrule certain style aspects. # This is preferred over using HTML_STYLESHEET since it does not replace the -# standard style sheet and is therefor more robust against future updates. +# standard style sheet and is therefore more robust against future updates. # Doxygen will copy the style sheet file to the output directory. For an example # see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1407,7 +1408,7 @@ FORMULA_FONTSIZE = 10 FORMULA_TRANSPARENT = YES # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see -# http://www.mathjax.org) which uses client side Javascript for the rendering +# http://www.mathjax.org) which uses client side JavaScript for the rendering # instead of using prerendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path @@ -1477,7 +1478,7 @@ MATHJAX_CODEFILE = SEARCHENGINE = NO # When the SERVER_BASED_SEARCH tag is enabled the search engine will be -# implemented using a web server instead of a web client using Javascript. There +# implemented using a web server instead of a web client using JavaScript. There # are two flavours of web server based searching depending on the # EXTERNAL_SEARCH setting. When disabled, doxygen will generate a PHP script for # searching and an index file used by the script. When EXTERNAL_SEARCH is @@ -1943,7 +1944,7 @@ INCLUDE_FILE_PATTERNS = *.h # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = "_MSC_VER=1400" \ +PREDEFINED = "_MSC_VER=1800" \ _CPPRTTI \ _WIN32 \ JSONCPP_DOC_EXCLUDE_IMPLEMENTATION @@ -1958,7 +1959,7 @@ PREDEFINED = "_MSC_VER=1400" \ EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will -# remove all refrences to function-like macros that are alone on a line, have an +# remove all references to function-like macros that are alone on a line, have an # all uppercase name, and do not end with a semicolon. Such function macros are # typically used for boiler-plate code, and will confuse the parser if not # removed. diff --git a/doc/footer.html b/doc/footer.html index c83e5bcc0..a24bf2b9b 100644 --- a/doc/footer.html +++ b/doc/footer.html @@ -1,3 +1,21 @@ -
- + + + + + + + + + diff --git a/doc/header.html b/doc/header.html index 4b2a5e921..f0b93e084 100644 --- a/doc/header.html +++ b/doc/header.html @@ -1,24 +1,64 @@ - + + + - -JsonCpp - JSON data format manipulation library - - - + + + + + + + +$treeview +$search +$mathjax + +$extrastylesheet + +
- + +
+ + + + + + + + + + + + + +
$searchbox
+
+ + - - + +
+ JsonCpp project page + + + Classes + + + + Namespace + + JsonCpp home page

+ diff --git a/doc/jsoncpp.dox b/doc/jsoncpp.dox index 47efc8a35..f340591fd 100644 --- a/doc/jsoncpp.dox +++ b/doc/jsoncpp.dox @@ -3,7 +3,7 @@ \section _intro Introduction JSON (JavaScript Object Notation) - is a lightweight data-interchange format. + is a lightweight data-interchange format. Here is an example of JSON data: \verbatim @@ -23,14 +23,14 @@ Here is an example of JSON data: { // Default encoding for text "encoding" : "UTF-8", - + // Plug-ins loaded at start-up "plug-ins" : [ "python", "c++", // trailing comment "ruby" ], - + // Tab indent size // (multi-line comment) "indent" : { /*embedded comment*/ "length" : 3, "use_space": true } @@ -67,7 +67,7 @@ const Json::Value plugins = root["plug-ins"]; // Iterate over the sequence elements. for ( int index = 0; index < plugins.size(); ++index ) loadPlugIn( plugins[index].asString() ); - + // Try other datatypes. Some are auto-convertible to others. foo::setIndentLength( root["indent"].get("length", 3).asInt() ); foo::setIndentUseSpace( root["indent"].get("use_space", true).asBool() ); @@ -124,7 +124,7 @@ reader->parse(start, stop, &value2, &errs); \endcode \section _pbuild Build instructions -The build instructions are located in the file +The build instructions are located in the file README.md in the top-directory of the project. The latest version of the source is available in the project's GitHub repository: @@ -132,7 +132,7 @@ The latest version of the source is available in the project's GitHub repository jsoncpp \section _news What's New? -The description of latest changes can be found in +The description of latest changes can be found in the NEWS wiki . @@ -152,7 +152,7 @@ The description of latest changes can be found in \section _license License See file LICENSE in the top-directory of the project. -Basically JsonCpp is licensed under MIT license, or public domain if desired +Basically JsonCpp is licensed under MIT license, or public domain if desired and recognized in your jurisdiction. \author Baptiste Lepilleur (originator) diff --git a/doc/web_doxyfile.in b/doc/web_doxyfile.in index 07d6819a7..df09dccae 100644 --- a/doc/web_doxyfile.in +++ b/doc/web_doxyfile.in @@ -44,7 +44,7 @@ PROJECT_NUMBER = %JSONCPP_VERSION% # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. -PROJECT_BRIEF = +PROJECT_BRIEF = "JSON data format manipulation library" # With the PROJECT_LOGO tag one can specify an logo or icon that is included in # the documentation. The maximum height of the logo should not exceed 55 pixels @@ -271,7 +271,7 @@ OPTIMIZE_OUTPUT_VHDL = NO # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and -# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, # C#, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL. For instance to make # doxygen treat .inc files as Fortran files (default is PHP), and .f files as C # (default is Fortran), use: inc=Fortran f=C. @@ -984,7 +984,8 @@ VERBATIM_HEADERS = NO # classes, structs, unions or interfaces. # The default value is: YES. -ALPHABETICAL_INDEX = NO +ALPHABETICAL_INDEX = YES +TOC_INCLUDE_HEADINGS = 2 # The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in # which the alphabetical index list will be split. @@ -1071,7 +1072,7 @@ HTML_STYLESHEET = # defined cascading style sheet that is included after the standard style sheets # created by doxygen. Using this option one can overrule certain style aspects. # This is preferred over using HTML_STYLESHEET since it does not replace the -# standard style sheet and is therefor more robust against future updates. +# standard style sheet and is therefore more robust against future updates. # Doxygen will copy the style sheet file to the output directory. For an example # see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1124,7 +1125,7 @@ HTML_COLORSTYLE_GAMMA = 80 # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_TIMESTAMP = YES +HTML_TIMESTAMP = NO # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the @@ -1360,7 +1361,7 @@ DISABLE_INDEX = NO # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. -GENERATE_TREEVIEW = NO +GENERATE_TREEVIEW = YES # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # doxygen will group on one line in the generated HTML documentation. @@ -1407,7 +1408,7 @@ FORMULA_FONTSIZE = 10 FORMULA_TRANSPARENT = YES # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see -# http://www.mathjax.org) which uses client side Javascript for the rendering +# http://www.mathjax.org) which uses client side JavaScript for the rendering # instead of using prerendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path @@ -1477,7 +1478,7 @@ MATHJAX_CODEFILE = SEARCHENGINE = NO # When the SERVER_BASED_SEARCH tag is enabled the search engine will be -# implemented using a web server instead of a web client using Javascript. There +# implemented using a web server instead of a web client using JavaScript. There # are two flavours of web server based searching depending on the # EXTERNAL_SEARCH setting. When disabled, doxygen will generate a PHP script for # searching and an index file used by the script. When EXTERNAL_SEARCH is @@ -1797,18 +1798,6 @@ GENERATE_XML = NO XML_OUTPUT = xml -# The XML_SCHEMA tag can be used to specify a XML schema, which can be used by a -# validating XML parser to check the syntax of the XML files. -# This tag requires that the tag GENERATE_XML is set to YES. - -XML_SCHEMA = - -# The XML_DTD tag can be used to specify a XML DTD, which can be used by a -# validating XML parser to check the syntax of the XML files. -# This tag requires that the tag GENERATE_XML is set to YES. - -XML_DTD = - # If the XML_PROGRAMLISTING tag is set to YES doxygen will dump the program # listings (including syntax highlighting and cross-referencing information) to # the XML output. Note that enabling this will significantly increase the size @@ -1943,7 +1932,7 @@ INCLUDE_FILE_PATTERNS = *.h # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = "_MSC_VER=1400" \ +PREDEFINED = "_MSC_VER=1800" \ _CPPRTTI \ _WIN32 \ JSONCPP_DOC_EXCLUDE_IMPLEMENTATION @@ -1958,7 +1947,7 @@ PREDEFINED = "_MSC_VER=1400" \ EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will -# remove all refrences to function-like macros that are alone on a line, have an +# remove all references to function-like macros that are alone on a line, have an # all uppercase name, and do not end with a semicolon. Such function macros are # typically used for boiler-plate code, and will confuse the parser if not # removed. diff --git a/doxybuild.py b/doxybuild.py index f18c9c069..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) @@ -156,9 +156,9 @@ def yesno(bool): def main(): usage = """%prog Generates doxygen documentation in build/doxygen. - Optionaly makes a tarball of the documentation to dist/. + 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/CMakeLists.txt b/include/CMakeLists.txt index cc866f173..dc40d95e8 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -1,2 +1,5 @@ -FILE(GLOB INCLUDE_FILES "json/*.h") -INSTALL(FILES ${INCLUDE_FILES} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/json) +file(GLOB INCLUDE_FILES "json/*.h") +install(FILES + ${INCLUDE_FILES} + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/json) + 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 2492758cd..459c34c61 100644 --- a/include/json/allocator.h +++ b/include/json/allocator.h @@ -1,98 +1,100 @@ -// Copyright 2007-2010 Baptiste Lepilleur +// 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 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 { - public: - // Type definitions - using value_type = T; - using pointer = T*; - using const_pointer = const T*; - using reference = T&; - using const_reference = const T&; - using size_type = std::size_t; - using difference_type = std::ptrdiff_t; - - /** - * Allocate memory for N items using the standard allocator. - */ - pointer allocate(size_type n) { - // allocate using "global operator new" - return static_cast(::operator new(n * sizeof(T))); - } - - /** - * 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)); - // free using "global operator delete" - ::operator delete(p); - } - - /** - * Construct an item in-place at pointer P. - */ - template - void construct(pointer p, Args&&... args) { - // construct using "placement new" and "perfect forwarding" - ::new (static_cast(p)) T(std::forward(args)...); - } - - size_type max_size() const { - return size_t(-1) / sizeof(T); - } - - pointer address( reference x ) const { - return std::addressof(x); - } - - const_pointer address( const_reference x ) const { - return std::addressof(x); - } - - /** - * Destroy an item in-place at pointer P. - */ - void destroy(pointer p) { - // destroy using "explicit destructor" - p->~T(); - } - - // Boilerplate - SecureAllocator() {} - template SecureAllocator(const SecureAllocator&) {} - template struct rebind { using other = SecureAllocator; }; +template class SecureAllocator { +public: + // Type definitions + using value_type = T; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + /** + * Allocate memory for N items using the standard allocator. + */ + pointer allocate(size_type n) { + // allocate using "global operator new" + return static_cast(::operator new(n * sizeof(T))); + } + + /** + * Release memory which was allocated for N items at pointer P. + * + * The memory block is filled with zeroes before being released. + */ + 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); + } + + /** + * Construct an item in-place at pointer P. + */ + template void construct(pointer p, Args&&... args) { + // construct using "placement new" and "perfect forwarding" + ::new (static_cast(p)) T(std::forward(args)...); + } + + size_type max_size() const { return size_t(-1) / sizeof(T); } + + pointer address(reference x) const { return std::addressof(x); } + + const_pointer address(const_reference x) const { return std::addressof(x); } + + /** + * Destroy an item in-place at pointer P. + */ + void destroy(pointer p) { + // destroy using "explicit destructor" + p->~T(); + } + + // Boilerplate + SecureAllocator() {} + template SecureAllocator(const SecureAllocator&) {} + template struct rebind { + using other = SecureAllocator; + }; }; - -template +template bool operator==(const SecureAllocator&, const SecureAllocator&) { - return true; + return true; } -template +template bool operator!=(const SecureAllocator&, const SecureAllocator&) { - return false; + return false; } -} //namespace Json +} // namespace Json #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 9c5f8bc0f..666fa7f54 100644 --- a/include/json/assertions.h +++ b/include/json/assertions.h @@ -1,12 +1,12 @@ -// Copyright 2007-2010 Baptiste Lepilleur +// 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 CPPTL_JSON_ASSERTIONS_H_INCLUDED -#define CPPTL_JSON_ASSERTIONS_H_INCLUDED +#ifndef JSON_ASSERTIONS_H_INCLUDED +#define JSON_ASSERTIONS_H_INCLUDED -#include +#include #include #if !defined(JSON_IS_AMALGAMATION) @@ -20,35 +20,42 @@ #if JSON_USE_EXCEPTION // @todo <= add detail about condition in exception -# define JSON_ASSERT(condition) \ - {if (!(condition)) {Json::throwLogicError( "assert json failed" );}} - -# define JSON_FAIL_MESSAGE(message) \ - { \ - JSONCPP_OSTRINGSTREAM oss; oss << message; \ +#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 -# define JSON_ASSERT(condition) assert(condition) +#define JSON_ASSERT(condition) assert(condition) // The call to assert() will show the failure message in debug builds. In // release builds we abort, for a core-dump or debugger. -# define JSON_FAIL_MESSAGE(message) \ +#define JSON_FAIL_MESSAGE(message) \ { \ - JSONCPP_OSTRINGSTREAM oss; oss << message; \ + OStringStream oss; \ + oss << message; \ assert(false && oss.str().c_str()); \ abort(); \ } - #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 6fcc8afac..000000000 --- a/include/json/autolink.h +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2007-2010 Baptiste Lepilleur -// 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 6e3f3ac64..7f6e2431b 100644 --- a/include/json/config.h +++ b/include/json/config.h @@ -1,23 +1,18 @@ -// Copyright 2007-2010 Baptiste Lepilleur +// 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_CONFIG_H_INCLUDED #define JSON_CONFIG_H_INCLUDED -#include -#include //typedef String -#include //typedef int64_t, uint64_t - -/// 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 +#include +#include +#include +#include +#include +#include +#include +#include // If non-zero, the library uses exceptions to report bad input instead of C // assertion macros. The default is to use exceptions. @@ -25,160 +20,131 @@ #define JSON_USE_EXCEPTION 1 #endif -/// If defined, indicates that the source file is amalgated +// Temporary, tracked for removal with issue #982. +#ifndef JSON_USE_NULLREF +#define JSON_USE_NULLREF 1 +#endif + +/// If defined, indicates that the source file is amalgamated /// to prevent private header inclusion. -/// Remarks: it is automatically defined in the generated amalgated header. +/// 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 -// If JSON_NO_INT64 is defined, then Json only support C++ "int" type for -// integer -// Storages, and 64 bits integer support is disabled. -// #define JSON_NO_INT64 1 +#if defined(_MSC_VER) && _MSC_VER < 1800 +#error \ + "ERROR: Visual Studio 12 (2013) with _MSC_VER=1800 is the oldest supported compiler with sufficient C++11 capabilities" +#endif -#if defined(_MSC_VER) // MSVC -# if _MSC_VER <= 1200 // MSVC 6 - // Microsoft Visual Studio 6 only support conversion from __int64 to double - // (no conversion from unsigned __int64). -# define JSON_USE_INT64_DOUBLE_CONVERSION 1 - // Disable warning 4786 for VS6 caused by STL (identifier was truncated to '255' - // characters in the debug information) - // All projects I've ever seen with VS6 were using this globally (not bothering - // with pragma push/pop). -# pragma warning(disable : 4786) -# endif // MSVC 6 - -# if _MSC_VER >= 1500 // MSVC 2008 - /// Indicates that the following function is deprecated. -# define JSONCPP_DEPRECATED(message) __declspec(deprecated(message)) -# endif - -#endif // defined(_MSC_VER) - -// In c++11 the override keyword allows you to explicity define that a function -// is intended to override the base-class version. This makes the code more -// managable and fixes a set of common hard-to-find bugs. -#if __cplusplus >= 201103L -# define JSONCPP_OVERRIDE override -# define JSONCPP_NOEXCEPT noexcept -#elif defined(_MSC_VER) && _MSC_VER > 1600 && _MSC_VER < 1900 -# define JSONCPP_OVERRIDE override -# define JSONCPP_NOEXCEPT throw() -#elif defined(_MSC_VER) && _MSC_VER >= 1900 -# define JSONCPP_OVERRIDE override -# define JSONCPP_NOEXCEPT noexcept +#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, ...); +#define jsoncpp_snprintf msvc_pre1900_c99_snprintf #else -# define JSONCPP_OVERRIDE -# define JSONCPP_NOEXCEPT throw() +#define jsoncpp_snprintf std::snprintf #endif -#ifndef JSON_HAS_RVALUE_REFERENCES +// If JSON_NO_INT64 is defined, then Json only support C++ "int" type for +// integer +// Storages, and 64 bits integer support is disabled. +// #define JSON_NO_INT64 1 -#if defined(_MSC_VER) && _MSC_VER >= 1600 // MSVC >= 2010 -#define JSON_HAS_RVALUE_REFERENCES 1 -#endif // MSVC >= 2010 +// JSONCPP_OVERRIDE is maintained for backwards compatibility of external tools. +// C++11 should be used directly in JSONCPP. +#define JSONCPP_OVERRIDE override #ifdef __clang__ -#if __has_feature(cxx_rvalue_references) -#define JSON_HAS_RVALUE_REFERENCES 1 -#endif // has_feature - -#elif defined __GNUC__ // not clang (gcc comes later since clang emulates gcc) -#if defined(__GXX_EXPERIMENTAL_CXX0X__) || (__cplusplus >= 201103L) -#define JSON_HAS_RVALUE_REFERENCES 1 -#endif // GXX_EXPERIMENTAL - -#endif // __clang__ || __GNUC__ - -#endif // not defined JSON_HAS_RVALUE_REFERENCES - -#ifndef JSON_HAS_RVALUE_REFERENCES -#define JSON_HAS_RVALUE_REFERENCES 0 +#if __has_extension(attribute_deprecated_with_message) +#define JSONCPP_DEPRECATED(message) __attribute__((deprecated(message))) #endif - -#ifdef __clang__ -#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 -#endif // __clang__ || __GNUC__ +#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 __GNUC__ >= 6 -# define JSON_USE_INT64_DOUBLE_CONVERSION 1 +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ >= 6)) +#define JSON_USE_INT64_DOUBLE_CONVERSION 1 #endif #if !defined(JSON_IS_AMALGAMATION) -# include "version.h" - -# if JSONCPP_USING_SECURE_MEMORY -# include "allocator.h" //typedef Allocator -# endif +#include "allocator.h" +#include "version.h" #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; -#endif // if defined(_MSC_VER) -typedef Int64 LargestInt; -typedef UInt64 LargestUInt; +using Int64 = int64_t; +using UInt64 = uint64_t; +#endif // if defined(_MSC_VER) +using LargestInt = Int64; +using LargestUInt = UInt64; #define JSON_HAS_INT64 #endif // if defined(JSON_NO_INT64) -#if JSONCPP_USING_SECURE_MEMORY -#define JSONCPP_STRING std::basic_string, Json::SecureAllocator > -#define JSONCPP_OSTRINGSTREAM std::basic_ostringstream, Json::SecureAllocator > -#define JSONCPP_OSTREAM std::basic_ostream> -#define JSONCPP_ISTRINGSTREAM std::basic_istringstream, Json::SecureAllocator > -#define JSONCPP_ISTREAM std::istream -#else -#define JSONCPP_STRING std::string -#define JSONCPP_OSTRINGSTREAM std::ostringstream -#define JSONCPP_OSTREAM std::ostream -#define JSONCPP_ISTRINGSTREAM std::istringstream -#define JSONCPP_ISTREAM std::istream -#endif // if JSONCPP_USING_SECURE_MEMORY -} // end namespace Json + +template +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 IStream = std::istream; +using OStream = std::ostream; +} // namespace Json + +// Legacy names (formerly macros). +using JSONCPP_STRING = Json::String; +using JSONCPP_ISTRINGSTREAM = Json::IStringStream; +using JSONCPP_OSTRINGSTREAM = Json::OStringStream; +using JSONCPP_ISTREAM = Json::IStream; +using JSONCPP_OSTREAM = Json::OStream; #endif // JSON_CONFIG_H_INCLUDED diff --git a/include/json/forwards.h b/include/json/forwards.h index ccfe09abf..affe33a7f 100644 --- a/include/json/forwards.h +++ b/include/json/forwards.h @@ -1,4 +1,4 @@ -// Copyright 2007-2010 Baptiste Lepilleur +// 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 @@ -13,17 +13,23 @@ namespace Json { // writer.h +class StreamWriter; +class StreamWriterBuilder; +class Writer; class FastWriter; class StyledWriter; +class StyledStreamWriter; // reader.h 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 8f10ac2bf..5c776a160 100644 --- a/include/json/json.h +++ b/include/json/json.h @@ -1,4 +1,4 @@ -// Copyright 2007-2010 Baptiste Lepilleur +// 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 @@ -6,10 +6,10 @@ #ifndef JSON_JSON_H_INCLUDED #define JSON_JSON_H_INCLUDED -#include "autolink.h" -#include "value.h" +#include "config.h" +#include "json_features.h" #include "reader.h" +#include "value.h" #include "writer.h" -#include "features.h" #endif // JSON_JSON_H_INCLUDED diff --git a/include/json/features.h b/include/json/json_features.h similarity index 81% rename from include/json/features.h rename to include/json/json_features.h index de45248fb..e4a61d6f1 100644 --- a/include/json/features.h +++ b/include/json/json_features.h @@ -1,16 +1,17 @@ -// Copyright 2007-2010 Baptiste Lepilleur +// 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 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 { @@ -41,21 +42,21 @@ class JSON_API Features { Features(); /// \c true if comments are allowed. Default: \c true. - bool allowComments_; + bool allowComments_{true}; /// \c true if root must be either an array or an object value. Default: \c /// false. - bool strictRoot_; + bool strictRoot_{false}; /// \c true if dropped null placeholders are allowed. Default: \c false. - bool allowDroppedNullPlaceholders_; + bool allowDroppedNullPlaceholders_{false}; /// \c true if numeric object key are allowed. Default: \c false. - bool allowNumericKeys_; + bool allowNumericKeys_{false}; }; } // namespace Json #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 6ab5bdbf7..d745378fc 100644 --- a/include/json/reader.h +++ b/include/json/reader.h @@ -1,20 +1,20 @@ -// Copyright 2007-2010 Baptiste Lepilleur +// 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 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 #include +#include #include #include -#include // Disable warning C4251: : needs to have dll-interface to // be used by... @@ -23,137 +23,135 @@ #pragma warning(disable : 4251) #endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) -#pragma pack(push, 8) +#pragma pack(push) +#pragma pack() namespace Json { /** \brief Unserialize a JSON document into a - *Value. + * Value. * * \deprecated Use CharReader and CharReaderBuilder. */ + 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. * * The offsets give the [start, limit) range of bytes within the text. Note * that this is bytes, not codepoints. - * */ struct StructuredError { ptrdiff_t offset_start; ptrdiff_t offset_limit; - JSONCPP_STRING message; + String message; }; - /** \brief Constructs a Reader allowing all features - * for parsing. + /** \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. + /** \brief Constructs a Reader allowing the specified feature set for parsing. + * \deprecated Use CharReader and CharReaderBuilder. */ Reader(const Features& features); /** \brief Read a Value from a JSON * document. - * \param document UTF-8 encoded string containing the document to read. - * \param root [out] Contains the root value of the document if it was - * successfully parsed. - * \param collectComments \c true to collect comment and allow writing them - * back during - * serialization, \c false to discard comments. - * This parameter is ignored if - * Features::allowComments_ - * is \c false. + * + * \param document UTF-8 encoded string containing the document + * to read. + * \param[out] root Contains the root value of the document if it + * was successfully parsed. + * \param collectComments \c true to collect comment and allow writing + * them back during serialization, \c false to + * discard comments. This parameter is ignored + * if Features::allowComments_ is \c false. * \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. - * \param beginDoc Pointer on the beginning of the UTF-8 encoded string of the - document to read. - * \param endDoc Pointer on the end of the UTF-8 encoded string of the - document to read. - * Must be >= beginDoc. - * \param root [out] Contains the root value of the document if it was - * successfully parsed. - * \param collectComments \c true to collect comment and allow writing them - back during - * serialization, \c false to discard comments. - * This parameter is ignored if - Features::allowComments_ - * is \c false. + * document. + * + * \param beginDoc Pointer on the beginning of the UTF-8 encoded + * string of the document to read. + * \param endDoc Pointer on the end of the UTF-8 encoded string + * of the document to read. Must be >= beginDoc. + * \param[out] root Contains the root value of the document if it + * was successfully parsed. + * \param collectComments \c true to collect comment and allow writing + * them back during serialization, \c false to + * discard comments. This parameter is ignored + * if Features::allowComments_ is \c false. * \return \c true if the document was successfully parsed, \c false if an - error occurred. + * 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. /// \see Json::operator>>(std::istream&, Json::Value&). - bool parse(JSONCPP_ISTREAM& is, Value& root, bool collectComments = true); + bool parse(IStream& is, Value& root, bool collectComments = true); /** \brief Returns a user friendly string that list errors in the parsed * document. - * \return Formatted error message with the list of errors with their location - * in - * the parsed document. An empty string is returned if no error - * occurred - * during parsing. + * + * \return Formatted error message with the list of errors with their + * location in the parsed document. An empty string is returned if no error + * occurred during parsing. * \deprecated Use getFormattedErrorMessages() instead (typo fix). */ JSONCPP_DEPRECATED("Use getFormattedErrorMessages() instead.") - JSONCPP_STRING getFormatedErrorMessages() const; + String getFormatedErrorMessages() const; /** \brief Returns a user friendly string that list errors in the parsed * document. - * \return Formatted error message with the list of errors with their location - * in - * the parsed document. An empty string is returned if no error - * occurred - * during parsing. + * + * \return Formatted error message with the list of errors with their + * location in the parsed document. An empty string is returned if no error + * occurred during parsing. */ - JSONCPP_STRING getFormattedErrorMessages() const; + String getFormattedErrorMessages() const; - /** \brief Returns a vector of structured erros encounted while parsing. + /** \brief Returns a vector of structured errors encountered while parsing. + * * \return A (possibly empty) vector of StructuredError objects. Currently - * only one error can be returned, but the caller should tolerate - * multiple - * errors. This can occur if the parser recovers from a non-fatal - * parse error and then encounters additional errors. + * only one error can be returned, but the caller should tolerate multiple + * errors. This can occur if the parser recovers from a non-fatal parse + * error and then encounters additional errors. */ std::vector getStructuredErrors() const; /** \brief Add a semantic error message. - * \param value JSON Value location associated with the error + * + * \param value JSON Value location associated with the error * \param message The error message. - * \return \c true if the error was successfully added, \c false if the - * Value offset exceeds the document size. + * \return \c true if the error was successfully added, \c false if the Value + * offset exceeds the document size. */ - bool pushError(const Value& value, const JSONCPP_STRING& message); + bool pushError(const Value& value, const String& message); /** \brief Add a semantic error message with extra context. - * \param value JSON Value location associated with the error + * + * \param value JSON Value location associated with the error * \param message The error message. - * \param extra Additional JSON Value location to contextualize the error + * \param extra Additional JSON Value location to contextualize the error * \return \c true if the error was successfully added, \c false if either * Value offset exceeds the document size. */ - bool pushError(const Value& value, const JSONCPP_STRING& message, const Value& extra); + bool pushError(const Value& value, const String& message, const Value& extra); /** \brief Return whether there are any errors. - * \return \c true if there are no errors to report \c false if - * errors have occurred. + * + * \return \c true if there are no errors to report \c false if errors have + * occurred. */ bool good() const; @@ -185,15 +183,16 @@ class JSON_API Reader { class ErrorInfo { public: Token token_; - JSONCPP_STRING message_; + String message_; 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(); @@ -205,139 +204,165 @@ class JSON_API Reader { bool decodeNumber(Token& token); bool decodeNumber(Token& token, Value& decoded); bool decodeString(Token& token); - bool decodeString(Token& token, JSONCPP_STRING& decoded); + 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 addError(const JSONCPP_STRING& message, Token& token, Location extra = 0); + 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 JSONCPP_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; - JSONCPP_STRING getLocationLineAndColumn(Location location) 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); - typedef std::stack Nodes; + static bool containsNewLine(Location begin, Location end); + static String normalizeEOL(Location begin, Location end); + + using Nodes = std::stack; Nodes nodes_; Errors errors_; - JSONCPP_STRING document_; - Location begin_; - Location end_; - Location current_; - Location lastValueEnd_; - Value* lastValue_; - JSONCPP_STRING commentsBefore_; + String document_; + Location begin_{}; + Location end_{}; + Location current_{}; + Location lastValueEnd_{}; + Value* lastValue_{}; + String commentsBefore_; Features features_; - bool collectComments_; -}; // Reader + bool collectComments_{}; +}; // Reader /** Interface for reading JSON from a char array. */ class JSON_API CharReader { public: - virtual ~CharReader() {} + 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 document to read. + * document. The document must be a UTF-8 encoded string containing the + * document to read. * - * \param beginDoc Pointer on the beginning of the UTF-8 encoded string of the - document to read. - * \param endDoc Pointer on the end of the UTF-8 encoded string of the - document to read. - * Must be >= beginDoc. - * \param root [out] Contains the root value of the document if it was - * successfully parsed. - * \param errs [out] Formatted error messages (if not NULL) - * a user friendly string that lists errors in the parsed - * document. + * \param beginDoc Pointer on the beginning of the UTF-8 encoded string + * of the document to read. + * \param endDoc Pointer on the end of the UTF-8 encoded string of the + * document to read. Must be >= beginDoc. + * \param[out] root Contains the root value of the document if it was + * successfully parsed. + * \param[out] errs Formatted error messages (if not NULL) a user + * friendly string that lists errors in the parsed + * document. * \return \c true if the document was successfully parsed, \c false if an - error occurred. + * error occurred. */ - virtual bool parse( - char const* beginDoc, char const* endDoc, - Value* root, JSONCPP_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: - virtual ~Factory() {} + virtual ~Factory() = default; /** \brief Allocate a CharReader via operator new(). * \throw std::exception if something goes wrong (e.g. invalid settings) */ virtual CharReader* newCharReader() const = 0; - }; // Factory -}; // CharReader + }; // Factory -/** \brief Build a CharReader implementation. +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; + }; -Usage: -\code - using namespace Json; - CharReaderBuilder builder; - builder["collectComments"] = false; - Value value; - JSONCPP_STRING errs; - bool ok = parseFromStream(builder, std::cin, &value, &errs); -\endcode -*/ + explicit CharReader(std::unique_ptr impl) : _impl(std::move(impl)) {} + +private: + std::unique_ptr _impl; +}; // CharReader + +/** \brief Build a CharReader implementation. + * + * Usage: + * \code + * using namespace Json; + * CharReaderBuilder builder; + * builder["collectComments"] = false; + * Value value; + * String errs; + * bool ok = parseFromStream(builder, std::cin, &value, &errs); + * \endcode + */ class JSON_API CharReaderBuilder : public CharReader::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. - These are case-sensitive. - Available settings (case-sensitive): - - `"collectComments": false or true` - - true to collect comment and allow writing them - back during serialization, false to discard comments. - This parameter is ignored if allowComments is false. - - `"allowComments": false or true` - - true if comments are allowed. - - `"strictRoot": false or true` - - true if root must be either an array or an object value - - `"allowDroppedNullPlaceholders": false or true` - - true if dropped null placeholders are allowed. (See StreamWriterBuilder.) - - `"allowNumericKeys": false or true` - - true if numeric object keys are allowed. - - `"allowSingleQuotes": false or true` - - true if '' are allowed for strings (both keys and values) - - `"stackLimit": integer` - - Exceeding stackLimit (recursive depth of `readValue()`) will - cause an exception. - - This is a security issue (seg-faults caused by deeply nested JSON), - so the default is low. - - `"failIfExtra": false or true` - - If true, `parse()` returns false when extra non-whitespace trails - the JSON value in the input string. - - `"rejectDupKeys": false or true` - - If true, `parse()` returns false when a key is duplicated within an object. - - `"allowSpecialFloats": false or true` - - If true, special float values (NaNs and infinities) are allowed - and their values are lossfree restorable. - - You can examine 'settings_` yourself - to see the defaults. You can also write and read them just like any - JSON Value. - \sa setDefaults() - */ + * These are case-sensitive. + * Available settings (case-sensitive): + * - `"collectComments": false or true` + * - true to collect comment and allow writing them back during + * serialization, false to discard comments. This parameter is ignored + * 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` + * - true if dropped null placeholders are allowed. (See + * StreamWriterBuilder.) + * - `"allowNumericKeys": false or true` + * - true if numeric object keys are allowed. + * - `"allowSingleQuotes": false or true` + * - true if '' are allowed for strings (both keys and values) + * - `"stackLimit": integer` + * - Exceeding stackLimit (recursive depth of `readValue()`) will cause an + * exception. + * - This is a security issue (seg-faults caused by deeply nested JSON), so + * the default is low. + * - `"failIfExtra": false or true` + * - If true, `parse()` returns false when extra non-whitespace trails the + * JSON value in the input string. + * - `"rejectDupKeys": false or true` + * - If true, `parse()` returns false when a key is duplicated within an + * object. + * - `"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. + * \sa setDefaults() + */ Json::Value settings_; CharReaderBuilder(); - ~CharReaderBuilder() JSONCPP_OVERRIDE; + ~CharReaderBuilder() override; - CharReader* newCharReader() const JSONCPP_OVERRIDE; + CharReader* newCharReader() const override; /** \return true if 'settings' are legal and consistent; * otherwise, indicate bad settings via 'invalid'. @@ -346,7 +371,7 @@ class JSON_API CharReaderBuilder : public CharReader::Factory { /** A simple way to update a specific setting. */ - Value& operator[](JSONCPP_STRING key); + Value& operator[](const String& key); /** Called by ctor, but you can use this to reset settings_. * \pre 'settings' != NULL (but Json::null is fine) @@ -360,42 +385,46 @@ 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&, - JSONCPP_ISTREAM&, - Value* root, std::string* errs); + * Someday we might have a real StreamReader, but for now this + * is convenient. + */ +bool JSON_API parseFromStream(CharReader::Factory const&, IStream&, Value* root, + String* errs); /** \brief Read from 'sin' into 'root'. - - Always keep comments from the input JSON. - - This can be used to read a file into a particular sub-object. - For example: - \code - Json::Value root; - cin >> root["dir"]["file"]; - cout << root; - \endcode - Result: - \verbatim - { - "dir": { - "file": { - // The input stream JSON would be nested here. - } - } - } - \endverbatim - \throw std::exception on parse error. - \see Json::operator<<() -*/ -JSON_API JSONCPP_ISTREAM& operator>>(JSONCPP_ISTREAM&, Value&); + * + * Always keep comments from the input JSON. + * + * This can be used to read a file into a particular sub-object. + * For example: + * \code + * Json::Value root; + * cin >> root["dir"]["file"]; + * cout << root; + * \endcode + * Result: + * \verbatim + * { + * "dir": { + * "file": { + * // The input stream JSON would be nested here. + * } + * } + * } + * \endverbatim + * \throw std::exception on parse error. + * \see Json::operator<<() + */ +JSON_API IStream& operator>>(IStream&, Value&); } // namespace Json @@ -405,4 +434,4 @@ JSON_API JSONCPP_ISTREAM& operator>>(JSONCPP_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 c39f423fa..5f6544329 100644 --- a/include/json/value.h +++ b/include/json/value.h @@ -1,92 +1,115 @@ -// Copyright 2007-2010 Baptiste Lepilleur +// 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 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) -#include -#include -#include -#ifndef JSON_USE_CPPTL_SMALLMAP -#include +// 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 -#include +#define JSONCPP_NORETURN [[noreturn]] #endif -#ifdef JSON_USE_CPPTL -#include #endif -//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) -# define JSONCPP_NORETURN __declspec(noreturn) -# elif defined(__GNUC__) -# define JSONCPP_NORETURN __attribute__ ((__noreturn__)) -# else -# define JSONCPP_NORETURN -# 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 + +#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). */ namespace Json { +#if JSON_USE_EXCEPTION /** Base class for all exceptions we throw. * * We use nothing but these internally. Of course, STL can throw others. */ class JSON_API Exception : public std::exception { public: - Exception(JSONCPP_STRING const& msg); - ~Exception() JSONCPP_NOEXCEPT JSONCPP_OVERRIDE; - char const* what() const JSONCPP_NOEXCEPT JSONCPP_OVERRIDE; + Exception(String msg); + ~Exception() noexcept override; + char const* what() const noexcept override; + protected: - JSONCPP_STRING msg_; + String msg_; }; /** Exceptions which the user cannot easily avoid. * * E.g. out-of-memory (when we use malloc), stack-overflow, malicious input - * + * * \remark derived from Json::Exception */ class JSON_API RuntimeError : public Exception { public: - RuntimeError(JSONCPP_STRING const& msg); + RuntimeError(String const& msg); }; /** Exceptions thrown by JSON_ASSERT/JSON_FAIL macros. * * These are precondition-violations (user bugs) and internal errors (our bugs). - * + * * \remark derived from Json::Exception */ class JSON_API LogicError : public Exception { public: - LogicError(JSONCPP_STRING const& msg); + LogicError(String const& msg); }; +#endif /// used internally -JSONCPP_NORETURN void throwRuntimeError(JSONCPP_STRING const& msg); +JSONCPP_NORETURN void throwRuntimeError(String const& msg); /// used internally -JSONCPP_NORETURN void throwLogicError(JSONCPP_STRING const& msg); +JSONCPP_NORETURN void throwLogicError(String const& msg); /** \brief Type of the value held by a Value object. */ @@ -109,14 +132,16 @@ enum CommentPlacement { numberOfCommentPlacement }; -//# ifdef JSON_USE_CPPTL -// typedef CppTL::AnyEnumerator EnumMemberNames; -// typedef CppTL::AnyEnumerator EnumValues; -//# endif +/** \brief Type of precision for formatting of real values. + */ +enum PrecisionType { + significantDigits = 0, ///< we set max number of significant digits in string + decimalPlaces ///< we set max number of digits after "." in string +}; /** \brief Lightweight wrapper to tag static string. * - * Value constructor and objectValue member assignement takes advantage of the + * Value constructor and objectValue member assignment takes advantage of the * StaticString and avoid the cost of string duplication when storing the * string or the member name. * @@ -165,7 +190,7 @@ class JSON_API StaticString { * The get() methods can be used to obtain default value in the case the * required element does not exist. * - * It is possible to iterate over the list of a #objectValue values using + * It is possible to iterate over the list of member keys of an object using * the getMemberNames() method. * * \note #Value string-length fit in size_t, but keys must be < 2^30. @@ -176,68 +201,86 @@ class JSON_API StaticString { */ 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; - static const Value& null; ///< We regret this reference to a global instance; prefer the simpler Value(). - static const Value& nullRef; ///< just a kludge for binary-compatibility; same as null - static Value const& nullSingleton(); ///< Prefer this to null or nullRef. + // Required for boost integration, e. g. BOOST_TEST + using value_type = std::string; + +#if JSON_USE_NULLREF + // Binary compatibility kludges, do not use. + static const Value& null; + static const Value& nullRef; +#endif + + // null and nullRef are deprecated, use this instead. + 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 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 +#ifdef __NVCC__ +public: +#else private: +#endif #ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION class CZString { public: - enum DuplicationPolicy { - noDuplication = 0, - duplicate, - duplicateOnCopy - }; + enum DuplicationPolicy { noDuplication = 0, duplicate, duplicateOnCopy }; CZString(ArrayIndex index); CZString(char const* str, unsigned length, DuplicationPolicy allocate); CZString(CZString const& other); -#if JSON_HAS_RVALUE_REFERENCES - CZString(CZString&& other); -#endif + CZString(CZString&& other) noexcept; ~CZString(); - CZString& operator=(CZString other); + CZString& operator=(const CZString& other); + CZString& operator=(CZString&& other) noexcept; + bool operator<(CZString const& other) const; bool operator==(CZString const& other) const; ArrayIndex index() const; - //const char* c_str() const; ///< \deprecated + // const char* c_str() const; ///< \deprecated char const* data() const; unsigned length() const; bool isStaticString() const; @@ -246,11 +289,11 @@ class JSON_API Value { void swap(CZString& other); struct StringStorage { - unsigned policy_: 2; - unsigned length_: 30; // 1GB max + unsigned policy_ : 2; + unsigned length_ : 30; // 1GB max }; - char const* cstr_; // actually, a prefixed string, unless policy is noDup + char const* cstr_; // actually, a prefixed string, unless policy is noDup union { ArrayIndex index_; StringStorage storage_; @@ -258,29 +301,26 @@ 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: - /** \brief Create a default Value of the given type. - - This is a very useful constructor. - To create an empty array, pass arrayValue. - To create an empty object, pass objectValue. - Another Value can then be set to this one by assignment. -This is useful since clear() and resize() will not alter types. - - Examples: -\code -Json::Value null_value; // null -Json::Value arr_value(Json::arrayValue); // [] -Json::Value obj_value(Json::objectValue); // {} -\endcode - */ + /** + * \brief Create a default Value of the given type. + * + * This is a very useful constructor. + * To create an empty array, pass arrayValue. + * To create an empty object, pass objectValue. + * Another Value can then be set to this one by assignment. + * This is useful since clear() and resize() will not alter types. + * + * Examples: + * \code + * Json::Value null_value; // null + * Json::Value arr_value(Json::arrayValue); // [] + * Json::Value obj_value(Json::objectValue); // {} + * \endcode + */ Value(ValueType type = nullValue); Value(Int value); Value(UInt value); @@ -291,43 +331,49 @@ Json::Value obj_value(Json::objectValue); // {} Value(double value); Value(const char* value); ///< Copy til first 0. (NULL causes to seg-fault.) Value(const char* begin, const char* end); ///< Copy all, incl zeroes. - /** \brief Constructs a value from a static string. - + /** + * \brief Constructs a value from a static string. + * * Like other value string constructor but do not duplicate the string for - * internal storage. The given string must remain alive after the call to this - * constructor. + * internal storage. The given string must remain alive after the call to + * this constructor. + * * \note This works only for null-terminated strings. (We cannot change the - * size of this class, so we have nowhere to store the length, - * which might be computed later for various operations.) + * size of this class, so we have nowhere to store the length, which might be + * computed later for various operations.) * * Example of usage: - * \code - * static StaticString foo("some text"); - * Json::Value aValue(foo); - * \endcode + * \code + * static StaticString foo("some text"); + * Json::Value aValue(foo); + * \endcode */ Value(const StaticString& value); - Value(const JSONCPP_STRING& value); ///< Copy data() til size(). Embedded zeroes too. -#ifdef JSON_USE_CPPTL - Value(const CppTL::ConstString& value); + Value(const String& value); +#ifdef JSONCPP_HAS_STRING_VIEW + Value(std::string_view value); #endif Value(bool value); - /// Deep copy. + Value(std::nullptr_t ptr) = delete; Value(const Value& other); -#if JSON_HAS_RVALUE_REFERENCES - /// Move constructor - Value(Value&& other); -#endif + Value(Value&& other) noexcept; ~Value(); - /// Deep copy, then swap(other). - /// \note Over-write existing comments. To preserve comments, use #swapPayload(). - Value& operator=(Value other); + /// \note Overwrite existing comments. To preserve comments, use + /// #swapPayload(). + Value& operator=(const Value& other); + Value& operator=(Value&& other) noexcept; + /// Swap everything. void swap(Value& other); /// Swap values but leave comments and source offsets in place. void swapPayload(Value& other); + /// copy everything. + void copy(const Value& other); + /// copy values but leave comments and source offsets in place. + void copyPayload(const Value& other); + ValueType type() const; /// Compare payload only, not comments etc. @@ -340,17 +386,20 @@ Json::Value obj_value(Json::objectValue); // {} int compare(const Value& other) const; const char* asCString() const; ///< Embedded zeroes could cause you trouble! -#if JSONCPP_USING_SECURE_MEMORY - unsigned getCStringLength() const; //Allows you to understand the length of the CString +#if JSONCPP_USE_SECURE_MEMORY + unsigned getCStringLength() const; // Allows you to understand the length of + // the CString #endif - JSONCPP_STRING asString() const; ///< Embedded zeroes are possible. + String asString() const; ///< Embedded zeroes are possible. /** Get raw char* of string-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; + bool getString(char const** begin, char const** end) 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; @@ -377,6 +426,10 @@ Json::Value obj_value(Json::objectValue); // {} 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 @@ -386,50 +439,41 @@ Json::Value obj_value(Json::objectValue); // {} /// otherwise, false. bool empty() const; - /// Return isNull() - bool operator!() const; + /// Return !isNull() + explicit operator bool() const; /// Remove all object members and array elements. /// \pre type() is arrayValue, objectValue, or nullValue /// \post type() is unchanged void clear(); - /// Resize the array to size elements. + /// Resize the array to newSize elements. /// New elements are initialized to null. /// May only be called on nullValue or arrayValue. /// \pre type() is arrayValue or nullValue /// \post type() is arrayValue - void resize(ArrayIndex size); + 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. + ///@{ + /// 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. /// (You may need to say 'value[0u]' to get your compiler to distinguish - /// this from the operator[] which takes a string.) + /// this from the operator[] which takes a string.) Value& operator[](ArrayIndex index); - - /// 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. - /// (You may need to say 'value[0u]' to get your compiler to distinguish - /// this from the operator[] which takes a string.) Value& operator[](int index); + ///@} - /// Access an array element (zero based 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.) + /// this from the operator[] which takes a string.) const Value& operator[](ArrayIndex index) const; - - /// 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[](int index) const; + ///@} /// If the array contains at least index+1 elements, returns the element - /// value, - /// otherwise returns defaultValue. + /// value, otherwise returns defaultValue. Value get(ArrayIndex index, const Value& defaultValue) const; /// Return true if index < size(). bool isValidIndex(ArrayIndex index) const; @@ -437,109 +481,152 @@ Json::Value obj_value(Json::objectValue); // {} /// /// Equivalent to jsonvalue[jsonvalue.size()] = 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. + /// Exceeding that will cause an exception. Value& operator[](const char* key); /// Access an object value by name, returns null if there is no member with /// that name. const Value& operator[](const char* key) const; /// Access an object value by name, create a null member if it does not exist. /// \param key may contain embedded nulls. - Value& operator[](const JSONCPP_STRING& key); + Value& operator[](const String& 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[](const JSONCPP_STRING& key) const; + const Value& operator[](const String& key) const; +#endif /** \brief Access an object value by name, create a null member if it does not - exist. - - * If the object has no entry for that name, then the member name used to store - * the new entry is not duplicated. + * exist. + * + * If the object has no entry for that name, then the member name used to + * store the new entry is not duplicated. * Example of use: - * \code - * Json::Value object; - * static const StaticString code("code"); - * object[code] = 1234; - * \endcode + * \code + * Json::Value object; + * static const StaticString code("code"); + * object[code] = 1234; + * \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 JSONCPP_STRING& key, const Value& defaultValue) const; -#ifdef JSON_USE_CPPTL + Value get(const String& key, const Value& defaultValue) const; +#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. - Value const* demand(char const* begin, char const* end); + Value* demand(char const* begin, char const* end); /// \brief Remove and return the named member. /// /// Do nothing if it did not exist. - /// \return the removed Value, or null. /// \pre type() is objectValue or nullValue /// \post type() is unchanged - /// \deprecated - Value removeMember(const char* key); +#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. - /// \deprecated - Value removeMember(const JSONCPP_STRING& key); + void removeMember(const String& key); +#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); - /** \brief Remove the named map member. - - Update 'removed' iff removed. - \param key may contain embedded nulls. - \return true iff removed (no exceptions) - */ - bool removeMember(JSONCPP_STRING const& key, Value* removed); - /// Same as removeMember(JSONCPP_STRING const& 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. + * + * O(n) expensive operations. + * Update 'removed' iff removed. + * \return true if removed (no exceptions) + */ + bool removeIndex(ArrayIndex index, Value* removed); - O(n) expensive operations. - Update 'removed' iff removed. - \return true iff removed (no exceptions) - */ - bool removeIndex(ArrayIndex i, 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 JSONCPP_STRING& key) const; - /// Same as isMember(JSONCPP_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; + bool isMember(const String& key) const; #endif + /// Same as isMember(String const& key)const + bool isMember(const char* begin, const char* end) const; /// \brief Return a list of the member names. /// @@ -548,23 +635,22 @@ Json::Value obj_value(Json::objectValue); // {} /// \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. - JSONCPP_DEPRECATED("Use setComment(JSONCPP_STRING const&) instead.") - void setComment(const char* comment, CommentPlacement placement); + JSONCPP_DEPRECATED("Use setComment(String const&) instead.") + void setComment(const char* comment, CommentPlacement placement) { + setComment(String(comment, strlen(comment)), placement); + } /// Comments must be //... or /* ... */ - void setComment(const char* comment, size_t len, CommentPlacement placement); + void setComment(const char* comment, size_t len, CommentPlacement placement) { + setComment(String(comment, len), placement); + } /// Comments must be //... or /* ... */ - void setComment(const JSONCPP_STRING& comment, CommentPlacement placement); + void setComment(String comment, CommentPlacement placement); bool hasComment(CommentPlacement placement) const; /// Include delimiters and embedded newlines. - JSONCPP_STRING getComment(CommentPlacement placement) const; + String getComment(CommentPlacement placement) const; - JSONCPP_STRING toStyledString() const; + String toStyledString() const; const_iterator begin() const; const_iterator end() const; @@ -572,6 +658,26 @@ Json::Value obj_value(Json::objectValue); // {} 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); @@ -580,20 +686,20 @@ Json::Value obj_value(Json::objectValue); // {} ptrdiff_t getOffsetLimit() const; private: + void setType(ValueType v) { + bits_.value_type_ = static_cast(v); + } + bool isAllocated() const { return bits_.allocated_; } + void setIsAllocated(bool v) { bits_.allocated_ = v; } + void initBasic(ValueType type, bool allocated = false); + void dupPayload(const Value& other); + void releasePayload(); + void dupMeta(const Value& other); Value& resolveReference(const char* key); Value& resolveReference(const char* key, const char* end); - struct CommentInfo { - CommentInfo(); - ~CommentInfo(); - - void setComment(const char* text, size_t len); - - char* comment_; - }; - // struct MemberNamesTransform //{ // typedef const char *result_type; @@ -608,13 +714,33 @@ Json::Value obj_value(Json::objectValue); // {} LargestUInt uint_; double real_; bool bool_; - char* string_; // actually ptr to unsigned, followed by str, unless !allocated_ + char* string_; // if allocated_, ptr to { unsigned, char[] }. ObjectValues* map_; } value_; - ValueType type_ : 8; - unsigned int allocated_ : 1; // Notes: if declared as bool, bitfield is useless. - // If not allocated_, string_ must be null-terminated. - CommentInfo* comments_; + + struct { + // Really a ValueType, but types should agree for bitfield packing. + unsigned int value_type_ : 8; + // Unless allocated_, string_ must be null-terminated. + unsigned int allocated_ : 1; + } bits_; + + class Comments { + public: + Comments() = default; + Comments(const Comments& that); + Comments(Comments&& that) noexcept; + Comments& operator=(const Comments& that); + Comments& operator=(Comments&& that) noexcept; + bool has(CommentPlacement slot) const; + String get(CommentPlacement slot) const; + void set(CommentPlacement slot, String comment); + + private: + using Array = std::array; + std::unique_ptr ptr_; + }; + Comments comments_; // [start, limit) byte offsets in the source JSON text from which this Value // was extracted. @@ -622,6 +748,36 @@ Json::Value obj_value(Json::objectValue); // {} 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. */ @@ -632,17 +788,13 @@ class JSON_API PathArgument { PathArgument(); PathArgument(ArrayIndex index); PathArgument(const char* key); - PathArgument(const JSONCPP_STRING& key); + PathArgument(String key); private: - enum Kind { - kindNone = 0, - kindIndex, - kindKey - }; - JSONCPP_STRING key_; - ArrayIndex index_; - Kind kind_; + enum Kind { kindNone = 0, kindIndex, kindKey }; + String key_; + ArrayIndex index_{}; + Kind kind_{kindNone}; }; /** \brief Experimental and untested: represents a "path" to access a node. @@ -654,12 +806,11 @@ class JSON_API PathArgument { * - ".name1.name2.name3" * - ".[0][1][2].name1[3]" * - ".%" => member name is provided as parameter - * - ".[%]" => index is provied as parameter + * - ".[%]" => index is provided as parameter */ class JSON_API Path { public: - Path(const JSONCPP_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(), @@ -672,15 +823,13 @@ 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 JSONCPP_STRING& path, const InArgs& in); - void addPathInArg(const JSONCPP_STRING& path, - const InArgs& in, - InArgs::const_iterator& itInArg, - PathArgument::Kind kind); - void invalidPath(const JSONCPP_STRING& path, int location); + void makePath(const String& path, const InArgs& in); + 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_; }; @@ -690,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); } @@ -707,17 +856,19 @@ class JSON_API ValueIteratorBase { /// Value. Value key() const; - /// Return the index of the referenced Value, or -1 if it is not an arrayValue. + /// Return the index of the referenced Value, or -1 if it is not an + /// arrayValue. UInt index() const; /// Return the member name of the referenced Value, or "" if it is not an /// objectValue. /// \note Avoid `c_str()` on result, as embedded zeroes are possible. - JSONCPP_STRING name() const; + String name() const; /// Return the member name of the referenced Value. "" if it is not an /// objectValue. - /// \deprecated This cannot be used for UTF-8 strings, since there can be embedded nulls. + /// \deprecated This cannot be used for UTF-8 strings, since there can be + /// embedded nulls. 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 @@ -726,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(); @@ -741,7 +899,7 @@ class JSON_API ValueIteratorBase { private: Value::ObjectValues::iterator current_; // Indicates that iterator is for a null value. - bool isNull_; + bool isNull_{true}; public: // For some reason, BORLAND needs these at the end, rather @@ -757,20 +915,21 @@ class JSON_API ValueConstIterator : public ValueIteratorBase { friend class Value; public: - typedef const Value value_type; - //typedef unsigned int size_t; - //typedef int difference_type; - typedef const Value& reference; - typedef const Value* pointer; - typedef ValueConstIterator SelfType; + using value_type = const Value; + // typedef unsigned int size_t; + // typedef int difference_type; + using reference = const Value&; + using pointer = const Value*; + using SelfType = ValueConstIterator; ValueConstIterator(); ValueConstIterator(ValueIterator const& other); private: -/*! \internal Use by Value to create an iterator. - */ + /*! \internal Use by Value to create an iterator. + */ explicit ValueConstIterator(const Value::ObjectValues::iterator& current); + public: SelfType& operator=(const ValueIteratorBase& other); @@ -807,21 +966,22 @@ 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); ValueIterator(const ValueIterator& other); private: -/*! \internal Use by Value to create an iterator. - */ + /*! \internal Use by Value to create an iterator. + */ explicit ValueIterator(const Value::ObjectValues::iterator& current); + public: SelfType& operator=(const SelfType& other); @@ -847,19 +1007,26 @@ 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()); } }; -} // namespace Json +inline void swap(Value& a, Value& b) { a.swap(b); } +inline const Value& Value::front() const { return *begin(); } -namespace std { -/// Specialize std::swap() for Json::Value. -template<> -inline void swap(Json::Value& a, Json::Value& b) { a.swap(b); } -} +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) @@ -867,4 +1034,4 @@ inline void swap(Json::Value& a, Json::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 9e5a66ee6..555152c8c 100644 --- a/include/json/version.h +++ b/include/json/version.h @@ -1,19 +1,27 @@ -// DO NOT EDIT. This file (and "version") is generated by CMake. -// Run CMake configure step to update it. #ifndef JSON_VERSION_H_INCLUDED -# define JSON_VERSION_H_INCLUDED +#define JSON_VERSION_H_INCLUDED -# define JSONCPP_VERSION_STRING "1.8.0" -# define JSONCPP_VERSION_MAJOR 1 -# define JSONCPP_VERSION_MINOR 8 -# define JSONCPP_VERSION_PATCH 0 -# define JSONCPP_VERSION_QUALIFIER -# define JSONCPP_VERSION_HEXA ((JSONCPP_VERSION_MAJOR << 24) | (JSONCPP_VERSION_MINOR << 16) | (JSONCPP_VERSION_PATCH << 8)) +// 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!! -#ifdef JSONCPP_USING_SECURE_MEMORY -#undef JSONCPP_USING_SECURE_MEMORY +#define JSONCPP_VERSION_STRING "1.9.7" +#define JSONCPP_VERSION_MAJOR 1 +#define JSONCPP_VERSION_MINOR 9 +#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)) + +#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 d0944eeb8..7c56a2107 100644 --- a/include/json/writer.h +++ b/include/json/writer.h @@ -1,4 +1,4 @@ -// Copyright 2007-2010 Baptiste Lepilleur +// 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 @@ -9,49 +9,50 @@ #if !defined(JSON_IS_AMALGAMATION) #include "value.h" #endif // if !defined(JSON_IS_AMALGAMATION) -#include -#include #include +#include +#include // Disable warning C4251: : needs to have dll-interface to // be used by... -#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) && defined(_MSC_VER) #pragma warning(push) #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: - JSONCPP_OSTREAM* sout_; // not owned; will not delete + OStream* sout_; // not owned; will not delete public: 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, JSONCPP_OSTREAM* sout) = 0; + virtual int write(Value const& root, OStream* sout) = 0; /** \brief A simple abstract factory. */ @@ -62,64 +63,71 @@ class JSON_API StreamWriter { * \throw std::exception if something goes wrong (e.g. invalid settings) */ virtual StreamWriter* newStreamWriter() const = 0; - }; // Factory -}; // StreamWriter + }; // Factory +}; // StreamWriter /** \brief Write into stringstream, then return string, for convenience. * A StreamWriter will be created from the factory, used, and then deleted. */ -JSONCPP_STRING JSON_API writeString(StreamWriter::Factory const& factory, Value const& root); - +String JSON_API writeString(StreamWriter::Factory const& factory, + Value const& root); /** \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": "" - - "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". - - 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(); - ~StreamWriterBuilder() JSONCPP_OVERRIDE; + ~StreamWriterBuilder() override; /** * \throw std::exception if something goes wrong (e.g. invalid settings) */ - StreamWriter* newStreamWriter() const JSONCPP_OVERRIDE; + StreamWriter* newStreamWriter() const override; /** \return true if 'settings' are legal and consistent; * otherwise, indicate bad settings via 'invalid'. @@ -127,7 +135,7 @@ class JSON_API StreamWriterBuilder : public StreamWriter::Factory { bool validate(Json::Value* invalid) const; /** A simple way to update a specific setting. */ - Value& operator[](JSONCPP_STRING key); + Value& operator[](const String& key); /** Called by ctor, but you can use this to reset settings_. * \pre 'settings' != NULL (but Json::null is fine) @@ -144,7 +152,7 @@ class JSON_API Writer { public: virtual ~Writer(); - virtual JSONCPP_STRING write(const Value& root) = 0; + virtual String write(const Value& root) = 0; }; /** \brief Outputs a Value in JSON format @@ -152,21 +160,24 @@ class JSON_API Writer { * * The JSON document is written in a single line. It is not intended for 'human' *consumption, - * but may be usefull to support feature such as RPC where bandwith is limited. + * but may be useful to support feature such as RPC where bandwidth is limited. * \sa Reader, Value * \deprecated Use StreamWriterBuilder. */ +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4996) // Deriving from deprecated class +#endif class JSON_API FastWriter : public Writer { - public: FastWriter(); - ~FastWriter() JSONCPP_OVERRIDE {} + ~FastWriter() override = default; void enableYAMLCompatibility(); /** \brief 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 + * fed to a browser's JavaScript, it makes for smaller output and the * browser can handle the output just fine. */ void dropNullPlaceholders(); @@ -174,16 +185,19 @@ class JSON_API FastWriter : public Writer { void omitEndingLineFeed(); public: // overridden from Writer - JSONCPP_STRING write(const Value& root) JSONCPP_OVERRIDE; + String write(const Value& root) override; private: void writeValue(const Value& value); - JSONCPP_STRING document_; - bool yamlCompatiblityEnabled_; - bool dropNullPlaceholders_; - bool omitEndingLineFeed_; + String document_; + bool yamlCompatibilityEnabled_{false}; + bool dropNullPlaceholders_{false}; + bool omitEndingLineFeed_{false}; }; +#if defined(_MSC_VER) +#pragma warning(pop) +#endif /** \brief Writes a Value in JSON format in a *human friendly way. @@ -203,47 +217,54 @@ class JSON_API FastWriter : public Writer { * - 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() * \deprecated Use StreamWriterBuilder. */ +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4996) // Deriving from deprecated class +#endif class JSON_API StyledWriter : public Writer { public: StyledWriter(); - ~StyledWriter() JSONCPP_OVERRIDE {} + ~StyledWriter() override = default; public: // overridden from Writer /** \brief Serialize a Value in JSON format. * \param root Value to serialize. * \return String containing the JSON document that represents the root value. */ - JSONCPP_STRING write(const Value& root) JSONCPP_OVERRIDE; + String write(const Value& root) override; private: void writeValue(const Value& value); void writeArrayValue(const Value& value); - bool isMultineArray(const Value& value); - void pushValue(const JSONCPP_STRING& value); + bool isMultilineArray(const Value& value); + void pushValue(const String& value); void writeIndent(); - void writeWithIndent(const JSONCPP_STRING& value); + void writeWithIndent(const String& value); void indent(); void unindent(); void writeCommentBeforeValue(const Value& root); void writeCommentAfterValueOnSameLine(const Value& root); - bool hasCommentForValue(const Value& value); - static JSONCPP_STRING normalizeEOL(const JSONCPP_STRING& text); + static bool hasCommentForValue(const Value& value); + static String normalizeEOL(const String& text); - typedef std::vector ChildValues; + using ChildValues = std::vector; ChildValues childValues_; - JSONCPP_STRING document_; - JSONCPP_STRING indentString_; - unsigned int rightMargin_; - unsigned int indentSize_; - bool addChildValues_; + String document_; + String indentString_; + unsigned int rightMargin_{74}; + unsigned int indentSize_{3}; + bool addChildValues_{false}; }; +#if defined(_MSC_VER) +#pragma warning(pop) +#endif /** \brief Writes a Value in JSON format in a human friendly way, @@ -264,17 +285,23 @@ class JSON_API StyledWriter : public Writer { * - 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. * - * \param indentation Each level will be indented by this amount extra. * \sa Reader, Value, Value::setComment() * \deprecated Use StreamWriterBuilder. */ +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4996) // Deriving from deprecated class +#endif class JSON_API StyledStreamWriter { public: - StyledStreamWriter(JSONCPP_STRING indentation = "\t"); - ~StyledStreamWriter() {} + /** + * \param indentation Each level will be indented by this amount extra. + */ + StyledStreamWriter(String indentation = "\t"); + ~StyledStreamWriter() = default; public: /** \brief Serialize a Value in JSON format. @@ -283,46 +310,52 @@ class JSON_API StyledStreamWriter { * \note There is no point in deriving from Writer, since write() should not * return a value. */ - void write(JSONCPP_OSTREAM& out, const Value& root); + void write(OStream& out, const Value& root); private: void writeValue(const Value& value); void writeArrayValue(const Value& value); - bool isMultineArray(const Value& value); - void pushValue(const JSONCPP_STRING& value); + bool isMultilineArray(const Value& value); + void pushValue(const String& value); void writeIndent(); - void writeWithIndent(const JSONCPP_STRING& value); + void writeWithIndent(const String& value); void indent(); void unindent(); void writeCommentBeforeValue(const Value& root); void writeCommentAfterValueOnSameLine(const Value& root); - bool hasCommentForValue(const Value& value); - static JSONCPP_STRING normalizeEOL(const JSONCPP_STRING& text); + static bool hasCommentForValue(const Value& value); + static String normalizeEOL(const String& text); - typedef std::vector ChildValues; + using ChildValues = std::vector; ChildValues childValues_; - JSONCPP_OSTREAM* document_; - JSONCPP_STRING indentString_; - unsigned int rightMargin_; - JSONCPP_STRING indentation_; + OStream* document_; + String indentString_; + unsigned int rightMargin_{74}; + String indentation_; bool addChildValues_ : 1; bool indented_ : 1; }; +#if defined(_MSC_VER) +#pragma warning(pop) +#endif #if defined(JSON_HAS_INT64) -JSONCPP_STRING JSON_API valueToString(Int value); -JSONCPP_STRING JSON_API valueToString(UInt value); +String JSON_API valueToString(Int value); +String JSON_API valueToString(UInt value); #endif // if defined(JSON_HAS_INT64) -JSONCPP_STRING JSON_API valueToString(LargestInt value); -JSONCPP_STRING JSON_API valueToString(LargestUInt value); -JSONCPP_STRING JSON_API valueToString(double value); -JSONCPP_STRING JSON_API valueToString(bool value); -JSONCPP_STRING JSON_API valueToQuotedString(const char* value); +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(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>>() -JSON_API JSONCPP_OSTREAM& operator<<(JSONCPP_OSTREAM&, const Value& root); +JSON_API OStream& operator<<(OStream&, const Value& root); } // namespace Json 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/makefiles/msvc2010/jsoncpp.sln b/makefiles/msvc2010/jsoncpp.sln deleted file mode 100644 index c4ecb9070..000000000 --- a/makefiles/msvc2010/jsoncpp.sln +++ /dev/null @@ -1,42 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lib_json", "lib_json.vcxproj", "{1E6C2C1C-6453-4129-AE3F-0EE8E6599C89}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jsontest", "jsontest.vcxproj", "{25AF2DD2-D396-4668-B188-488C33B8E620}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test_lib_json", "test_lib_json.vcxproj", "{B7A96B78-2782-40D2-8F37-A2DEF2B9C26D}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Win32 = Debug|Win32 - Debug|x64 = Debug|x64 - Release|Win32 = Release|Win32 - Release|x64 = Release|x64 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {1E6C2C1C-6453-4129-AE3F-0EE8E6599C89}.Debug|Win32.ActiveCfg = Debug|Win32 - {1E6C2C1C-6453-4129-AE3F-0EE8E6599C89}.Debug|Win32.Build.0 = Debug|Win32 - {1E6C2C1C-6453-4129-AE3F-0EE8E6599C89}.Debug|x64.ActiveCfg = Debug|x64 - {1E6C2C1C-6453-4129-AE3F-0EE8E6599C89}.Debug|x64.Build.0 = Debug|x64 - {1E6C2C1C-6453-4129-AE3F-0EE8E6599C89}.Release|Win32.ActiveCfg = Release|Win32 - {1E6C2C1C-6453-4129-AE3F-0EE8E6599C89}.Release|Win32.Build.0 = Release|Win32 - {1E6C2C1C-6453-4129-AE3F-0EE8E6599C89}.Release|x64.ActiveCfg = Release|x64 - {1E6C2C1C-6453-4129-AE3F-0EE8E6599C89}.Release|x64.Build.0 = Release|x64 - {25AF2DD2-D396-4668-B188-488C33B8E620}.Debug|Win32.ActiveCfg = Debug|Win32 - {25AF2DD2-D396-4668-B188-488C33B8E620}.Debug|Win32.Build.0 = Debug|Win32 - {25AF2DD2-D396-4668-B188-488C33B8E620}.Debug|x64.ActiveCfg = Debug|Win32 - {25AF2DD2-D396-4668-B188-488C33B8E620}.Release|Win32.ActiveCfg = Release|Win32 - {25AF2DD2-D396-4668-B188-488C33B8E620}.Release|Win32.Build.0 = Release|Win32 - {25AF2DD2-D396-4668-B188-488C33B8E620}.Release|x64.ActiveCfg = Release|Win32 - {B7A96B78-2782-40D2-8F37-A2DEF2B9C26D}.Debug|Win32.ActiveCfg = Debug|Win32 - {B7A96B78-2782-40D2-8F37-A2DEF2B9C26D}.Debug|Win32.Build.0 = Debug|Win32 - {B7A96B78-2782-40D2-8F37-A2DEF2B9C26D}.Debug|x64.ActiveCfg = Debug|Win32 - {B7A96B78-2782-40D2-8F37-A2DEF2B9C26D}.Release|Win32.ActiveCfg = Release|Win32 - {B7A96B78-2782-40D2-8F37-A2DEF2B9C26D}.Release|Win32.Build.0 = Release|Win32 - {B7A96B78-2782-40D2-8F37-A2DEF2B9C26D}.Release|x64.ActiveCfg = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/makefiles/msvc2010/jsontest.vcxproj b/makefiles/msvc2010/jsontest.vcxproj deleted file mode 100644 index 939d440dd..000000000 --- a/makefiles/msvc2010/jsontest.vcxproj +++ /dev/null @@ -1,96 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - - {25AF2DD2-D396-4668-B188-488C33B8E620} - Win32Proj - - - - Application - MultiByte - - - Application - MultiByte - - - - - - - - - - - - - <_ProjectFileVersion>10.0.40219.1 - ../../build/vs71/debug/jsontest\ - ../../build/vs71/debug/jsontest\ - true - ../../build/vs71/release/jsontest\ - ../../build/vs71/release/jsontest\ - false - - - - Disabled - ../../include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - EnableFastChecks - MultiThreadedDebug - - - Level3 - EditAndContinue - - - $(OutDir)jsontest.exe - true - $(OutDir)jsontest.pdb - Console - MachineX86 - - - - - ../../include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - MultiThreaded - - - Level3 - ProgramDatabase - - - $(OutDir)jsontest.exe - true - Console - true - true - MachineX86 - - - - - - - - {1e6c2c1c-6453-4129-ae3f-0ee8e6599c89} - - - - - - \ No newline at end of file diff --git a/makefiles/msvc2010/jsontest.vcxproj.filters b/makefiles/msvc2010/jsontest.vcxproj.filters deleted file mode 100644 index 610b540ea..000000000 --- a/makefiles/msvc2010/jsontest.vcxproj.filters +++ /dev/null @@ -1,13 +0,0 @@ - - - - - {903591b3-ade3-4ce4-b1f9-1e175e62b014} - - - - - Source Files - - - \ No newline at end of file diff --git a/makefiles/msvc2010/lib_json.vcxproj b/makefiles/msvc2010/lib_json.vcxproj deleted file mode 100644 index 3cfd0f936..000000000 --- a/makefiles/msvc2010/lib_json.vcxproj +++ /dev/null @@ -1,143 +0,0 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - - - - - - - - - - - {1E6C2C1C-6453-4129-AE3F-0EE8E6599C89} - Win32Proj - jsoncpp - - - - StaticLibrary - true - Unicode - - - StaticLibrary - true - Unicode - - - StaticLibrary - false - true - Unicode - - - StaticLibrary - false - true - Unicode - - - - - - - - - - - - - - - - - - - - - NotUsing - Level3 - Disabled - WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) - ../../include - MultiThreadedDebug - - - Windows - true - - - - - NotUsing - Level3 - Disabled - WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) - ../../include - MultiThreadedDebug - - - Windows - true - - - - - Level3 - NotUsing - MaxSpeed - true - true - WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) - ../../include - MultiThreaded - - - Windows - true - true - true - - - - - Level3 - NotUsing - MaxSpeed - true - true - WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) - ../../include - MultiThreaded - - - Windows - true - true - true - - - - - - \ No newline at end of file diff --git a/makefiles/msvc2010/lib_json.vcxproj.filters b/makefiles/msvc2010/lib_json.vcxproj.filters deleted file mode 100644 index 63c740331..000000000 --- a/makefiles/msvc2010/lib_json.vcxproj.filters +++ /dev/null @@ -1,33 +0,0 @@ - - - - - {c110bc57-c46e-476c-97ea-84d8014f431c} - - - {ed718592-5acf-47b5-8f2b-b8224590da6a} - - - - - Source Files - - - Source Files - - - Source Files - - - - - Header Files - - - Header Files - - - Header Files - - - \ No newline at end of file diff --git a/makefiles/msvc2010/test_lib_json.vcxproj b/makefiles/msvc2010/test_lib_json.vcxproj deleted file mode 100644 index 068af613e..000000000 --- a/makefiles/msvc2010/test_lib_json.vcxproj +++ /dev/null @@ -1,109 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - - {B7A96B78-2782-40D2-8F37-A2DEF2B9C26D} - test_lib_json - Win32Proj - - - - Application - MultiByte - - - Application - MultiByte - - - - - - - - - - - - - <_ProjectFileVersion>10.0.40219.1 - ../../build/vs71/debug/test_lib_json\ - ../../build/vs71/debug/test_lib_json\ - true - ../../build/vs71/release/test_lib_json\ - ../../build/vs71/release/test_lib_json\ - false - - - - Disabled - ../../include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - EnableFastChecks - MultiThreadedDebug - - - Level3 - EditAndContinue - - - $(OutDir)test_lib_json.exe - true - $(OutDir)test_lib_json.pdb - Console - MachineX86 - - - Running all unit tests - $(TargetPath) - - - - - ../../include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - MultiThreaded - - - Level3 - ProgramDatabase - - - $(OutDir)test_lib_json.exe - true - Console - true - true - MachineX86 - - - Running all unit tests - $(TargetPath) - - - - - - - - - - - - {1e6c2c1c-6453-4129-ae3f-0ee8e6599c89} - - - - - - \ No newline at end of file diff --git a/makefiles/msvc2010/test_lib_json.vcxproj.filters b/makefiles/msvc2010/test_lib_json.vcxproj.filters deleted file mode 100644 index 8f0a17b99..000000000 --- a/makefiles/msvc2010/test_lib_json.vcxproj.filters +++ /dev/null @@ -1,24 +0,0 @@ - - - - - Source Filter - - - Source Filter - - - - - {bf40cbfc-8e98-40b4-b9f3-7e8d579cbae2} - - - {5fd39074-89e6-4939-aa3f-694fefd296b1} - - - - - Header Files - - - \ No newline at end of file diff --git a/makefiles/vs71/jsoncpp.sln b/makefiles/vs71/jsoncpp.sln deleted file mode 100644 index dd2f91b44..000000000 --- a/makefiles/vs71/jsoncpp.sln +++ /dev/null @@ -1,46 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 8.00 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lib_json", "lib_json.vcproj", "{B84F7231-16CE-41D8-8C08-7B523FF4225B}" - ProjectSection(ProjectDependencies) = postProject - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jsontest", "jsontest.vcproj", "{25AF2DD2-D396-4668-B188-488C33B8E620}" - ProjectSection(ProjectDependencies) = postProject - {B84F7231-16CE-41D8-8C08-7B523FF4225B} = {B84F7231-16CE-41D8-8C08-7B523FF4225B} - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test_lib_json", "test_lib_json.vcproj", "{B7A96B78-2782-40D2-8F37-A2DEF2B9C26D}" - ProjectSection(ProjectDependencies) = postProject - {B84F7231-16CE-41D8-8C08-7B523FF4225B} = {B84F7231-16CE-41D8-8C08-7B523FF4225B} - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfiguration) = preSolution - Debug = Debug - dummy = dummy - Release = Release - EndGlobalSection - GlobalSection(ProjectConfiguration) = postSolution - {B84F7231-16CE-41D8-8C08-7B523FF4225B}.Debug.ActiveCfg = Debug|Win32 - {B84F7231-16CE-41D8-8C08-7B523FF4225B}.Debug.Build.0 = Debug|Win32 - {B84F7231-16CE-41D8-8C08-7B523FF4225B}.dummy.ActiveCfg = dummy|Win32 - {B84F7231-16CE-41D8-8C08-7B523FF4225B}.dummy.Build.0 = dummy|Win32 - {B84F7231-16CE-41D8-8C08-7B523FF4225B}.Release.ActiveCfg = Release|Win32 - {B84F7231-16CE-41D8-8C08-7B523FF4225B}.Release.Build.0 = Release|Win32 - {25AF2DD2-D396-4668-B188-488C33B8E620}.Debug.ActiveCfg = Debug|Win32 - {25AF2DD2-D396-4668-B188-488C33B8E620}.Debug.Build.0 = Debug|Win32 - {25AF2DD2-D396-4668-B188-488C33B8E620}.dummy.ActiveCfg = Debug|Win32 - {25AF2DD2-D396-4668-B188-488C33B8E620}.dummy.Build.0 = Debug|Win32 - {25AF2DD2-D396-4668-B188-488C33B8E620}.Release.ActiveCfg = Release|Win32 - {25AF2DD2-D396-4668-B188-488C33B8E620}.Release.Build.0 = Release|Win32 - {B7A96B78-2782-40D2-8F37-A2DEF2B9C26D}.Debug.ActiveCfg = Debug|Win32 - {B7A96B78-2782-40D2-8F37-A2DEF2B9C26D}.Debug.Build.0 = Debug|Win32 - {B7A96B78-2782-40D2-8F37-A2DEF2B9C26D}.dummy.ActiveCfg = Debug|Win32 - {B7A96B78-2782-40D2-8F37-A2DEF2B9C26D}.dummy.Build.0 = Debug|Win32 - {B7A96B78-2782-40D2-8F37-A2DEF2B9C26D}.Release.ActiveCfg = Release|Win32 - {B7A96B78-2782-40D2-8F37-A2DEF2B9C26D}.Release.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - EndGlobalSection - GlobalSection(ExtensibilityAddIns) = postSolution - EndGlobalSection -EndGlobal diff --git a/makefiles/vs71/jsontest.vcproj b/makefiles/vs71/jsontest.vcproj deleted file mode 100644 index 562c71f61..000000000 --- a/makefiles/vs71/jsontest.vcproj +++ /dev/null @@ -1,119 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/makefiles/vs71/lib_json.vcproj b/makefiles/vs71/lib_json.vcproj deleted file mode 100644 index 24c5dd411..000000000 --- a/makefiles/vs71/lib_json.vcproj +++ /dev/null @@ -1,205 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/makefiles/vs71/test_lib_json.vcproj b/makefiles/vs71/test_lib_json.vcproj deleted file mode 100644 index 9ebb986a6..000000000 --- a/makefiles/vs71/test_lib_json.vcproj +++ /dev/null @@ -1,130 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/makerelease.py b/makerelease.py deleted file mode 100644 index 6f9a2f24f..000000000 --- a/makerelease.py +++ /dev/null @@ -1,390 +0,0 @@ -# Copyright 2010 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 developping/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 tarbal 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 new file mode 100644 index 000000000..2648c3071 --- /dev/null +++ b/meson.build @@ -0,0 +1,157 @@ +project( + 'jsoncpp', + 'cpp', + + # 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', + default_options : [ + 'buildtype=release', + 'cpp_std=c++11', + 'warning_level=1'], + license : 'Public Domain', + meson_version : '>= 0.54.0') + + +jsoncpp_headers = files([ + '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', +]) +jsoncpp_include_directories = include_directories('include') + +install_headers( + jsoncpp_headers, + subdir : 'json') + +if get_option('default_library') == 'shared' and meson.get_compiler('cpp').get_id() == 'msvc' + dll_export_flag = '-DJSON_DLL_BUILD' + dll_import_flag = '-DJSON_DLL' +else + dll_export_flag = [] + dll_import_flag = [] +endif + +jsoncpp_lib = library( + 'jsoncpp', files([ + 'src/lib_json/json_reader.cpp', + 'src/lib_json/json_value.cpp', + 'src/lib_json/json_writer.cpp', + ]), + soversion : 27, + install : true, + include_directories : jsoncpp_include_directories, + cpp_args: dll_export_flag) + +import('pkgconfig').generate( + libraries : jsoncpp_lib, + version : meson.project_version(), + name : 'jsoncpp', + 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()) + +# tests +if meson.is_subproject() or not get_option('tests') + subdir_done() +endif + +python = find_program('python3') + +jsoncpp_test = executable( + 'jsoncpp_test', files([ + 'src/test_lib_json/jsontest.cpp', + 'src/test_lib_json/main.cpp', + 'src/test_lib_json/fuzz.cpp', + ]), + include_directories : jsoncpp_include_directories, + link_with : jsoncpp_lib, + install : false, + cpp_args: dll_import_flag) +test( + 'unittest_jsoncpp_test', + jsoncpp_test) + +jsontestrunner = executable( + 'jsontestrunner', + 'src/jsontestrunner/main.cpp', + include_directories : jsoncpp_include_directories, + link_with : jsoncpp_lib, + install : false, + cpp_args: dll_import_flag) +test( + 'unittest_jsontestrunner', + python, + args : [ + '-B', + join_paths(meson.current_source_dir(), 'test/runjsontests.py'), + jsontestrunner, + 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/scons-tools/globtool.py b/scons-tools/globtool.py deleted file mode 100644 index 1aa73fc79..000000000 --- a/scons-tools/globtool.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright 2009 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 - -import fnmatch -import os - -def generate(env): - def Glob(env, includes = None, excludes = None, dir = '.'): - """Adds Glob(includes = Split('*'), excludes = None, dir = '.') - helper function to environment. - - Glob both the file-system files. - - includes: list of file name pattern included in the return list when matched. - excludes: list of file name pattern exluced from the return list. - - Example: - sources = env.Glob(("*.cpp", '*.h'), "~*.cpp", "#src") - """ - def filterFilename(path): - abs_path = os.path.join(dir, path) - if not os.path.isfile(abs_path): - return 0 - fn = os.path.basename(path) - match = 0 - for include in includes: - if fnmatch.fnmatchcase(fn, include): - match = 1 - break - if match == 1 and not excludes is None: - for exclude in excludes: - if fnmatch.fnmatchcase(fn, exclude): - match = 0 - break - return match - if includes is None: - includes = ('*',) - elif type(includes) in (type(''), type(u'')): - includes = (includes,) - if type(excludes) in (type(''), type(u'')): - excludes = (excludes,) - dir = env.Dir(dir).abspath - paths = os.listdir(dir) - def makeAbsFileNode(path): - return env.File(os.path.join(dir, path)) - nodes = filter(filterFilename, paths) - return map(makeAbsFileNode, nodes) - - from SCons.Script import Environment - Environment.Glob = Glob - -def exists(env): - """ - Tool always exists. - """ - return True diff --git a/scons-tools/srcdist.py b/scons-tools/srcdist.py deleted file mode 100644 index 3c9b1418e..000000000 --- a/scons-tools/srcdist.py +++ /dev/null @@ -1,183 +0,0 @@ -# Copyright 2007 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 - -import os -import os.path -from fnmatch import fnmatch -import targz - -##def DoxyfileParse(file_contents): -## """ -## Parse a Doxygen source file and return a dictionary of all the values. -## Values will be strings and lists of strings. -## """ -## data = {} -## -## import shlex -## lex = shlex.shlex(instream = file_contents, posix = True) -## lex.wordchars += "*+./-:" -## lex.whitespace = lex.whitespace.replace("\n", "") -## lex.escape = "" -## -## lineno = lex.lineno -## last_backslash_lineno = lineno -## token = lex.get_token() -## key = token # the first token should be a key -## last_token = "" -## key_token = False -## next_key = False -## new_data = True -## -## def append_data(data, key, new_data, token): -## if new_data or len(data[key]) == 0: -## data[key].append(token) -## else: -## data[key][-1] += token -## -## while token: -## if token in ['\n']: -## if last_token not in ['\\']: -## key_token = True -## elif token in ['\\']: -## pass -## elif key_token: -## key = token -## key_token = False -## else: -## if token == "+=": -## if not data.has_key(key): -## data[key] = list() -## elif token == "=": -## data[key] = list() -## else: -## append_data(data, key, new_data, token) -## new_data = True -## -## last_token = token -## token = lex.get_token() -## -## if last_token == '\\' and token != '\n': -## new_data = False -## append_data(data, key, new_data, '\\') -## -## # compress lists of len 1 into single strings -## for (k, v) in data.items(): -## if len(v) == 0: -## data.pop(k) -## -## # items in the following list will be kept as lists and not converted to strings -## if k in ["INPUT", "FILE_PATTERNS", "EXCLUDE_PATTERNS"]: -## continue -## -## if len(v) == 1: -## data[k] = v[0] -## -## return data -## -##def DoxySourceScan(node, env, path): -## """ -## Doxygen Doxyfile source scanner. This should scan the Doxygen file and add -## any files used to generate docs to the list of source files. -## """ -## default_file_patterns = [ -## '*.c', '*.cc', '*.cxx', '*.cpp', '*.c++', '*.java', '*.ii', '*.ixx', -## '*.ipp', '*.i++', '*.inl', '*.h', '*.hh ', '*.hxx', '*.hpp', '*.h++', -## '*.idl', '*.odl', '*.cs', '*.php', '*.php3', '*.inc', '*.m', '*.mm', -## '*.py', -## ] -## -## default_exclude_patterns = [ -## '*~', -## ] -## -## sources = [] -## -## data = DoxyfileParse(node.get_contents()) -## -## if data.get("RECURSIVE", "NO") == "YES": -## recursive = True -## else: -## recursive = False -## -## file_patterns = data.get("FILE_PATTERNS", default_file_patterns) -## exclude_patterns = data.get("EXCLUDE_PATTERNS", default_exclude_patterns) -## -## for node in data.get("INPUT", []): -## if os.path.isfile(node): -## sources.add(node) -## elif os.path.isdir(node): -## if recursive: -## for root, dirs, files in os.walk(node): -## for f in files: -## filename = os.path.join(root, f) -## -## pattern_check = reduce(lambda x, y: x or bool(fnmatch(filename, y)), file_patterns, False) -## exclude_check = reduce(lambda x, y: x and fnmatch(filename, y), exclude_patterns, True) -## -## if pattern_check and not exclude_check: -## sources.append(filename) -## else: -## for pattern in file_patterns: -## sources.extend(glob.glob("/".join([node, pattern]))) -## sources = map(lambda path: env.File(path), sources) -## return sources -## -## -##def DoxySourceScanCheck(node, env): -## """Check if we should scan this file""" -## return os.path.isfile(node.path) - -def srcDistEmitter(source, target, env): -## """Doxygen Doxyfile emitter""" -## # possible output formats and their default values and output locations -## output_formats = { -## "HTML": ("YES", "html"), -## "LATEX": ("YES", "latex"), -## "RTF": ("NO", "rtf"), -## "MAN": ("YES", "man"), -## "XML": ("NO", "xml"), -## } -## -## data = DoxyfileParse(source[0].get_contents()) -## -## targets = [] -## out_dir = data.get("OUTPUT_DIRECTORY", ".") -## -## # add our output locations -## for (k, v) in output_formats.items(): -## if data.get("GENERATE_" + k, v[0]) == "YES": -## targets.append(env.Dir(os.path.join(out_dir, data.get(k + "_OUTPUT", v[1])))) -## -## # don't clobber targets -## for node in targets: -## env.Precious(node) -## -## # set up cleaning stuff -## for node in targets: -## env.Clean(node, node) -## -## return (targets, source) - return (target,source) - -def generate(env): - """ - Add builders and construction variables for the - SrcDist tool. - """ -## doxyfile_scanner = env.Scanner(## DoxySourceScan, -## "DoxySourceScan", -## scan_check = DoxySourceScanCheck, -##) - - if targz.exists(env): - srcdist_builder = targz.makeBuilder(srcDistEmitter) - - env['BUILDERS']['SrcDist'] = srcdist_builder - -def exists(env): - """ - Make sure srcdist exists. - """ - return targz.exists(env) diff --git a/scons-tools/substinfile.py b/scons-tools/substinfile.py deleted file mode 100644 index 5d235e7ee..000000000 --- a/scons-tools/substinfile.py +++ /dev/null @@ -1,85 +0,0 @@ -# Copyright 2010 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 - -import re -from SCons.Script import * # the usual scons stuff you get in a SConscript -import collections - -def generate(env): - """ - Add builders and construction variables for the - SubstInFile tool. - - Adds SubstInFile builder, which substitutes the keys->values of SUBST_DICT - from the source to the target. - The values of SUBST_DICT first have any construction variables expanded - (its keys are not expanded). - If a value of SUBST_DICT is a python callable function, it is called and - the result is expanded as the value. - If there's more than one source and more than one target, each target gets - substituted from the corresponding source. - """ - def do_subst_in_file(targetfile, sourcefile, dict): - """Replace all instances of the keys of dict with their values. - For example, if dict is {'%VERSION%': '1.2345', '%BASE%': 'MyProg'}, - then all instances of %VERSION% in the file will be replaced with 1.2345 etc. - """ - try: - f = open(sourcefile, 'rb') - contents = f.read() - f.close() - except: - raise SCons.Errors.UserError("Can't read source file %s"%sourcefile) - for (k,v) in list(dict.items()): - contents = re.sub(k, v, contents) - try: - f = open(targetfile, 'wb') - f.write(contents) - f.close() - except: - raise SCons.Errors.UserError("Can't write target file %s"%targetfile) - return 0 # success - - def subst_in_file(target, source, env): - if 'SUBST_DICT' not in env: - raise SCons.Errors.UserError("SubstInFile requires SUBST_DICT to be set.") - d = dict(env['SUBST_DICT']) # copy it - for (k,v) in list(d.items()): - if isinstance(v, collections.Callable): - d[k] = env.subst(v()).replace('\\','\\\\') - elif SCons.Util.is_String(v): - d[k] = env.subst(v).replace('\\','\\\\') - else: - raise SCons.Errors.UserError("SubstInFile: key %s: %s must be a string or callable"%(k, repr(v))) - for (t,s) in zip(target, source): - return do_subst_in_file(str(t), str(s), d) - - def subst_in_file_string(target, source, env): - """This is what gets printed on the console.""" - return '\n'.join(['Substituting vars from %s into %s'%(str(s), str(t)) - for (t,s) in zip(target, source)]) - - def subst_emitter(target, source, env): - """Add dependency from substituted SUBST_DICT to target. - Returns original target, source tuple unchanged. - """ - d = env['SUBST_DICT'].copy() # copy it - for (k,v) in list(d.items()): - if isinstance(v, collections.Callable): - d[k] = env.subst(v()) - elif SCons.Util.is_String(v): - d[k]=env.subst(v) - Depends(target, SCons.Node.Python.Value(d)) - return target, source - -## env.Append(TOOLS = 'substinfile') # this should be automaticaly done by Scons ?!? - subst_action = SCons.Action.Action(subst_in_file, subst_in_file_string) - env['BUILDERS']['SubstInFile'] = Builder(action=subst_action, emitter=subst_emitter) - -def exists(env): - """ - Make sure tool exists. - """ - return True diff --git a/scons-tools/targz.py b/scons-tools/targz.py deleted file mode 100644 index 9e2e8d468..000000000 --- a/scons-tools/targz.py +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright 2007 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 - -"""tarball - -Tool-specific initialization for tarball. - -""" - -## Commands to tackle a command based implementation: -##to unpack on the fly... -##gunzip < FILE.tar.gz | tar xvf - -##to pack on the fly... -##tar cvf - FILE-LIST | gzip -c > FILE.tar.gz - -import os.path - -import SCons.Builder -import SCons.Node.FS -import SCons.Util - -try: - import gzip - import tarfile - internal_targz = 1 -except ImportError: - internal_targz = 0 - -TARGZ_DEFAULT_COMPRESSION_LEVEL = 9 - -if internal_targz: - def targz(target, source, env): - def archive_name(path): - path = os.path.normpath(os.path.abspath(path)) - common_path = os.path.commonprefix((base_dir, path)) - archive_name = path[len(common_path):] - return archive_name - - def visit(tar, dirname, names): - for name in names: - path = os.path.join(dirname, name) - if os.path.isfile(path): - tar.add(path, archive_name(path)) - compression = env.get('TARGZ_COMPRESSION_LEVEL',TARGZ_DEFAULT_COMPRESSION_LEVEL) - base_dir = os.path.normpath(env.get('TARGZ_BASEDIR', env.Dir('.')).abspath) - target_path = str(target[0]) - fileobj = gzip.GzipFile(target_path, 'wb', compression) - tar = tarfile.TarFile(os.path.splitext(target_path)[0], 'w', fileobj) - for source in source: - source_path = str(source) - if source.isdir(): - os.path.walk(source_path, visit, tar) - else: - tar.add(source_path, archive_name(source_path)) # filename, arcname - tar.close() - - targzAction = SCons.Action.Action(targz, varlist=['TARGZ_COMPRESSION_LEVEL','TARGZ_BASEDIR']) - - def makeBuilder(emitter = None): - return SCons.Builder.Builder(action = SCons.Action.Action('$TARGZ_COM', '$TARGZ_COMSTR'), - source_factory = SCons.Node.FS.Entry, - source_scanner = SCons.Defaults.DirScanner, - suffix = '$TARGZ_SUFFIX', - multi = 1) - TarGzBuilder = makeBuilder() - - def generate(env): - """Add Builders and construction variables for zip to an Environment. - The following environnement variables may be set: - TARGZ_COMPRESSION_LEVEL: integer, [0-9]. 0: no compression, 9: best compression (same as gzip compression level). - TARGZ_BASEDIR: base-directory used to determine archive name (this allow archive name to be relative - to something other than top-dir). - """ - env['BUILDERS']['TarGz'] = TarGzBuilder - env['TARGZ_COM'] = targzAction - env['TARGZ_COMPRESSION_LEVEL'] = TARGZ_DEFAULT_COMPRESSION_LEVEL # range 0-9 - env['TARGZ_SUFFIX'] = '.tar.gz' - env['TARGZ_BASEDIR'] = env.Dir('.') # Sources archive name are made relative to that directory. -else: - def generate(env): - pass - - -def exists(env): - return internal_targz diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ca8ac15e2..0f82a7463 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,5 +1,5 @@ -ADD_SUBDIRECTORY(lib_json) -IF(JSONCPP_WITH_TESTS) - ADD_SUBDIRECTORY(jsontestrunner) - ADD_SUBDIRECTORY(test_lib_json) -ENDIF() +add_subdirectory(lib_json) +if(JSONCPP_WITH_TESTS) + add_subdirectory(jsontestrunner) + add_subdirectory(test_lib_json) +endif() diff --git a/src/jsontestrunner/CMakeLists.txt b/src/jsontestrunner/CMakeLists.txt index 20d01e626..1fc71ea87 100644 --- a/src/jsontestrunner/CMakeLists.txt +++ b/src/jsontestrunner/CMakeLists.txt @@ -1,25 +1,51 @@ -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 - ) +add_executable(jsontestrunner_exe + main.cpp +) -IF(BUILD_SHARED_LIBS) - ADD_DEFINITIONS( -DJSON_DLL ) - TARGET_LINK_LIBRARIES(jsontestrunner_exe jsoncpp_lib) -ELSE(BUILD_SHARED_LIBS) - TARGET_LINK_LIBRARIES(jsontestrunner_exe jsoncpp_lib_static) -ENDIF() +if(BUILD_SHARED_LIBS) + 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() -SET_TARGET_PROPERTIES(jsontestrunner_exe PROPERTIES OUTPUT_NAME jsontestrunner_exe) +set_target_properties(jsontestrunner_exe PROPERTIES OUTPUT_NAME jsontestrunner_exe) -IF(PYTHONINTERP_FOUND) +if(PYTHONINTERP_FOUND) # Run end to end parser/writer tests - SET(TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../test) - SET(RUNJSONTESTS_PATH ${TEST_DIR}/runjsontests.py) - ADD_CUSTOM_TARGET(jsoncpp_readerwriter_tests - "${PYTHON_EXECUTABLE}" -B "${RUNJSONTESTS_PATH}" $ "${TEST_DIR}/data" - DEPENDS jsontestrunner_exe jsoncpp_test - ) - ADD_CUSTOM_TARGET(jsoncpp_check DEPENDS jsoncpp_readerwriter_tests) -ENDIF() + set(TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../test) + set(RUNJSONTESTS_PATH ${TEST_DIR}/runjsontests.py) + + # 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 + ) + 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" + ) + add_test(NAME jsoncpp_readerwriter_json_checker + 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 45db464a1..ab6a80039 100644 --- a/src/jsontestrunner/main.cpp +++ b/src/jsontestrunner/main.cpp @@ -1,49 +1,49 @@ -// Copyright 2007-2010 Baptiste Lepilleur +// 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 +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif defined(_MSC_VER) +#pragma warning(disable : 4996) +#endif + /* This executable is used for testing parser/writer using real JSON files. */ -#include #include // sort +#include +#include +#include +#include #include -#include - -#if defined(_MSC_VER) && _MSC_VER >= 1310 -#pragma warning(disable : 4996) // disable fopen deprecation warning -#endif -struct Options -{ - JSONCPP_STRING path; +struct Options { + Json::String path; Json::Features features; bool parseOnly; - typedef JSONCPP_STRING (*writeFuncType)(Json::Value const&); + using writeFuncType = Json::String (*)(Json::Value const&); writeFuncType write; }; -static JSONCPP_STRING normalizeFloatingPointStr(double value) { +static Json::String normalizeFloatingPointStr(double value) { char buffer[32]; -#if defined(_MSC_VER) && defined(__STDC_SECURE_LIB__) - sprintf_s(buffer, sizeof(buffer), "%.16g", value); -#else - snprintf(buffer, sizeof(buffer), "%.16g", value); -#endif + jsoncpp_snprintf(buffer, sizeof(buffer), "%.16g", value); buffer[sizeof(buffer) - 1] = 0; - JSONCPP_STRING s(buffer); - JSONCPP_STRING::size_type index = s.find_last_of("eE"); - if (index != JSONCPP_STRING::npos) { - JSONCPP_STRING::size_type hasSign = + Json::String s(buffer); + 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; - JSONCPP_STRING::size_type exponentStartIndex = index + 1 + hasSign; - JSONCPP_STRING normalized = s.substr(0, exponentStartIndex); - JSONCPP_STRING::size_type indexDigit = + 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); - JSONCPP_STRING exponent = "0"; - if (indexDigit != - JSONCPP_STRING::npos) // There is an exponent different from 0 + Json::String exponent = "0"; + if (indexDigit != Json::String::npos) // There is an exponent different + // from 0 { exponent = s.substr(indexDigit); } @@ -52,17 +52,17 @@ static JSONCPP_STRING normalizeFloatingPointStr(double value) { return s; } -static JSONCPP_STRING readInputTestFile(const char* path) { +static Json::String readInputTestFile(const char* path) { FILE* file = fopen(path, "rb"); if (!file) - return JSONCPP_STRING(""); + return ""; fseek(file, 0, SEEK_END); - long const size = ftell(file); - unsigned long const usize = static_cast(size); + auto const size = ftell(file); + auto const usize = static_cast(size); fseek(file, 0, SEEK_SET); - JSONCPP_STRING text; - 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) text = buffer; fclose(file); @@ -70,8 +70,8 @@ static JSONCPP_STRING readInputTestFile(const char* path) { return text; } -static void -printValueTree(FILE* fout, Json::Value& value, const JSONCPP_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()); } @@ -80,21 +80,15 @@ printValueTree(FILE* fout, Json::Value& value, const JSONCPP_STRING& path = ".") fprintf(fout, "%s=null\n", path.c_str()); break; case Json::intValue: - fprintf(fout, - "%s=%s\n", - path.c_str(), + fprintf(fout, "%s=%s\n", path.c_str(), Json::valueToString(value.asLargestInt()).c_str()); break; case Json::uintValue: - fprintf(fout, - "%s=%s\n", - path.c_str(), + fprintf(fout, "%s=%s\n", path.c_str(), Json::valueToString(value.asLargestUInt()).c_str()); break; case Json::realValue: - fprintf(fout, - "%s=%s\n", - path.c_str(), + fprintf(fout, "%s=%s\n", path.c_str(), normalizeFloatingPointStr(value.asDouble()).c_str()); break; case Json::stringValue: @@ -108,11 +102,7 @@ printValueTree(FILE* fout, Json::Value& value, const JSONCPP_STRING& path = ".") Json::ArrayIndex size = value.size(); for (Json::ArrayIndex index = 0; index < size; ++index) { static char buffer[16]; -#if defined(_MSC_VER) && defined(__STDC_SECURE_LIB__) - sprintf_s(buffer, sizeof(buffer), "[%d]", index); -#else - snprintf(buffer, sizeof(buffer), "[%d]", index); -#endif + jsoncpp_snprintf(buffer, sizeof(buffer), "[%u]", index); printValueTree(fout, value[index], path + buffer); } } break; @@ -120,11 +110,8 @@ printValueTree(FILE* fout, Json::Value& value, const JSONCPP_STRING& path = ".") fprintf(fout, "%s={}\n", path.c_str()); Json::Value::Members members(value.getMemberNames()); std::sort(members.begin(), members.end()); - JSONCPP_STRING suffix = *(path.end() - 1) == '.' ? "" : "."; - for (Json::Value::Members::iterator it = members.begin(); - it != members.end(); - ++it) { - const JSONCPP_STRING name = *it; + Json::String suffix = *(path.end() - 1) == '.' ? "" : "."; + for (const auto& name : members) { printValueTree(fout, value[name], path + suffix + name); } } break; @@ -137,25 +124,49 @@ printValueTree(FILE* fout, Json::Value& value, const JSONCPP_STRING& path = ".") } } -static int parseAndSaveValueTree(const JSONCPP_STRING& input, - const JSONCPP_STRING& actual, - const JSONCPP_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; +static int parseAndSaveValueTree(const Json::String& input, + const Json::String& actual, + const Json::String& kind, + 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); @@ -163,41 +174,33 @@ static int parseAndSaveValueTree(const JSONCPP_STRING& input, } return 0; } -// static JSONCPP_STRING useFastWriter(Json::Value const& root) { +// static Json::String useFastWriter(Json::Value const& root) { // Json::FastWriter writer; // writer.enableYAMLCompatibility(); // return writer.write(root); // } -static JSONCPP_STRING useStyledWriter( - Json::Value const& root) -{ +static Json::String useStyledWriter(Json::Value const& root) { Json::StyledWriter writer; return writer.write(root); } -static JSONCPP_STRING useStyledStreamWriter( - Json::Value const& root) -{ +static Json::String useStyledStreamWriter(Json::Value const& root) { Json::StyledStreamWriter writer; - JSONCPP_OSTRINGSTREAM sout; + Json::OStringStream sout; writer.write(sout, root); return sout.str(); } -static JSONCPP_STRING useBuiltStyledStreamWriter( - Json::Value const& root) -{ +static Json::String useBuiltStyledStreamWriter(Json::Value const& root) { Json::StreamWriterBuilder builder; return Json::writeString(builder, root); } -static int rewriteValueTree( - const JSONCPP_STRING& rewritePath, - const Json::Value& root, - Options::writeFuncType write, - JSONCPP_STRING* rewrite) -{ +static int rewriteValueTree(const Json::String& rewritePath, + const Json::Value& root, + Options::writeFuncType write, + Json::String* rewrite) { *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()); @@ -205,51 +208,53 @@ static int rewriteValueTree( return 0; } -static JSONCPP_STRING removeSuffix(const JSONCPP_STRING& path, - const JSONCPP_STRING& extension) { +static Json::String removeSuffix(const Json::String& path, + const Json::String& extension) { if (extension.length() >= path.length()) - return JSONCPP_STRING(""); - JSONCPP_STRING suffix = path.substr(path.length() - extension.length()); + return Json::String(""); + Json::String suffix = path.substr(path.length() - extension.length()); if (suffix != extension) - return JSONCPP_STRING(""); + return Json::String(""); return path.substr(0, path.length() - extension.length()); } 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; } -static int parseCommandLine( - int argc, const char* argv[], Options* opts) -{ +static int parseCommandLine(int argc, const char* argv[], Options* opts) { opts->parseOnly = false; opts->write = &useStyledWriter; if (argc < 2) { return printUsage(argv); } int index = 1; - if (JSONCPP_STRING(argv[index]) == "--json-checker") { - opts->features = Json::Features::strictMode(); + if (Json::String(argv[index]) == "--parse-only") { opts->parseOnly = true; ++index; } - if (JSONCPP_STRING(argv[index]) == "--json-config") { + if (Json::String(argv[index]) == "--strict") { + opts->features = Json::Features::strictMode(); + ++index; + } + if (Json::String(argv[index]) == "--json-config") { printConfig(); return 3; } - if (JSONCPP_STRING(argv[index]) == "--json-writer") { + if (Json::String(argv[index]) == "--json-writer") { ++index; - JSONCPP_STRING const writerName(argv[index++]); + Json::String const writerName(argv[index++]); if (writerName == "StyledWriter") { opts->write = &useStyledWriter; } else if (writerName == "StyledStreamWriter") { @@ -257,7 +262,7 @@ static int parseCommandLine( } 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; } } @@ -267,60 +272,75 @@ static int parseCommandLine( opts->path = argv[index]; return 0; } -static int runTest(Options const& opts) -{ + +static int runTest(Options const& opts, bool use_legacy) { int exitCode = 0; - JSONCPP_STRING input = readInputTestFile(opts.path.c_str()); + 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; } - JSONCPP_STRING basePath = removeSuffix(opts.path, ".json"); + 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; } - JSONCPP_STRING const actualPath = basePath + ".actual"; - JSONCPP_STRING const rewritePath = basePath + ".rewrite"; - JSONCPP_STRING const rewriteActualPath = basePath + ".actual-rewrite"; + Json::String const actualPath = basePath + ".actual"; + Json::String const rewritePath = basePath + ".rewrite"; + Json::String const rewriteActualPath = basePath + ".actual-rewrite"; Json::Value root; - exitCode = parseAndSaveValueTree( - input, actualPath, "input", - opts.features, opts.parseOnly, &root); + exitCode = parseAndSaveValueTree(input, actualPath, "input", opts.features, + opts.parseOnly, &root, use_legacy); if (exitCode || opts.parseOnly) { return exitCode; } - JSONCPP_STRING rewrite; + + 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; + exitCode = parseAndSaveValueTree(rewrite, rewriteActualPath, "rewrite", + 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."); - return exitCode; - } - return runTest(opts); - } - catch (const std::exception& e) { - printf("Unhandled exception:\n%s\n", e.what()); + int exitCode = parseCommandLine(argc, argv, &opts); + if (exitCode != 0) { + std::cerr << "Failed to parse command-line." << std::endl; + return exitCode; + } + + 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) { + std::cerr << "Unhandled exception:" << std::endl << e.what() << std::endl; return 1; } + return 0; } + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif diff --git a/src/jsontestrunner/sconscript b/src/jsontestrunner/sconscript deleted file mode 100644 index 6e68e3153..000000000 --- a/src/jsontestrunner/sconscript +++ /dev/null @@ -1,9 +0,0 @@ -Import( 'env_testing buildJSONTests' ) - -buildJSONTests( env_testing, Split( """ - main.cpp - """ ), - 'jsontestrunner' ) - -# For 'check' to work, 'libs' must be built first. -env_testing.Depends('jsontestrunner', '#libs') diff --git a/src/lib_json/CMakeLists.txt b/src/lib_json/CMakeLists.txt index c64aab2ef..3037eb020 100644 --- a/src/lib_json/CMakeLists.txt +++ b/src/lib_json/CMakeLists.txt @@ -1,113 +1,205 @@ -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) -INCLUDE(CheckStructHasMember) -INCLUDE(CheckCXXSymbolExists) +include(CheckIncludeFileCXX) +include(CheckTypeSize) +include(CheckStructHasMember) +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() - -IF(NOT (HAVE_CLOCALE AND HAVE_LCONV_SIZE AND HAVE_DECIMAL_POINT AND HAVE_LOCALECONV)) - MESSAGE(WARNING "Locale functionality is not supported") - ADD_DEFINITIONS(-DJSONCPP_NO_LOCALE_SUPPORT) -ENDIF() - -SET( JSONCPP_INCLUDE_DIR ../../include ) - -SET( PUBLIC_HEADERS +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") + 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(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 - ${JSONCPP_INCLUDE_DIR}/json/version.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 - version.h.in) +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) - SET(INSTALL_EXPORT) -ENDIF() - -IF(BUILD_SHARED_LIBS) - ADD_DEFINITIONS( -DJSON_DLL_BUILD ) - ADD_LIBRARY(jsoncpp_lib SHARED ${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} ) +if(JSONCPP_WITH_CMAKE_PACKAGE) + set(INSTALL_EXPORT EXPORT jsoncpp) +else() + set(INSTALL_EXPORT) +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 +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. + #MSVC 1900 cxx_attributes # Generic attributes, as defined in N2761. + cxx_auto_type # Automatic type deduction, as defined in N1984. + #MSVC 1900 cxx_constexpr # Constant expressions, as defined in N2235. + cxx_decltype # Decltype, as defined in N2343. + cxx_default_function_template_args # Default template arguments for function templates, as defined in DR226 + cxx_defaulted_functions # Defaulted functions, as defined in N2346. + #MSVC 1900 cxx_defaulted_move_initializers # Defaulted move initializers, as defined in N3053. + cxx_delegating_constructors # Delegating constructors, as defined in N1986. + #MSVC 1900 cxx_deleted_functions # Deleted functions, as defined in N2346. + cxx_enum_forward_declarations # Enum forward declarations, as defined in N2764. + cxx_explicit_conversions # Explicit conversion operators, as defined in N2437. + cxx_extended_friend_declarations # Extended friend declarations, as defined in N1791. + cxx_extern_templates # Extern templates, as defined in N1987. + cxx_final # Override control final keyword, as defined in N2928, N3206 and N3272. + #MSVC 1900 cxx_func_identifier # Predefined __func__ identifier, as defined in N2340. + #MSVC 1900 cxx_generalized_initializers # Initializer lists, as defined in N2672. + #MSVC 1900 cxx_inheriting_constructors # Inheriting constructors, as defined in N2540. + #MSVC 1900 cxx_inline_namespaces # Inline namespaces, as defined in N2535. + cxx_lambdas # Lambda functions, as defined in N2927. + #MSVC 1900 cxx_local_type_template_args # Local and unnamed types as template arguments, as defined in N2657. + cxx_long_long_type # long long type, as defined in N1811. + #MSVC 1900 cxx_noexcept # Exception specifications, as defined in N3050. + #MSVC 1900 cxx_nonstatic_member_init # Non-static data member initialization, as defined in N2756. + cxx_nullptr # Null pointer, as defined in N2431. + cxx_override # Override control override keyword, as defined in N2928, N3206 and N3272. + cxx_range_for # Range-based for, as defined in N2930. + cxx_raw_string_literals # Raw string literals, as defined in N2442. + #MSVC 1900 cxx_reference_qualified_functions # Reference qualified functions, as defined in N2439. + cxx_right_angle_brackets # Right angle bracket parsing, as defined in N1757. + cxx_rvalue_references # R-value references, as defined in N2118. + #MSVC 1900 cxx_sizeof_member # Size of non-static data members, as defined in N2253. + cxx_static_assert # Static assert, as defined in N1720. + cxx_strong_enums # Strongly typed enums, as defined in N2347. + #MSVC 1900 cxx_thread_local # Thread-local variables, as defined in N2659. + cxx_trailing_return_types # Automatic function return type, as defined in N2541. + #MSVC 1900 cxx_unicode_literals # Unicode string literals, as defined in N2442. + cxx_uniform_initialization # Uniform initialization, as defined in N2640. + #MSVC 1900 cxx_unrestricted_unions # Unrestricted unions, as defined in N2544. + #MSVC 1900 cxx_user_literals # User-defined literals, as defined in N2765. + cxx_variadic_macros # Variadic macros, as defined in N1653. + cxx_variadic_templates # Variadic templates, as defined in N2242. +) + + +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( jsoncpp_lib PROPERTIES INSTALL_RPATH "@loader_path/." ) - ENDIF() - - 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 - $ - $) - ENDIF() - -ENDIF() - -IF(BUILD_STATIC_LIBS) - ADD_LIBRARY(jsoncpp_lib_static STATIC ${PUBLIC_HEADERS} ${jsoncpp_sources}) - SET_TARGET_PROPERTIES( jsoncpp_lib_static PROPERTIES VERSION ${JSONCPP_VERSION} SOVERSION ${JSONCPP_SOVERSION}) - SET_TARGET_PROPERTIES( jsoncpp_lib_static PROPERTIES OUTPUT_NAME jsoncpp - DEBUG_OUTPUT_NAME jsoncpp${DEBUG_LIBNAME_SUFFIX} ) - - INSTALL( TARGETS jsoncpp_lib_static ${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_static PUBLIC - $ - $ - ) - ENDIF() - -ENDIF() + 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 ec7907525..5b6299906 100644 --- a/src/lib_json/json_reader.cpp +++ b/src/lib_json/json_reader.cpp @@ -1,73 +1,59 @@ -// Copyright 2007-2011 Baptiste Lepilleur +// Copyright 2007-2011 Baptiste Lepilleur and The JsonCpp Authors // Copyright (C) 2016 InfoTeCS JSC. All rights reserved. // 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 #if !defined(JSON_IS_AMALGAMATION) +#include "json_tool.h" #include #include #include -#include "json_tool.h" #endif // if !defined(JSON_IS_AMALGAMATION) -#include -#include +#include #include +#include #include +#include #include -#include +#include #include #include -#include +#include +#include -#if defined(_MSC_VER) -#if !defined(WINCE) && defined(__STDC_SECURE_LIB__) && _MSC_VER >= 1500 // VC++ 9.0 and above -#define snprintf sprintf_s -#elif _MSC_VER >= 1900 // VC++ 14.0 and above -#define snprintf std::snprintf -#else -#define snprintf _snprintf -#endif -#elif defined(__ANDROID__) || defined(__QNXNTO__) -#define snprintf snprintf -#elif __cplusplus >= 201103L -#if !defined(__MINGW32__) && !defined(__CYGWIN__) -#define snprintf std::snprintf -#endif -#endif +#include -#if defined(__QNXNTO__) -#define sscanf std::sscanf -#endif +#if defined(_MSC_VER) +#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(_MSC_VER) && _MSC_VER >= 1400 // VC++ 8.0 +#if defined(_MSC_VER) // Disable warning about strdup being deprecated. #pragma warning(disable : 4996) #endif -// Define JSONCPP_DEPRECATED_STACK_LIMIT as an appropriate integer at compile time to change the stack limit +// Define JSONCPP_DEPRECATED_STACK_LIMIT as an appropriate integer at compile +// time to change the stack limit #if !defined(JSONCPP_DEPRECATED_STACK_LIMIT) #define JSONCPP_DEPRECATED_STACK_LIMIT 1000 #endif -static size_t const stackLimit_g = JSONCPP_DEPRECATED_STACK_LIMIT; // see readValue() +static size_t const stackLimit_g = + JSONCPP_DEPRECATED_STACK_LIMIT; // see readValue() 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 // //////////////////////////////// -Features::Features() - : allowComments_(true), strictRoot_(false), - allowDroppedNullPlaceholders_(false), allowNumericKeys_(false) {} +Features::Features() = default; -Features Features::all() { return Features(); } +Features Features::all() { return {}; } Features Features::strictMode() { Features features; @@ -81,51 +67,38 @@ Features Features::strictMode() { // Implementation of class Reader // //////////////////////////////// -static bool containsNewLine(Reader::Location begin, Reader::Location end) { - for (; begin < end; ++begin) - if (*begin == '\n' || *begin == '\r') - return true; - return false; +bool Reader::containsNewLine(Reader::Location begin, Reader::Location end) { + return std::any_of(begin, end, [](char b) { return b == '\n' || b == '\r'; }); } // Class Reader // ////////////////////////////////////////////////////////////////// -Reader::Reader() - : errors_(), document_(), begin_(), end_(), current_(), lastValueEnd_(), - lastValue_(), commentsBefore_(), features_(Features::all()), - collectComments_() {} +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 collectComments) { - JSONCPP_STRING documentCopy(document.data(), document.data() + document.capacity()); - std::swap(documentCopy, document_); +bool Reader::parse(const std::string& document, Value& root, + bool collectComments) { + document_.assign(document.begin(), document.end()); const char* begin = document_.c_str(); const char* end = begin + document_.length(); return parse(begin, end, root, collectComments); } -bool Reader::parse(std::istream& sin, Value& root, bool collectComments) { - // std::istream_iterator begin(sin); +bool Reader::parse(std::istream& is, Value& root, bool collectComments) { + // std::istream_iterator begin(is); // std::istream_iterator end; // Those would allow streamed input from a file, if parse() were a // template function. - // Since JSONCPP_STRING is reference-counted, this at least does not + // Since String is reference-counted, this at least does not // create an extra copy. - JSONCPP_STRING doc; - std::getline(sin, 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; @@ -135,8 +108,8 @@ bool Reader::parse(const char* beginDoc, end_ = endDoc; collectComments_ = collectComments; current_ = begin_; - lastValueEnd_ = 0; - lastValue_ = 0; + lastValueEnd_ = nullptr; + lastValue_ = nullptr; commentsBefore_.clear(); errors_.clear(); while (!nodes_.empty()) @@ -145,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_) { @@ -166,12 +139,14 @@ bool Reader::parse(const char* beginDoc, bool Reader::readValue() { // readValue() may call itself only if it calls readObject() or ReadArray(). - // These methods execute nodes_.push() just before and nodes_.pop)() just after calling readValue(). - // parse() executes one nodes_.push(), so > instead of >=. - if (nodes_.size() > stackLimit_g) throwRuntimeError("Exceeded stackLimit in readValue()."); + // These methods execute nodes_.push() just before and nodes_.pop)() just + // after calling readValue(). parse() executes one nodes_.push(), so > instead + // of >=. + if (nodes_.size() > stackLimit_g) + throwRuntimeError("Exceeded stackLimit in readValue()."); Token token; - skipCommentTokens(token); + readTokenSkippingComments(token); bool successful = true; if (collectComments_ && !commentsBefore_.empty()) { @@ -194,30 +169,24 @@ bool Reader::readValue() { case tokenString: successful = decodeString(token); break; - case tokenTrue: - { + case tokenTrue: { Value v(true); currentValue().swapPayload(v); currentValue().setOffsetStart(token.start_ - begin_); currentValue().setOffsetLimit(token.end_ - begin_); - } - break; - case tokenFalse: - { + } break; + case tokenFalse: { Value v(false); currentValue().swapPayload(v); currentValue().setOffsetStart(token.start_ - begin_); currentValue().setOffsetLimit(token.end_ - begin_); - } - break; - case tokenNull: - { + } break; + case tokenNull: { Value v; currentValue().swapPayload(v); currentValue().setOffsetStart(token.start_ - begin_); currentValue().setOffsetLimit(token.end_ - begin_); - } - break; + } break; case tokenArraySeparator: case tokenObjectEnd: case tokenArrayEnd: @@ -245,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) { @@ -323,7 +292,7 @@ bool Reader::readToken(Token& token) { if (!ok) token.type_ = tokenError; token.end_ = current_; - return true; + return ok; } void Reader::skipSpaces() { @@ -336,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; @@ -370,16 +339,16 @@ bool Reader::readComment() { return true; } -static JSONCPP_STRING normalizeEOL(Reader::Location begin, Reader::Location end) { - JSONCPP_STRING normalized; +String Reader::normalizeEOL(Reader::Location begin, Reader::Location end) { + String normalized; normalized.reserve(static_cast(end - begin)); Reader::Location current = begin; while (current != end) { char c = *current++; if (c == '\r') { if (current != end && *current == '\n') - // convert dos EOL - ++current; + // convert dos EOL + ++current; // convert Mac EOL normalized += '\n'; } else { @@ -389,12 +358,12 @@ static JSONCPP_STRING normalizeEOL(Reader::Location begin, Reader::Location end) return normalized; } -void -Reader::addComment(Location begin, Location end, CommentPlacement placement) { +void Reader::addComment(Location begin, Location end, + CommentPlacement placement) { assert(collectComments_); - const JSONCPP_STRING& normalized = normalizeEOL(begin, end); + const String& normalized = normalizeEOL(begin, end); if (placement == commentAfterOnSameLine) { - assert(lastValue_ != 0); + assert(lastValue_ != nullptr); lastValue_->setComment(normalized, placement); } else { commentsBefore_ += normalized; @@ -427,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') @@ -460,18 +429,13 @@ bool Reader::readString() { return c == '"'; } -bool Reader::readObject(Token& tokenStart) { +bool Reader::readObject(Token& token) { Token tokenName; - JSONCPP_STRING name; + String name; Value init(objectValue); currentValue().swapPayload(init); - currentValue().setOffsetStart(tokenStart.start_ - begin_); - while (readToken(tokenName)) { - bool initialTokenOk = true; - while (tokenName.type_ == tokenComment && initialTokenOk) - initialTokenOk = readToken(tokenName); - if (!initialTokenOk) - break; + currentValue().setOffsetStart(token.start_ - begin_); + while (readTokenSkippingComments(tokenName)) { if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object return true; name.clear(); @@ -482,15 +446,15 @@ bool Reader::readObject(Token& tokenStart) { Value numberName; if (!decodeNumber(tokenName, numberName)) return recoverFromError(tokenObjectEnd); - name = JSONCPP_STRING(numberName.asCString()); + name = numberName.asString(); } else { break; } Token colon; if (!readToken(colon) || colon.type_ != tokenMemberSeparator) { - return addErrorAndRecover( - "Missing ':' after object member name", colon, tokenObjectEnd); + return addErrorAndRecover("Missing ':' after object member name", colon, + tokenObjectEnd); } Value& value = currentValue()[name]; nodes_.push(&value); @@ -500,26 +464,22 @@ bool Reader::readObject(Token& tokenStart) { return recoverFromError(tokenObjectEnd); Token comma; - if (!readToken(comma) || - (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator && - comma.type_ != tokenComment)) { - return addErrorAndRecover( - "Missing ',' or '}' in object declaration", comma, tokenObjectEnd); + 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; } - return addErrorAndRecover( - "Missing '}' or object member name", tokenName, tokenObjectEnd); + return addErrorAndRecover("Missing '}' or object member name", tokenName, + tokenObjectEnd); } -bool Reader::readArray(Token& tokenStart) { +bool Reader::readArray(Token& token) { Value init(arrayValue); currentValue().swapPayload(init); - currentValue().setOffsetStart(tokenStart.start_ - begin_); + currentValue().setOffsetStart(token.start_ - begin_); skipSpaces(); if (current_ != end_ && *current_ == ']') // empty array { @@ -536,19 +496,16 @@ bool Reader::readArray(Token& tokenStart) { if (!ok) // error already set return recoverFromError(tokenArrayEnd); - Token token; + Token currentToken; // Accept Comment after last item in the array. - ok = readToken(token); - while (token.type_ == tokenComment && ok) { - ok = readToken(token); - } - bool badTokenType = - (token.type_ != tokenArraySeparator && token.type_ != tokenArrayEnd); + ok = readTokenSkippingComments(currentToken); + bool badTokenType = (currentToken.type_ != tokenArraySeparator && + currentToken.type_ != tokenArrayEnd); if (!ok || badTokenType) { - return addErrorAndRecover( - "Missing ',' or ']' in array declaration", token, tokenArrayEnd); + return addErrorAndRecover("Missing ',' or ']' in array declaration", + currentToken, tokenArrayEnd); } - if (token.type_ == tokenArrayEnd) + if (currentToken.type_ == tokenArrayEnd) break; } return true; @@ -572,7 +529,8 @@ bool Reader::decodeNumber(Token& token, Value& decoded) { bool isNegative = *current == '-'; if (isNegative) ++current; - // TODO: Help the compiler do the div and mod at compile time or get rid of them. + // TODO: Help the compiler do the div and mod at compile time or get rid of + // them. Value::LargestUInt maxIntegerValue = isNegative ? Value::LargestUInt(Value::maxLargestInt) + 1 : Value::maxLargestUInt; @@ -582,7 +540,7 @@ bool Reader::decodeNumber(Token& token, Value& decoded) { Char c = *current++; if (c < '0' || c > '9') return decodeDouble(token, decoded); - Value::UInt digit(static_cast(c - '0')); + 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, b) this is the last digit, and @@ -618,18 +576,22 @@ bool Reader::decodeDouble(Token& token) { bool Reader::decodeDouble(Token& token, Value& decoded) { double value = 0; - JSONCPP_STRING buffer(token.start_, token.end_); - JSONCPP_ISTRINGSTREAM is(buffer); - if (!(is >> value)) - return addError("'" + JSONCPP_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; } bool Reader::decodeString(Token& token) { - JSONCPP_STRING decoded_string; + String decoded_string; if (!decodeString(token, decoded_string)) return false; Value decoded(decoded_string); @@ -639,7 +601,7 @@ bool Reader::decodeString(Token& token) { return true; } -bool Reader::decodeString(Token& token, JSONCPP_STRING& decoded) { +bool Reader::decodeString(Token& token, String& decoded) { decoded.reserve(static_cast(token.end_ - token.start_ - 2)); Location current = token.start_ + 1; // skip '"' Location end = token.end_ - 1; // do not include '"' @@ -647,7 +609,7 @@ bool Reader::decodeString(Token& token, JSONCPP_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++; @@ -692,10 +654,8 @@ bool Reader::decodeString(Token& token, JSONCPP_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; @@ -704,10 +664,9 @@ bool Reader::decodeUnicodeCodePoint(Token& token, if (end - current < 6) return addError( "additional six characters expected to parse unicode surrogate pair.", - token, - current); - unsigned int surrogatePair; + token, current); if (*(current++) == '\\' && *(current++) == 'u') { + unsigned int surrogatePair; if (decodeUnicodeEscapeSequence(token, current, end, surrogatePair)) { unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF); } else @@ -715,20 +674,17 @@ bool Reader::decodeUnicodeCodePoint(Token& token, } else return addError("expecting another \\u token to begin the second half of " "a unicode surrogate pair", - token, - current); + token, current); } 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) return addError( - "Bad unicode escape sequence in string: four digits expected.", - token, + "Bad unicode escape sequence in string: four digits expected.", token, current); int unicode = 0; for (int index = 0; index < 4; ++index) { @@ -743,15 +699,13 @@ bool Reader::decodeUnicodeEscapeSequence(Token& token, else return addError( "Bad unicode escape sequence in string: hexadecimal digit expected.", - token, - current); + token, current); } ret_unicode = static_cast(unicode); return true; } -bool -Reader::addError(const JSONCPP_STRING& message, Token& token, Location extra) { +bool Reader::addError(const String& message, Token& token, Location extra) { ErrorInfo info; info.token_ = token; info.message_ = message; @@ -773,8 +727,7 @@ bool Reader::recoverFromError(TokenType skipUntilToken) { return false; } -bool Reader::addErrorAndRecover(const JSONCPP_STRING& message, - Token& token, +bool Reader::addErrorAndRecover(const String& message, Token& token, TokenType skipUntilToken) { addError(message, token); return recoverFromError(skipUntilToken); @@ -788,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; @@ -797,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; @@ -811,25 +763,22 @@ void Reader::getLocationLineAndColumn(Location location, ++line; } -JSONCPP_STRING Reader::getLocationLineAndColumn(Location location) const { +String Reader::getLocationLineAndColumn(Location location) const { int line, column; getLocationLineAndColumn(location, line, column); char buffer[18 + 16 + 16 + 1]; - snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); + jsoncpp_snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); return buffer; } // Deprecated. Preserved for backward compatibility -JSONCPP_STRING Reader::getFormatedErrorMessages() const { +String Reader::getFormatedErrorMessages() const { return getFormattedErrorMessages(); } -JSONCPP_STRING Reader::getFormattedErrorMessages() const { - JSONCPP_STRING formattedMessage; - for (Errors::const_iterator itError = errors_.begin(); - itError != errors_.end(); - ++itError) { - const ErrorInfo& error = *itError; +String Reader::getFormattedErrorMessages() const { + String formattedMessage; + for (const auto& error : errors_) { formattedMessage += "* " + getLocationLineAndColumn(error.token_.start_) + "\n"; formattedMessage += " " + error.message_ + "\n"; @@ -842,10 +791,7 @@ JSONCPP_STRING Reader::getFormattedErrorMessages() const { std::vector Reader::getStructuredErrors() const { std::vector allErrors; - for (Errors::const_iterator itError = errors_.begin(); - itError != errors_.end(); - ++itError) { - const ErrorInfo& error = *itError; + for (const auto& error : errors_) { Reader::StructuredError structured; structured.offset_start = error.token_.start_ - begin_; structured.offset_limit = error.token_.end_ - begin_; @@ -855,28 +801,27 @@ std::vector Reader::getStructuredErrors() const { return allErrors; } -bool Reader::pushError(const Value& value, const JSONCPP_STRING& message) { +bool Reader::pushError(const Value& value, const String& message) { ptrdiff_t const length = end_ - begin_; - if(value.getOffsetStart() > length - || value.getOffsetLimit() > length) + if (value.getOffsetStart() > length || value.getOffsetLimit() > length) return false; Token token; token.type_ = tokenError; token.start_ = begin_ + value.getOffsetStart(); - token.end_ = end_ + value.getOffsetLimit(); + token.end_ = begin_ + value.getOffsetLimit(); ErrorInfo info; info.token_ = token; info.message_ = message; - info.extra_ = 0; + info.extra_ = nullptr; errors_.push_back(info); return true; } -bool Reader::pushError(const Value& value, const JSONCPP_STRING& message, const Value& extra) { +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 - || extra.getOffsetLimit() > length) + if (value.getOffsetStart() > length || value.getOffsetLimit() > length || + extra.getOffsetLimit() > length) return false; Token token; token.type_ = tokenError; @@ -890,15 +835,15 @@ bool Reader::pushError(const Value& value, const JSONCPP_STRING& message, const return true; } -bool Reader::good() const { - return !errors_.size(); -} +bool Reader::good() const { return errors_.empty(); } -// exact copy of Features +// Originally copied from the Features class (now deprecated), used internally +// for features implementation. class OurFeatures { public: static OurFeatures all(); bool allowComments_; + bool allowTrailingCommas_; bool strictRoot_; bool allowDroppedNullPlaceholders_; bool allowNumericKeys_; @@ -906,42 +851,31 @@ class OurFeatures { bool failIfExtra_; bool rejectDupKeys_; bool allowSpecialFloats_; - int stackLimit_; -}; // OurFeatures - -// exact copy of Implementation of class Features -// //////////////////////////////// + bool skipBom_; + size_t stackLimit_; +}; // OurFeatures -OurFeatures OurFeatures::all() { return OurFeatures(); } +OurFeatures OurFeatures::all() { return {}; } // Implementation of class Reader // //////////////////////////////// -// exact copy of Reader, renamed to OurReader +// Originally copied from the Reader class (now deprecated), used internally +// for implementing JSON reading. class OurReader { public: - typedef char Char; - typedef const Char* Location; - struct StructuredError { - ptrdiff_t offset_start; - ptrdiff_t offset_limit; - JSONCPP_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); - JSONCPP_STRING getFormattedErrorMessages() const; - std::vector getStructuredErrors() const; - bool pushError(const Value& value, const JSONCPP_STRING& message); - bool pushError(const Value& value, const JSONCPP_STRING& message, const Value& extra); - bool good() const; + String getFormattedErrorMessages() const; + std::vector getStructuredErrors() const; private: - OurReader(OurReader const&); // no impl - void operator=(OurReader const&); // no impl + OurReader(OurReader const&); // no impl + void operator=(OurReader const&); // no impl enum TokenType { tokenEndOfStream = 0, @@ -973,17 +907,19 @@ class OurReader { class ErrorInfo { public: Token token_; - JSONCPP_STRING message_; + String message_; 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(); @@ -994,58 +930,56 @@ class OurReader { bool decodeNumber(Token& token); bool decodeNumber(Token& token, Value& decoded); bool decodeString(Token& token); - bool decodeString(Token& token, JSONCPP_STRING& decoded); + 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 addError(const JSONCPP_STRING& message, Token& token, Location extra = 0); + 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 JSONCPP_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; - JSONCPP_STRING getLocationLineAndColumn(Location location) 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); - - typedef std::stack Nodes; - Nodes nodes_; - Errors errors_; - JSONCPP_STRING document_; - Location begin_; - Location end_; - Location current_; - Location lastValueEnd_; - Value* lastValue_; - JSONCPP_STRING commentsBefore_; + + static String normalizeEOL(Location begin, Location end); + static bool containsNewLine(Location begin, Location end); + + 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_; -}; // OurReader + bool collectComments_ = false; +}; // OurReader // complete copy of Read impl, for OurReader -OurReader::OurReader(OurFeatures const& features) - : errors_(), document_(), begin_(), end_(), current_(), lastValueEnd_(), - lastValue_(), commentsBefore_(), - features_(features), collectComments_() { +bool OurReader::containsNewLine(OurReader::Location begin, + OurReader::Location end) { + return std::any_of(begin, end, [](char b) { return b == '\n' || b == '\r'; }); } -bool OurReader::parse(const char* beginDoc, - const char* endDoc, - Value& root, - bool collectComments) { +OurReader::OurReader(OurFeatures const& features) : features_(features) {} + +bool OurReader::parse(const char* beginDoc, const char* endDoc, Value& root, + bool collectComments) { if (!features_.allowComments_) { collectComments = false; } @@ -1054,22 +988,23 @@ bool OurReader::parse(const char* beginDoc, end_ = endDoc; collectComments_ = collectComments; current_ = begin_; - lastValueEnd_ = 0; - lastValue_ = 0; + lastValueEnd_ = nullptr; + lastValue_ = nullptr; commentsBefore_.clear(); errors_.clear(); while (!nodes_.empty()) 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); @@ -1091,9 +1026,10 @@ bool OurReader::parse(const char* beginDoc, bool OurReader::readValue() { // To preserve the old behaviour we cast size_t to int. - if (static_cast(nodes_.size()) > features_.stackLimit_) throwRuntimeError("Exceeded stackLimit in readValue()."); + if (nodes_.size() > features_.stackLimit_) + throwRuntimeError("Exceeded stackLimit in readValue()."); Token token; - skipCommentTokens(token); + readTokenSkippingComments(token); bool successful = true; if (collectComments_ && !commentsBefore_.empty()) { @@ -1116,54 +1052,42 @@ bool OurReader::readValue() { case tokenString: successful = decodeString(token); break; - case tokenTrue: - { + case tokenTrue: { Value v(true); currentValue().swapPayload(v); currentValue().setOffsetStart(token.start_ - begin_); currentValue().setOffsetLimit(token.end_ - begin_); - } - break; - case tokenFalse: - { + } break; + case tokenFalse: { Value v(false); currentValue().swapPayload(v); currentValue().setOffsetStart(token.start_ - begin_); currentValue().setOffsetLimit(token.end_ - begin_); - } - break; - case tokenNull: - { + } break; + case tokenNull: { Value v; currentValue().swapPayload(v); currentValue().setOffsetStart(token.start_ - begin_); currentValue().setOffsetLimit(token.end_ - begin_); - } - break; - case tokenNaN: - { + } break; + case tokenNaN: { Value v(std::numeric_limits::quiet_NaN()); currentValue().swapPayload(v); currentValue().setOffsetStart(token.start_ - begin_); currentValue().setOffsetLimit(token.end_ - begin_); - } - break; - case tokenPosInf: - { + } break; + case tokenPosInf: { Value v(std::numeric_limits::infinity()); currentValue().swapPayload(v); currentValue().setOffsetStart(token.start_ - begin_); currentValue().setOffsetLimit(token.end_ - begin_); - } - break; - case tokenNegInf: - { + } break; + case tokenNegInf: { Value v(-std::numeric_limits::infinity()); currentValue().swapPayload(v); currentValue().setOffsetStart(token.start_ - begin_); currentValue().setOffsetLimit(token.end_ - begin_); - } - break; + } break; case tokenArraySeparator: case tokenObjectEnd: case tokenArrayEnd: @@ -1185,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) { @@ -1225,10 +1150,13 @@ bool OurReader::readToken(Token& token) { break; case '\'': if (features_.allowSingleQuotes_) { - token.type_ = tokenString; - ok = readStringSingleQuote(); + token.type_ = tokenString; + ok = readStringSingleQuote(); + } else { + // If we don't allow single quotes, this is a failure case. + ok = false; + } break; - } // else continue case '/': token.type_ = tokenComment; ok = readComment(); @@ -1254,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); @@ -1298,7 +1234,7 @@ bool OurReader::readToken(Token& token) { if (!ok) token.type_ = tokenError; token.end_ = current_; - return true; + return ok; } void OurReader::skipSpaces() { @@ -1311,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; @@ -1323,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); @@ -1345,24 +1302,49 @@ bool OurReader::readComment() { return true; } -void -OurReader::addComment(Location begin, Location end, CommentPlacement placement) { +String OurReader::normalizeEOL(OurReader::Location begin, + OurReader::Location end) { + String normalized; + normalized.reserve(static_cast(end - begin)); + OurReader::Location current = begin; + while (current != end) { + char c = *current++; + if (c == '\r') { + if (current != end && *current == '\n') + // convert dos EOL + ++current; + // convert Mac EOL + normalized += '\n'; + } else { + normalized += c; + } + } + return normalized; +} + +void OurReader::addComment(Location begin, Location end, + CommentPlacement placement) { assert(collectComments_); - const JSONCPP_STRING& normalized = normalizeEOL(begin, end); + const String& normalized = normalizeEOL(begin, end); if (placement == commentAfterOnSameLine) { - assert(lastValue_ != 0); + assert(lastValue_ != nullptr); lastValue_->setComment(normalized, placement); } else { commentsBefore_ += normalized; } } -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() == '/'; } @@ -1383,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; @@ -1420,7 +1402,6 @@ bool OurReader::readString() { return c == '"'; } - bool OurReader::readStringSingleQuote() { Char c = 0; while (current_ != end_) { @@ -1433,19 +1414,16 @@ bool OurReader::readStringSingleQuote() { return c == '\''; } -bool OurReader::readObject(Token& tokenStart) { +bool OurReader::readObject(Token& token) { Token tokenName; - JSONCPP_STRING name; + String name; Value init(objectValue); currentValue().swapPayload(init); - currentValue().setOffsetStart(tokenStart.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 + currentValue().setOffsetStart(token.start_ - begin_); + while (readTokenSkippingComments(tokenName)) { + if (tokenName.type_ == tokenObjectEnd && + (name.empty() || + features_.allowTrailingCommas_)) // empty object or trailing comma return true; name.clear(); if (tokenName.type_ == tokenString) { @@ -1459,17 +1437,17 @@ bool OurReader::readObject(Token& tokenStart) { } else { break; } + if (name.length() >= (1U << 30)) + throwRuntimeError("keylength >= 2^30"); + if (features_.rejectDupKeys_ && currentValue().isMember(name)) { + String msg = "Duplicate key: '" + name + "'"; + return addErrorAndRecover(msg, tokenName, tokenObjectEnd); + } Token colon; if (!readToken(colon) || colon.type_ != tokenMemberSeparator) { - return addErrorAndRecover( - "Missing ':' after object member name", colon, tokenObjectEnd); - } - if (name.length() >= (1U<<30)) throwRuntimeError("keylength >= 2^30"); - if (features_.rejectDupKeys_ && currentValue().isMember(name)) { - JSONCPP_STRING msg = "Duplicate key: '" + name + "'"; - return addErrorAndRecover( - msg, tokenName, tokenObjectEnd); + return addErrorAndRecover("Missing ':' after object member name", colon, + tokenObjectEnd); } Value& value = currentValue()[name]; nodes_.push(&value); @@ -1479,35 +1457,35 @@ bool OurReader::readObject(Token& tokenStart) { return recoverFromError(tokenObjectEnd); Token comma; - if (!readToken(comma) || - (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator && - comma.type_ != tokenComment)) { - return addErrorAndRecover( - "Missing ',' or '}' in object declaration", comma, tokenObjectEnd); + 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; } - return addErrorAndRecover( - "Missing '}' or object member name", tokenName, tokenObjectEnd); + return addErrorAndRecover("Missing '}' or object member name", tokenName, + tokenObjectEnd); } -bool OurReader::readArray(Token& tokenStart) { +bool OurReader::readArray(Token& token) { Value init(arrayValue); currentValue().swapPayload(init); - currentValue().setOffsetStart(tokenStart.start_ - begin_); - skipSpaces(); - if (current_ != end_ && *current_ == ']') // empty array - { - Token endArray; - readToken(endArray); - return true; - } + currentValue().setOffsetStart(token.start_ - begin_); 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(); @@ -1515,19 +1493,16 @@ bool OurReader::readArray(Token& tokenStart) { if (!ok) // error already set return recoverFromError(tokenArrayEnd); - Token token; + Token currentToken; // Accept Comment after last item in the array. - ok = readToken(token); - while (token.type_ == tokenComment && ok) { - ok = readToken(token); - } - bool badTokenType = - (token.type_ != tokenArraySeparator && token.type_ != tokenArrayEnd); + ok = readTokenSkippingComments(currentToken); + bool badTokenType = (currentToken.type_ != tokenArraySeparator && + currentToken.type_ != tokenArrayEnd); if (!ok || badTokenType) { - return addErrorAndRecover( - "Missing ',' or ']' in array declaration", token, tokenArrayEnd); + return addErrorAndRecover("Missing ',' or ']' in array declaration", + currentToken, tokenArrayEnd); } - if (token.type_ == tokenArrayEnd) + if (currentToken.type_ == tokenArrayEnd) break; } return true; @@ -1548,38 +1523,78 @@ 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: Help the compiler do the div and mod at compile time or get rid of them. - Value::LargestUInt maxIntegerValue = - isNegative ? Value::LargestUInt(-Value::minLargestInt) - : Value::maxLargestUInt; - Value::LargestUInt threshold = maxIntegerValue / 10; + } + + // 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; while (current < token.end_) { Char c = *current++; if (c < '0' || c > '9') return decodeDouble(token, decoded); - Value::UInt digit(static_cast(c - '0')); + + 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, b) this is the last digit, and + // 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 > maxIntegerValue % 10) { + digit > max_last_digit) { return decodeDouble(token, decoded); } } value = value * 10 + digit; } - if (isNegative) - decoded = -Value::LargestInt(value); - else if (value <= Value::LargestUInt(Value::maxInt)) + + 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; } @@ -1595,44 +1610,22 @@ 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); - } - size_t 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 { - JSONCPP_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("'" + JSONCPP_STRING(token.start_, token.end_) + - "' is not a number.", - token); decoded = value; return true; } bool OurReader::decodeString(Token& token) { - JSONCPP_STRING decoded_string; + String decoded_string; if (!decodeString(token, decoded_string)) return false; Value decoded(decoded_string); @@ -1642,7 +1635,7 @@ bool OurReader::decodeString(Token& token) { return true; } -bool OurReader::decodeString(Token& token, JSONCPP_STRING& decoded) { +bool OurReader::decodeString(Token& token, String& decoded) { decoded.reserve(static_cast(token.end_ - token.start_ - 2)); Location current = token.start_ + 1; // skip '"' Location end = token.end_ - 1; // do not include '"' @@ -1650,7 +1643,7 @@ bool OurReader::decodeString(Token& token, JSONCPP_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++; @@ -1695,10 +1688,8 @@ bool OurReader::decodeString(Token& token, JSONCPP_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; @@ -1707,10 +1698,9 @@ bool OurReader::decodeUnicodeCodePoint(Token& token, if (end - current < 6) return addError( "additional six characters expected to parse unicode surrogate pair.", - token, - current); - unsigned int surrogatePair; + token, current); if (*(current++) == '\\' && *(current++) == 'u') { + unsigned int surrogatePair; if (decodeUnicodeEscapeSequence(token, current, end, surrogatePair)) { unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF); } else @@ -1718,20 +1708,17 @@ bool OurReader::decodeUnicodeCodePoint(Token& token, } else return addError("expecting another \\u token to begin the second half of " "a unicode surrogate pair", - token, - current); + token, current); } return true; } -bool OurReader::decodeUnicodeEscapeSequence(Token& token, - Location& current, - Location end, - unsigned int& ret_unicode) { +bool OurReader::decodeUnicodeEscapeSequence(Token& token, Location& current, + Location end, + unsigned int& ret_unicode) { if (end - current < 4) return addError( - "Bad unicode escape sequence in string: four digits expected.", - token, + "Bad unicode escape sequence in string: four digits expected.", token, current); int unicode = 0; for (int index = 0; index < 4; ++index) { @@ -1746,15 +1733,13 @@ bool OurReader::decodeUnicodeEscapeSequence(Token& token, else return addError( "Bad unicode escape sequence in string: hexadecimal digit expected.", - token, - current); + token, current); } ret_unicode = static_cast(unicode); return true; } -bool -OurReader::addError(const JSONCPP_STRING& message, Token& token, Location extra) { +bool OurReader::addError(const String& message, Token& token, Location extra) { ErrorInfo info; info.token_ = token; info.message_ = message; @@ -1776,9 +1761,8 @@ bool OurReader::recoverFromError(TokenType skipUntilToken) { return false; } -bool OurReader::addErrorAndRecover(const JSONCPP_STRING& message, - Token& token, - TokenType skipUntilToken) { +bool OurReader::addErrorAndRecover(const String& message, Token& token, + TokenType skipUntilToken) { addError(message, token); return recoverFromError(skipUntilToken); } @@ -1791,16 +1775,15 @@ OurReader::Char OurReader::getNextChar() { return *current_++; } -void OurReader::getLocationLineAndColumn(Location location, - int& line, - int& column) const { +void OurReader::getLocationLineAndColumn(Location location, int& line, + int& column) const { Location current = begin_; Location lastLineStart = current; line = 0; while (current < location && current != end_) { Char c = *current++; if (c == '\r') { - if (*current == '\n') + if (current != end_ && *current == '\n') ++current; lastLineStart = current; ++line; @@ -1814,20 +1797,17 @@ void OurReader::getLocationLineAndColumn(Location location, ++line; } -JSONCPP_STRING OurReader::getLocationLineAndColumn(Location location) const { +String OurReader::getLocationLineAndColumn(Location location) const { int line, column; getLocationLineAndColumn(location, line, column); char buffer[18 + 16 + 16 + 1]; - snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); + jsoncpp_snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); return buffer; } -JSONCPP_STRING OurReader::getFormattedErrorMessages() const { - JSONCPP_STRING formattedMessage; - for (Errors::const_iterator itError = errors_.begin(); - itError != errors_.end(); - ++itError) { - const ErrorInfo& error = *itError; +String OurReader::getFormattedErrorMessages() const { + String formattedMessage; + for (const auto& error : errors_) { formattedMessage += "* " + getLocationLineAndColumn(error.token_.start_) + "\n"; formattedMessage += " " + error.message_ + "\n"; @@ -1838,13 +1818,11 @@ JSONCPP_STRING OurReader::getFormattedErrorMessages() const { return formattedMessage; } -std::vector OurReader::getStructuredErrors() const { - std::vector allErrors; - for (Errors::const_iterator itError = errors_.begin(); - itError != errors_.end(); - ++itError) { - const ErrorInfo& error = *itError; - OurReader::StructuredError structured; +std::vector +OurReader::getStructuredErrors() const { + std::vector allErrors; + for (const auto& error : errors_) { + CharReader::StructuredError structured; structured.offset_start = error.token_.start_ - begin_; structured.offset_limit = error.token_.end_ - begin_; structured.message = error.message_; @@ -1853,128 +1831,97 @@ std::vector OurReader::getStructuredErrors() const { return allErrors; } -bool OurReader::pushError(const Value& value, const JSONCPP_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_ = end_ + value.getOffsetLimit(); - ErrorInfo info; - info.token_ = token; - info.message_ = message; - info.extra_ = 0; - errors_.push_back(info); - return true; -} - -bool OurReader::pushError(const Value& value, const JSONCPP_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; -} +class OurCharReader : public CharReader { -bool OurReader::good() const { - return !errors_.size(); -} +public: + OurCharReader(bool collectComments, OurFeatures const& features) + : 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; + } -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, JSONCPP_STRING* errs) JSONCPP_OVERRIDE { - bool ok = reader_.parse(beginDoc, endDoc, *root, collectComments_); - if (errs) { - *errs = reader_.getFormattedErrorMessages(); + std::vector + getStructuredErrors() const override { + return reader_.getStructuredErrors(); } - return ok; - } + + private: + bool const collectComments_; + OurReader reader_; + }; }; -CharReaderBuilder::CharReaderBuilder() -{ - setDefaults(&settings_); -} -CharReaderBuilder::~CharReaderBuilder() -{} -CharReader* CharReaderBuilder::newCharReader() const -{ +CharReaderBuilder::CharReaderBuilder() { setDefaults(&settings_); } +CharReaderBuilder::~CharReaderBuilder() = default; +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(); + features.allowDroppedNullPlaceholders_ = + settings_["allowDroppedNullPlaceholders"].asBool(); features.allowNumericKeys_ = settings_["allowNumericKeys"].asBool(); features.allowSingleQuotes_ = settings_["allowSingleQuotes"].asBool(); - features.stackLimit_ = settings_["stackLimit"].asInt(); + + // Stack limit is always a size_t, so we get this as an unsigned int + // regardless of it we have 64-bit integer support enabled. + features.stackLimit_ = static_cast(settings_["stackLimit"].asUInt()); 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) { - JSONCPP_STRING const& key = keys[i]; - if (valid_keys.find(key) == valid_keys.end()) { - inv[key] = settings_[key]; - } + +bool CharReaderBuilder::validate(Json::Value* invalid) const { + 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 0u == inv.size(); + return invalid ? invalid->empty() : true; } -Value& CharReaderBuilder::operator[](JSONCPP_STRING key) -{ + +Value& CharReaderBuilder::operator[](const String& key) { return settings_[key]; } // static -void CharReaderBuilder::strictMode(Json::Value* settings) -{ -//! [CharReaderBuilderStrictMode] +void CharReaderBuilder::strictMode(Json::Value* settings) { + //! [CharReaderBuilderStrictMode] (*settings)["allowComments"] = false; + (*settings)["allowTrailingCommas"] = false; (*settings)["strictRoot"] = true; (*settings)["allowDroppedNullPlaceholders"] = false; (*settings)["allowNumericKeys"] = false; @@ -1983,14 +1930,15 @@ void CharReaderBuilder::strictMode(Json::Value* settings) (*settings)["failIfExtra"] = true; (*settings)["rejectDupKeys"] = true; (*settings)["allowSpecialFloats"] = false; -//! [CharReaderBuilderStrictMode] + (*settings)["skipBom"] = true; + //! [CharReaderBuilderStrictMode] } // static -void CharReaderBuilder::setDefaults(Json::Value* settings) -{ -//! [CharReaderBuilderDefaults] +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; @@ -1999,19 +1947,44 @@ void CharReaderBuilder::setDefaults(Json::Value* settings) (*settings)["failIfExtra"] = false; (*settings)["rejectDupKeys"] = false; (*settings)["allowSpecialFloats"] = false; -//! [CharReaderBuilderDefaults] + (*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, JSONCPP_ISTREAM& sin, - Value* root, JSONCPP_STRING* errs) -{ - JSONCPP_OSTRINGSTREAM ssin; +bool parseFromStream(CharReader::Factory const& fact, IStream& sin, Value* root, + String* errs) { + OStringStream ssin; ssin << sin.rdbuf(); - JSONCPP_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. @@ -2019,15 +1992,11 @@ bool parseFromStream( return reader->parse(begin, end, root, errs); } -JSONCPP_ISTREAM& operator>>(JSONCPP_ISTREAM& sin, Value& root) { +IStream& operator>>(IStream& sin, Value& root) { CharReaderBuilder b; - JSONCPP_STRING errs; + String errs; bool ok = parseFromStream(b, sin, &root, &errs); if (!ok) { - fprintf(stderr, - "Error from reader: %s", - errs.c_str()); - throwRuntimeError(errs); } return sin; diff --git a/src/lib_json/json_tool.h b/src/lib_json/json_tool.h index 41d0f49d2..b952c1916 100644 --- a/src/lib_json/json_tool.h +++ b/src/lib_json/json_tool.h @@ -1,4 +1,4 @@ -// Copyright 2007-2010 Baptiste Lepilleur +// 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 @@ -6,6 +6,9 @@ #ifndef LIB_JSONCPP_JSON_TOOL_H_INCLUDED #define LIB_JSONCPP_JSON_TOOL_H_INCLUDED +#if !defined(JSON_IS_AMALGAMATION) +#include +#endif // Also support old flag NO_LOCALE_SUPPORT #ifdef NO_LOCALE_SUPPORT @@ -23,7 +26,7 @@ */ namespace Json { -static char getDecimalPoint() { +static inline char getDecimalPoint() { #ifdef JSONCPP_NO_LOCALE_SUPPORT return '\0'; #else @@ -33,8 +36,8 @@ static char getDecimalPoint() { } /// Converts a unicode code-point to UTF-8. -static inline JSONCPP_STRING codePointToUTF8(unsigned int cp) { - JSONCPP_STRING result; +static inline String codePointToUTF8(unsigned int cp) { + String result; // based on description from http://en.wikipedia.org/wiki/UTF-8 @@ -61,9 +64,6 @@ static inline JSONCPP_STRING codePointToUTF8(unsigned int cp) { return result; } -/// Returns true if ch is a control character (in range [1,31]). -static inline bool isControlCharacter(char ch) { return ch > 0 && ch <= 0x1F; } - enum { /// Constant that specify the size of the buffer that must be passed to /// uintToString. @@ -71,10 +71,10 @@ 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 interger to convert to string + * @param value Unsigned integer to convert to string * @param current Input/Output string buffer. * Must have at least uintToStringBufferSize chars free. */ @@ -91,27 +91,48 @@ static inline void uintToString(LargestUInt value, char*& current) { * We had a sophisticated way, but it did not work in WinCE. * @see https://github.com/open-source-parsers/jsoncpp/pull/9 */ -static inline void fixNumericLocale(char* begin, char* end) { - while (begin < end) { +template Iter fixNumericLocale(Iter begin, Iter end) { + for (; begin != end; ++begin) { if (*begin == ',') { *begin = '.'; } - ++begin; } + return begin; } -static inline void fixNumericLocaleInput(char* begin, char* end) { +template void fixNumericLocaleInput(Iter begin, Iter end) { char decimalPoint = getDecimalPoint(); - if (decimalPoint != '\0' && decimalPoint != '.') { - while (begin < end) { - if (*begin == '.') { - *begin = decimalPoint; + if (decimalPoint == '\0' || decimalPoint == '.') { + return; + } + for (; begin != end; ++begin) { + if (*begin == '.') { + *begin = decimalPoint; + } + } +} + +/** + * 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, 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) && begin != (end - 2) && *(end - 2) == '.') { + if (precision) { + return end; } - ++begin; + return end - 2; } } + return end; } -} // namespace Json { +} // namespace Json #endif // LIB_JSONCPP_JSON_TOOL_H_INCLUDED diff --git a/src/lib_json/json_value.cpp b/src/lib_json/json_value.cpp index dc129645a..a875d28b2 100644 --- a/src/lib_json/json_value.cpp +++ b/src/lib_json/json_value.cpp @@ -1,4 +1,4 @@ -// Copyright 2011 Baptiste Lepilleur +// Copyright 2011 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 @@ -8,20 +8,58 @@ #include #include #endif // if !defined(JSON_IS_AMALGAMATION) -#include +#include +#include +#include +#include +#include +#include #include #include -#include -#include -#ifdef JSON_USE_CPPTL -#include + +#ifdef JSONCPP_HAS_STRING_VIEW +#include +#endif + +// 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) { + int count = -1; + if (size != 0) + count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap); + if (count == -1) + count = _vscprintf(format, ap); + return count; +} + +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); + va_end(ap); + return count; +} +#endif + +// Disable warning C4702 : unreachable code +#if defined(_MSC_VER) +#pragma warning(disable : 4702) #endif -#include // size_t -#include // min() #define JSON_ASSERT_UNREACHABLE assert(false) namespace Json { +template +static std::unique_ptr cloneUnique(const std::unique_ptr& p) { + std::unique_ptr r; + if (p) { + r = std::unique_ptr(new T(*p)); + } + return r; +} // This is a walkaround to avoid the static initialization of Value::null. // kNull must be word-aligned to avoid crashing on ARM. We use an alignment of @@ -31,50 +69,35 @@ namespace Json { #else #define ALIGNAS(byte_alignment) #endif -//static const unsigned char ALIGNAS(8) kNull[sizeof(Value)] = { 0 }; -//const unsigned char& kNullRef = kNull[0]; -//const Value& Value::null = reinterpret_cast(kNullRef); -//const Value& Value::nullRef = null; // static -Value const& Value::nullSingleton() -{ - static Value const nullStatic; - return nullStatic; +Value const& Value::nullSingleton() { + static Value const nullStatic; + return nullStatic; } -// for backwards compatibility, we'll leave these global references around, but DO NOT -// use them in JSONCPP library code any more! +#if JSON_USE_NULLREF +// for backwards compatibility, we'll leave these global references around, but +// DO NOT use them in JSONCPP library code any more! +// static Value const& Value::null = Value::nullSingleton(); -Value const& Value::nullRef = Value::nullSingleton(); -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); +// static +Value const& Value::nullRef = Value::nullSingleton(); +#endif #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) { - return static_cast(Int64(value / 2)) * 2.0 + static_cast(Int64(value & 1)); + return static_cast(Int64(value / 2)) * 2.0 + + static_cast(Int64(value & 1)); } template static inline double integerToDouble(T value) { @@ -83,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) @@ -94,19 +118,16 @@ static inline bool InRange(double d, T min, U max) { * computed using strlen(value). * @return Pointer on the duplicate instance of string. */ -static inline char* duplicateStringValue(const char* value, - size_t length) -{ +static inline char* duplicateStringValue(const char* value, size_t length) { // Avoid an integer overflow in the call to malloc below by limiting length // to a sane value. if (length >= static_cast(Value::maxInt)) length = Value::maxInt - 1; - char* newString = static_cast(malloc(length + 1)); - if (newString == NULL) { - throwRuntimeError( - "in Json::Value::duplicateStringValue(): " - "Failed to allocate string value buffer"); + auto newString = static_cast(malloc(length + 1)); + if (newString == nullptr) { + throwRuntimeError("in Json::Value::duplicateStringValue(): " + "Failed to allocate string value buffer"); } memcpy(newString, value, length); newString[length] = 0; @@ -115,31 +136,28 @@ static inline char* duplicateStringValue(const char* value, /* Record the length as a prefix. */ -static inline char* duplicateAndPrefixStringValue( - const char* value, - unsigned int length) -{ +static inline char* duplicateAndPrefixStringValue(const char* value, + unsigned int length) { // Avoid an integer overflow in the call to malloc below by limiting length // to a sane value. - JSON_ASSERT_MESSAGE(length <= static_cast(Value::maxInt) - sizeof(unsigned) - 1U, + JSON_ASSERT_MESSAGE(length <= static_cast(Value::maxInt) - + 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)); - if (newString == 0) { - throwRuntimeError( - "in Json::Value::duplicateAndPrefixStringValue(): " - "Failed to allocate string value buffer"); + 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"); } *reinterpret_cast(newString) = length; memcpy(newString + sizeof(unsigned), value, length); - newString[actualLength - 1U] = 0; // to avoid buffer over-run accidents by users later + newString[actualLength - 1U] = + 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; @@ -148,9 +166,10 @@ inline static void decodePrefixedString( *value = prefixed + sizeof(unsigned); } } -/** Free the string duplicated by duplicateStringValue()/duplicateAndPrefixStringValue(). +/** 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; @@ -161,18 +180,14 @@ static inline void releasePrefixedStringValue(char* value) { } static inline void releaseStringValue(char* value, unsigned length) { // length==0 => we allocated the strings memory - size_t size = (length==0) ? strlen(value) : length; + size_t size = (length == 0) ? strlen(value) : length; memset(value, 0, size); free(value); } -#else // !JSONCPP_USING_SECURE_MEMORY -static inline void releasePrefixedStringValue(char* value) { - free(value); -} -static inline void releaseStringValue(char* value, unsigned) { - free(value); -} -#endif // 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_USE_SECURE_MEMORY } // namespace Json @@ -190,58 +205,28 @@ static inline void releaseStringValue(char* value, unsigned) { namespace Json { -Exception::Exception(JSONCPP_STRING const& msg) - : msg_(msg) -{} -Exception::~Exception() JSONCPP_NOEXCEPT -{} -char const* Exception::what() const JSONCPP_NOEXCEPT -{ - return msg_.c_str(); -} -RuntimeError::RuntimeError(JSONCPP_STRING const& msg) - : Exception(msg) -{} -LogicError::LogicError(JSONCPP_STRING const& msg) - : Exception(msg) -{} -JSONCPP_NORETURN void throwRuntimeError(JSONCPP_STRING const& msg) -{ +#if JSON_USE_EXCEPTION +Exception::Exception(String msg) : msg_(std::move(msg)) {} +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) {} +JSONCPP_NORETURN void throwRuntimeError(String const& msg) { throw RuntimeError(msg); } -JSONCPP_NORETURN void throwLogicError(JSONCPP_STRING const& msg) -{ +JSONCPP_NORETURN void throwLogicError(String const& msg) { throw LogicError(msg); } - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class Value::CommentInfo -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - -Value::CommentInfo::CommentInfo() : comment_(0) -{} - -Value::CommentInfo::~CommentInfo() { - if (comment_) - releaseStringValue(comment_, 0u); +#else // !JSON_USE_EXCEPTION +JSONCPP_NORETURN void throwRuntimeError(String const& msg) { + std::cerr << msg << std::endl; + abort(); } - -void Value::CommentInfo::setComment(const char* text, size_t len) { - if (comment_) { - releaseStringValue(comment_, 0u); - comment_ = 0; - } - JSON_ASSERT(text != 0); - JSON_ASSERT_MESSAGE( - text[0] == '\0' || text[0] == '/', - "in Json::Value::setComment(): Comments must start with /"); - // It seems that /**/ style comments are acceptable as well. - comment_ = duplicateStringValue(text, len); +JSONCPP_NORETURN void throwLogicError(String const& msg) { + std::cerr << msg << std::endl; + abort(); } +#endif // ////////////////////////////////////////////////////////////////// // ////////////////////////////////////////////////////////////////// @@ -254,36 +239,44 @@ void Value::CommentInfo::setComment(const char* text, size_t len) { // Notes: policy_ indicates if the string was allocated when // a string is stored. -Value::CZString::CZString(ArrayIndex aindex) : cstr_(0), index_(aindex) {} +Value::CZString::CZString(ArrayIndex index) : cstr_(nullptr), index_(index) {} -Value::CZString::CZString(char const* str, unsigned ulength, DuplicationPolicy allocate) +Value::CZString::CZString(char const* str, unsigned length, + DuplicationPolicy allocate) : cstr_(str) { // allocate != duplicate storage_.policy_ = allocate & 0x3; - storage_.length_ = ulength & 0x3FFFFFFF; + storage_.length_ = length & 0x3FFFFFFF; } Value::CZString::CZString(const CZString& other) { - cstr_ = (other.storage_.policy_ != noDuplication && other.cstr_ != 0 - ? duplicateStringValue(other.cstr_, other.storage_.length_) - : other.cstr_); - storage_.policy_ = static_cast(other.cstr_ - ? (static_cast(other.storage_.policy_) == noDuplication - ? noDuplication : duplicate) - : static_cast(other.storage_.policy_)) & 3U; + cstr_ = (other.storage_.policy_ != noDuplication && other.cstr_ != nullptr + ? duplicateStringValue(other.cstr_, other.storage_.length_) + : other.cstr_); + storage_.policy_ = + static_cast( + other.cstr_ + ? (static_cast(other.storage_.policy_) == + noDuplication + ? noDuplication + : duplicate) + : static_cast(other.storage_.policy_)) & + 3U; storage_.length_ = other.storage_.length_; } -#if JSON_HAS_RVALUE_REFERENCES -Value::CZString::CZString(CZString&& other) - : cstr_(other.cstr_), index_(other.index_) { +Value::CZString::CZString(CZString&& other) noexcept + : cstr_(other.cstr_), index_(other.index_) { other.cstr_ = nullptr; } -#endif Value::CZString::~CZString() { if (cstr_ && storage_.policy_ == duplicate) { - releaseStringValue(const_cast(cstr_), storage_.length_ + 1u); //+1 for null terminating character for sake of completeness but not actually necessary + releaseStringValue(const_cast(cstr_), + storage_.length_ + 1U); // +1 for null terminating + // character for sake of + // completeness but not actually + // necessary } } @@ -292,32 +285,45 @@ void Value::CZString::swap(CZString& other) { std::swap(index_, other.index_); } -Value::CZString& Value::CZString::operator=(CZString other) { - swap(other); +Value::CZString& Value::CZString::operator=(const CZString& other) { + cstr_ = other.cstr_; + index_ = other.index_; + return *this; +} + +Value::CZString& Value::CZString::operator=(CZString&& other) noexcept { + cstr_ = other.cstr_; + index_ = other.index_; + other.cstr_ = nullptr; return *this; } bool Value::CZString::operator<(const CZString& other) const { - if (!cstr_) return index_ < other.index_; - //return strcmp(cstr_, other.cstr_) < 0; + if (!cstr_) + return index_ < other.index_; + // return strcmp(cstr_, other.cstr_) < 0; // Assume both are strings. unsigned this_len = this->storage_.length_; unsigned other_len = other.storage_.length_; unsigned min_len = std::min(this_len, other_len); JSON_ASSERT(this->cstr_ && other.cstr_); int comp = memcmp(this->cstr_, other.cstr_, min_len); - if (comp < 0) return true; - if (comp > 0) return false; + if (comp < 0) + return true; + if (comp > 0) + return false; return (this_len < other_len); } bool Value::CZString::operator==(const CZString& other) const { - if (!cstr_) return index_ == other.index_; - //return strcmp(cstr_, other.cstr_) == 0; + if (!cstr_) + return index_ == other.index_; + // return strcmp(cstr_, other.cstr_) == 0; // Assume both are strings. unsigned this_len = this->storage_.length_; unsigned other_len = other.storage_.length_; - if (this_len != other_len) return false; + if (this_len != other_len) + return false; JSON_ASSERT(this->cstr_ && other.cstr_); int comp = memcmp(this->cstr_, other.cstr_, this_len); return comp == 0; @@ -325,10 +331,12 @@ bool Value::CZString::operator==(const CZString& other) const { ArrayIndex Value::CZString::index() const { return index_; } -//const char* Value::CZString::c_str() const { return cstr_; } +// const char* Value::CZString::c_str() const { return cstr_; } const char* Value::CZString::data() const { return cstr_; } unsigned Value::CZString::length() const { return storage_.length_; } -bool Value::CZString::isStaticString() const { return storage_.policy_ == noDuplication; } +bool Value::CZString::isStaticString() const { + return storage_.policy_ == noDuplication; +} // ////////////////////////////////////////////////////////////////// // ////////////////////////////////////////////////////////////////// @@ -342,10 +350,10 @@ bool Value::CZString::isStaticString() const { return storage_.policy_ == noDupl * memset( this, 0, sizeof(Value) ) * This optimization is used in ValueInternalMap fast allocator. */ -Value::Value(ValueType vtype) { +Value::Value(ValueType type) { static char const emptyString[] = ""; - initBasic(vtype); - switch (vtype) { + initBasic(type); + switch (type) { case nullValue: break; case intValue: @@ -398,129 +406,75 @@ Value::Value(double value) { Value::Value(const char* value) { initBasic(stringValue, true); - JSON_ASSERT_MESSAGE(value != NULL, "Null Value Passed to Value Constructor"); - value_.string_ = duplicateAndPrefixStringValue(value, static_cast(strlen(value))); + JSON_ASSERT_MESSAGE(value != nullptr, + "Null Value Passed to Value Constructor"); + value_.string_ = duplicateAndPrefixStringValue( + value, static_cast(strlen(value))); } -Value::Value(const char* beginValue, const char* endValue) { +Value::Value(const char* begin, const char* end) { initBasic(stringValue, true); value_.string_ = - duplicateAndPrefixStringValue(beginValue, static_cast(endValue - beginValue)); + duplicateAndPrefixStringValue(begin, static_cast(end - begin)); } -Value::Value(const JSONCPP_STRING& value) { +Value::Value(const String& value) { initBasic(stringValue, true); - value_.string_ = - duplicateAndPrefixStringValue(value.data(), static_cast(value.length())); + value_.string_ = duplicateAndPrefixStringValue( + value.data(), static_cast(value.length())); } +#ifdef JSONCPP_HAS_STRING_VIEW +Value::Value(std::string_view value) { + initBasic(stringValue, true); + value_.string_ = duplicateAndPrefixStringValue( + value.data(), static_cast(value.length())); +} +#endif + Value::Value(const StaticString& value) { initBasic(stringValue); value_.string_ = const_cast(value.c_str()); } -#ifdef JSON_USE_CPPTL -Value::Value(const CppTL::ConstString& value) { - initBasic(stringValue, true); - value_.string_ = duplicateAndPrefixStringValue(value, static_cast(value.length())); -} -#endif - Value::Value(bool value) { initBasic(booleanValue); value_.bool_ = value; } -Value::Value(Value const& other) - : type_(other.type_), allocated_(false) - , - comments_(0), start_(other.start_), limit_(other.limit_) -{ - switch (type_) { - case nullValue: - case intValue: - case uintValue: - case realValue: - case booleanValue: - value_ = other.value_; - break; - case stringValue: - if (other.value_.string_ && other.allocated_) { - unsigned len; - char const* str; - decodePrefixedString(other.allocated_, other.value_.string_, - &len, &str); - value_.string_ = duplicateAndPrefixStringValue(str, len); - allocated_ = true; - } else { - value_.string_ = other.value_.string_; - allocated_ = false; - } - break; - case arrayValue: - case objectValue: - value_.map_ = new ObjectValues(*other.value_.map_); - break; - default: - JSON_ASSERT_UNREACHABLE; - } - if (other.comments_) { - comments_ = new CommentInfo[numberOfCommentPlacement]; - for (int comment = 0; comment < numberOfCommentPlacement; ++comment) { - const CommentInfo& otherComment = other.comments_[comment]; - if (otherComment.comment_) - comments_[comment].setComment( - otherComment.comment_, strlen(otherComment.comment_)); - } - } +Value::Value(const Value& other) { + dupPayload(other); + dupMeta(other); } -#if JSON_HAS_RVALUE_REFERENCES -// Move constructor -Value::Value(Value&& other) { +Value::Value(Value&& other) noexcept { initBasic(nullValue); swap(other); } -#endif Value::~Value() { - switch (type_) { - case nullValue: - case intValue: - case uintValue: - case realValue: - case booleanValue: - break; - case stringValue: - if (allocated_) - releasePrefixedStringValue(value_.string_); - break; - case arrayValue: - case objectValue: - delete value_.map_; - break; - default: - JSON_ASSERT_UNREACHABLE; - } - - delete[] comments_; - + releasePayload(); value_.uint_ = 0; } -Value& Value::operator=(Value other) { - swap(other); +Value& Value::operator=(const Value& other) { + Value(other).swap(*this); + return *this; +} + +Value& Value::operator=(Value&& other) noexcept { + other.swap(*this); return *this; } void Value::swapPayload(Value& other) { - ValueType temp = type_; - type_ = other.type_; - other.type_ = temp; + std::swap(bits_, other.bits_); std::swap(value_, other.value_); - int temp2 = allocated_; - allocated_ = other.allocated_; - other.allocated_ = temp2 & 0x1; +} + +void Value::copyPayload(const Value& other) { + releasePayload(); + dupPayload(other); } void Value::swap(Value& other) { @@ -530,7 +484,14 @@ void Value::swap(Value& other) { std::swap(limit_, other.limit_); } -ValueType Value::type() const { return type_; } +void Value::copy(const Value& other) { + copyPayload(other); + dupMeta(other); +} + +ValueType Value::type() const { + return static_cast(bits_.value_type_); +} int Value::compare(const Value& other) const { if (*this < other) @@ -541,10 +502,10 @@ int Value::compare(const Value& other) const { } bool Value::operator<(const Value& other) const { - int typeDelta = type_ - other.type_; + int typeDelta = type() - other.type(); if (typeDelta) - return typeDelta < 0 ? true : false; - switch (type_) { + return typeDelta < 0; + switch (type()) { case nullValue: return false; case intValue: @@ -555,30 +516,33 @@ bool Value::operator<(const Value& other) const { return value_.real_ < other.value_.real_; case booleanValue: return value_.bool_ < other.value_.bool_; - case stringValue: - { - if ((value_.string_ == 0) || (other.value_.string_ == 0)) { - if (other.value_.string_) return true; - else return false; + case stringValue: { + if ((value_.string_ == nullptr) || (other.value_.string_ == nullptr)) { + return other.value_.string_ != nullptr; } unsigned this_len; unsigned other_len; char const* this_str; char const* other_str; - decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str); - decodePrefixedString(other.allocated_, other.value_.string_, &other_len, &other_str); + decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, + &this_str); + decodePrefixedString(other.isAllocated(), other.value_.string_, &other_len, + &other_str); unsigned min_len = std::min(this_len, other_len); JSON_ASSERT(this_str && other_str); int comp = memcmp(this_str, other_str, min_len); - if (comp < 0) return true; - if (comp > 0) return false; + if (comp < 0) + return true; + if (comp > 0) + return false; return (this_len < other_len); } 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: @@ -594,14 +558,9 @@ bool Value::operator>=(const Value& other) const { return !(*this < other); } bool Value::operator>(const Value& other) const { return other < *this; } bool Value::operator==(const Value& other) const { - // if ( type_ != other.type_ ) - // GCC 2.95.3 says: - // attempt to take address of bit-field structure member `Json::Value::type_' - // Beats me, but a temp solves the problem. - int temp = other.type_; - if (type_ != temp) + if (type() != other.type()) return false; - switch (type_) { + switch (type()) { case nullValue: return true; case intValue: @@ -612,18 +571,20 @@ bool Value::operator==(const Value& other) const { return value_.real_ == other.value_.real_; case booleanValue: return value_.bool_ == other.value_.bool_; - case stringValue: - { - if ((value_.string_ == 0) || (other.value_.string_ == 0)) { + case stringValue: { + if ((value_.string_ == nullptr) || (other.value_.string_ == nullptr)) { return (value_.string_ == other.value_.string_); } unsigned this_len; unsigned other_len; char const* this_str; char const* other_str; - decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str); - decodePrefixedString(other.allocated_, other.value_.string_, &other_len, &other_str); - if (this_len != other_len) return false; + decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, + &this_str); + decodePrefixedString(other.isAllocated(), other.value_.string_, &other_len, + &other_str); + if (this_len != other_len) + return false; JSON_ASSERT(this_str && other_str); int comp = memcmp(this_str, other_str, this_len); return comp == 0; @@ -641,47 +602,70 @@ bool Value::operator==(const Value& other) const { bool Value::operator!=(const Value& other) const { return !(*this == other); } const char* Value::asCString() const { - JSON_ASSERT_MESSAGE(type_ == stringValue, + JSON_ASSERT_MESSAGE(type() == stringValue, "in Json::Value::asCString(): requires stringValue"); - if (value_.string_ == 0) return 0; + if (value_.string_ == nullptr) + return nullptr; unsigned this_len; char const* this_str; - decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str); + decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, + &this_str); 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"); - if (value_.string_ == 0) return 0; + JSON_ASSERT_MESSAGE(type() == stringValue, + "in Json::Value::asCString(): requires stringValue"); + if (value_.string_ == 0) + return 0; unsigned this_len; char const* this_str; - decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str); + decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, + &this_str); return this_len; } #endif -bool Value::getString(char const** str, char const** cend) const { - if (type_ != stringValue) return false; - if (value_.string_ == 0) return false; +bool Value::getString(char const** begin, char const** end) const { + if (type() != stringValue) + return false; + if (value_.string_ == nullptr) + return false; unsigned length; - decodePrefixedString(this->allocated_, this->value_.string_, &length, str); - *cend = *str + length; + decodePrefixedString(this->isAllocated(), this->value_.string_, &length, + begin); + *end = *begin + length; return true; } -JSONCPP_STRING Value::asString() const { - switch (type_) { +#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: return ""; - case stringValue: - { - if (value_.string_ == 0) return ""; + case stringValue: { + if (value_.string_ == nullptr) + return ""; unsigned this_len; char const* this_str; - decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str); - return JSONCPP_STRING(this_str, this_len); + decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, + &this_str); + return String(this_str, this_len); } case booleanValue: return value_.bool_ ? "true" : "false"; @@ -696,18 +680,8 @@ JSONCPP_STRING Value::asString() const { } } -#ifdef JSON_USE_CPPTL -CppTL::ConstString Value::asConstString() const { - unsigned len; - char const* str; - decodePrefixedString(allocated_, value_.string_, - &len, &str); - return CppTL::ConstString(str, len); -} -#endif - Value::Int Value::asInt() const { - switch (type_) { + switch (type()) { case intValue: JSON_ASSERT_MESSAGE(isInt(), "LargestInt out of Int range"); return Int(value_.int_); @@ -729,7 +703,7 @@ Value::Int Value::asInt() const { } Value::UInt Value::asUInt() const { - switch (type_) { + switch (type()) { case intValue: JSON_ASSERT_MESSAGE(isUInt(), "LargestInt out of UInt range"); return UInt(value_.int_); @@ -737,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: @@ -753,13 +727,18 @@ Value::UInt Value::asUInt() const { #if defined(JSON_HAS_INT64) Value::Int64 Value::asInt64() const { - switch (type_) { + switch (type()) { case intValue: return Int64(value_.int_); case uintValue: 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_); @@ -774,14 +753,14 @@ Value::Int64 Value::asInt64() const { } Value::UInt64 Value::asUInt64() const { - switch (type_) { + switch (type()) { case intValue: JSON_ASSERT_MESSAGE(isUInt64(), "LargestInt out of UInt64 range"); return UInt64(value_.int_); 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: @@ -812,7 +791,7 @@ LargestUInt Value::asLargestUInt() const { } double Value::asDouble() const { - switch (type_) { + switch (type()) { case intValue: return static_cast(value_.int_); case uintValue: @@ -834,7 +813,7 @@ double Value::asDouble() const { } float Value::asFloat() const { - switch (type_) { + switch (type()) { case intValue: return static_cast(value_.int_); case uintValue: @@ -849,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; } @@ -857,18 +836,20 @@ float Value::asFloat() const { } bool Value::asBool() const { - switch (type_) { + switch (type()) { case booleanValue: return value_.bool_; case nullValue: return false; case intValue: - return value_.int_ ? true : false; + return value_.int_ != 0; case uintValue: - return value_.uint_ ? true : false; - case realValue: - // This is kind of strange. Not recommended. - return (value_.real_ != 0.0) ? 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_); + return value_classification != FP_ZERO && value_classification != FP_NAN; + } default: break; } @@ -879,30 +860,30 @@ bool Value::isConvertibleTo(ValueType other) const { switch (other) { case nullValue: return (isNumeric() && asDouble() == 0.0) || - (type_ == booleanValue && value_.bool_ == false) || - (type_ == stringValue && asString().empty()) || - (type_ == arrayValue && value_.map_->size() == 0) || - (type_ == objectValue && value_.map_->size() == 0) || - type_ == nullValue; + (type() == booleanValue && !value_.bool_) || + (type() == stringValue && asString().empty()) || + (type() == arrayValue && value_.map_->empty()) || + (type() == objectValue && value_.map_->empty()) || + type() == nullValue; case intValue: return isInt() || - (type_ == realValue && InRange(value_.real_, minInt, maxInt)) || - type_ == booleanValue || type_ == nullValue; + (type() == realValue && InRange(value_.real_, minInt, maxInt)) || + type() == booleanValue || type() == nullValue; case uintValue: return isUInt() || - (type_ == realValue && InRange(value_.real_, 0, maxUInt)) || - type_ == booleanValue || type_ == nullValue; + (type() == realValue && InRange(value_.real_, 0u, maxUInt)) || + type() == booleanValue || type() == nullValue; case realValue: - return isNumeric() || type_ == booleanValue || type_ == nullValue; + return isNumeric() || type() == booleanValue || type() == nullValue; case booleanValue: - return isNumeric() || type_ == booleanValue || type_ == nullValue; + return isNumeric() || type() == booleanValue || type() == nullValue; case stringValue: - return isNumeric() || type_ == booleanValue || type_ == stringValue || - type_ == nullValue; + return isNumeric() || type() == booleanValue || type() == stringValue || + type() == nullValue; case arrayValue: - return type_ == arrayValue || type_ == nullValue; + return type() == arrayValue || type() == nullValue; case objectValue: - return type_ == objectValue || type_ == nullValue; + return type() == objectValue || type() == nullValue; } JSON_ASSERT_UNREACHABLE; return false; @@ -910,7 +891,7 @@ bool Value::isConvertibleTo(ValueType other) const { /// Number of values in array or object ArrayIndex Value::size() const { - switch (type_) { + switch (type()) { case nullValue: case intValue: case uintValue: @@ -934,20 +915,19 @@ ArrayIndex Value::size() const { bool Value::empty() const { if (isNull() || isArray() || isObject()) - return size() == 0u; - else - return false; + return size() == 0U; + return false; } -bool Value::operator!() const { return isNull(); } +Value::operator bool() const { return !isNull(); } void Value::clear() { - JSON_ASSERT_MESSAGE(type_ == nullValue || type_ == arrayValue || - type_ == objectValue, + JSON_ASSERT_MESSAGE(type() == nullValue || type() == arrayValue || + type() == objectValue, "in Json::Value::clear(): requires complex value"); start_ = 0; limit_ = 0; - switch (type_) { + switch (type()) { case arrayValue: case objectValue: value_.map_->clear(); @@ -958,15 +938,16 @@ void Value::clear() { } void Value::resize(ArrayIndex newSize) { - JSON_ASSERT_MESSAGE(type_ == nullValue || type_ == arrayValue, + JSON_ASSERT_MESSAGE(type() == nullValue || type() == arrayValue, "in Json::Value::resize(): requires arrayValue"); - if (type_ == nullValue) + if (type() == nullValue) *this = Value(arrayValue); ArrayIndex oldSize = size(); if (newSize == 0) clear(); else if (newSize > oldSize) - (*this)[newSize - 1]; + for (ArrayIndex i = oldSize; i < newSize; ++i) + (*this)[i]; else { for (ArrayIndex index = newSize; index < oldSize; ++index) { value_.map_->erase(index); @@ -977,12 +958,12 @@ void Value::resize(ArrayIndex newSize) { Value& Value::operator[](ArrayIndex index) { JSON_ASSERT_MESSAGE( - type_ == nullValue || type_ == arrayValue, + type() == nullValue || type() == arrayValue, "in Json::Value::operator[](ArrayIndex): requires arrayValue"); - if (type_ == nullValue) + if (type() == nullValue) *this = Value(arrayValue); CZString key(index); - ObjectValues::iterator it = value_.map_->lower_bound(key); + auto it = value_.map_->lower_bound(key); if (it != value_.map_->end() && (*it).first == key) return (*it).second; @@ -1000,9 +981,9 @@ Value& Value::operator[](int index) { const Value& Value::operator[](ArrayIndex index) const { JSON_ASSERT_MESSAGE( - type_ == nullValue || type_ == arrayValue, + type() == nullValue || type() == arrayValue, "in Json::Value::operator[](ArrayIndex)const: requires arrayValue"); - if (type_ == nullValue) + if (type() == nullValue) return nullSingleton(); CZString key(index); ObjectValues::const_iterator it = value_.map_->find(key); @@ -1018,26 +999,85 @@ const Value& Value::operator[](int index) const { return (*this)[ArrayIndex(index)]; } -void Value::initBasic(ValueType vtype, bool allocated) { - type_ = vtype; - allocated_ = allocated; - comments_ = 0; +void Value::initBasic(ValueType type, bool allocated) { + setType(type); + setIsAllocated(allocated); + comments_ = Comments{}; start_ = 0; limit_ = 0; } +void Value::dupPayload(const Value& other) { + setType(other.type()); + setIsAllocated(false); + switch (type()) { + case nullValue: + case intValue: + case uintValue: + case realValue: + case booleanValue: + value_ = other.value_; + break; + case stringValue: + if (other.value_.string_ && other.isAllocated()) { + unsigned len; + char const* str; + decodePrefixedString(other.isAllocated(), other.value_.string_, &len, + &str); + value_.string_ = duplicateAndPrefixStringValue(str, len); + setIsAllocated(true); + } else { + value_.string_ = other.value_.string_; + } + break; + case arrayValue: + case objectValue: + value_.map_ = new ObjectValues(*other.value_.map_); + break; + default: + JSON_ASSERT_UNREACHABLE; + } +} + +void Value::releasePayload() { + switch (type()) { + case nullValue: + case intValue: + case uintValue: + case realValue: + case booleanValue: + break; + case stringValue: + if (isAllocated()) + releasePrefixedStringValue(value_.string_); + break; + case arrayValue: + case objectValue: + delete value_.map_; + break; + default: + JSON_ASSERT_UNREACHABLE; + } +} + +void Value::dupMeta(const Value& other) { + comments_ = other.comments_; + start_ = other.start_; + limit_ = other.limit_; +} + // Access an object value by name, create a null member if it does not exist. // @pre Type of '*this' is object or null. // @param key is null-terminated. Value& Value::resolveReference(const char* key) { JSON_ASSERT_MESSAGE( - type_ == nullValue || type_ == objectValue, + type() == nullValue || type() == objectValue, "in Json::Value::resolveReference(): requires objectValue"); - if (type_ == nullValue) + if (type() == nullValue) *this = Value(objectValue); - CZString actualKey( - key, static_cast(strlen(key)), CZString::noDuplication); // NOTE! - ObjectValues::iterator it = value_.map_->lower_bound(actualKey); + CZString actualKey(key, static_cast(strlen(key)), + CZString::noDuplication); // NOTE! + auto it = value_.map_->lower_bound(actualKey); if (it != value_.map_->end() && (*it).first == actualKey) return (*it).second; @@ -1048,16 +1088,15 @@ Value& Value::resolveReference(const char* key) { } // @param key is not null-terminated. -Value& Value::resolveReference(char const* key, char const* cend) -{ +Value& Value::resolveReference(char const* key, char const* end) { JSON_ASSERT_MESSAGE( - type_ == nullValue || type_ == objectValue, + type() == nullValue || type() == objectValue, "in Json::Value::resolveReference(key, end): requires objectValue"); - if (type_ == nullValue) + if (type() == nullValue) *this = Value(objectValue); - CZString actualKey( - key, static_cast(cend-key), CZString::duplicateOnCopy); - ObjectValues::iterator it = value_.map_->lower_bound(actualKey); + CZString actualKey(key, static_cast(end - key), + CZString::duplicateOnCopy); + auto it = value_.map_->lower_bound(actualKey); if (it != value_.map_->end() && (*it).first == actualKey) return (*it).second; @@ -1074,27 +1113,87 @@ Value Value::get(ArrayIndex index, const Value& defaultValue) const { bool Value::isValidIndex(ArrayIndex index) const { return index < size(); } -Value const* Value::find(char const* key, char const* cend) const -{ - JSON_ASSERT_MESSAGE( - type_ == nullValue || type_ == objectValue, - "in Json::Value::find(key, end, found): requires objectValue or nullValue"); - if (type_ == nullValue) return NULL; - CZString actualKey(key, static_cast(cend-key), CZString::noDuplication); +Value const* Value::find(char const* begin, char const* end) const { + JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, + "in Json::Value::find(begin, end): requires " + "objectValue or nullValue"); + if (type() == nullValue) + return nullptr; + CZString actualKey(begin, static_cast(end - begin), + CZString::noDuplication); ObjectValues::const_iterator it = value_.map_->find(actualKey); - if (it == value_.map_->end()) return NULL; + if (it == value_.map_->end()) + return nullptr; return &(*it).second; } -const Value& Value::operator[](const char* key) const -{ +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) return nullSingleton(); + if (!found) + return nullSingleton(); return *found; } -Value const& Value::operator[](JSONCPP_STRING const& key) const -{ - Value const* found = find(key.data(), key.data() + key.length()); - if (!found) return nullSingleton(); +Value const& Value::operator[](const String& key) const { + Value const* found = find(key); + if (!found) + return nullSingleton(); return *found; } @@ -1102,183 +1201,181 @@ Value& Value::operator[](const char* key) { return resolveReference(key, key + strlen(key)); } -Value& Value::operator[](const JSONCPP_STRING& 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& 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; } -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; + +bool Value::insert(ArrayIndex index, const Value& newValue) { + return insert(index, Value(newValue)); } -#endif -Value& Value::append(const Value& value) { return (*this)[size()] = 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* key, char const* cend, Value const& defaultValue) const -{ - Value const* found = find(key, cend); +Value Value::get(char const* begin, char const* end, + Value const& defaultValue) const { + Value const* found = find(begin, end); return !found ? defaultValue : *found; } -Value Value::get(char const* key, Value const& defaultValue) const -{ +#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(JSONCPP_STRING const& key, Value const& defaultValue) const -{ +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* key, const char* cend, Value* removed) -{ - if (type_ != objectValue) { +bool Value::removeMember(const char* begin, const char* end, Value* removed) { + if (type() != objectValue) { return false; } - CZString actualKey(key, static_cast(cend-key), CZString::noDuplication); - ObjectValues::iterator it = value_.map_->find(actualKey); + CZString actualKey(begin, static_cast(end - begin), + CZString::noDuplication); + auto it = value_.map_->find(actualKey); if (it == value_.map_->end()) return false; - *removed = it->second; + if (removed) + *removed = std::move(it->second); value_.map_->erase(it); return true; } -bool Value::removeMember(const char* key, Value* removed) -{ +#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(JSONCPP_STRING const& key, Value* removed) -{ +bool Value::removeMember(String const& key, Value* removed) { return removeMember(key.data(), key.data() + key.length(), removed); } -Value Value::removeMember(const char* key) -{ - JSON_ASSERT_MESSAGE(type_ == nullValue || type_ == objectValue, +#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 nullSingleton(); + if (type() == nullValue) + return; - Value removed; // null - removeMember(key, key + strlen(key), &removed); - return removed; // still null if removeMember() did nothing + CZString actualKey(key.data(), unsigned(key.length()), + CZString::noDuplication); + value_.map_->erase(actualKey); } -Value Value::removeMember(const JSONCPP_STRING& key) -{ - return removeMember(key.c_str()); +#else +void Value::removeMember(const char* key) { + JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, + "in Json::Value::removeMember(): requires objectValue"); + if (type() == nullValue) + return; + + CZString actualKey(key, unsigned(strlen(key)), CZString::noDuplication); + 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) { + if (type() != arrayValue) { return false; } CZString key(index); - ObjectValues::iterator it = value_.map_->find(key); + auto it = value_.map_->find(key); if (it == value_.map_->end()) { return false; } - *removed = it->second; + if (removed) + *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){ + for (ArrayIndex i = index; i < (oldSize - 1); ++i) { CZString keey(i); (*value_.map_)[keey] = (*this)[i + 1]; } // erase the last one ("leftover") CZString keyLast(oldSize - 1); - ObjectValues::iterator itLast = value_.map_->find(keyLast); + auto itLast = value_.map_->find(keyLast); value_.map_->erase(itLast); 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); +bool Value::isMember(char const* begin, char const* end) const { + Value const* value = find(begin, end); + return nullptr != value; } -#endif - -bool Value::isMember(char const* key, char const* cend) const -{ - Value const* value = find(key, cend); - return NULL != value; +#ifdef JSONCPP_HAS_STRING_VIEW +bool Value::isMember(std::string_view key) const { + return isMember(key.data(), key.data() + key.length()); } -bool Value::isMember(char const* key) const -{ +#else +bool Value::isMember(char const* key) const { return isMember(key, key + strlen(key)); } -bool Value::isMember(JSONCPP_STRING const& key) const -{ +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 { JSON_ASSERT_MESSAGE( - type_ == nullValue || type_ == objectValue, + type() == nullValue || type() == objectValue, "in Json::Value::getMemberNames(), value must be objectValue"); - if (type_ == nullValue) + if (type() == nullValue) return Value::Members(); Members members; members.reserve(value_.map_->size()); ObjectValues::const_iterator it = value_.map_->begin(); ObjectValues::const_iterator itEnd = value_.map_->end(); for (; it != itEnd; ++it) { - members.push_back(JSONCPP_STRING((*it).first.data(), - (*it).first.length())); + members.push_back(String((*it).first.data(), (*it).first.length())); } 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; return modf(d, &integral_part) == 0.0; } -bool Value::isNull() const { return type_ == nullValue; } +bool Value::isNull() const { return type() == nullValue; } -bool Value::isBool() const { return type_ == booleanValue; } +bool Value::isBool() const { return type() == booleanValue; } bool Value::isInt() const { - switch (type_) { + switch (type()) { case intValue: #if defined(JSON_HAS_INT64) return value_.int_ >= minInt && value_.int_ <= maxInt; @@ -1297,7 +1394,7 @@ bool Value::isInt() const { } bool Value::isUInt() const { - switch (type_) { + switch (type()) { case intValue: #if defined(JSON_HAS_INT64) return value_.int_ >= 0 && LargestUInt(value_.int_) <= LargestUInt(maxUInt); @@ -1321,7 +1418,7 @@ bool Value::isUInt() const { bool Value::isInt64() const { #if defined(JSON_HAS_INT64) - switch (type_) { + switch (type()) { case intValue: return true; case uintValue: @@ -1330,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; } @@ -1341,7 +1442,7 @@ bool Value::isInt64() const { bool Value::isUInt64() const { #if defined(JSON_HAS_INT64) - switch (type_) { + switch (type()) { case intValue: return value_.int_ >= 0; case uintValue: @@ -1360,61 +1461,94 @@ bool Value::isUInt64() const { } bool Value::isIntegral() const { - switch (type_) { - case intValue: - case uintValue: - return true; - case realValue: + switch (type()) { + case intValue: + case uintValue: + return true; + case realValue: #if defined(JSON_HAS_INT64) - // 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) && value_.real_ < maxUInt64AsDouble && IsIntegral(value_.real_); + // 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. + // 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 && IsIntegral(value_.real_); + return value_.real_ >= minInt && value_.real_ <= maxUInt && + IsIntegral(value_.real_); #endif // JSON_HAS_INT64 - default: - break; + default: + break; } return false; } -bool Value::isDouble() const { return type_ == intValue || type_ == uintValue || type_ == realValue; } +bool Value::isDouble() const { + return type() == intValue || type() == uintValue || type() == realValue; +} bool Value::isNumeric() const { return isDouble(); } -bool Value::isString() const { return type_ == stringValue; } +bool Value::isString() const { return type() == stringValue; } -bool Value::isArray() const { return type_ == arrayValue; } +bool Value::isArray() const { return type() == arrayValue; } -bool Value::isObject() const { return type_ == objectValue; } +bool Value::isObject() const { return type() == objectValue; } -void Value::setComment(const char* comment, size_t len, CommentPlacement placement) { - if (!comments_) - comments_ = new CommentInfo[numberOfCommentPlacement]; - if ((len > 0) && (comment[len-1] == '\n')) { - // Always discard trailing newline, to aid indentation. - len -= 1; - } - comments_[placement].setComment(comment, len); +Value::Comments::Comments(const Comments& that) + : ptr_{cloneUnique(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) noexcept { + ptr_ = std::move(that.ptr_); + return *this; +} + +bool Value::Comments::has(CommentPlacement slot) const { + return ptr_ && !(*ptr_)[slot].empty(); } -void Value::setComment(const char* comment, CommentPlacement placement) { - setComment(comment, strlen(comment), placement); +String Value::Comments::get(CommentPlacement slot) const { + if (!ptr_) + return {}; + return (*ptr_)[slot]; } -void Value::setComment(const JSONCPP_STRING& comment, CommentPlacement placement) { - setComment(comment.c_str(), comment.length(), placement); +void Value::Comments::set(CommentPlacement slot, String comment) { + if (slot >= CommentPlacement::numberOfCommentPlacement) + return; + if (!ptr_) + ptr_ = std::unique_ptr(new Array()); + (*ptr_)[slot] = std::move(comment); +} + +void Value::setComment(String comment, CommentPlacement placement) { + if (!comment.empty() && (comment.back() == '\n')) { + // Always discard trailing newline, to aid indentation. + comment.pop_back(); + } + JSON_ASSERT_MESSAGE( + comment.empty() || comment[0] == '/', + "in Json::Value::setComment(): Comments must start with /"); + comments_.set(placement, std::move(comment)); } bool Value::hasComment(CommentPlacement placement) const { - return comments_ != 0 && comments_[placement].comment_ != 0; + return comments_.has(placement); } -JSONCPP_STRING Value::getComment(CommentPlacement placement) const { - if (hasComment(placement)) - return comments_[placement].comment_; - return ""; +String Value::getComment(CommentPlacement placement) const { + return comments_.get(placement); } void Value::setOffsetStart(ptrdiff_t start) { start_ = start; } @@ -1425,13 +1559,18 @@ ptrdiff_t Value::getOffsetStart() const { return start_; } ptrdiff_t Value::getOffsetLimit() const { return limit_; } -JSONCPP_STRING Value::toStyledString() const { - StyledWriter writer; - return writer.write(*this); +String Value::toStyledString() const { + StreamWriterBuilder builder; + + String out = this->hasComment(commentBefore) ? "\n" : ""; + out += Json::writeString(builder, *this); + out += '\n'; + + return out; } Value::const_iterator Value::begin() const { - switch (type_) { + switch (type()) { case arrayValue: case objectValue: if (value_.map_) @@ -1440,11 +1579,11 @@ Value::const_iterator Value::begin() const { default: break; } - return const_iterator(); + return {}; } Value::const_iterator Value::end() const { - switch (type_) { + switch (type()) { case arrayValue: case objectValue: if (value_.map_) @@ -1453,11 +1592,11 @@ Value::const_iterator Value::end() const { default: break; } - return const_iterator(); + return {}; } Value::iterator Value::begin() { - switch (type_) { + switch (type()) { case arrayValue: case objectValue: if (value_.map_) @@ -1470,7 +1609,7 @@ Value::iterator Value::begin() { } Value::iterator Value::end() { - switch (type_) { + switch (type()) { case arrayValue: case objectValue: if (value_.map_) @@ -1485,25 +1624,20 @@ Value::iterator Value::end() { // class PathArgument // ////////////////////////////////////////////////////////////////// -PathArgument::PathArgument() : key_(), index_(), kind_(kindNone) {} +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 JSONCPP_STRING& key) - : key_(key.c_str()), index_(), kind_(kindKey) {} +PathArgument::PathArgument(String key) : key_(std::move(key)), kind_(kindKey) {} // class Path // ////////////////////////////////////////////////////////////////// -Path::Path(const JSONCPP_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); @@ -1515,10 +1649,10 @@ Path::Path(const JSONCPP_STRING& path, makePath(path, in); } -void Path::makePath(const JSONCPP_STRING& path, const InArgs& in) { +void Path::makePath(const String& path, const InArgs& in) { const char* current = path.c_str(); const char* end = current + path.length(); - InArgs::const_iterator itInArg = in.begin(); + auto itInArg = in.begin(); while (current != end) { if (*current == '[') { ++current; @@ -1541,13 +1675,12 @@ void Path::makePath(const JSONCPP_STRING& path, const InArgs& in) { const char* beginName = current; while (current != end && !strchr("[.", *current)) ++current; - args_.push_back(JSONCPP_STRING(beginName, current)); + args_.push_back(String(beginName, current)); } } } -void Path::addPathInArg(const JSONCPP_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()) { @@ -1559,30 +1692,29 @@ void Path::addPathInArg(const JSONCPP_STRING& /*path*/, } } -void Path::invalidPath(const JSONCPP_STRING& /*path*/, int /*location*/) { +void Path::invalidPath(const String& /*path*/, int /*location*/) { // Error: invalid path. } const Value& Path::resolve(const Value& root) const { const Value* node = &root; - for (Args::const_iterator it = args_.begin(); it != args_.end(); ++it) { - const PathArgument& arg = *it; + for (const auto& arg : args_) { if (arg.kind_ == PathArgument::kindIndex) { if (!node->isArray() || !node->isValidIndex(arg.index_)) { - // Error: unable to resolve path (array value expected at position... - return Value::null; + // Error: unable to resolve path (array value expected at position... ) + return Value::nullSingleton(); } node = &((*node)[arg.index_]); } else if (arg.kind_ == PathArgument::kindKey) { if (!node->isObject()) { // Error: unable to resolve path (object value expected at position...) - return Value::null; + return Value::nullSingleton(); } node = &((*node)[arg.key_]); if (node == &Value::nullSingleton()) { // Error: unable to resolve path (object has no member named '' at // position...) - return Value::null; + return Value::nullSingleton(); } } } @@ -1591,8 +1723,7 @@ const Value& Path::resolve(const Value& root) const { Value Path::resolve(const Value& root, const Value& defaultValue) const { const Value* node = &root; - for (Args::const_iterator it = args_.begin(); it != args_.end(); ++it) { - const PathArgument& arg = *it; + for (const auto& arg : args_) { if (arg.kind_ == PathArgument::kindIndex) { if (!node->isArray() || !node->isValidIndex(arg.index_)) return defaultValue; @@ -1610,8 +1741,7 @@ Value Path::resolve(const Value& root, const Value& defaultValue) const { Value& Path::make(Value& root) const { Value* node = &root; - for (Args::const_iterator it = args_.begin(); it != args_.end(); ++it) { - const PathArgument& arg = *it; + for (const auto& arg : args_) { if (arg.kind_ == PathArgument::kindIndex) { if (!node->isArray()) { // Error: node is not an array at position ... diff --git a/src/lib_json/json_valueiterator.inl b/src/lib_json/json_valueiterator.inl index b45162b4a..d6128b8ed 100644 --- a/src/lib_json/json_valueiterator.inl +++ b/src/lib_json/json_valueiterator.inl @@ -1,4 +1,4 @@ -// Copyright 2007-2010 Baptiste Lepilleur +// 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 @@ -15,31 +15,21 @@ namespace Json { // ////////////////////////////////////////////////////////////////// // ////////////////////////////////////////////////////////////////// -ValueIteratorBase::ValueIteratorBase() - : current_(), isNull_(true) { -} +ValueIteratorBase::ValueIteratorBase() : current_() {} 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_; -} +void ValueIteratorBase::increment() { ++current_; } -void ValueIteratorBase::decrement() { - --current_; -} +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 @@ -60,7 +50,6 @@ ValueIteratorBase::computeDistance(const SelfType& other) const { ++myDistance; } return myDistance; -#endif } bool ValueIteratorBase::isEqual(const SelfType& other) const { @@ -92,12 +81,13 @@ UInt ValueIteratorBase::index() const { return Value::UInt(-1); } -JSONCPP_STRING ValueIteratorBase::name() const { +String ValueIteratorBase::name() const { char const* keey; char const* end; keey = memberName(&end); - if (!keey) return JSONCPP_STRING(); - return JSONCPP_STRING(keey, end); + if (!keey) + return String(); + return String(keey, end); } char const* ValueIteratorBase::memberName() const { @@ -108,8 +98,8 @@ char const* ValueIteratorBase::memberName() const { char const* ValueIteratorBase::memberName(char const** end) const { const char* cname = (*current_).first.data(); if (!cname) { - *end = NULL; - return NULL; + *end = nullptr; + return nullptr; } *end = cname + (*current_).first.length(); return cname; @@ -123,7 +113,7 @@ char const* ValueIteratorBase::memberName(char const** end) const { // ////////////////////////////////////////////////////////////////// // ////////////////////////////////////////////////////////////////// -ValueConstIterator::ValueConstIterator() {} +ValueConstIterator::ValueConstIterator() = default; ValueConstIterator::ValueConstIterator( const Value::ObjectValues::iterator& current) @@ -146,7 +136,7 @@ operator=(const ValueIteratorBase& other) { // ////////////////////////////////////////////////////////////////// // ////////////////////////////////////////////////////////////////// -ValueIterator::ValueIterator() {} +ValueIterator::ValueIterator() = default; ValueIterator::ValueIterator(const Value::ObjectValues::iterator& current) : ValueIteratorBase(current) {} @@ -156,8 +146,7 @@ ValueIterator::ValueIterator(const ValueConstIterator& other) throwRuntimeError("ConstIterator to Iterator should never be allowed."); } -ValueIterator::ValueIterator(const ValueIterator& other) - : ValueIteratorBase(other) {} +ValueIterator::ValueIterator(const ValueIterator& other) = default; ValueIterator& ValueIterator::operator=(const SelfType& other) { copy(other); diff --git a/src/lib_json/json_writer.cpp b/src/lib_json/json_writer.cpp index 045421532..ac14eb11f 100644 --- a/src/lib_json/json_writer.cpp +++ b/src/lib_json/json_writer.cpp @@ -1,105 +1,34 @@ -// Copyright 2011 Baptiste Lepilleur +// Copyright 2011 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 #if !defined(JSON_IS_AMALGAMATION) -#include #include "json_tool.h" +#include #endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#include +#include +#include #include #include +#include #include #include -#include -#include -#include -#include - -#if defined(_MSC_VER) && _MSC_VER >= 1200 && _MSC_VER < 1800 // Between VC++ 6.0 and VC++ 11.0 -#include -#define isfinite _finite -#elif defined(__sun) && defined(__SVR4) //Solaris -#if !defined(isfinite) -#include -#define isfinite finite -#endif -#elif defined(_AIX) -#if !defined(isfinite) -#include -#define isfinite finite -#endif -#elif defined(__hpux) -#if !defined(isfinite) -#if defined(__ia64) && !defined(finite) -#define isfinite(x) ((sizeof(x) == sizeof(float) ? \ - _Isfinitef(x) : _IsFinite(x))) -#else -#include -#define isfinite finite -#endif -#endif -#else -#include -#if !(defined(__QNXNTO__)) // QNX already defines isfinite -#define isfinite std::isfinite -#endif -#endif #if defined(_MSC_VER) -#if !defined(WINCE) && defined(__STDC_SECURE_LIB__) && _MSC_VER >= 1500 // VC++ 9.0 and above -#define snprintf sprintf_s -#elif _MSC_VER >= 1900 // VC++ 14.0 and above -#define snprintf std::snprintf -#else -#define snprintf _snprintf -#endif -#elif defined(__ANDROID__) || defined(__QNXNTO__) -#define snprintf snprintf -#elif __cplusplus >= 201103L -#if !defined(__MINGW32__) && !defined(__CYGWIN__) -#define snprintf std::snprintf -#endif -#endif - -#if defined(__BORLANDC__) -#include -#define isfinite _finite -#define snprintf _snprintf -#endif - -#if defined(_MSC_VER) && _MSC_VER >= 1400 // VC++ 8.0 // Disable warning about strdup being deprecated. #pragma warning(disable : 4996) #endif namespace Json { -#if __cplusplus >= 201103L || (defined(_CPPLIB_VER) && _CPPLIB_VER >= 520) -typedef std::unique_ptr StreamWriterPtr; -#else -typedef std::auto_ptr StreamWriterPtr; -#endif - -static bool containsControlCharacter(const char* str) { - while (*str) { - if (isControlCharacter(*(str++))) - return true; - } - return false; -} - -static bool containsControlCharacter0(const char* str, unsigned len) { - char const* end = str + len; - while (end != str) { - if (isControlCharacter(*str) || 0==*str) - return true; - ++str; - } - return false; -} +using StreamWriterPtr = std::unique_ptr; -JSONCPP_STRING valueToString(LargestInt value) { +String valueToString(LargestInt value) { UIntToStringBuffer buffer; char* current = buffer + sizeof(buffer); if (value == Value::minLargestInt) { @@ -115,7 +44,7 @@ JSONCPP_STRING valueToString(LargestInt value) { return current; } -JSONCPP_STRING valueToString(LargestUInt value) { +String valueToString(LargestUInt value) { UIntToStringBuffer buffer; char* current = buffer + sizeof(buffer); uintToString(value, current); @@ -125,148 +54,174 @@ JSONCPP_STRING valueToString(LargestUInt value) { #if defined(JSON_HAS_INT64) -JSONCPP_STRING valueToString(Int value) { - return valueToString(LargestInt(value)); -} +String valueToString(Int value) { return valueToString(LargestInt(value)); } -JSONCPP_STRING valueToString(UInt value) { - return valueToString(LargestUInt(value)); -} +String valueToString(UInt value) { return valueToString(LargestUInt(value)); } #endif // # if defined(JSON_HAS_INT64) namespace { -JSONCPP_STRING valueToString(double value, bool useSpecialFloats, unsigned int precision) { - // Allocate a buffer that is more than large enough to store the 16 digits of - // precision requested below. - char buffer[36]; - int len = -1; - - char formatString[6]; - snprintf(formatString, sizeof(formatString), "%%.%dg", precision); - +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 distingish the + // that always has a decimal point because JSON doesn't distinguish the // concepts of reals and integers. - if (isfinite(value)) { - len = snprintf(buffer, sizeof(buffer), formatString, value); - - // try to ensure we preserve the fact that this was given to us as a double on input - if (!strstr(buffer, ".") && !strstr(buffer, "e")) { - strcat(buffer, ".0"); - } + 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"; + } - } else { - // IEEE standard states that NaN values will not compare to themselves - if (value != value) { - len = snprintf(buffer, sizeof(buffer), useSpecialFloats ? "NaN" : "null"); - } else if (value < 0) { - len = snprintf(buffer, sizeof(buffer), useSpecialFloats ? "-Infinity" : "-1e+9999"); - } else { - len = snprintf(buffer, sizeof(buffer), useSpecialFloats ? "Infinity" : "1e+9999"); + String buffer(size_t(36), '\0'); + while (true) { + int len = jsoncpp_snprintf( + &*buffer.begin(), buffer.size(), + (precisionType == PrecisionType::significantDigits) ? "%.*g" : "%.*f", + precision, value); + assert(len >= 0); + auto wouldPrint = static_cast(len); + if (wouldPrint >= buffer.size()) { + buffer.resize(wouldPrint + 1); + continue; } - // For those, we do not need to call fixNumLoc, but it is fast. + buffer.resize(wouldPrint); + break; + } + + buffer.erase(fixNumericLocale(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()); } - assert(len >= 0); - fixNumericLocale(buffer, buffer + len); + return buffer; } +} // namespace + +String valueToString(double value, unsigned int precision, + PrecisionType precisionType) { + return valueToString(value, false, precision, precisionType); } -JSONCPP_STRING valueToString(double value) { return valueToString(value, false, 17); } +String valueToString(bool value) { return value ? "true" : "false"; } -JSONCPP_STRING valueToString(bool value) { return value ? "true" : "false"; } +static bool doesAnyCharRequireEscaping(char const* s, size_t n) { + assert(s || !n); -JSONCPP_STRING valueToQuotedString(const char* value) { - if (value == NULL) - return ""; - // Not sure how to handle unicode... - if (strpbrk(value, "\"\\\b\f\n\r\t") == NULL && - !containsControlCharacter(value)) - return JSONCPP_STRING("\"") + value + "\""; - // We have to walk value and escape any special characters. - // Appending to JSONCPP_STRING is not efficient, but this should be rare. - // (Note: forward slashes are *not* rare, but I am not escaping them.) - JSONCPP_STRING::size_type maxsize = - strlen(value) * 2 + 3; // allescaped+quotes+NULL - JSONCPP_STRING result; - result.reserve(maxsize); // to avoid lots of mallocs - result += "\""; - for (const char* c = value; *c != 0; ++c) { - switch (*c) { - case '\"': - result += "\\\""; - break; - case '\\': - result += "\\\\"; - break; - case '\b': - result += "\\b"; - break; - case '\f': - result += "\\f"; - break; - case '\n': - result += "\\n"; - break; - case '\r': - result += "\\r"; - break; - case '\t': - result += "\\t"; - break; - // case '/': - // Even though \/ is considered a legal escape in JSON, a bare - // slash is also legal, so I see no reason to escape it. - // (I hope I am not misunderstanding something. - // blep notes: actually escaping \/ may be useful in javascript to avoid (*c); - result += oss.str(); - } else { - result += *c; - } - break; - } + 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) { + const unsigned int REPLACEMENT_CHARACTER = 0xFFFD; + + unsigned int firstByte = static_cast(*s); + + if (firstByte < 0x80) + return firstByte; + + if (firstByte < 0xE0) { + if (e - s < 2) + return REPLACEMENT_CHARACTER; + + unsigned int calculated = + ((firstByte & 0x1F) << 6) | (static_cast(s[1]) & 0x3F); + s += 1; + // oversized encoded characters are invalid + return calculated < 0x80 ? REPLACEMENT_CHARACTER : calculated; } - result += "\""; + + if (firstByte < 0xF0) { + if (e - s < 3) + return REPLACEMENT_CHARACTER; + + unsigned int calculated = ((firstByte & 0x0F) << 12) | + ((static_cast(s[1]) & 0x3F) << 6) | + (static_cast(s[2]) & 0x3F); + s += 2; + // surrogates aren't valid codepoints itself + // shouldn't be UTF-8 encoded + if (calculated >= 0xD800 && calculated <= 0xDFFF) + return REPLACEMENT_CHARACTER; + // oversized encoded characters are invalid + return calculated < 0x800 ? REPLACEMENT_CHARACTER : calculated; + } + + if (firstByte < 0xF8) { + if (e - s < 4) + return REPLACEMENT_CHARACTER; + + unsigned int calculated = ((firstByte & 0x07) << 18) | + ((static_cast(s[1]) & 0x3F) << 12) | + ((static_cast(s[2]) & 0x3F) << 6) | + (static_cast(s[3]) & 0x3F); + s += 3; + // oversized encoded characters are invalid + return calculated < 0x10000 ? REPLACEMENT_CHARACTER : calculated; + } + + return REPLACEMENT_CHARACTER; +} + +static const char hex2[] = "000102030405060708090a0b0c0d0e0f" + "101112131415161718191a1b1c1d1e1f" + "202122232425262728292a2b2c2d2e2f" + "303132333435363738393a3b3c3d3e3f" + "404142434445464748494a4b4c4d4e4f" + "505152535455565758595a5b5c5d5e5f" + "606162636465666768696a6b6c6d6e6f" + "707172737475767778797a7b7c7d7e7f" + "808182838485868788898a8b8c8d8e8f" + "909192939495969798999a9b9c9d9e9f" + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf" + "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" + "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" + "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" + "e0e1e2e3e4e5e6e7e8e9eaebecedeeef" + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"; + +static String toHex16Bit(unsigned int x) { + const unsigned int hi = (x >> 8) & 0xff; + const unsigned int lo = x & 0xff; + String result(4, ' '); + result[0] = hex2[2 * hi]; + result[1] = hex2[2 * hi + 1]; + result[2] = hex2[2 * lo]; + result[3] = hex2[2 * lo + 1]; return result; } -// https://github.com/upcaste/upcaste/blob/master/src/upcore/src/cstring/strnpbrk.cpp -static char const* strnpbrk(char const* s, char const* accept, size_t n) { - assert((s || !n) && accept); +static void appendRaw(String& result, unsigned ch) { + result += static_cast(ch); +} - char const* const end = s + n; - for (char const* cur = s; cur < end; ++cur) { - int const c = *cur; - for (char const* a = accept; *a; ++a) { - if (*a == c) { - return cur; - } - } - } - return NULL; +static void appendHex(String& result, unsigned ch) { + result.append("\\u").append(toHex16Bit(ch)); } -static JSONCPP_STRING valueToQuotedStringN(const char* value, unsigned length) { - if (value == NULL) + +static String valueToQuotedStringN(const char* value, size_t length, + bool emitUTF8 = false) { + if (value == nullptr) return ""; - // Not sure how to handle unicode... - if (strnpbrk(value, "\"\\\b\f\n\r\t", length) == NULL && - !containsControlCharacter0(value, length)) - return JSONCPP_STRING("\"") + value + "\""; + + if (!doesAnyCharRequireEscaping(value, length)) + return String("\"") + value + "\""; // We have to walk value and escape any special characters. - // Appending to JSONCPP_STRING is not efficient, but this should be rare. + // Appending to String is not efficient, but this should be rare. // (Note: forward slashes are *not* rare, but I am not escaping them.) - JSONCPP_STRING::size_type maxsize = - length * 2 + 3; // allescaped+quotes+NULL - JSONCPP_STRING result; + String::size_type maxsize = length * 2 + 3; // allescaped+quotes+NULL + String result; result.reserve(maxsize); // to avoid lots of mallocs result += "\""; char const* end = value + length; @@ -301,44 +256,67 @@ static JSONCPP_STRING valueToQuotedStringN(const char* value, unsigned length) { // sequence. // Should add a flag to allow this compatibility mode and prevent this // sequence from occurring. - default: - if ((isControlCharacter(*c)) || (*c == 0)) { - JSONCPP_OSTRINGSTREAM oss; - oss << "\\u" << std::hex << std::uppercase << std::setfill('0') - << std::setw(4) << static_cast(*c); - result += oss.str(); + default: { + if (emitUTF8) { + unsigned codepoint = static_cast(*c); + if (codepoint < 0x20) { + appendHex(result, codepoint); + } else { + appendRaw(result, codepoint); + } } else { - result += *c; + 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; + } break; } } result += "\""; return result; } +String valueToQuotedString(const char* value) { + return valueToQuotedStringN(value, strlen(value)); +} + +String valueToQuotedString(const char* value, size_t length) { + return valueToQuotedStringN(value, length); +} + // Class Writer // ////////////////////////////////////////////////////////////////// -Writer::~Writer() {} +Writer::~Writer() = default; // Class FastWriter // ////////////////////////////////////////////////////////////////// FastWriter::FastWriter() - : yamlCompatiblityEnabled_(false), dropNullPlaceholders_(false), - omitEndingLineFeed_(false) {} -void FastWriter::enableYAMLCompatibility() { yamlCompatiblityEnabled_ = true; } + = default; + +void FastWriter::enableYAMLCompatibility() { yamlCompatibilityEnabled_ = true; } void FastWriter::dropNullPlaceholders() { dropNullPlaceholders_ = true; } void FastWriter::omitEndingLineFeed() { omitEndingLineFeed_ = true; } -JSONCPP_STRING FastWriter::write(const Value& root) { +String FastWriter::write(const Value& root) { document_.clear(); writeValue(root); if (!omitEndingLineFeed_) - document_ += "\n"; + document_ += '\n'; return document_; } @@ -357,13 +335,13 @@ void FastWriter::writeValue(const Value& value) { case realValue: document_ += valueToString(value.asDouble()); break; - case stringValue: - { + case stringValue: { // Is NULL possible for value.string_? No. char const* str; char const* end; bool ok = value.getString(&str, &end); - if (ok) document_ += valueToQuotedStringN(str, static_cast(end-str)); + if (ok) + document_ += valueToQuotedStringN(str, static_cast(end - str)); break; } case booleanValue: @@ -382,13 +360,12 @@ void FastWriter::writeValue(const Value& value) { case objectValue: { Value::Members members(value.getMemberNames()); document_ += '{'; - for (Value::Members::iterator it = members.begin(); it != members.end(); - ++it) { - const JSONCPP_STRING& name = *it; + for (auto it = members.begin(); it != members.end(); ++it) { + const String& name = *it; if (it != members.begin()) document_ += ','; - document_ += valueToQuotedStringN(name.data(), static_cast(name.length())); - document_ += yamlCompatiblityEnabled_ ? ": " : ":"; + document_ += valueToQuotedStringN(name.data(), name.length()); + document_ += yamlCompatibilityEnabled_ ? ": " : ":"; writeValue(value[name]); } document_ += '}'; @@ -399,17 +376,16 @@ void FastWriter::writeValue(const Value& value) { // Class StyledWriter // ////////////////////////////////////////////////////////////////// -StyledWriter::StyledWriter() - : rightMargin_(74), indentSize_(3), addChildValues_() {} +StyledWriter::StyledWriter() = default; -JSONCPP_STRING StyledWriter::write(const Value& root) { +String StyledWriter::write(const Value& root) { document_.clear(); addChildValues_ = false; indentString_.clear(); writeCommentBeforeValue(root); writeValue(root); writeCommentAfterValueOnSameLine(root); - document_ += "\n"; + document_ += '\n'; return document_; } @@ -427,14 +403,15 @@ void StyledWriter::writeValue(const Value& value) { case realValue: pushValue(valueToString(value.asDouble())); break; - case stringValue: - { + case stringValue: { // Is NULL possible for value.string_? No. char const* str; char const* end; bool ok = value.getString(&str, &end); - if (ok) pushValue(valueToQuotedStringN(str, static_cast(end-str))); - else pushValue(""); + if (ok) + pushValue(valueToQuotedStringN(str, static_cast(end - str))); + else + pushValue(""); break; } case booleanValue: @@ -450,12 +427,12 @@ void StyledWriter::writeValue(const Value& value) { else { writeWithIndent("{"); indent(); - Value::Members::iterator it = members.begin(); + auto it = members.begin(); for (;;) { - const JSONCPP_STRING& name = *it; + 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()) { @@ -473,16 +450,16 @@ 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 { - bool isArrayMultiLine = isMultineArray(value); + bool isArrayMultiLine = isMultilineArray(value); if (isArrayMultiLine) { writeWithIndent("["); indent(); bool hasChildValue = !childValues_.empty(); - unsigned index = 0; + ArrayIndex index = 0; for (;;) { const Value& childValue = value[index]; writeCommentBeforeValue(childValue); @@ -505,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]; @@ -515,14 +492,14 @@ void StyledWriter::writeArrayValue(const Value& value) { } } -bool StyledWriter::isMultineArray(const Value& value) { +bool StyledWriter::isMultilineArray(const Value& value) { ArrayIndex const size = value.size(); bool isMultiLine = size * 3 >= rightMargin_; childValues_.clear(); for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) { const Value& childValue = value[index]; isMultiLine = ((childValue.isArray() || childValue.isObject()) && - childValue.size() > 0); + !childValue.empty()); } if (!isMultiLine) // check if line length > max line length { @@ -542,7 +519,7 @@ bool StyledWriter::isMultineArray(const Value& value) { return isMultiLine; } -void StyledWriter::pushValue(const JSONCPP_STRING& value) { +void StyledWriter::pushValue(const String& value) { if (addChildValues_) childValues_.push_back(value); else @@ -560,12 +537,12 @@ void StyledWriter::writeIndent() { document_ += indentString_; } -void StyledWriter::writeWithIndent(const JSONCPP_STRING& value) { +void StyledWriter::writeWithIndent(const String& value) { writeIndent(); document_ += value; } -void StyledWriter::indent() { indentString_ += JSONCPP_STRING(indentSize_, ' '); } +void StyledWriter::indent() { indentString_ += String(indentSize_, ' '); } void StyledWriter::unindent() { assert(indentString_.size() >= indentSize_); @@ -576,20 +553,19 @@ void StyledWriter::writeCommentBeforeValue(const Value& root) { if (!root.hasComment(commentBefore)) return; - document_ += "\n"; + document_ += '\n'; writeIndent(); - const JSONCPP_STRING& comment = root.getComment(commentBefore); - JSONCPP_STRING::const_iterator iter = comment.begin(); + const String& comment = root.getComment(commentBefore); + String::const_iterator iter = comment.begin(); while (iter != comment.end()) { document_ += *iter; - if (*iter == '\n' && - (iter != comment.end() && *(iter + 1) == '/')) + if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/')) writeIndent(); ++iter; } // Comments are stripped of trailing newlines, so add one here - document_ += "\n"; + document_ += '\n'; } void StyledWriter::writeCommentAfterValueOnSameLine(const Value& root) { @@ -597,9 +573,9 @@ void StyledWriter::writeCommentAfterValueOnSameLine(const Value& root) { document_ += " " + root.getComment(commentAfterOnSameLine); if (root.hasComment(commentAfter)) { - document_ += "\n"; + document_ += '\n'; document_ += root.getComment(commentAfter); - document_ += "\n"; + document_ += '\n'; } } @@ -612,22 +588,23 @@ bool StyledWriter::hasCommentForValue(const Value& value) { // Class StyledStreamWriter // ////////////////////////////////////////////////////////////////// -StyledStreamWriter::StyledStreamWriter(JSONCPP_STRING indentation) - : document_(NULL), rightMargin_(74), indentation_(indentation), - addChildValues_() {} +StyledStreamWriter::StyledStreamWriter(String indentation) + : document_(nullptr), indentation_(std::move(indentation)), + addChildValues_(), indented_(false) {} -void StyledStreamWriter::write(JSONCPP_OSTREAM& out, const Value& root) { +void StyledStreamWriter::write(OStream& out, const Value& root) { document_ = &out; addChildValues_ = false; indentString_.clear(); indented_ = true; writeCommentBeforeValue(root); - if (!indented_) writeIndent(); + if (!indented_) + writeIndent(); indented_ = true; writeValue(root); writeCommentAfterValueOnSameLine(root); *document_ << "\n"; - document_ = NULL; // Forget the stream, for safety. + document_ = nullptr; // Forget the stream, for safety. } void StyledStreamWriter::writeValue(const Value& value) { @@ -644,14 +621,15 @@ void StyledStreamWriter::writeValue(const Value& value) { case realValue: pushValue(valueToString(value.asDouble())); break; - case stringValue: - { + case stringValue: { // Is NULL possible for value.string_? No. char const* str; char const* end; bool ok = value.getString(&str, &end); - if (ok) pushValue(valueToQuotedStringN(str, static_cast(end-str))); - else pushValue(""); + if (ok) + pushValue(valueToQuotedStringN(str, static_cast(end - str))); + else + pushValue(""); break; } case booleanValue: @@ -667,12 +645,12 @@ void StyledStreamWriter::writeValue(const Value& value) { else { writeWithIndent("{"); indent(); - Value::Members::iterator it = members.begin(); + auto it = members.begin(); for (;;) { - const JSONCPP_STRING& name = *it; + 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()) { @@ -694,7 +672,7 @@ void StyledStreamWriter::writeArrayValue(const Value& value) { if (size == 0) pushValue("[]"); else { - bool isArrayMultiLine = isMultineArray(value); + bool isArrayMultiLine = isMultilineArray(value); if (isArrayMultiLine) { writeWithIndent("["); indent(); @@ -706,7 +684,8 @@ void StyledStreamWriter::writeArrayValue(const Value& value) { if (hasChildValue) writeWithIndent(childValues_[index]); else { - if (!indented_) writeIndent(); + if (!indented_) + writeIndent(); indented_ = true; writeValue(childValue); indented_ = false; @@ -734,14 +713,14 @@ void StyledStreamWriter::writeArrayValue(const Value& value) { } } -bool StyledStreamWriter::isMultineArray(const Value& value) { +bool StyledStreamWriter::isMultilineArray(const Value& value) { ArrayIndex const size = value.size(); bool isMultiLine = size * 3 >= rightMargin_; childValues_.clear(); for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) { const Value& childValue = value[index]; isMultiLine = ((childValue.isArray() || childValue.isObject()) && - childValue.size() > 0); + !childValue.empty()); } if (!isMultiLine) // check if line length > max line length { @@ -761,7 +740,7 @@ bool StyledStreamWriter::isMultineArray(const Value& value) { return isMultiLine; } -void StyledStreamWriter::pushValue(const JSONCPP_STRING& value) { +void StyledStreamWriter::pushValue(const String& value) { if (addChildValues_) childValues_.push_back(value); else @@ -776,8 +755,9 @@ void StyledStreamWriter::writeIndent() { *document_ << '\n' << indentString_; } -void StyledStreamWriter::writeWithIndent(const JSONCPP_STRING& value) { - if (!indented_) writeIndent(); +void StyledStreamWriter::writeWithIndent(const String& value) { + if (!indented_) + writeIndent(); *document_ << value; indented_ = false; } @@ -793,13 +773,13 @@ void StyledStreamWriter::writeCommentBeforeValue(const Value& root) { if (!root.hasComment(commentBefore)) return; - if (!indented_) writeIndent(); - const JSONCPP_STRING& comment = root.getComment(commentBefore); - JSONCPP_STRING::const_iterator iter = comment.begin(); + if (!indented_) + writeIndent(); + const String& comment = root.getComment(commentBefore); + String::const_iterator iter = comment.begin(); while (iter != comment.end()) { *document_ << *iter; - if (*iter == '\n' && - (iter != comment.end() && *(iter + 1) == '/')) + if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/')) // writeIndent(); // would include newline *document_ << indentString_; ++iter; @@ -831,84 +811,73 @@ bool StyledStreamWriter::hasCommentForValue(const Value& value) { struct CommentStyle { /// Decide whether to write comments. enum Enum { - None, ///< Drop all comments. - Most, ///< Recover odd behavior of previous versions (not implemented yet). - All ///< Keep all comments. + None, ///< Drop all comments. + Most, ///< Recover odd behavior of previous versions (not implemented yet). + All ///< Keep all comments. }; }; -struct BuiltStyledStreamWriter : public StreamWriter -{ - BuiltStyledStreamWriter( - JSONCPP_STRING const& indentation, - CommentStyle::Enum cs, - JSONCPP_STRING const& colonSymbol, - JSONCPP_STRING const& nullSymbol, - JSONCPP_STRING const& endingLineFeedSymbol, - bool useSpecialFloats, - unsigned int precision); - int write(Value const& root, JSONCPP_OSTREAM* sout) JSONCPP_OVERRIDE; +struct BuiltStyledStreamWriter : public StreamWriter { + 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; + private: void writeValue(Value const& value); void writeArrayValue(Value const& value); - bool isMultineArray(Value const& value); - void pushValue(JSONCPP_STRING const& value); + bool isMultilineArray(Value const& value); + void pushValue(String const& value); void writeIndent(); - void writeWithIndent(JSONCPP_STRING const& value); + void writeWithIndent(String const& value); void indent(); void unindent(); void writeCommentBeforeValue(Value const& root); void writeCommentAfterValueOnSameLine(Value const& root); static bool hasCommentForValue(const Value& value); - typedef std::vector ChildValues; + using ChildValues = std::vector; ChildValues childValues_; - JSONCPP_STRING indentString_; + String indentString_; unsigned int rightMargin_; - JSONCPP_STRING indentation_; + String indentation_; CommentStyle::Enum cs_; - JSONCPP_STRING colonSymbol_; - JSONCPP_STRING nullSymbol_; - JSONCPP_STRING endingLineFeedSymbol_; + String colonSymbol_; + String nullSymbol_; + String endingLineFeedSymbol_; bool addChildValues_ : 1; bool indented_ : 1; bool useSpecialFloats_ : 1; + bool emitUTF8_ : 1; unsigned int precision_; + PrecisionType precisionType_; }; BuiltStyledStreamWriter::BuiltStyledStreamWriter( - JSONCPP_STRING const& indentation, - CommentStyle::Enum cs, - JSONCPP_STRING const& colonSymbol, - JSONCPP_STRING const& nullSymbol, - JSONCPP_STRING const& endingLineFeedSymbol, - bool useSpecialFloats, - unsigned int precision) - : rightMargin_(74) - , indentation_(indentation) - , cs_(cs) - , colonSymbol_(colonSymbol) - , nullSymbol_(nullSymbol) - , endingLineFeedSymbol_(endingLineFeedSymbol) - , addChildValues_(false) - , indented_(false) - , useSpecialFloats_(useSpecialFloats) - , precision_(precision) -{ -} -int BuiltStyledStreamWriter::write(Value const& root, JSONCPP_OSTREAM* sout) -{ + 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), emitUTF8_(emitUTF8), + precision_(precision), precisionType_(precisionType) {} +int BuiltStyledStreamWriter::write(Value const& root, OStream* sout) { sout_ = sout; addChildValues_ = false; indented_ = true; indentString_.clear(); writeCommentBeforeValue(root); - if (!indented_) writeIndent(); + if (!indented_) + writeIndent(); indented_ = true; writeValue(root); writeCommentAfterValueOnSameLine(root); *sout_ << endingLineFeedSymbol_; - sout_ = NULL; + sout_ = nullptr; return 0; } void BuiltStyledStreamWriter::writeValue(Value const& value) { @@ -923,16 +892,19 @@ void BuiltStyledStreamWriter::writeValue(Value const& value) { pushValue(valueToString(value.asLargestUInt())); break; case realValue: - pushValue(valueToString(value.asDouble(), useSpecialFloats_, precision_)); + pushValue(valueToString(value.asDouble(), useSpecialFloats_, precision_, + precisionType_)); break; - case stringValue: - { + case stringValue: { // Is NULL is possible for value.string_? No. char const* str; char const* end; bool ok = value.getString(&str, &end); - if (ok) pushValue(valueToQuotedStringN(str, static_cast(end-str))); - else pushValue(""); + if (ok) + pushValue( + valueToQuotedStringN(str, static_cast(end - str), emitUTF8_)); + else + pushValue(""); break; } case booleanValue: @@ -948,12 +920,13 @@ void BuiltStyledStreamWriter::writeValue(Value const& value) { else { writeWithIndent("{"); indent(); - Value::Members::iterator it = members.begin(); + auto it = members.begin(); for (;;) { - JSONCPP_STRING const& name = *it; + 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()) { @@ -975,7 +948,7 @@ void BuiltStyledStreamWriter::writeArrayValue(Value const& value) { if (size == 0) pushValue("[]"); else { - bool isMultiLine = (cs_ == CommentStyle::All) || isMultineArray(value); + bool isMultiLine = (cs_ == CommentStyle::All) || isMultilineArray(value); if (isMultiLine) { writeWithIndent("["); indent(); @@ -987,7 +960,8 @@ void BuiltStyledStreamWriter::writeArrayValue(Value const& value) { if (hasChildValue) writeWithIndent(childValues_[index]); else { - if (!indented_) writeIndent(); + if (!indented_) + writeIndent(); indented_ = true; writeValue(childValue); indented_ = false; @@ -1005,26 +979,28 @@ void BuiltStyledStreamWriter::writeArrayValue(Value const& value) { { assert(childValues_.size() == size); *sout_ << "["; - if (!indentation_.empty()) *sout_ << " "; + if (!indentation_.empty()) + *sout_ << " "; for (unsigned index = 0; index < size; ++index) { if (index > 0) *sout_ << ((!indentation_.empty()) ? ", " : ","); *sout_ << childValues_[index]; } - if (!indentation_.empty()) *sout_ << " "; + if (!indentation_.empty()) + *sout_ << " "; *sout_ << "]"; } } } -bool BuiltStyledStreamWriter::isMultineArray(Value const& value) { +bool BuiltStyledStreamWriter::isMultilineArray(Value const& value) { ArrayIndex const size = value.size(); bool isMultiLine = size * 3 >= rightMargin_; childValues_.clear(); for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) { Value const& childValue = value[index]; isMultiLine = ((childValue.isArray() || childValue.isObject()) && - childValue.size() > 0); + !childValue.empty()); } if (!isMultiLine) // check if line length > max line length { @@ -1044,7 +1020,7 @@ bool BuiltStyledStreamWriter::isMultineArray(Value const& value) { return isMultiLine; } -void BuiltStyledStreamWriter::pushValue(JSONCPP_STRING const& value) { +void BuiltStyledStreamWriter::pushValue(String const& value) { if (addChildValues_) childValues_.push_back(value); else @@ -1063,8 +1039,9 @@ void BuiltStyledStreamWriter::writeIndent() { } } -void BuiltStyledStreamWriter::writeWithIndent(JSONCPP_STRING const& value) { - if (!indented_) writeIndent(); +void BuiltStyledStreamWriter::writeWithIndent(String const& value) { + if (!indented_) + writeIndent(); *sout_ << value; indented_ = false; } @@ -1077,17 +1054,18 @@ void BuiltStyledStreamWriter::unindent() { } void BuiltStyledStreamWriter::writeCommentBeforeValue(Value const& root) { - if (cs_ == CommentStyle::None) return; + if (cs_ == CommentStyle::None) + return; if (!root.hasComment(commentBefore)) return; - if (!indented_) writeIndent(); - const JSONCPP_STRING& comment = root.getComment(commentBefore); - JSONCPP_STRING::const_iterator iter = comment.begin(); + if (!indented_) + writeIndent(); + const String& comment = root.getComment(commentBefore); + String::const_iterator iter = comment.begin(); while (iter != comment.end()) { *sout_ << *iter; - if (*iter == '\n' && - (iter != comment.end() && *(iter + 1) == '/')) + if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/')) // writeIndent(); // would write extra newline *sout_ << indentString_; ++iter; @@ -1095,8 +1073,10 @@ void BuiltStyledStreamWriter::writeCommentBeforeValue(Value const& root) { indented_ = false; } -void BuiltStyledStreamWriter::writeCommentAfterValueOnSameLine(Value const& root) { - if (cs_ == CommentStyle::None) return; +void BuiltStyledStreamWriter::writeCommentAfterValueOnSameLine( + Value const& root) { + if (cs_ == CommentStyle::None) + return; if (root.hasComment(commentAfterOnSameLine)) *sout_ << " " + root.getComment(commentAfterOnSameLine); @@ -1116,28 +1096,19 @@ bool BuiltStyledStreamWriter::hasCommentForValue(const Value& value) { /////////////// // StreamWriter -StreamWriter::StreamWriter() - : sout_(NULL) -{ -} -StreamWriter::~StreamWriter() -{ -} -StreamWriter::Factory::~Factory() -{} -StreamWriterBuilder::StreamWriterBuilder() -{ - setDefaults(&settings_); -} -StreamWriterBuilder::~StreamWriterBuilder() -{} -StreamWriter* StreamWriterBuilder::newStreamWriter() const -{ - JSONCPP_STRING indentation = settings_["indentation"].asString(); - JSONCPP_STRING cs_str = settings_["commentStyle"].asString(); - bool eyc = settings_["enableYAMLCompatibility"].asBool(); - bool dnp = settings_["dropNullPlaceholders"].asBool(); - bool usf = settings_["useSpecialFloats"].asBool(); +StreamWriter::StreamWriter() : sout_(nullptr) {} +StreamWriter::~StreamWriter() = default; +StreamWriter::Factory::~Factory() = default; +StreamWriterBuilder::StreamWriterBuilder() { setDefaults(&settings_); } +StreamWriterBuilder::~StreamWriterBuilder() = default; +StreamWriter* StreamWriterBuilder::newStreamWriter() const { + 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") { @@ -1147,74 +1118,80 @@ StreamWriter* StreamWriterBuilder::newStreamWriter() const } else { throwRuntimeError("commentStyle must be 'All' or 'None'"); } - JSONCPP_STRING colonSymbol = " : "; + PrecisionType precisionType(significantDigits); + if (pt_str == "significant") { + precisionType = PrecisionType::significantDigits; + } else if (pt_str == "decimal") { + precisionType = PrecisionType::decimalPlaces; + } else { + throwRuntimeError("precisionType must be 'significant' or 'decimal'"); + } + String colonSymbol = " : "; if (eyc) { colonSymbol = ": "; } else if (indentation.empty()) { colonSymbol = ":"; } - JSONCPP_STRING nullSymbol = "null"; + String nullSymbol = "null"; if (dnp) { nullSymbol.clear(); } - if (pre > 17) pre = 17; - JSONCPP_STRING endingLineFeedSymbol; - return new BuiltStyledStreamWriter( - indentation, cs, - colonSymbol, nullSymbol, endingLineFeedSymbol, usf, pre); + if (pre > 17) + pre = 17; + String endingLineFeedSymbol; + return new BuiltStyledStreamWriter(indentation, cs, colonSymbol, nullSymbol, + 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"); -} -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) { - JSONCPP_STRING const& key = keys[i]; - if (valid_keys.find(key) == valid_keys.end()) { - inv[key] = settings_[key]; - } + +bool StreamWriterBuilder::validate(Json::Value* invalid) const { + 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 0u == inv.size(); + return invalid ? invalid->empty() : true; } -Value& StreamWriterBuilder::operator[](JSONCPP_STRING key) -{ + +Value& StreamWriterBuilder::operator[](const String& key) { return settings_[key]; } // static -void StreamWriterBuilder::setDefaults(Json::Value* settings) -{ +void StreamWriterBuilder::setDefaults(Json::Value* settings) { //! [StreamWriterBuilderDefaults] (*settings)["commentStyle"] = "All"; (*settings)["indentation"] = "\t"; (*settings)["enableYAMLCompatibility"] = false; (*settings)["dropNullPlaceholders"] = false; (*settings)["useSpecialFloats"] = false; + (*settings)["emitUTF8"] = false; (*settings)["precision"] = 17; + (*settings)["precisionType"] = "significant"; //! [StreamWriterBuilderDefaults] } -JSONCPP_STRING writeString(StreamWriter::Factory const& builder, Value const& root) { - JSONCPP_OSTRINGSTREAM sout; - StreamWriterPtr const writer(builder.newStreamWriter()); +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(); } -JSONCPP_OSTREAM& operator<<(JSONCPP_OSTREAM& sout, Value const& root) { +OStream& operator<<(OStream& sout, Value const& root) { StreamWriterBuilder builder; StreamWriterPtr const writer(builder.newStreamWriter()); writer->write(root, &sout); diff --git a/src/lib_json/sconscript b/src/lib_json/sconscript deleted file mode 100644 index 6e7c6c8a0..000000000 --- a/src/lib_json/sconscript +++ /dev/null @@ -1,8 +0,0 @@ -Import( 'env buildLibrary' ) - -buildLibrary( env, Split( """ - json_reader.cpp - json_value.cpp - json_writer.cpp - """ ), - 'json' ) diff --git a/src/lib_json/version.h.in b/src/lib_json/version.h.in deleted file mode 100644 index 47aac69bd..000000000 --- a/src/lib_json/version.h.in +++ /dev/null @@ -1,20 +0,0 @@ -// DO NOT EDIT. This file (and "version") is generated by CMake. -// Run CMake configure step to update it. -#ifndef JSON_VERSION_H_INCLUDED -# define JSON_VERSION_H_INCLUDED - -# define JSONCPP_VERSION_STRING "@JSONCPP_VERSION@" -# define JSONCPP_VERSION_MAJOR @JSONCPP_VERSION_MAJOR@ -# define JSONCPP_VERSION_MINOR @JSONCPP_VERSION_MINOR@ -# define JSONCPP_VERSION_PATCH @JSONCPP_VERSION_PATCH@ -# 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 -#endif -#define JSONCPP_USING_SECURE_MEMORY @JSONCPP_USE_SECURE_MEMORY@ -// If non-zero, the library zeroes any memory that it has allocated before -// it frees its memory. - -#endif // JSON_VERSION_H_INCLUDED diff --git a/src/test_lib_json/CMakeLists.txt b/src/test_lib_json/CMakeLists.txt index 7000264a7..1c3fce913 100644 --- a/src/test_lib_json/CMakeLists.txt +++ b/src/test_lib_json/CMakeLists.txt @@ -1,38 +1,39 @@ # vim: et ts=4 sts=4 sw=4 tw=0 -ADD_EXECUTABLE( jsoncpp_test - jsontest.cpp - jsontest.h - main.cpp - ) +add_executable(jsoncpp_test + jsontest.cpp + jsontest.h + fuzz.cpp + fuzz.h + main.cpp +) -IF(BUILD_SHARED_LIBS) - ADD_DEFINITIONS( -DJSON_DLL ) - TARGET_LINK_LIBRARIES(jsoncpp_test jsoncpp_lib) -ELSE(BUILD_SHARED_LIBS) - TARGET_LINK_LIBRARIES(jsoncpp_test jsoncpp_lib_static) -ENDIF() +if(BUILD_SHARED_LIBS) + 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() # 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 $) - ELSE(BUILD_SHARED_LIBS) - # Just run the test executable. - ADD_CUSTOM_COMMAND( TARGET jsoncpp_test - POST_BUILD - COMMAND $) - ENDIF() -ENDIF() - -SET_TARGET_PROPERTIES(jsoncpp_test PROPERTIES OUTPUT_NAME jsoncpp_test) +if(JSONCPP_WITH_POST_BUILD_UNITTEST) + add_custom_command(TARGET jsoncpp_test + POST_BUILD + COMMAND ${CMAKE_CROSSCOMPILING_EMULATOR} $ + ) +endif() diff --git a/src/test_lib_json/fuzz.cpp b/src/test_lib_json/fuzz.cpp new file mode 100644 index 000000000..5b75c22e6 --- /dev/null +++ b/src/test_lib_json/fuzz.cpp @@ -0,0 +1,54 @@ +// Copyright 2007-2019 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 + +#include "fuzz.h" + +#include +#include +#include +#include +#include + +namespace Json { +class Exception; +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + Json::CharReaderBuilder builder; + + if (size < sizeof(uint32_t)) { + return 0; + } + + 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); + builder.settings_["strictRoot_"] = hash_settings & (1 << 2); + builder.settings_["allowDroppedNullPlaceholders_"] = hash_settings & (1 << 3); + builder.settings_["allowNumericKeys_"] = hash_settings & (1 << 4); + builder.settings_["allowSingleQuotes_"] = hash_settings & (1 << 5); + 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 auto data_str = reinterpret_cast(data); + try { + reader->parse(data_str, data_str + size, &root, nullptr); + } catch (Json::Exception const&) { + } + // Whether it succeeded or not doesn't matter. + return 0; +} 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/fuzz.h b/src/test_lib_json/fuzz.h new file mode 100644 index 000000000..0816d2757 --- /dev/null +++ b/src/test_lib_json/fuzz.h @@ -0,0 +1,14 @@ +// Copyright 2007-2010 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 FUZZ_H_INCLUDED +#define FUZZ_H_INCLUDED + +#include +#include + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size); + +#endif // ifndef FUZZ_H_INCLUDED diff --git a/src/test_lib_json/jsontest.cpp b/src/test_lib_json/jsontest.cpp index 4c10a37cf..508067a77 100644 --- a/src/test_lib_json/jsontest.cpp +++ b/src/test_lib_json/jsontest.cpp @@ -1,11 +1,11 @@ -// Copyright 2007-2010 Baptiste Lepilleur +// 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 #define _CRT_SECURE_NO_WARNINGS 1 // Prevents deprecation warning with MSVC #include "jsontest.h" -#include +#include #include #if defined(_MSC_VER) @@ -73,28 +73,27 @@ namespace JsonTest { // class TestResult // ////////////////////////////////////////////////////////////////// -TestResult::TestResult() - : predicateId_(1), lastUsedPredicateId_(0), messageTarget_(0) { +TestResult::TestResult() { // The root predicate has id 0 rootPredicateNode_.id_ = 0; - rootPredicateNode_.next_ = 0; + rootPredicateNode_.next_ = nullptr; predicateStackTail_ = &rootPredicateNode_; } -void TestResult::setTestName(const JSONCPP_STRING& name) { name_ = name; } +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; PredicateContext* lastNode = rootPredicateNode_.next_; - for (; lastNode != 0; lastNode = lastNode->next_) { + for (; lastNode != nullptr; lastNode = lastNode->next_) { if (lastNode->id_ > lastUsedPredicateId_) // new PredicateContext { lastUsedPredicateId_ = lastNode->id_; - addFailureInfo( - lastNode->file_, lastNode->line_, lastNode->expr_, nestingLevel); + addFailureInfo(lastNode->file_, lastNode->line_, lastNode->expr_, + nestingLevel); // Link the PredicateContext to the failure for message target when // popping the PredicateContext. lastNode->failure_ = &(failures_.back()); @@ -108,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; @@ -124,32 +121,22 @@ void TestResult::addFailureInfo(const char* file, TestResult& TestResult::popPredicateContext() { PredicateContext* lastNode = &rootPredicateNode_; - while (lastNode->next_ != 0 && lastNode->next_->next_ != 0) { + while (lastNode->next_ != nullptr && lastNode->next_->next_ != nullptr) { lastNode = lastNode->next_; } // Set message target to popped failure PredicateContext* tail = lastNode->next_; - if (tail != 0 && tail->failure_ != 0) { + if (tail != nullptr && tail->failure_ != nullptr) { messageTarget_ = tail->failure_; } // Remove tail from list predicateStackTail_ = lastNode; - lastNode->next_ = 0; + lastNode->next_ = nullptr; return *this; } bool TestResult::failed() const { return !failures_.empty(); } -unsigned int TestResult::getAssertionNestingLevel() const { - unsigned int level = 0; - const PredicateContext* lastNode = &rootPredicateNode_; - while (lastNode->next_ != 0) { - lastNode = lastNode->next_; - ++level; - } - return level; -} - void TestResult::printFailure(bool printTestName) const { if (failures_.empty()) { return; @@ -160,12 +147,10 @@ void TestResult::printFailure(bool printTestName) const { } // Print in reverse to display the callstack in the right order - Failures::const_iterator itEnd = failures_.end(); - for (Failures::const_iterator it = failures_.begin(); it != itEnd; ++it) { - const Failure& failure = *it; - JSONCPP_STRING indent(failure.nestingLevel_ * 2, ' '); + for (const auto& failure : failures_) { + Json::String indent(failure.nestingLevel_ * 2, ' '); if (failure.file_) { - printf("%s%s(%d): ", indent.c_str(), failure.file_, failure.line_); + printf("%s%s(%u): ", indent.c_str(), failure.file_, failure.line_); } if (!failure.expr_.empty()) { printf("%s\n", failure.expr_.c_str()); @@ -173,19 +158,19 @@ void TestResult::printFailure(bool printTestName) const { printf("\n"); } if (!failure.message_.empty()) { - JSONCPP_STRING reindented = indentText(failure.message_, indent + " "); + Json::String reindented = indentText(failure.message_, indent + " "); printf("%s\n", reindented.c_str()); } } } -JSONCPP_STRING TestResult::indentText(const JSONCPP_STRING& text, - const JSONCPP_STRING& indent) { - JSONCPP_STRING reindented; - JSONCPP_STRING::size_type lastIndex = 0; +Json::String TestResult::indentText(const Json::String& text, + const Json::String& indent) { + Json::String reindented; + Json::String::size_type lastIndex = 0; while (lastIndex < text.size()) { - JSONCPP_STRING::size_type nextIndex = text.find('\n', lastIndex); - if (nextIndex == JSONCPP_STRING::npos) { + Json::String::size_type nextIndex = text.find('\n', lastIndex); + if (nextIndex == Json::String::npos) { nextIndex = text.size() - 1; } reindented += indent; @@ -195,8 +180,8 @@ JSONCPP_STRING TestResult::indentText(const JSONCPP_STRING& text, return reindented; } -TestResult& TestResult::addToLastFailure(const JSONCPP_STRING& message) { - if (messageTarget_ != 0) { +TestResult& TestResult::addToLastFailure(const Json::String& message) { + if (messageTarget_ != nullptr) { messageTarget_->message_ += message; } return *this; @@ -217,9 +202,9 @@ TestResult& TestResult::operator<<(bool value) { // class TestCase // ////////////////////////////////////////////////////////////////// -TestCase::TestCase() : result_(0) {} +TestCase::TestCase() = default; -TestCase::~TestCase() {} +TestCase::~TestCase() = default; void TestCase::run(TestResult& result) { result_ = &result; @@ -229,25 +214,23 @@ void TestCase::run(TestResult& result) { // class Runner // ////////////////////////////////////////////////////////////////// -Runner::Runner() {} +Runner::Runner() = default; Runner& Runner::add(TestCaseFactory factory) { tests_.push_back(factory); return *this; } -unsigned int Runner::testCount() const { - return static_cast(tests_.size()); -} +size_t Runner::testCount() const { return tests_.size(); } -JSONCPP_STRING Runner::testNameAt(unsigned int index) const { +Json::String Runner::testNameAt(size_t index) const { TestCase* test = tests_[index](); - JSONCPP_STRING name = test->testName(); + Json::String name = test->testName(); delete test; return name; } -void Runner::runTestAt(unsigned int index, TestResult& result) const { +void Runner::runTestAt(size_t index, TestResult& result) const { TestCase* test = tests_[index](); result.setTestName(test->testName()); printf("Testing %s: ", test->testName()); @@ -257,8 +240,7 @@ void Runner::runTestAt(unsigned int index, TestResult& result) const { #endif // if JSON_USE_EXCEPTION test->run(result); #if JSON_USE_EXCEPTION - } - catch (const std::exception& e) { + } catch (const std::exception& e) { result.addFailure(__FILE__, __LINE__, "Unexpected exception caught:") << e.what(); } @@ -270,9 +252,9 @@ void Runner::runTestAt(unsigned int index, TestResult& result) const { } bool Runner::runAllTest(bool printSummary) const { - unsigned int count = testCount(); + size_t const count = testCount(); std::deque failures; - for (unsigned int index = 0; index < count; ++index) { + for (size_t index = 0; index < count; ++index) { TestResult result; runTestAt(index, result); if (result.failed()) { @@ -282,31 +264,26 @@ bool Runner::runAllTest(bool printSummary) const { if (failures.empty()) { if (printSummary) { - printf("All %d tests passed\n", count); + printf("All %zu tests passed\n", count); } return true; - } else { - for (unsigned int index = 0; index < failures.size(); ++index) { - TestResult& result = failures[index]; - result.printFailure(count > 1); - } + } + for (auto& result : failures) { + result.printFailure(count > 1); + } - if (printSummary) { - unsigned int failedCount = static_cast(failures.size()); - unsigned int passedCount = count - failedCount; - printf("%d/%d tests passed (%d 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 JSONCPP_STRING& testName, - unsigned int& indexOut) const { - unsigned int count = testCount(); - for (unsigned int index = 0; index < count; ++index) { +bool Runner::testIndex(const Json::String& testName, size_t& indexOut) const { + const size_t count = testCount(); + for (size_t index = 0; index < count; ++index) { if (testNameAt(index) == testName) { indexOut = index; return true; @@ -316,26 +293,27 @@ bool Runner::testIndex(const JSONCPP_STRING& testName, } void Runner::listTests() const { - unsigned int count = testCount(); - for (unsigned int index = 0; index < count; ++index) { + const size_t count = testCount(); + for (size_t index = 0; index < count; ++index) { printf("%s\n", testNameAt(index).c_str()); } } int Runner::runCommandLine(int argc, const char* argv[]) const { - // typedef std::deque TestNames; + // typedef std::deque TestNames; Runner subrunner; for (int index = 1; index < argc; ++index) { - JSONCPP_STRING opt = argv[index]; + Json::String opt = argv[index]; if (opt == "--list-tests") { listTests(); return 0; - } else if (opt == "--test-auto") { + } + if (opt == "--test-auto") { preventDialogOnCrash(); } else if (opt == "--test") { ++index; if (index < argc) { - unsigned int testNameIndex; + size_t testNameIndex; if (testIndex(argv[index], testNameIndex)) { subrunner.add(tests_[testNameIndex]); } else { @@ -362,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 @@ -398,8 +376,8 @@ void Runner::preventDialogOnCrash() { _CrtSetReportHook(&msvcrtSilentReportHook); #endif // if defined(_MSC_VER) -// @todo investiguate 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 @@ -426,26 +404,21 @@ void Runner::printUsage(const char* appName) { // Assertion functions // ////////////////////////////////////////////////////////////////// -JSONCPP_STRING ToJsonString(const char* toConvert) { - return JSONCPP_STRING(toConvert); +Json::String ToJsonString(const char* toConvert) { + return Json::String(toConvert); } -JSONCPP_STRING ToJsonString(JSONCPP_STRING in) { - return in; -} +Json::String ToJsonString(Json::String in) { return in; } -#if JSONCPP_USING_SECURE_MEMORY -JSONCPP_STRING ToJsonString(std::string in) { - return JSONCPP_STRING(in.data(), in.data() + in.length()); +#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 JSONCPP_STRING& expected, - const JSONCPP_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 f0ba1fafb..69e3264b9 100644 --- a/src/test_lib_json/jsontest.h +++ b/src/test_lib_json/jsontest.h @@ -1,4 +1,4 @@ -// Copyright 2007-2010 Baptiste Lepilleur +// 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 @@ -6,11 +6,12 @@ #ifndef JSONTEST_H_INCLUDED #define JSONTEST_H_INCLUDED +#include +#include +#include #include #include #include -#include -#include #include #include @@ -32,8 +33,8 @@ class Failure { public: const char* file_; unsigned int line_; - JSONCPP_STRING expr_; - JSONCPP_STRING message_; + Json::String expr_; + Json::String message_; unsigned int nestingLevel_; }; @@ -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_; @@ -60,20 +61,20 @@ class TestResult { /// Not encapsulated to prevent step into when debugging failed assertions /// Incremented by one on assertion predicate entry, decreased by one /// by addPredicateContext(). - PredicateContext::Id predicateId_; + PredicateContext::Id predicateId_{1}; /// \internal Implementation detail for predicate macros PredicateContext* predicateStackTail_; - void setTestName(const JSONCPP_STRING& name); + void setTestName(const Json::String& name); /// Adds an assertion failure. - TestResult& - addFailure(const char* file, unsigned int line, const char* expr = 0); + 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; @@ -82,10 +83,8 @@ class TestResult { // Generic operator that will work with anything ostream can deal with. template TestResult& operator<<(const T& value) { - JSONCPP_OSTRINGSTREAM oss; - oss.precision(16); - oss.setf(std::ios_base::floatfield); - oss << value; + Json::OStringStream oss; + oss << std::setprecision(16) << std::hexfloat << value; return addToLastFailure(oss.str()); } @@ -96,23 +95,20 @@ class TestResult { TestResult& operator<<(Json::UInt64 value); private: - TestResult& addToLastFailure(const JSONCPP_STRING& message); - unsigned int getAssertionNestingLevel() const; + 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 JSONCPP_STRING indentText(const JSONCPP_STRING& text, - const JSONCPP_STRING& indent); + static Json::String indentText(const Json::String& text, + const Json::String& indent); - typedef std::deque Failures; + using Failures = std::deque; Failures failures_; - JSONCPP_STRING name_; + Json::String name_; PredicateContext rootPredicateNode_; - PredicateContext::Id lastUsedPredicateId_; + PredicateContext::Id lastUsedPredicateId_{0}; /// Failure which is the target of the messages added using operator << - Failure* messageTarget_; + Failure* messageTarget_{nullptr}; }; class TestCase { @@ -126,14 +122,14 @@ class TestCase { virtual const char* testName() const = 0; protected: - TestResult* result_; + TestResult* result_{nullptr}; private: virtual void runTestCase() = 0; }; /// Function pointer type for TestCase factory -typedef TestCase* (*TestCaseFactory)(); +using TestCaseFactory = TestCase* (*)(); class Runner { public: @@ -152,37 +148,33 @@ class Runner { bool runAllTest(bool printSummary) const; /// Returns the number of test case in the suite - unsigned int testCount() const; + size_t testCount() const; /// Returns the name of the test case at the specified index - JSONCPP_STRING testNameAt(unsigned int index) const; + Json::String testNameAt(size_t index) const; /// Runs the test case at the specified index using the specified TestResult - void runTestAt(unsigned int index, TestResult& result) const; + void runTestAt(size_t index, TestResult& result) const; static void printUsage(const char* appName); private: // prevents copy construction and assignment - Runner(const Runner& other); - Runner& operator=(const Runner& other); + Runner(const Runner& other) = delete; + Runner& operator=(const Runner& other) = delete; private: void listTests() const; - bool testIndex(const JSONCPP_STRING& testName, unsigned int& index) const; + bool testIndex(const Json::String& testName, size_t& indexOut) const; 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"; @@ -191,18 +183,15 @@ TestResult& checkEqual(TestResult& result, return result; } -JSONCPP_STRING ToJsonString(const char* toConvert); -JSONCPP_STRING ToJsonString(JSONCPP_STRING in); -#if JSONCPP_USING_SECURE_MEMORY -JSONCPP_STRING ToJsonString(std::string in); +Json::String ToJsonString(const char* toConvert); +Json::String ToJsonString(Json::String in); +#if JSONCPP_USE_SECURE_MEMORY +Json::String ToJsonString(std::string in); #endif -TestResult& checkStringEqual(TestResult& result, - const JSONCPP_STRING& expected, - const JSONCPP_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 @@ -212,55 +201,46 @@ TestResult& checkStringEqual(TestResult& result, #define JSONTEST_ASSERT(expr) \ if (expr) { \ } else \ - result_->addFailure(__FILE__, __LINE__, #expr) + result_->addFailure(__FILE__, __LINE__, #expr) /// \brief Asserts that the given predicate is true. /// 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_->predicateId_, __FILE__, __LINE__, #expr, NULL, NULL}; \ result_->predicateStackTail_->next_ = &_minitest_Context; \ result_->predicateId_ += 1; \ result_->predicateStackTail_ = &_minitest_Context; \ (expr); \ result_->popPredicateContext(); \ - } + } while (0) /// \brief Asserts that two values are equals. #define JSONTEST_ASSERT_EQUAL(expected, actual) \ - JsonTest::checkEqual(*result_, \ - expected, \ - actual, \ - __FILE__, \ - __LINE__, \ + JsonTest::checkEqual(*result_, expected, actual, __FILE__, __LINE__, \ #expected " == " #actual) /// \brief Asserts that two values are equals. #define JSONTEST_ASSERT_STRING_EQUAL(expected, actual) \ - JsonTest::checkStringEqual(*result_, \ - JsonTest::ToJsonString(expected), \ - JsonTest::ToJsonString(actual), \ - __FILE__, \ - __LINE__, \ - #expected " == " #actual) + JsonTest::checkStringEqual(*result_, JsonTest::ToJsonString(expected), \ + JsonTest::ToJsonString(actual), __FILE__, \ + __LINE__, #expected " == " #actual) /// \brief Asserts that a given expression throws an exception #define JSONTEST_ASSERT_THROWS(expr) \ - { \ + do { \ bool _threw = false; \ try { \ expr; \ - } \ - catch (...) { \ + } catch (...) { \ _threw = true; \ } \ if (!_threw) \ - result_->addFailure( \ - __FILE__, __LINE__, "expected exception thrown: " #expr); \ - } + result_->addFailure(__FILE__, __LINE__, \ + "expected exception thrown: " #expr); \ + } while (0) /// \brief Begin a fixture test case. #define JSONTEST_FIXTURE(FixtureType, name) \ @@ -270,9 +250,9 @@ TestResult& checkStringEqual(TestResult& result, return new Test##FixtureType##name(); \ } \ \ - public: /* overidden from TestCase */ \ - const char* testName() const JSONCPP_OVERRIDE { return #FixtureType "/" #name; } \ - void runTestCase() JSONCPP_OVERRIDE; \ + public: /* overridden from TestCase */ \ + const char* testName() const override { return #FixtureType "/" #name; } \ + void runTestCase() override; \ }; \ \ void Test##FixtureType##name::runTestCase() @@ -283,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 47cd981aa..60f149d5e 100644 --- a/src/test_lib_json/main.cpp +++ b/src/test_lib_json/main.cpp @@ -1,16 +1,33 @@ -// Copyright 2007-2010 Baptiste Lepilleur +// 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 +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif defined(_MSC_VER) +#pragma warning(disable : 4996) +#endif + +#include "fuzz.h" #include "jsontest.h" +#include +#include +#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. @@ -21,8 +38,8 @@ #define kint64min Json::Value::minInt64 #define kuint64max Json::Value::maxUInt64 -//static const double kdint64max = double(kint64max); -//static const float kfint64max = float(kint64max); +// static const double kdint64max = double(kint64max); +// static const float kfint64max = float(kint64max); static const float kfint32max = float(kint32max); static const float kfuint32max = float(kuint32max); @@ -43,50 +60,80 @@ 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 { /// Initialize all checks to \c false by default. IsCheck(); - bool isObject_; - bool isArray_; - bool isBool_; - bool isString_; - bool isNull_; - - bool isInt_; - bool isInt64_; - bool isUInt_; - bool isUInt64_; - bool isIntegral_; - bool isDouble_; - bool isNumeric_; + bool isObject_{false}; + bool isArray_{false}; + bool isBool_{false}; + bool isString_{false}; + bool isNull_{false}; + + bool isInt_{false}; + bool isInt64_{false}; + bool isUInt_{false}; + bool isUInt64_{false}; + bool isIntegral_{false}; + bool isDouble_{false}; + bool isNumeric_{false}; }; void checkConstMemberCount(const Json::Value& value, @@ -102,54 +149,52 @@ struct ValueTest : JsonTest::TestCase { /// Normalize the representation of floating-point number by stripped leading /// 0 in exponent. - static JSONCPP_STRING normalizeFloatingPointStr(const JSONCPP_STRING& s); + static Json::String normalizeFloatingPointStr(const Json::String& s); }; -JSONCPP_STRING ValueTest::normalizeFloatingPointStr(const JSONCPP_STRING& s) { - JSONCPP_STRING::size_type index = s.find_last_of("eE"); - if (index != JSONCPP_STRING::npos) { - JSONCPP_STRING::size_type hasSign = - (s[index + 1] == '+' || s[index + 1] == '-') ? 1 : 0; - JSONCPP_STRING::size_type exponentStartIndex = index + 1 + hasSign; - JSONCPP_STRING normalized = s.substr(0, exponentStartIndex); - JSONCPP_STRING::size_type indexDigit = - s.find_first_not_of('0', exponentStartIndex); - JSONCPP_STRING exponent = "0"; - if (indexDigit != - JSONCPP_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+0", - normalizeFloatingPointStr("1234.0e+0")); - JSONTEST_ASSERT_STRING_EQUAL("1234e-1", normalizeFloatingPointStr("1234e-1")); - 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_ASSERT_STRING_EQUAL("1234e+1", - normalizeFloatingPointStr("1234e+001")); -} - -JSONTEST_FIXTURE(ValueTest, memberCount) { +Json::String ValueTest::normalizeFloatingPointStr(const Json::String& s) { + 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)); @@ -162,9 +207,12 @@ JSONTEST_FIXTURE(ValueTest, memberCount) { JSONTEST_ASSERT_PRED(checkMemberCount(emptyString_, 0)); JSONTEST_ASSERT_PRED(checkMemberCount(string_, 0)); JSONTEST_ASSERT_PRED(checkMemberCount(true_, 0)); + JSONTEST_ASSERT_PRED(checkMemberCount(false_, 0)); + JSONTEST_ASSERT_PRED(checkMemberCount(string1_, 0)); + JSONTEST_ASSERT_PRED(checkMemberCount(float_, 0)); } -JSONTEST_FIXTURE(ValueTest, objects) { +JSONTEST_FIXTURE_LOCAL(ValueTest, objects) { // Types IsCheck checks; checks.isObject_ = true; @@ -196,6 +244,94 @@ JSONTEST_FIXTURE(ValueTest, objects) { JSONTEST_ASSERT_EQUAL(Json::Value(1234), constObject["id"]); JSONTEST_ASSERT_EQUAL(Json::Value(), constObject["unknown id"]); + // Access through find() + const char idKey[] = "id"; + const Json::Value* foundId = object1_.find(idKey, idKey + strlen(idKey)); + 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 = + object1_.find(yetAnotherIdKey, yetAnotherIdKey + strlen(yetAnotherIdKey)); + JSONTEST_ASSERT_EQUAL(nullptr, foundYetAnotherId); + Json::Value* demandedYetAnotherId = object1_.demand( + yetAnotherIdKey, yetAnotherIdKey + strlen(yetAnotherIdKey)); + JSONTEST_ASSERT(demandedYetAnotherId != nullptr); + *demandedYetAnotherId = "baz"; + + JSONTEST_ASSERT_EQUAL(Json::Value("baz"), object1_["yet another id"]); + // Access through non-const reference JSONTEST_ASSERT_EQUAL(Json::Value(1234), object1_["id"]); JSONTEST_ASSERT_EQUAL(Json::Value(), object1_["unknown id"]); @@ -214,9 +350,30 @@ JSONTEST_FIXTURE(ValueTest, objects) { did = object1_.removeMember("some other id", &got); JSONTEST_ASSERT_EQUAL(Json::Value("bar"), got); JSONTEST_ASSERT_EQUAL(false, did); + + object1_["some other id"] = "foo"; + Json::Value* gotPtr = nullptr; + 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 @@ -248,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]); @@ -261,22 +422,141 @@ 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; - root["array"] = Json::Value::nullRef; - for (int i = 0; i < count; i++) - { + root["array"] = Json::Value::nullSingleton(); + for (int i = 0; i < count; i++) { item["a"] = i; item["b"] = i; root["array"][i] = item; } - //JSONTEST_ASSERT_EQUAL(5, root["array"].size()); + // JSONTEST_ASSERT_EQUAL(5, root["array"].size()); +} + +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(ValueTest, null) { +JSONTEST_FIXTURE_LOCAL(ValueTest, null) { JSONTEST_ASSERT_EQUAL(Json::nullValue, null_.type()); IsCheck checks; @@ -300,10 +580,16 @@ JSONTEST_FIXTURE(ValueTest, null) { JSONTEST_ASSERT_EQUAL(0.0, null_.asFloat()); JSONTEST_ASSERT_STRING_EQUAL("", null_.asString()); - JSONTEST_ASSERT_EQUAL(Json::Value::null, null_); + JSONTEST_ASSERT_EQUAL(Json::Value::nullSingleton(), null_); + + // Test using a Value in a boolean context (false iff null) + JSONTEST_ASSERT_EQUAL(null_, false); + JSONTEST_ASSERT_EQUAL(object1_, true); + JSONTEST_ASSERT_EQUAL(!null_, true); + JSONTEST_ASSERT_EQUAL(!object1_, false); } -JSONTEST_FIXTURE(ValueTest, strings) { +JSONTEST_FIXTURE_LOCAL(ValueTest, strings) { JSONTEST_ASSERT_EQUAL(Json::stringValue, string1_.type()); IsCheck checks; @@ -332,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; @@ -374,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; @@ -646,8 +932,9 @@ JSONTEST_FIXTURE(ValueTest, integers) { JSONTEST_ASSERT_EQUAL((1 << 20), val.asDouble()); JSONTEST_ASSERT_EQUAL((1 << 20), val.asFloat()); JSONTEST_ASSERT_EQUAL(true, val.asBool()); - JSONTEST_ASSERT_STRING_EQUAL("1048576.0", - normalizeFloatingPointStr(JsonTest::ToJsonString(val.asString()))); + JSONTEST_ASSERT_STRING_EQUAL( + "1048576.0", + normalizeFloatingPointStr(JsonTest::ToJsonString(val.asString()))); // -2^20 val = Json::Value(-(1 << 20)); @@ -887,8 +1174,9 @@ JSONTEST_FIXTURE(ValueTest, integers) { JSONTEST_ASSERT_EQUAL((Json::Int64(1) << 40), val.asDouble()); JSONTEST_ASSERT_EQUAL((Json::Int64(1) << 40), val.asFloat()); JSONTEST_ASSERT_EQUAL(true, val.asBool()); - JSONTEST_ASSERT_STRING_EQUAL("1099511627776.0", - normalizeFloatingPointStr(JsonTest::ToJsonString(val.asString()))); + JSONTEST_ASSERT_STRING_EQUAL( + "1099511627776.0", + normalizeFloatingPointStr(JsonTest::ToJsonString(val.asString()))); // -2^40 val = Json::Value(-(Json::Int64(1) << 40)); @@ -959,11 +1247,12 @@ JSONTEST_FIXTURE(ValueTest, integers) { JSONTEST_ASSERT_EQUAL(Json::UInt64(1) << 63, val.asUInt64()); JSONTEST_ASSERT_EQUAL(Json::UInt64(1) << 63, val.asLargestUInt()); JSONTEST_ASSERT_EQUAL(uint64ToDouble(Json::UInt64(1) << 63), val.asDouble()); - JSONTEST_ASSERT_EQUAL(float(uint64ToDouble(Json::UInt64(1) << 63)), - val.asFloat()); + JSONTEST_ASSERT_EQUAL(float(Json::UInt64(1) << 63), val.asFloat()); + JSONTEST_ASSERT_EQUAL(true, val.asBool()); - JSONTEST_ASSERT_STRING_EQUAL("9.2233720368547758e+18", - normalizeFloatingPointStr(JsonTest::ToJsonString(val.asString()))); + JSONTEST_ASSERT_STRING_EQUAL( + "9.2233720368547758e+18", + normalizeFloatingPointStr(JsonTest::ToJsonString(val.asString()))); // int64 min val = Json::Value(Json::Int64(kint64min)); @@ -988,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)); @@ -1005,16 +1292,15 @@ 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()); - JSONTEST_ASSERT_STRING_EQUAL("-9.2233720368547758e+18", - normalizeFloatingPointStr(JsonTest::ToJsonString(val.asString()))); + JSONTEST_ASSERT_STRING_EQUAL( + "-9.2233720368547758e+18", + normalizeFloatingPointStr(JsonTest::ToJsonString(val.asString()))); // 10^19 - const Json::UInt64 ten_to_19 = static_cast(1e19); + const auto ten_to_19 = static_cast(1e19); val = Json::Value(Json::UInt64(ten_to_19)); JSONTEST_ASSERT_EQUAL(Json::uintValue, val.type()); @@ -1057,8 +1343,9 @@ JSONTEST_FIXTURE(ValueTest, integers) { JSONTEST_ASSERT_EQUAL(1e19, val.asDouble()); JSONTEST_ASSERT_EQUAL(1e19, val.asFloat()); JSONTEST_ASSERT_EQUAL(true, val.asBool()); - JSONTEST_ASSERT_STRING_EQUAL("1e+19", - normalizeFloatingPointStr(JsonTest::ToJsonString(val.asString()))); + JSONTEST_ASSERT_STRING_EQUAL( + "1e+19", + normalizeFloatingPointStr(JsonTest::ToJsonString(val.asString()))); // uint64 max val = Json::Value(Json::UInt64(kuint64max)); @@ -1101,12 +1388,13 @@ JSONTEST_FIXTURE(ValueTest, integers) { JSONTEST_ASSERT_EQUAL(18446744073709551616.0, val.asDouble()); JSONTEST_ASSERT_EQUAL(18446744073709551616.0, val.asFloat()); JSONTEST_ASSERT_EQUAL(true, val.asBool()); - JSONTEST_ASSERT_STRING_EQUAL("1.8446744073709552e+19", - normalizeFloatingPointStr(JsonTest::ToJsonString(val.asString()))); + JSONTEST_ASSERT_STRING_EQUAL( + "1.8446744073709552e+19", + normalizeFloatingPointStr(JsonTest::ToJsonString(val.asString()))); #endif } -JSONTEST_FIXTURE(ValueTest, nonIntegers) { +JSONTEST_FIXTURE_LOCAL(ValueTest, nonIntegers) { IsCheck checks; Json::Value val; @@ -1191,8 +1479,9 @@ JSONTEST_FIXTURE(ValueTest, nonIntegers) { JSONTEST_ASSERT_EQUAL(2147483647U, val.asLargestUInt()); #endif JSONTEST_ASSERT_EQUAL(true, val.asBool()); - JSONTEST_ASSERT_EQUAL("2147483647.5", - normalizeFloatingPointStr(JsonTest::ToJsonString(val.asString()))); + JSONTEST_ASSERT_EQUAL( + "2147483647.5", + normalizeFloatingPointStr(JsonTest::ToJsonString(val.asString()))); // A bit under int32 min val = Json::Value(kint32min - 0.5); @@ -1219,8 +1508,9 @@ JSONTEST_FIXTURE(ValueTest, nonIntegers) { JSONTEST_ASSERT_EQUAL(-(Json::Int64(1) << 31), val.asLargestInt()); #endif JSONTEST_ASSERT_EQUAL(true, val.asBool()); - JSONTEST_ASSERT_EQUAL("-2147483648.5", - normalizeFloatingPointStr(JsonTest::ToJsonString(val.asString()))); + JSONTEST_ASSERT_EQUAL( + "-2147483648.5", + normalizeFloatingPointStr(JsonTest::ToJsonString(val.asString()))); // A bit over uint32 max val = Json::Value(kuint32max + 0.5); @@ -1249,30 +1539,35 @@ JSONTEST_FIXTURE(ValueTest, nonIntegers) { val.asLargestUInt()); #endif JSONTEST_ASSERT_EQUAL(true, val.asBool()); - JSONTEST_ASSERT_EQUAL("4294967295.5", - normalizeFloatingPointStr(JsonTest::ToJsonString(val.asString()))); + JSONTEST_ASSERT_EQUAL( + "4294967295.5", + normalizeFloatingPointStr(JsonTest::ToJsonString(val.asString()))); val = Json::Value(1.2345678901234); - JSONTEST_ASSERT_STRING_EQUAL("1.2345678901234001", - normalizeFloatingPointStr(JsonTest::ToJsonString(val.asString()))); + JSONTEST_ASSERT_STRING_EQUAL( + "1.2345678901234001", + normalizeFloatingPointStr(JsonTest::ToJsonString(val.asString()))); // A 16-digit floating point number. val = Json::Value(2199023255552000.0f); JSONTEST_ASSERT_EQUAL(float(2199023255552000.0f), val.asFloat()); - JSONTEST_ASSERT_STRING_EQUAL("2199023255552000.0", - normalizeFloatingPointStr(JsonTest::ToJsonString(val.asString()))); + JSONTEST_ASSERT_STRING_EQUAL( + "2199023255552000.0", + normalizeFloatingPointStr(JsonTest::ToJsonString(val.asString()))); // A very large floating point number. val = Json::Value(3.402823466385289e38); JSONTEST_ASSERT_EQUAL(float(3.402823466385289e38), val.asFloat()); - JSONTEST_ASSERT_STRING_EQUAL("3.402823466385289e+38", - normalizeFloatingPointStr(JsonTest::ToJsonString(val.asString()))); + JSONTEST_ASSERT_STRING_EQUAL( + "3.402823466385289e+38", + normalizeFloatingPointStr(JsonTest::ToJsonString(val.asString()))); // An even larger floating point number. val = Json::Value(1.2345678e300); JSONTEST_ASSERT_EQUAL(double(1.2345678e300), val.asDouble()); - JSONTEST_ASSERT_STRING_EQUAL("1.2345678e+300", - normalizeFloatingPointStr(JsonTest::ToJsonString(val.asString()))); + JSONTEST_ASSERT_STRING_EQUAL( + "1.2345678e+300", + normalizeFloatingPointStr(JsonTest::ToJsonString(val.asString()))); } void ValueTest::checkConstMemberCount(const Json::Value& value, @@ -1299,11 +1594,7 @@ void ValueTest::checkMemberCount(Json::Value& value, JSONTEST_ASSERT_PRED(checkConstMemberCount(value, expectedCount)); } -ValueTest::IsCheck::IsCheck() - : isObject_(false), isArray_(false), isBool_(false), isString_(false), - isNull_(false), isInt_(false), isInt64_(false), isUInt_(false), - isUInt64_(false), isIntegral_(false), isDouble_(false), - isNumeric_(false) {} +ValueTest::IsCheck::IsCheck() = default; void ValueTest::checkIs(const Json::Value& value, const IsCheck& check) { JSONTEST_ASSERT_EQUAL(check.isObject_, value.isObject()); @@ -1326,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")); @@ -1361,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; @@ -1382,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))); @@ -1420,6 +1743,58 @@ JSONTEST_FIXTURE(ValueTest, compareType) { Json::Value(Json::objectValue))); } +JSONTEST_FIXTURE_LOCAL(ValueTest, CopyObject) { + Json::Value arrayVal; + arrayVal.append("val1"); + arrayVal.append("val2"); + arrayVal.append("val3"); + Json::Value stringVal("string value"); + Json::Value copy1, copy2; + { + Json::Value arrayCopy, stringCopy; + arrayCopy.copy(arrayVal); + stringCopy.copy(stringVal); + JSONTEST_ASSERT_PRED(checkIsEqual(arrayCopy, arrayVal)); + JSONTEST_ASSERT_PRED(checkIsEqual(stringCopy, stringVal)); + arrayCopy.append("val4"); + JSONTEST_ASSERT(arrayCopy.size() == 4); + arrayVal.append("new4"); + arrayVal.append("new5"); + JSONTEST_ASSERT(arrayVal.size() == 5); + JSONTEST_ASSERT(!(arrayCopy == arrayVal)); + stringCopy = "another string"; + JSONTEST_ASSERT(!(stringCopy == stringVal)); + copy1.copy(arrayCopy); + copy2.copy(stringCopy); + } + JSONTEST_ASSERT(arrayVal.size() == 5); + JSONTEST_ASSERT(stringVal == "string value"); + JSONTEST_ASSERT(copy1.size() == 4); + JSONTEST_ASSERT(copy2 == "another string"); + copy1.copy(stringVal); + 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) { JSONTEST_ASSERT(x < y); JSONTEST_ASSERT(y > x); @@ -1450,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); @@ -1516,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); @@ -1535,10 +1910,10 @@ 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); - JSONCPP_STRING regular(mutant); + Json::String regular(mutant); mutant[1] = 'a'; JSONTEST_ASSERT_STRING_EQUAL("hallo", ss.c_str()); JSONTEST_ASSERT_STRING_EQUAL("hello", regular.c_str()); @@ -1558,17 +1933,40 @@ JSONTEST_FIXTURE(ValueTest, StaticString) { } } -JSONTEST_FIXTURE(ValueTest, CommentBefore) { +JSONTEST_FIXTURE_LOCAL(ValueTest, WideString) { + // https://github.com/open-source-parsers/jsoncpp/issues/756 + const std::string uni = u8"\u5f0f\uff0c\u8fdb"; // "式,进" + std::string styled; + { + Json::Value v; + v["abc"] = uni; + styled = v.toStyledString(); + } + Json::Value root; + { + JSONCPP_STRING errs; + std::istringstream iss(styled); + bool ok = parseFromStream(Json::CharReaderBuilder(), iss, &root, &errs); + JSONTEST_ASSERT(ok); + if (!ok) { + std::cerr << "errs: " << errs << std::endl; + } + } + JSONTEST_ASSERT_STRING_EQUAL(root["abc"].asString(), uni); +} + +JSONTEST_FIXTURE_LOCAL(ValueTest, CommentBefore) { Json::Value val; // fill val - val.setComment(JSONCPP_STRING("// this comment should appear before"), Json::commentBefore); + val.setComment(Json::String("// this comment should appear before"), + Json::commentBefore); Json::StreamWriterBuilder wbuilder; wbuilder.settings_["commentStyle"] = "All"; { char const expected[] = "// this comment should appear before\nnull"; - JSONCPP_STRING result = Json::writeString(wbuilder, val); + Json::String result = Json::writeString(wbuilder, val); JSONTEST_ASSERT_STRING_EQUAL(expected, result); - JSONCPP_STRING res2 = val.toStyledString(); - JSONCPP_STRING exp2 = "\n"; + Json::String res2 = val.toStyledString(); + Json::String exp2 = "\n"; exp2 += expected; exp2 += "\n"; JSONTEST_ASSERT_STRING_EQUAL(exp2, res2); @@ -1577,33 +1975,33 @@ JSONTEST_FIXTURE(ValueTest, CommentBefore) { val.swapPayload(other); { char const expected[] = "// this comment should appear before\n\"hello\""; - JSONCPP_STRING result = Json::writeString(wbuilder, val); + Json::String result = Json::writeString(wbuilder, val); JSONTEST_ASSERT_STRING_EQUAL(expected, result); - JSONCPP_STRING res2 = val.toStyledString(); - JSONCPP_STRING exp2 = "\n"; + Json::String res2 = val.toStyledString(); + Json::String exp2 = "\n"; exp2 += expected; exp2 += "\n"; JSONTEST_ASSERT_STRING_EQUAL(exp2, res2); JSONTEST_ASSERT_STRING_EQUAL("null\n", other.toStyledString()); } val = "hello"; - // val.setComment("// this comment should appear before", Json::CommentPlacement::commentBefore); - // Assignment over-writes comments. + // val.setComment("// this comment should appear before", + // Json::CommentPlacement::commentBefore); Assignment over-writes comments. { char const expected[] = "\"hello\""; - JSONCPP_STRING result = Json::writeString(wbuilder, val); + Json::String result = Json::writeString(wbuilder, val); JSONTEST_ASSERT_STRING_EQUAL(expected, result); - JSONCPP_STRING res2 = val.toStyledString(); - JSONCPP_STRING exp2 = ""; + Json::String res2 = val.toStyledString(); + 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"; - JSONCPP_STRING binary(cstr, sizeof(cstr)); // include trailing 0 + Json::String binary(cstr, sizeof(cstr)); // include trailing 0 JSONTEST_ASSERT_EQUAL(4U, binary.length()); Json::StreamWriterBuilder b; { @@ -1618,20 +2016,18 @@ JSONTEST_FIXTURE(ValueTest, zeroes) { JSONTEST_ASSERT_STRING_EQUAL(binary, root[top].asString()); Json::Value removed; bool did; - did = root.removeMember(top, top + sizeof(top) - 1U, - &removed); + did = root.removeMember(top, top + sizeof(top) - 1U, &removed); JSONTEST_ASSERT(did); JSONTEST_ASSERT_STRING_EQUAL(binary, removed.asString()); - did = root.removeMember(top, top + sizeof(top) - 1U, - &removed); + did = root.removeMember(top, top + sizeof(top) - 1U, &removed); JSONTEST_ASSERT(!did); JSONTEST_ASSERT_STRING_EQUAL(binary, removed.asString()); // still } } -JSONTEST_FIXTURE(ValueTest, zeroesInKeys) { +JSONTEST_FIXTURE_LOCAL(ValueTest, zeroesInKeys) { char const cstr[] = "h\0i"; - JSONCPP_STRING binary(cstr, sizeof(cstr)); // include trailing 0 + Json::String binary(cstr, sizeof(cstr)); // include trailing 0 JSONTEST_ASSERT_EQUAL(4U, binary.length()); { Json::Value root; @@ -1639,29 +2035,31 @@ JSONTEST_FIXTURE(ValueTest, zeroesInKeys) { JSONTEST_ASSERT_STRING_EQUAL("there", root[binary].asString()); JSONTEST_ASSERT(!root.isMember("h")); JSONTEST_ASSERT(root.isMember(binary)); - JSONTEST_ASSERT_STRING_EQUAL("there", root.get(binary, Json::Value::nullRef).asString()); + JSONTEST_ASSERT_STRING_EQUAL( + "there", root.get(binary, Json::Value::nullSingleton()).asString()); Json::Value removed; bool did; did = root.removeMember(binary.data(), binary.data() + binary.length(), - &removed); + &removed); JSONTEST_ASSERT(did); JSONTEST_ASSERT_STRING_EQUAL("there", removed.asString()); did = root.removeMember(binary.data(), binary.data() + binary.length(), - &removed); + &removed); JSONTEST_ASSERT(!did); JSONTEST_ASSERT_STRING_EQUAL("there", removed.asString()); // still JSONTEST_ASSERT(!root.isMember(binary)); - JSONTEST_ASSERT_STRING_EQUAL("", root.get(binary, Json::Value::nullRef).asString()); + JSONTEST_ASSERT_STRING_EQUAL( + "", root.get(binary, Json::Value::nullSingleton()).asString()); } } -JSONTEST_FIXTURE(ValueTest, specialFloats) { +JSONTEST_FIXTURE_LOCAL(ValueTest, specialFloats) { Json::StreamWriterBuilder b; b.settings_["useSpecialFloats"] = true; Json::Value v = std::numeric_limits::quiet_NaN(); - JSONCPP_STRING expected = "NaN"; - JSONCPP_STRING result = Json::writeString(b, v); + Json::String expected = "NaN"; + Json::String result = Json::writeString(b, v); JSONTEST_ASSERT_STRING_EQUAL(expected, result); v = std::numeric_limits::infinity(); @@ -1675,598 +2073,1535 @@ JSONTEST_FIXTURE(ValueTest, specialFloats) { JSONTEST_ASSERT_STRING_EQUAL(expected, result); } -JSONTEST_FIXTURE(ValueTest, precision) { - Json::StreamWriterBuilder b; - b.settings_["precision"] = 5; +JSONTEST_FIXTURE_LOCAL(ValueTest, precision) { + Json::StreamWriterBuilder b; + b.settings_["precision"] = 5; - Json::Value v = 100.0/3; - JSONCPP_STRING expected = "33.333"; - JSONCPP_STRING result = Json::writeString(b, v); - JSONTEST_ASSERT_STRING_EQUAL(expected, result); - - v = 0.25000000; - expected = "0.25"; - result = Json::writeString(b, v); - JSONTEST_ASSERT_STRING_EQUAL(expected, result); + Json::Value v = 100.0 / 3; + Json::String expected = "33.333"; + Json::String result = Json::writeString(b, v); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); - v = 0.2563456; - expected = "0.25635"; - result = Json::writeString(b, v); - JSONTEST_ASSERT_STRING_EQUAL(expected, result); + v = 0.25000000; + expected = "0.25"; + result = Json::writeString(b, v); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); - b.settings_["precision"] = 1; - expected = "0.3"; - result = Json::writeString(b, v); - JSONTEST_ASSERT_STRING_EQUAL(expected, result); + v = 0.2563456; + expected = "0.25635"; + result = Json::writeString(b, v); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); - b.settings_["precision"] = 17; - v = 1234857476305.256345694873740545068; - expected = "1234857476305.2563"; - result = Json::writeString(b, v); - JSONTEST_ASSERT_STRING_EQUAL(expected, result); + b.settings_["precision"] = 1; + expected = "0.3"; + result = Json::writeString(b, v); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); - b.settings_["precision"] = 24; - v = 0.256345694873740545068; - expected = "0.25634569487374054"; - result = Json::writeString(b, v); - JSONTEST_ASSERT_STRING_EQUAL(expected, result); -} + b.settings_["precision"] = 17; + v = 1234857476305.256345694873740545068; + expected = "1234857476305.2563"; + result = Json::writeString(b, v); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); -struct WriterTest : JsonTest::TestCase {}; + b.settings_["precision"] = 24; + v = 0.256345694873740545068; + expected = "0.25634569487374054"; + result = Json::writeString(b, v); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); -JSONTEST_FIXTURE(WriterTest, dropNullPlaceholders) { - Json::FastWriter writer; - Json::Value nullValue; - JSONTEST_ASSERT(writer.write(nullValue) == "null\n"); + b.settings_["precision"] = 5; + b.settings_["precisionType"] = "decimal"; + v = 0.256345694873740545068; + expected = "0.25635"; + result = Json::writeString(b, v); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); - writer.dropNullPlaceholders(); - JSONTEST_ASSERT(writer.write(nullValue) == "\n"); -} + b.settings_["precision"] = 1; + b.settings_["precisionType"] = "decimal"; + v = 0.256345694873740545068; + expected = "0.3"; + result = Json::writeString(b, v); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); -struct StreamWriterTest : JsonTest::TestCase {}; + b.settings_["precision"] = 0; + b.settings_["precisionType"] = "decimal"; + v = 123.56345694873740545068; + expected = "124"; + result = Json::writeString(b, v); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); -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) == ""); + 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; + expected = "0.233"; + 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; -JSONTEST_FIXTURE(StreamWriterTest, writeZeroes) { - JSONCPP_STRING binary("hi", 3); // include trailing 0 - JSONTEST_ASSERT_EQUAL(3, binary.length()); - JSONCPP_STRING expected("\"hi\\u0000\""); // unicoded zero - Json::StreamWriterBuilder b; { - Json::Value root; - root = binary; - JSONTEST_ASSERT_STRING_EQUAL(binary, root.asString()); - JSONCPP_STRING out = Json::writeString(b, root); - JSONTEST_ASSERT_EQUAL(expected.size(), out.size()); - JSONTEST_ASSERT_STRING_EQUAL(expected, out); + 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); } { - Json::Value root; - root["top"] = binary; - JSONTEST_ASSERT_STRING_EQUAL(binary, root["top"].asString()); - JSONCPP_STRING out = Json::writeString(b, root["top"]); - JSONTEST_ASSERT_STRING_EQUAL(expected, out); + 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 {}; -struct ReaderTest : JsonTest::TestCase {}; +JSONTEST_FIXTURE_LOCAL(FastWriterTest, dropNullPlaceholders) { + Json::FastWriter writer; + Json::Value nullValue; + JSONTEST_ASSERT(writer.write(nullValue) == "null\n"); -JSONTEST_FIXTURE(ReaderTest, parseWithNoErrors) { - Json::Reader reader; - Json::Value root; - bool ok = reader.parse("{ \"property\" : \"value\" }", root); - JSONTEST_ASSERT(ok); - JSONTEST_ASSERT(reader.getFormattedErrorMessages().size() == 0); - JSONTEST_ASSERT(reader.getStructuredErrors().size() == 0); + writer.dropNullPlaceholders(); + JSONTEST_ASSERT(writer.write(nullValue) == "\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().size() == 0); - JSONTEST_ASSERT(reader.getStructuredErrors().size() == 0); - 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; +JSONTEST_FIXTURE_LOCAL(FastWriterTest, enableYAMLCompatibility) { + Json::FastWriter writer; 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."); + root["hello"] = "world"; + + JSONTEST_ASSERT(writer.write(root) == "{\"hello\":\"world\"}\n"); + + writer.enableYAMLCompatibility(); + JSONTEST_ASSERT(writer.write(root) == "{\"hello\": \"world\"}\n"); } -JSONTEST_FIXTURE(ReaderTest, parseChineseWithOneError) { - Json::Reader reader; - 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."); +JSONTEST_FIXTURE_LOCAL(FastWriterTest, omitEndingLineFeed) { + Json::FastWriter writer; + Json::Value nullValue; + + JSONTEST_ASSERT(writer.write(nullValue) == "null\n"); + + writer.omitEndingLineFeed(); + JSONTEST_ASSERT(writer.write(nullValue) == "null"); } -JSONTEST_FIXTURE(ReaderTest, parseWithDetailError) { - 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("{ \"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["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); } -struct CharReaderTest : JsonTest::TestCase {}; - -JSONTEST_FIXTURE(CharReaderTest, parseWithNoErrors) { - Json::CharReaderBuilder b; - Json::CharReader* reader(b.newCharReader()); - JSONCPP_STRING errs; +JSONTEST_FIXTURE_LOCAL(FastWriterTest, writeArrays) { + Json::FastWriter writer; + const Json::String expected("{" + "\"property1\":[\"value1\",\"value2\"]," + "\"property2\":[]" + "}\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.size() == 0); - 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, parseWithNoErrorsTestingOffsets) { - Json::CharReaderBuilder b; - Json::CharReader* reader(b.newCharReader()); - JSONCPP_STRING errs; - 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.size() == 0); - 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, parseWithOneError) { - Json::CharReaderBuilder b; - Json::CharReader* reader(b.newCharReader()); - JSONCPP_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\" }"; - 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["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, parseChineseWithOneError) { - Json::CharReaderBuilder b; - Json::CharReader* reader(b.newCharReader()); - JSONCPP_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[] = - "{ \"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; + 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, parseWithDetailError) { - Json::CharReaderBuilder b; - Json::CharReader* reader(b.newCharReader()); - JSONCPP_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, 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, parseWithStackLimit) { - Json::CharReaderBuilder b; - Json::Value root; - char const doc[] = - "{ \"property\" : \"value\" }"; +JSONTEST_FIXTURE_LOCAL(StyledWriterTest, multiLineArray) { + Json::StyledWriter writer; { - b.settings_["stackLimit"] = 2; - Json::CharReader* reader(b.newCharReader()); - JSONCPP_STRING errs; - bool ok = reader->parse( - doc, doc + std::strlen(doc), - &root, &errs); - JSONTEST_ASSERT(ok); - JSONTEST_ASSERT(errs == ""); - JSONTEST_ASSERT_EQUAL("value", root["property"]); - delete reader; + // 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); } { - b.settings_["stackLimit"] = 1; - Json::CharReader* reader(b.newCharReader()); - JSONCPP_STRING errs; - JSONTEST_ASSERT_THROWS(reader->parse( - doc, doc + std::strlen(doc), - &root, &errs)); - delete reader; + // 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); } } -struct CharReaderStrictModeTest : JsonTest::TestCase {}; - -JSONTEST_FIXTURE(CharReaderStrictModeTest, dupKeys) { - Json::CharReaderBuilder b; - Json::Value root; - char const doc[] = - "{ \"property\" : \"value\", \"key\" : \"val1\", \"key\" : \"val2\" }"; +JSONTEST_FIXTURE_LOCAL(StyledWriterTest, writeValueWithComment) { + Json::StyledWriter writer; { - b.strictMode(&b.settings_); - Json::CharReader* reader(b.newCharReader()); - JSONCPP_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; + 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); } -} -struct CharReaderFailIfExtraTest : JsonTest::TestCase {}; - -JSONTEST_FIXTURE(CharReaderFailIfExtraTest, issue164) { - // This is interpreted as a string value followed by a colon. - Json::CharReaderBuilder b; - Json::Value root; - char const doc[] = - " \"property\" : \"value\" }"; { - b.settings_["failIfExtra"] = false; - Json::CharReader* reader(b.newCharReader()); - JSONCPP_STRING errs; - bool ok = reader->parse( - doc, doc + std::strlen(doc), - &root, &errs); - JSONTEST_ASSERT(ok); - JSONTEST_ASSERT(errs == ""); - JSONTEST_ASSERT_EQUAL("property", root); - 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); } { - b.settings_["failIfExtra"] = true; - Json::CharReader* reader(b.newCharReader()); - JSONCPP_STRING errs; - bool ok = reader->parse( - doc, doc + std::strlen(doc), - &root, &errs); - JSONTEST_ASSERT(!ok); - JSONTEST_ASSERT_STRING_EQUAL(errs, - "* Line 1, Column 13\n" - " Extra non-whitespace after JSON value.\n"); - JSONTEST_ASSERT_EQUAL("property", root); - delete reader; + 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 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"); + + Json::Value root; + 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); +} + +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; + 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) { + { + // 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"] = false; - b.strictMode(&b.settings_); - Json::CharReader* reader(b.newCharReader()); - JSONCPP_STRING errs; - bool ok = reader->parse( - doc, doc + std::strlen(doc), - &root, &errs); - JSONTEST_ASSERT(!ok); - JSONTEST_ASSERT_STRING_EQUAL(errs, - "* Line 1, Column 13\n" - " Extra non-whitespace after JSON value.\n"); - 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(CharReaderFailIfExtraTest, issue107) { - // This is interpretted as an int value followed by a colon. - Json::CharReaderBuilder b; + +JSONTEST_FIXTURE_LOCAL(StyledStreamWriterTest, writeValueWithComment) { + Json::StyledStreamWriter writer("\t"); + { + 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; - char const doc[] = - "1:2:3"; - b.settings_["failIfExtra"] = true; - Json::CharReader* reader(b.newCharReader()); - JSONCPP_STRING errs; - bool ok = reader->parse( - doc, doc + std::strlen(doc), - &root, &errs); - JSONTEST_ASSERT(!ok); - JSONTEST_ASSERT_STRING_EQUAL( - "* Line 1, Column 2\n" - " Extra non-whitespace after JSON value.\n", - errs); - JSONTEST_ASSERT_EQUAL(1, root.asInt()); - 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 = Json::writeString(writer, root); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); } -JSONTEST_FIXTURE(CharReaderFailIfExtraTest, commentAfterObject) { - Json::CharReaderBuilder b; + +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"; { - char const doc[] = - "{ \"property\" : \"value\" } //trailing\n//comment\n"; - b.settings_["failIfExtra"] = true; - Json::CharReader* reader(b.newCharReader()); - JSONCPP_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; + // 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" })"); } -JSONTEST_FIXTURE(CharReaderFailIfExtraTest, commentAfterArray) { + +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[] = - "[ \"property\" , \"value\" ] //trailing\n//comment\n"; - b.settings_["failIfExtra"] = true; - Json::CharReader* reader(b.newCharReader()); - JSONCPP_STRING errs; - bool ok = reader->parse( - doc, doc + std::strlen(doc), - &root, &errs); + char const doc[] = R"({ "property" : "value" })"; + 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_ASSERT(errs.empty()); } -JSONTEST_FIXTURE(CharReaderFailIfExtraTest, commentAfterBool) { + +JSONTEST_FIXTURE_LOCAL(CharReaderTest, parseWithNoErrorsTestingOffsets) { Json::CharReaderBuilder b; + CharReaderPtr reader(b.newCharReader()); + Json::String errs; Json::Value root; - char const doc[] = - " true /*trailing\ncomment*/"; - b.settings_["failIfExtra"] = true; - Json::CharReader* reader(b.newCharReader()); - JSONCPP_STRING errs; - bool ok = reader->parse( - doc, doc + std::strlen(doc), - &root, &errs); + 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_STRING_EQUAL("", errs); - JSONTEST_ASSERT_EQUAL(true, root.asBool()); - delete reader; + JSONTEST_ASSERT(errs.empty()); } -struct CharReaderAllowDropNullTest : JsonTest::TestCase {}; -JSONTEST_FIXTURE(CharReaderAllowDropNullTest, issue178) { +JSONTEST_FIXTURE_LOCAL(CharReaderTest, parseNumber) { Json::CharReaderBuilder b; - b.settings_["allowDroppedNullPlaceholders"] = true; + CharReaderPtr reader(b.newCharReader()); + Json::String errs; Json::Value root; - JSONCPP_STRING errs; - Json::CharReader* reader(b.newCharReader()); { - char const doc[] = "{\"a\":,\"b\":true}"; - bool ok = reader->parse( - doc, doc + std::strlen(doc), - &root, &errs); + // 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_STRING_EQUAL("", errs); - JSONTEST_ASSERT_EQUAL(2u, root.size()); - JSONTEST_ASSERT_EQUAL(Json::nullValue, root.get("a", true)); + 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[] = "{\"a\":}"; - bool ok = reader->parse( - doc, doc + std::strlen(doc), - &root, &errs); + 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(1u, root.size()); - JSONTEST_ASSERT_EQUAL(Json::nullValue, root.get("a", true)); + JSONTEST_ASSERT(errs.empty()); + JSONTEST_ASSERT_EQUAL("", root[0]); } { - char const doc[] = "[]"; - bool ok = reader->parse( - doc, doc + std::strlen(doc), - &root, &errs); + char const doc[] = R"(["\u8A2a"])"; + bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); JSONTEST_ASSERT(ok); - JSONTEST_ASSERT(errs == ""); - JSONTEST_ASSERT_EQUAL(0u, root.size()); - JSONTEST_ASSERT_EQUAL(Json::arrayValue, root); + JSONTEST_ASSERT(errs.empty()); + JSONTEST_ASSERT_EQUAL(u8"\u8A2a", root[0].asString()); // "訪" } { - char const doc[] = "[null]"; - bool ok = reader->parse( - doc, doc + std::strlen(doc), - &root, &errs); - JSONTEST_ASSERT(ok); - JSONTEST_ASSERT(errs == ""); - JSONTEST_ASSERT_EQUAL(1u, root.size()); + 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[] = "[,]"; - 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[] = 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[] = "[,,,]"; - 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[] = 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[] = "[null,]"; - bool ok = reader->parse( - doc, doc + std::strlen(doc), - &root, &errs); + 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[] = "[,null]"; - bool ok = reader->parse( - doc, doc + std::strlen(doc), - &root, &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 == ""); - JSONTEST_ASSERT_EQUAL(2u, root.size()); + JSONTEST_ASSERT(errs.empty()); + JSONTEST_ASSERT_EQUAL("value", root["property"]); } { - 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()); + 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[] = "[null,,]"; - bool ok = reader->parse( - doc, doc + std::strlen(doc), - &root, &errs); + 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_STRING_EQUAL("", errs); - JSONTEST_ASSERT_EQUAL(3u, root.size()); + 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[] = "[,null,]"; - bool ok = reader->parse( - doc, doc + std::strlen(doc), - &root, &errs); + 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_STRING_EQUAL("", errs); - JSONTEST_ASSERT_EQUAL(3u, root.size()); + 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" })"; { - char const doc[] = "[,,null]"; - bool ok = reader->parse( - doc, doc + std::strlen(doc), - &root, &errs); + 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(errs == ""); - JSONTEST_ASSERT_EQUAL(3u, root.size()); + 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 13\n" + " Extra non-whitespace after JSON value.\n", + errs); + JSONTEST_ASSERT_EQUAL("property", root); + } + { + 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_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; + 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 2\n" + " Extra non-whitespace after JSON value.\n", + errs); + JSONTEST_ASSERT_EQUAL(1, root.asInt()); +} +JSONTEST_FIXTURE_LOCAL(CharReaderFailIfExtraTest, commentAfterObject) { + Json::CharReaderBuilder b; + Json::Value root; { - char const doc[] = "[[],,,]"; - bool ok = reader->parse( - doc, doc + std::strlen(doc), - &root, &errs); + char const doc[] = "{ \"property\" : \"value\" } //trailing\n//comment\n"; + 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("", errs); - JSONTEST_ASSERT_EQUAL(4u, root.size()); - JSONTEST_ASSERT_EQUAL(Json::arrayValue, root[0u]); + JSONTEST_ASSERT_EQUAL("value", root["property"]); } +} +JSONTEST_FIXTURE_LOCAL(CharReaderFailIfExtraTest, commentAfterArray) { + Json::CharReaderBuilder b; + Json::Value root; + char const doc[] = "[ \"property\" , \"value\" ] //trailing\n//comment\n"; + 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("", errs); + JSONTEST_ASSERT_EQUAL("value", root[1u]); +} +JSONTEST_FIXTURE_LOCAL(CharReaderFailIfExtraTest, commentAfterBool) { + Json::CharReaderBuilder b; + Json::Value root; + char const doc[] = " true /*trailing\ncomment*/"; + 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("", errs); + JSONTEST_ASSERT_EQUAL(true, root.asBool()); +} + +JSONTEST_FIXTURE_LOCAL(CharReaderFailIfExtraTest, parseComment) { + Json::CharReaderBuilder b; + b.settings_["failIfExtra"] = true; + CharReaderPtr reader(b.newCharReader()); + Json::Value root; + Json::String errs; { - char const doc[] = "[,[],,]"; - bool ok = reader->parse( - doc, doc + std::strlen(doc), - &root, &errs); + 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(4u, root.size()); - JSONTEST_ASSERT_EQUAL(Json::arrayValue, root[1u]); + JSONTEST_ASSERT_EQUAL(true, root.asBool()); + } + { + char const doc[] = " true //com\rment"; + bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); + 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[] = "[,,,[]]"; - bool ok = reader->parse( - doc, doc + std::strlen(doc), - &root, &errs); + char const doc[] = " true //com\nment"; + bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs); + 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()); + } +} + +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); }; + } + + static ValueCheck objGetAnd(std::string idx, ValueCheck f) { + return [=](const Value& root) { f(root.get(idx, true)); }; + } + + static ValueCheck arrGetAnd(int idx, ValueCheck f) { + return [=](const Value& root) { f(root[idx]); }; + } +}; + +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 == ""); - 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; - JSONCPP_STRING errs; - Json::CharReader* reader(b.newCharReader()); + Json::String errs; + CharReaderPtr reader(b.newCharReader()); { char const doc[] = "{'a':true,\"b\":true}"; - bool ok = reader->parse( - doc, doc + std::strlen(doc), - &root, &errs); + 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()); @@ -2275,31 +3610,26 @@ JSONTEST_FIXTURE(CharReaderAllowSingleQuotesTest, issue182) { } { char const doc[] = "{'a': 'x', \"b\":'y'}"; - bool ok = reader->parse( - doc, doc + std::strlen(doc), - &root, &errs); + 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_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; - JSONCPP_STRING errs; - Json::CharReader* reader(b.newCharReader()); + Json::String errs; + CharReaderPtr reader(b.newCharReader()); { char const doc[] = "{'a':true,\"b\":true}"; - bool ok = reader->parse( - doc, doc + std::strlen(doc), - &root, &errs); + 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()); @@ -2308,96 +3638,161 @@ JSONTEST_FIXTURE(CharReaderAllowZeroesTest, issue176) { } { char const doc[] = "{'a': 'x', \"b\":'y'}"; - bool ok = reader->parse( - doc, doc + std::strlen(doc), - &root, &errs); + 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_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; - JSONCPP_STRING errs; - Json::CharReader* reader(b.newCharReader()); + Json::String errs; + CharReaderPtr reader(b.newCharReader()); { - char const doc[] = "{\"a\":NaN,\"b\":Infinity,\"c\":-Infinity}"; - bool ok = reader->parse( - doc, doc + std::strlen(doc), - &root, &errs); + 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(n != 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(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 { int line; bool ok; - JSONCPP_STRING in; + Json::String in; }; const TestData test_data[] = { - {__LINE__, 1, "{\"a\":9}"}, - {__LINE__, 0, "{\"a\":0Infinity}"}, - {__LINE__, 0, "{\"a\":1Infinity}"}, - {__LINE__, 0, "{\"a\":9Infinity}"}, - {__LINE__, 0, "{\"a\":0nfinity}"}, - {__LINE__, 0, "{\"a\":1nfinity}"}, - {__LINE__, 0, "{\"a\":9nfinity}"}, - {__LINE__, 0, "{\"a\":nfinity}"}, - {__LINE__, 0, "{\"a\":.nfinity}"}, - {__LINE__, 0, "{\"a\":9nfinity}"}, - {__LINE__, 0, "{\"a\":-nfinity}"}, - {__LINE__, 1, "{\"a\":Infinity}"}, - {__LINE__, 0, "{\"a\":.Infinity}"}, - {__LINE__, 0, "{\"a\":_Infinity}"}, - {__LINE__, 0, "{\"a\":_nfinity}"}, - {__LINE__, 1, "{\"a\":-Infinity}"} + {__LINE__, true, "{\"a\":9}"}, // + {__LINE__, false, "{\"a\":0Infinity}"}, // + {__LINE__, false, "{\"a\":1Infinity}"}, // + {__LINE__, false, "{\"a\":9Infinity}"}, // + {__LINE__, false, "{\"a\":0nfinity}"}, // + {__LINE__, false, "{\"a\":1nfinity}"}, // + {__LINE__, false, "{\"a\":9nfinity}"}, // + {__LINE__, false, "{\"a\":nfinity}"}, // + {__LINE__, false, "{\"a\":.nfinity}"}, // + {__LINE__, false, "{\"a\":9nfinity}"}, // + {__LINE__, false, "{\"a\":-nfinity}"}, // + {__LINE__, true, "{\"a\":Infinity}"}, // + {__LINE__, false, "{\"a\":.Infinity}"}, // + {__LINE__, false, "{\"a\":_Infinity}"}, // + {__LINE__, false, "{\"a\":_nfinity}"}, // + {__LINE__, true, "{\"a\":-Infinity}"}, // + {__LINE__, true, "{\"a\":+Infinity}"} // }; - for (size_t tdi = 0; tdi < sizeof(test_data) / sizeof(*test_data); ++tdi) { - const TestData& td = test_data[tdi]; - bool ok = reader->parse(&*td.in.begin(), - &*td.in.begin() + td.in.size(), + 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"; - } - - { - char const doc[] = "{\"posInf\": Infinity, \"NegInf\": -Infinity}"; - bool ok = reader->parse( - doc, doc + std::strlen(doc), - &root, &errs); + // 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[] = R"({"posInf": +Infinity, "NegInf": -Infinity})"; + 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(std::numeric_limits::infinity(), root["posInf"].asDouble()); - JSONTEST_ASSERT_EQUAL(-std::numeric_limits::infinity(), root["NegInf"].asDouble()); + JSONTEST_ASSERT_EQUAL(std::numeric_limits::infinity(), + root["posInf"].asDouble()); + 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; @@ -2418,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"; - int dist = 0; - JSONCPP_STRING str; - for (Json::ValueIterator it = json.begin(); it != json.end(); ++it) { - dist = it - json.begin(); - str = it->asString().c_str(); + 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"; + 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; + } } - JSONTEST_ASSERT_EQUAL(1, dist); - JSONTEST_ASSERT_STRING_EQUAL("b", str); + { + 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_FIXTURE_LOCAL(IteratorTest, staticStringKey) { + Json::Value json; + json[Json::StaticString("k1")] = "a"; + JSONTEST_ASSERT_EQUAL(Json::Value("k1"), json.begin().key()); } -JSONTEST_FIXTURE(IteratorTest, names) { +JSONTEST_FIXTURE_LOCAL(IteratorTest, names) { Json::Value json; json["k1"] = "a"; json["k2"] = "b"; @@ -2442,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"; @@ -2470,120 +3960,147 @@ 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; - for(int i = 9; i < 12; ++i) - { - JSONCPP_OSTRINGSTREAM out; + for (int i = 9; i < 12; ++i) { + Json::OStringStream out; out << std::setw(2) << i; - JSONCPP_STRING str = out.str(); + Json::String str = out.str(); value[str] = str; } - JSONCPP_OSTRINGSTREAM out; - //in old code, this will get a compile error + Json::OStringStream out; + // in old code, this will get a compile error Json::Value::const_iterator iter = value.begin(); - for(; iter != value.end(); ++iter) - { + for (; iter != value.end(); ++iter) { out << *iter << ','; } - JSONCPP_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) { -#if JSON_HAS_RVALUE_REFERENCES +JSONTEST_FIXTURE_LOCAL(RValueTest, moveConstruction) { Json::Value json; json["key"] = "value"; Json::Value moved = std::move(json); - JSONTEST_ASSERT(moved != json); // Possibly not nullValue; definitely not equal. + JSONTEST_ASSERT(moved != json); // Possibly not nullValue; definitely not + // equal. JSONTEST_ASSERT_EQUAL(Json::objectValue, moved.type()); JSONTEST_ASSERT_EQUAL(Json::stringValue, moved["key"].type()); -#endif +} + +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_LOCAL(FuzzTest, fuzzDoesntCrash) { + const std::string example = "{}"; + JSONTEST_ASSERT_EQUAL( + 0, + LLVMFuzzerTestOneInput(reinterpret_cast(example.c_str()), + 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, offsetAccessors); - JSONTEST_REGISTER_FIXTURE(runner, ValueTest, typeChecksThrowExceptions); - JSONTEST_REGISTER_FIXTURE(runner, ValueTest, StaticString); - 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); + + 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/src/test_lib_json/sconscript b/src/test_lib_json/sconscript deleted file mode 100644 index 915fd01c0..000000000 --- a/src/test_lib_json/sconscript +++ /dev/null @@ -1,10 +0,0 @@ -Import( 'env_testing buildUnitTests' ) - -buildUnitTests( env_testing, Split( """ - main.cpp - jsontest.cpp - """ ), - 'test_lib_json' ) - -# For 'check' to work, 'libs' must be built first. -env_testing.Depends('test_lib_json', '#libs') diff --git a/test/cleantests.py b/test/cleantests.py index 6a441dc5a..36d5b9b46 100644 --- a/test/cleantests.py +++ b/test/cleantests.py @@ -1,4 +1,4 @@ -# Copyright 2007 The JsonCpp Authors +# Copyright 2007 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 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/generate_expected.py b/test/generate_expected.py index 4d56bce73..e049ab5f4 100644 --- a/test/generate_expected.py +++ b/test/generate_expected.py @@ -1,4 +1,4 @@ -# Copyright 2007 The JsonCpp Authors +# Copyright 2007 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 diff --git a/test/pyjsontestrunner.py b/test/pyjsontestrunner.py index 6111ea9e8..8acdbd2de 100644 --- a/test/pyjsontestrunner.py +++ b/test/pyjsontestrunner.py @@ -1,4 +1,4 @@ -# Copyright 2007 The JsonCpp Authors +# Copyright 2007 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 @@ -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 d0c938685..49cc7a960 100644 --- a/test/runjsontests.py +++ b/test/runjsontests.py @@ -1,4 +1,4 @@ -# Copyright 2007 The JsonCpp Authors +# Copyright 2007 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 @@ -55,13 +55,17 @@ def safeGetLine(lines, index): """ % (message, diff_line, safeGetLine(expected,diff_line), safeGetLine(actual,diff_line)) - + def safeReadFile(path): try: return open(path, 'rt', encoding = 'utf-8').read() 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,21 +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: - test_jsonchecker = glob(os.path.join(input_dir, '../jsonchecker', '*.json')) + 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') @@ -125,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 @@ -151,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) diff --git a/test/rununittests.py b/test/rununittests.py index cffe2c70e..6634e7241 100644 --- a/test/rununittests.py +++ b/test/rununittests.py @@ -1,4 +1,4 @@ -# Copyright 2009 The JsonCpp Authors +# Copyright 2009 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 diff --git a/travis.sh b/travis.sh deleted file mode 100755 index a9811ec7c..000000000 --- a/travis.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env sh -# 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: -# - SHARED_LIB=ON/OFF -# - STATIC_LIB=ON/OFF -# - CMAKE_PKG=ON/OFF -# - BUILD_TYPE=release/debug -# - VERBOSE_MAKE=false/true -# - VERBOSE (set or not) - -# -e: fail on error -# -v: show commands -# -x: show expanded commands -set -vex - -env | sort - -cmake -DJSONCPP_WITH_CMAKE_PACKAGE=$CMAKE_PKG -DBUILD_SHARED_LIBS=$SHARED_LIB -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_VERBOSE_MAKEFILE=$VERBOSE_MAKE . -make -cmake -DJSONCPP_WITH_CMAKE_PACKAGE=$CMAKE_PKG -DBUILD_SHARED_LIBS=$SHARED_LIB -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_VERBOSE_MAKEFILE=$VERBOSE_MAKE -DJSONCPP_USE_SECURE_MEMORY=1 . -make - -# Python is not available in Travis for osx. -# https://github.com/travis-ci/travis-ci/issues/2320 -if [ "$TRAVIS_OS_NAME" != "osx" ] -then - make jsoncpp_check - valgrind --error-exitcode=42 --leak-check=full ./src/test_lib_json/jsoncpp_test -fi diff --git a/version b/version deleted file mode 100644 index 27f9cd322..000000000 --- a/version +++ /dev/null @@ -1 +0,0 @@ -1.8.0