├── .clang-format ├── .clang-tidy ├── .cmake-format.yaml ├── .github ├── FUNDING.yml ├── actions │ └── setup_cache │ │ └── action.yml └── workflows │ ├── auto-clang-format.yml │ ├── ci.yml │ └── codeql-analysis.yml ├── .gitignore ├── .gitlab-ci.yml ├── .lgtm.yml ├── CMakeLists.txt ├── Dependencies.cmake ├── Dockerfile ├── LICENSE ├── ProjectOptions.cmake ├── README.md ├── cmake ├── CPM.cmake ├── Cache.cmake ├── CompilerWarnings.cmake ├── Cuda.cmake ├── Doxygen.cmake ├── Hardening.cmake ├── InterproceduralOptimization.cmake ├── LibFuzzer.cmake ├── Linker.cmake ├── PackageProject.cmake ├── PreventInSourceBuilds.cmake ├── Sanitizers.cmake ├── StandardProjectSettings.cmake ├── StaticAnalyzers.cmake ├── SystemLink.cmake ├── Tests.cmake ├── Utilities.cmake ├── VCEnvironment.cmake └── _FORTIFY_SOURCE.hpp ├── examples ├── Energy+.schema.epJSON ├── RefBldgMediumOfficeNew2004_Chicago_epJSON.epJSON ├── allof_integers_and_numbers.schema.json ├── array_doubles_10_20_30_40.json ├── array_integers_10_20_30_40.json └── test.json ├── fuzz_test ├── CMakeLists.txt └── fuzz_tester.cpp ├── include └── json2cpp │ ├── json2cpp.hpp │ └── json2cpp_adapter.hpp ├── src ├── CMakeLists.txt ├── json2cpp.cpp ├── json2cpp.hpp ├── main.cpp └── schema_validator.cpp └── test ├── CMakeLists.txt ├── catch_main.cpp ├── constexpr_schema_tests.cpp ├── constexpr_tests.cpp ├── tests.cpp └── valijson_tests.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | AccessModifierOffset: -2 2 | AlignAfterOpenBracket: DontAlign 3 | AlignConsecutiveAssignments: false 4 | AlignConsecutiveDeclarations: false 5 | AlignEscapedNewlines: Left 6 | AlignOperands: true 7 | AlignTrailingComments: false 8 | AllowAllParametersOfDeclarationOnNextLine: false 9 | AllowShortBlocksOnASingleLine: true 10 | AllowShortCaseLabelsOnASingleLine: false 11 | AllowShortFunctionsOnASingleLine: All 12 | AllowShortIfStatementsOnASingleLine: true 13 | AllowShortLoopsOnASingleLine: true 14 | AlwaysBreakAfterDefinitionReturnType: None 15 | AlwaysBreakAfterReturnType: None 16 | AlwaysBreakBeforeMultilineStrings: true 17 | AlwaysBreakTemplateDeclarations: false 18 | BinPackArguments: false 19 | BinPackParameters: false 20 | BraceWrapping: 21 | AfterClass: true 22 | AfterControlStatement: false 23 | AfterEnum: false 24 | AfterFunction: true 25 | AfterNamespace: false 26 | AfterObjCDeclaration: false 27 | AfterStruct: true 28 | AfterUnion: false 29 | BeforeCatch: false 30 | BeforeElse: false 31 | IndentBraces: false 32 | SplitEmptyFunction: false 33 | SplitEmptyNamespace: true 34 | SplitEmptyRecord: true 35 | BreakAfterJavaFieldAnnotations: true 36 | BreakBeforeBinaryOperators: NonAssignment 37 | BreakBeforeBraces: Custom 38 | BreakBeforeInheritanceComma: true 39 | BreakBeforeTernaryOperators: true 40 | BreakConstructorInitializers: BeforeColon 41 | BreakConstructorInitializersBeforeComma: false 42 | BreakStringLiterals: true 43 | ColumnLimit: 120 44 | CommentPragmas: '^ IWYU pragma:' 45 | CompactNamespaces: false 46 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 47 | ConstructorInitializerIndentWidth: 2 48 | ContinuationIndentWidth: 2 49 | Cpp11BracedListStyle: false 50 | DerivePointerAlignment: false 51 | DisableFormat: false 52 | ExperimentalAutoDetectBinPacking: true 53 | FixNamespaceComments: true 54 | ForEachMacros: 55 | - foreach 56 | - Q_FOREACH 57 | - BOOST_FOREACH 58 | IncludeCategories: 59 | - Priority: 2 60 | Regex: ^"(llvm|llvm-c|clang|clang-c)/ 61 | - Priority: 3 62 | Regex: ^(<|"(gtest|gmock|isl|json)/) 63 | - Priority: 1 64 | Regex: .* 65 | IncludeIsMainRegex: (Test)?$ 66 | IndentCaseLabels: false 67 | IndentWidth: 2 68 | IndentWrappedFunctionNames: true 69 | JavaScriptQuotes: Leave 70 | JavaScriptWrapImports: true 71 | KeepEmptyLinesAtTheStartOfBlocks: true 72 | Language: Cpp 73 | MacroBlockBegin: '' 74 | MacroBlockEnd: '' 75 | MaxEmptyLinesToKeep: 2 76 | NamespaceIndentation: Inner 77 | ObjCBlockIndentWidth: 7 78 | ObjCSpaceAfterProperty: true 79 | ObjCSpaceBeforeProtocolList: false 80 | PointerAlignment: Right 81 | ReflowComments: true 82 | SortIncludes: true 83 | SortUsingDeclarations: false 84 | SpaceAfterCStyleCast: false 85 | SpaceAfterTemplateKeyword: false 86 | SpaceBeforeAssignmentOperators: true 87 | SpaceBeforeParens: ControlStatements 88 | SpaceInEmptyParentheses: false 89 | SpacesBeforeTrailingComments: 0 90 | SpacesInAngles: false 91 | SpacesInCStyleCastParentheses: false 92 | SpacesInContainerLiterals: true 93 | SpacesInParentheses: false 94 | SpacesInSquareBrackets: false 95 | Standard: c++20 96 | TabWidth: 8 97 | UseTab: Never 98 | 99 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: "*, 3 | -abseil-*, 4 | -altera-*, 5 | -android-*, 6 | -fuchsia-*, 7 | -google-*, 8 | -llvm*, 9 | -modernize-use-trailing-return-type, 10 | -zircon-*, 11 | -readability-else-after-return, 12 | -readability-static-accessed-through-instance, 13 | -readability-avoid-const-params-in-decls, 14 | -cppcoreguidelines-non-private-member-variables-in-classes, 15 | -misc-non-private-member-variables-in-classes, 16 | -misc-no-recursion 17 | " 18 | WarningsAsErrors: '' 19 | HeaderFilterRegex: '' 20 | FormatStyle: none 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /.cmake-format.yaml: -------------------------------------------------------------------------------- 1 | additional_commands: 2 | foo: 3 | flags: 4 | - BAR 5 | - BAZ 6 | kwargs: 7 | DEPENDS: '*' 8 | HEADERS: '*' 9 | SOURCES: '*' 10 | bullet_char: '*' 11 | dangle_parens: false 12 | enum_char: . 13 | line_ending: unix 14 | line_width: 120 15 | max_pargs_hwrap: 3 16 | separate_ctrl_name_with_space: false 17 | separate_fn_name_with_space: false 18 | tab_size: 2 19 | 20 | markup: 21 | enable_markup: false 22 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: lefticus 4 | patreon: lefticus 5 | -------------------------------------------------------------------------------- /.github/actions/setup_cache/action.yml: -------------------------------------------------------------------------------- 1 | 2 | name: 'setup_cache' 3 | description: 'sets up the shared cache' 4 | inputs: 5 | compiler: 6 | required: true 7 | type: string 8 | build_type: 9 | required: true 10 | type: string 11 | generator: 12 | required: true 13 | type: string 14 | developer_mode: 15 | required: true 16 | type: string 17 | 18 | 19 | runs: 20 | using: "composite" 21 | steps: 22 | - name: Cache 23 | uses: actions/cache@v2 24 | with: 25 | # You might want to add .ccache to your cache configuration? 26 | path: | 27 | ~/.cache/pip 28 | ${{ env.HOME }}/.cache/vcpkg/archives 29 | ${{ env.XDG_CACHE_HOME }}/vcpkg/archives 30 | ${{ env.LOCALAPPDATA }}\vcpkg\archives 31 | ${{ env.APPDATA }}\vcpkg\archives 32 | key: ${{ runner.os }}-${{ inputs.compiler }}-${{ inputs.build_type }}-${{ inputs.generator }}-${{ inputs.developer_mode }}-${{ hashFiles('**/CMakeLists.txt') }}-${{ hashFiles('./vcpkg.json')}} 33 | restore-keys: | 34 | ${{ runner.os }}-${{ inputs.compiler }}-${{ inputs.build_type }} 35 | 36 | -------------------------------------------------------------------------------- /.github/workflows/auto-clang-format.yml: -------------------------------------------------------------------------------- 1 | name: auto-clang-format 2 | on: [pull_request] 3 | 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | 8 | steps: 9 | - uses: actions/checkout@v2 10 | - uses: DoozyX/clang-format-lint-action@v0.13 11 | with: 12 | source: '.' 13 | exclude: './third_party ./external' 14 | extensions: 'h,cpp,hpp' 15 | clangFormatVersion: 12 16 | inplace: True 17 | - uses: EndBug/add-and-commit@v4 18 | with: 19 | author_name: Clang Robot 20 | author_email: robot@example.com 21 | message: ':art: Committing clang-format changes' 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | pull_request: 4 | release: 5 | types: [published] 6 | 7 | push: 8 | tags: 9 | branches: 10 | - main 11 | - develop 12 | 13 | env: 14 | CLANG_TIDY_VERSION: "15.0.2" 15 | VERBOSE: 1 16 | 17 | jobs: 18 | Test: 19 | runs-on: ${{ matrix.os }} 20 | strategy: 21 | fail-fast: false 22 | 23 | # Recommendations: 24 | # * support at least 2 operating systems 25 | # * support at least 2 compilers 26 | # * make sure all supported configurations for your project are built 27 | # 28 | # Disable/enable builds in this list to meet the above recommendations 29 | # and your own projects needs 30 | matrix: 31 | os: 32 | - ubuntu-20.04 33 | - macos-10.15 34 | - windows-2019 35 | compiler: 36 | # you can specify the version after `-` like "llvm-15.0.2". 37 | - llvm-15.0.2 38 | - gcc-11 39 | generator: 40 | - "Ninja Multi-Config" 41 | build_type: 42 | - Release 43 | - Debug 44 | package_maintainer_mode: 45 | - ON 46 | - OFF 47 | build_shared: 48 | - OFF 49 | 50 | exclude: 51 | # mingw is determined by this author to be too buggy to support 52 | - os: windows-2019 53 | compiler: gcc-11 54 | 55 | include: 56 | # Add appropriate variables for gcov version required. This will intentionally break 57 | # if you try to use a compiler that does not have gcov set 58 | - compiler: gcc-11 59 | gcov_executable: gcov 60 | enable_ipo: On 61 | 62 | - compiler: llvm-15.0.2 63 | enable_ipo: Off 64 | gcov_executable: "llvm-cov gcov" 65 | 66 | - os: macos-10.15 67 | enable_ipo: Off 68 | enable_cppcheck: OFF 69 | enable_clang_tidy: OFF 70 | 71 | - os: ubuntu-20.04 72 | enable_clang_tidy: On 73 | enable_cppcheck: On 74 | 75 | 76 | # Set up preferred package generators, for given build configurations 77 | - build_type: Release 78 | package_maintainer_mode: OFF 79 | package_generator: TBZ2 80 | 81 | # This exists solely to make sure a non-multiconfig build works 82 | - os: ubuntu-20.04 83 | compiler: gcc-11 84 | generator: "Unix Makefiles" 85 | build_type: Debug 86 | gcov_executable: gcov 87 | package_maintainer_mode: On 88 | enable_ipo: Off 89 | enable_clang_tidy: On 90 | enable_cppcheck: On 91 | 92 | 93 | # Windows msvc builds 94 | - os: windows-2022 95 | compiler: msvc 96 | generator: "Visual Studio 17 2022" 97 | build_type: Debug 98 | package_maintainer_mode: On 99 | enable_ipo: On 100 | enable_clang_tidy: Off 101 | enable_cppcheck: Off 102 | 103 | 104 | - os: windows-2022 105 | compiler: msvc 106 | generator: "Visual Studio 17 2022" 107 | build_type: Release 108 | package_maintainer_mode: On 109 | enable_ipo: On 110 | enable_clang_tidy: Off 111 | enable_cppcheck: Off 112 | 113 | 114 | - os: windows-2022 115 | compiler: msvc 116 | generator: "Visual Studio 17 2022" 117 | build_type: Debug 118 | package_maintainer_mode: Off 119 | enable_clang_tidy: Off 120 | enable_cppcheck: Off 121 | 122 | 123 | - os: windows-2022 124 | compiler: msvc 125 | generator: "Visual Studio 17 2022" 126 | build_type: Release 127 | package_maintainer_mode: Off 128 | package_generator: ZIP 129 | enable_clang_tidy: Off 130 | enable_cppcheck: Off 131 | 132 | 133 | - os: windows-2022 134 | compiler: msvc 135 | generator: "Visual Studio 17 2022" 136 | build_type: Release 137 | package_maintainer_mode: On 138 | enable_ipo: On 139 | build_shared: On 140 | enable_clang_tidy: Off 141 | enable_cppcheck: Off 142 | 143 | 144 | 145 | steps: 146 | - name: Check for llvm version mismatches 147 | if: ${{ contains(matrix.compiler, 'llvm') && !contains(matrix.compiler, env.CLANG_TIDY_VERSION) }} 148 | uses: actions/github-script@v3 149 | with: 150 | script: | 151 | core.setFailed('There is a mismatch between configured llvm compiler and clang-tidy version chosen') 152 | 153 | - uses: actions/checkout@v2 154 | 155 | - name: Setup Cache 156 | uses: ./.github/actions/setup_cache 157 | with: 158 | compiler: ${{ matrix.compiler }} 159 | build_type: ${{ matrix.build_type }} 160 | package_maintainer_mode: ${{ matrix.package_maintainer_mode }} 161 | generator: ${{ matrix.generator }} 162 | 163 | - name: Setup Cpp 164 | uses: aminya/setup-cpp@v1 165 | with: 166 | compiler: ${{ matrix.compiler }} 167 | vcvarsall: ${{ contains(matrix.os, 'windows' )}} 168 | 169 | cmake: true 170 | ninja: true 171 | vcpkg: false 172 | ccache: true 173 | clangtidy: ${{ env.CLANG_TIDY_VERSION }} 174 | 175 | 176 | cppcheck: true 177 | 178 | gcovr: true 179 | opencppcoverage: true 180 | 181 | - name: Configure CMake 182 | run: | 183 | cmake -S . -B ./build -G "${{matrix.generator}}" -Djson2cpp_ENABLE_CLANG_TIDY:BOOL=${{ matrix.enable_clang_tidy }} -Djson2cpp_ENABLE_CPPCHECK:BOOL=${{ matrix.enable_cppcheck }} -Djson2cpp_ENABLE_IPO=${{matrix.enable_ipo }} -DCMAKE_BUILD_TYPE:STRING=${{matrix.build_type}} -Djson2cpp_PACKAGING_MAINTAINER_MODE:BOOL=${{matrix.package_maintainer_mode}} -Djson2cpp_ENABLE_COVERAGE:BOOL=${{ matrix.build_type == 'Debug' }} -DGIT_SHA:STRING=${{ github.sha }} 184 | 185 | - name: Build 186 | # Execute the build. You can specify a specific target with "--target " 187 | run: | 188 | cmake --build ./build --config ${{matrix.build_type}} 189 | 190 | - name: Unix - Test and coverage 191 | if: runner.os != 'Windows' 192 | working-directory: ./build 193 | # Execute tests defined by the CMake configuration. 194 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 195 | run: | 196 | ctest -C ${{matrix.build_type}} 197 | gcovr -j ${{env.nproc}} --delete --root ../ --print-summary --xml-pretty --xml coverage.xml . --gcov-executable '${{ matrix.gcov_executable }}' 198 | 199 | - name: Windows - Test and coverage 200 | if: runner.os == 'Windows' 201 | working-directory: ./build 202 | run: | 203 | OpenCppCoverage.exe --export_type cobertura:coverage.xml --cover_children -- ctest -C ${{matrix.build_type}} 204 | 205 | - name: CPack 206 | if: matrix.package_generator != '' 207 | working-directory: ./build 208 | run: | 209 | cpack -C ${{matrix.build_type}} -G ${{matrix.package_generator}} 210 | 211 | - name: Publish Snapshot Release 212 | uses: softprops/action-gh-release@v1 213 | if: ${{ (github.ref == 'refs/heads/main') && matrix.package_generator != '' }} 214 | with: 215 | tag_name: "snapshot-${{ github.sha }}" 216 | files: | 217 | build/*-*${{ matrix.build_type }}*-*.* 218 | 219 | 220 | - name: Publish Tagged Release 221 | uses: softprops/action-gh-release@v1 222 | if: ${{ startsWith(github.ref, 'refs/tags/') && matrix.package_generator != '' }} 223 | with: 224 | files: | 225 | build/*-*${{ matrix.build_type }}*-*.* 226 | 227 | 228 | - name: Publish to codecov 229 | uses: codecov/codecov-action@v2 230 | with: 231 | flags: ${{ runner.os }} 232 | name: ${{ runner.os }}-coverage 233 | files: ./build/coverage.xml 234 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main, develop ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main, develop ] 20 | schedule: 21 | - cron: '38 0 * * 5' 22 | 23 | 24 | jobs: 25 | analyze: 26 | name: Analyze 27 | runs-on: ubuntu-latest 28 | permissions: 29 | actions: read 30 | contents: read 31 | security-events: write 32 | 33 | strategy: 34 | fail-fast: false 35 | matrix: 36 | language: [ 'cpp' ] 37 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 38 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 39 | compiler: 40 | # you can specify the version after `-` like "llvm-13.0.0". 41 | - gcc-11 42 | generator: 43 | - "Ninja Multi-Config" 44 | build_type: 45 | - Debug 46 | packaging_maintainer_mode: 47 | - ON 48 | 49 | 50 | steps: 51 | - uses: actions/checkout@v2 52 | 53 | - name: Setup Cache 54 | uses: ./.github/actions/setup_cache 55 | with: 56 | compiler: ${{ matrix.compiler }} 57 | build_type: ${{ matrix.build_type }} 58 | packaging_maintainer_mode: ${{ matrix.packaging_maintainer_mode }} 59 | generator: ${{ matrix.generator }} 60 | 61 | 62 | - name: Setup Cpp 63 | uses: aminya/setup-cpp@v1 64 | with: 65 | compiler: ${{ matrix.compiler }} 66 | vcvarsall: ${{ contains(matrix.os, 'windows' )}} 67 | 68 | cmake: true 69 | ninja: true 70 | vcpkg: false 71 | ccache: true 72 | clangtidy: false 73 | 74 | cppcheck: false 75 | 76 | gcovr: false 77 | opencppcoverage: false 78 | 79 | # make sure coverage is only enabled for Debug builds, since it sets -O0 to make sure coverage 80 | # has meaningful results 81 | - name: Configure CMake 82 | run: | 83 | cmake -S . -B ./build -G "${{matrix.generator}}" -DCMAKE_BUILD_TYPE:STRING=${{matrix.build_type}} -Djson2cpp_PACKAGING_MAINTAINER_MODE:BOOL=${{matrix.packaging_maintainer_mode}} -Djson2cpp_ENABLE_COVERAGE:BOOL=${{ matrix.build_type == 'Debug' }} 84 | 85 | # Initializes the CodeQL tools for scanning. 86 | - name: Initialize CodeQL 87 | uses: github/codeql-action/init@v2 88 | with: 89 | languages: ${{ matrix.language }} 90 | # If you wish to specify custom queries, you can do so here or in a config file. 91 | # By default, queries listed here will override any specified in a config file. 92 | # Prefix the list here with "+" to use these queries and those in the config file. 93 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 94 | 95 | 96 | - name: Build 97 | # Execute the build. You can specify a specific target with "--target " 98 | run: | 99 | cmake --build ./build --config ${{matrix.build_type}} 100 | 101 | - name: Perform CodeQL Analysis 102 | uses: github/codeql-action/analyze@v2 103 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build directories and binary files 2 | build/ 3 | out/ 4 | cmake-build-*/ 5 | 6 | # IDE files 7 | .vs/ 8 | .idea/ 9 | .vscode/ 10 | !.vscode/settings.json 11 | !.vscode/tasks.json 12 | !.vscode/launch.json 13 | !.vscode/extensions.json 14 | *.swp 15 | *~ 16 | 17 | # OS Generated Files 18 | .DS_Store 19 | .AppleDouble 20 | .LSOverride 21 | ._* 22 | .Spotlight-V100 23 | .Trashes 24 | .Trash-* 25 | $RECYCLE.BIN/ 26 | .TemporaryItems 27 | ehthumbs.db 28 | Thumbs.db -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: ubuntu:latest 2 | 3 | stages: 4 | - test 5 | 6 | .setup_linux: &setup_linux | 7 | DEBIAN_FRONTEND=noninteractive 8 | 9 | # set time-zone 10 | TZ=Canada/Pacific 11 | ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 12 | 13 | # for downloading 14 | apt-get update -qq 15 | apt-get install -y --no-install-recommends curl gnupg ca-certificates 16 | 17 | # keys used by apt 18 | apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32 19 | apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 40976EAF437D05B5 20 | apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 1E9377A2BA9EF27F 21 | 22 | .setup_cpp: &setup_cpp | 23 | curl -LJO "https://github.com/aminya/setup-cpp/releases/download/v0.5.8/setup_cpp_linux" 24 | chmod +x setup_cpp_linux 25 | ./setup_cpp_linux --compiler $compiler --cmake true --ninja true --conan true --ccache true 26 | source ~/.profile 27 | 28 | .test: &test | 29 | # Build and Test 30 | cmake -S . -B ./build -DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo 31 | cmake --build ./build --config RelWithDebInfo 32 | 33 | test_linux_llvm: 34 | stage: test 35 | variables: 36 | compiler: llvm 37 | script: 38 | - *setup_linux 39 | - *setup_cpp 40 | - *test 41 | 42 | test_linux_gcc: 43 | stage: test 44 | variables: 45 | compiler: gcc 46 | script: 47 | - *setup_linux 48 | - *setup_cpp 49 | - *test -------------------------------------------------------------------------------- /.lgtm.yml: -------------------------------------------------------------------------------- 1 | # for full syntax documentation see: https://lgtm.com/help/lgtm/lgtm.yml-configuration-file 2 | path_classifiers: 3 | test: 4 | - "*/fuzz_test/**/*" 5 | - "*/test/**/*" 6 | extraction: 7 | cpp: 8 | prepare: 9 | packages: 10 | - g++-10 11 | - ccache 12 | script: 13 | - mkdir ~/.conan 14 | - cat /usr/local/share/ca-certificates/semmle-cache-ca/semmle-cache-ca.crt >> ~/.conan/cacert.pem 15 | - python3 -m pip install --upgrade pip setuptools 16 | - python3 -m pip install conan 17 | - python3 -m pip install cmake 18 | - source ~/.profile 19 | configure: 20 | command: 21 | - mkdir build 22 | - cmake -D ENABLE_LARGE_TESTS:BOOL=FALSE -D OPT_ENABLE_COVERAGE:BOOL=TRUE -D ENABLE_DEVELOPER_MODE:BOOL=FALSE -D CMAKE_BUILD_TYPE:STRING=Debug -S . -B build 23 | index: 24 | build_command: cmake --build ./build -- -j2 25 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.21) 2 | 3 | # This template attempts to be "fetch_content"-able 4 | # so that it works well with tools like CPM or other 5 | # manual dependency management 6 | 7 | # Only set the cxx_standard if it is not set by someone else 8 | if (NOT DEFINED CMAKE_CXX_STANDARD) 9 | set(CMAKE_CXX_STANDARD 20) 10 | endif() 11 | 12 | # strongly encouraged to enable this globally to avoid conflicts between 13 | # -Wpedantic being enabled and -std=c++20 and -std=gnu++20 for example 14 | # when compiling with PCH enabled 15 | set(CMAKE_CXX_EXTENSIONS OFF) 16 | 17 | # Set the project name and language 18 | project( 19 | json2cpp 20 | VERSION 0.0.1 21 | DESCRIPTION "" 22 | HOMEPAGE_URL "%%myurl%%" 23 | LANGUAGES CXX C) 24 | 25 | include(cmake/PreventInSourceBuilds.cmake) 26 | include(ProjectOptions.cmake) 27 | 28 | 29 | json2cpp_setup_options() 30 | 31 | json2cpp_global_options() 32 | include(Dependencies.cmake) 33 | json2cpp_setup_dependencies() 34 | 35 | json2cpp_local_options() 36 | 37 | # don't know if this should be set globally from here or not... 38 | set(CMAKE_CXX_VISIBILITY_PRESET hidden) 39 | 40 | set(GIT_SHA 41 | "Unknown" 42 | CACHE STRING "SHA this build was generated from") 43 | string( 44 | SUBSTRING "${GIT_SHA}" 45 | 0 46 | 8 47 | GIT_SHORT_SHA) 48 | 49 | target_compile_features(json2cpp_options INTERFACE cxx_std_${CMAKE_CXX_STANDARD}) 50 | 51 | add_library(json2cpp_headers INTERFACE) 52 | target_include_directories(json2cpp_headers INTERFACE "${CMAKE_SOURCE_DIR}/include") 53 | add_library(json2cpp::json2cpp_headers ALIAS json2cpp_headers) 54 | 55 | add_library(json2cpp::json2cpp_options ALIAS json2cpp_options) 56 | add_library(json2cpp::json2cpp_warnings ALIAS json2cpp_warnings) 57 | 58 | #add_library(json2cpp::json2cpp_options INTERFACE IMPORTED) 59 | #add_library(json2cpp::json2cpp_warnings INTERFACE IMPORTED) 60 | 61 | # configure files based on CMake configuration options 62 | 63 | # Adding the src: 64 | add_subdirectory(src) 65 | 66 | # Don't even look at tests if we're not top level 67 | if(NOT PROJECT_IS_TOP_LEVEL) 68 | return() 69 | endif() 70 | 71 | # Adding the tests: 72 | include(CTest) 73 | 74 | if(BUILD_TESTING) 75 | add_subdirectory(test) 76 | endif() 77 | 78 | 79 | if(json2cpp_BUILD_FUZZ_TESTS) 80 | message(AUTHOR_WARNING "Building Fuzz Tests, using fuzzing sanitizer https://www.llvm.org/docs/LibFuzzer.html") 81 | add_subdirectory(fuzz_test) 82 | endif() 83 | 84 | # If MSVC is being used, and ASAN is enabled, we need to set the debugger environment 85 | # so that it behaves well with MSVC's debugger, and we can run the target from visual studio 86 | if(MSVC) 87 | get_all_installable_targets(all_targets) 88 | message("all_targets=${all_targets}") 89 | set_target_properties(${all_targets} PROPERTIES VS_DEBUGGER_ENVIRONMENT "PATH=$(VC_ExecutablePath_x64);%PATH%") 90 | endif() 91 | 92 | # set the startup project for the "play" button in MSVC 93 | set_property(DIRECTORY PROPERTY VS_STARTUP_PROJECT json2cpp) 94 | 95 | if(CMAKE_SKIP_INSTALL_RULES) 96 | return() 97 | endif() 98 | 99 | include(cmake/PackageProject.cmake) 100 | 101 | # Add other targets that you want installed here, by default we just package the one executable 102 | # we know we want to ship 103 | json2cpp_package_project( 104 | TARGETS 105 | json2cpp 106 | json2cpp_options 107 | json2cpp_warnings 108 | # FIXME: this does not work! CK 109 | # PRIVATE_DEPENDENCIES_CONFIGURED project_options project_warnings 110 | ) 111 | 112 | # Experience shows that explicit package naming can help make it easier to sort 113 | # out potential ABI related issues before they start, while helping you 114 | # track a build to a specific GIT SHA 115 | set(CPACK_PACKAGE_FILE_NAME 116 | "${CMAKE_PROJECT_NAME}-${CMAKE_PROJECT_VERSION}-${GIT_SHORT_SHA}-${CMAKE_SYSTEM_NAME}-${CMAKE_BUILD_TYPE}-${CMAKE_CXX_COMPILER_ID}-${CMAKE_CXX_COMPILER_VERSION}" 117 | ) 118 | 119 | include(CPack) 120 | -------------------------------------------------------------------------------- /Dependencies.cmake: -------------------------------------------------------------------------------- 1 | include(cmake/CPM.cmake) 2 | 3 | # Done as a function so that updates to variables like 4 | # CMAKE_CXX_FLAGS don't propagate out to other 5 | # targets 6 | function(json2cpp_setup_dependencies) 7 | 8 | # For each dependency, see if it's 9 | # already been provided to us by a parent project 10 | 11 | if(NOT TARGET fmtlib::fmtlib) 12 | cpmaddpackage("gh:fmtlib/fmt#9.1.0") 13 | endif() 14 | 15 | if(NOT TARGET spdlog::spdlog) 16 | cpmaddpackage( 17 | NAME 18 | spdlog 19 | VERSION 20 | 1.11.0 21 | GITHUB_REPOSITORY 22 | "gabime/spdlog" 23 | OPTIONS 24 | "SPDLOG_FMT_EXTERNAL ON") 25 | endif() 26 | 27 | if(NOT TARGET Catch2::Catch2WithMain) 28 | cpmaddpackage("gh:catchorg/Catch2@3.3.2") 29 | endif() 30 | 31 | if(NOT TARGET CLI11::CLI11) 32 | cpmaddpackage("gh:CLIUtils/CLI11@2.3.2") 33 | endif() 34 | 35 | if(NOT TARGET ftxui::screen) 36 | cpmaddpackage("gh:ArthurSonzogni/FTXUI#e23dbc7473654024852ede60e2121276c5aab660") 37 | endif() 38 | 39 | if(NOT TARGET nlohmann_json::nlohmann_json) 40 | cpmaddpackage("gh:nlohmann/json@3.11.2") 41 | endif() 42 | 43 | if(NOT TARGET ValiJSON::valijson) 44 | cpmaddpackage("gh:tristanpenman/valijson@1.0") 45 | endif() 46 | 47 | 48 | endfunction() 49 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:bionic 2 | 3 | # Install packages available from standard repos 4 | RUN apt-get update -qq && \ 5 | apt-get install -y --no-install-recommends \ 6 | software-properties-common wget git gpg-agent file \ 7 | python3 python3-pip doxygen graphviz ccache cppcheck build-essential \ 8 | neovim emacs nano 9 | 10 | # Install conan 11 | RUN python3 -m pip install --upgrade pip setuptools && \ 12 | python3 -m pip install conan && \ 13 | conan --version 14 | 15 | # User-settable versions: 16 | # This Dockerfile should support gcc-[7, 8, 9, 10] and clang-[10, 11] 17 | # Earlier versions of clang will require significant modifications to the IWYU section 18 | ARG GCC_VER="10" 19 | ARG LLVM_VER="11" 20 | 21 | # Add gcc-${GCC_VER} 22 | RUN add-apt-repository -y ppa:ubuntu-toolchain-r/test && \ 23 | apt-get update -qq && \ 24 | apt-get install -y --no-install-recommends gcc-${GCC_VER} g++-${GCC_VER} 25 | 26 | # Add clang-${LLVM_VER} 27 | RUN wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - 2>/dev/null && \ 28 | add-apt-repository -y "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-${LLVM_VER} main" && \ 29 | apt-get update -qq && \ 30 | apt-get install -y --no-install-recommends \ 31 | clang-${LLVM_VER} lldb-${LLVM_VER} lld-${LLVM_VER} clangd-${LLVM_VER} \ 32 | llvm-${LLVM_VER}-dev libclang-${LLVM_VER}-dev clang-tidy-${LLVM_VER} 33 | 34 | # Add current cmake/ccmake, from Kitware 35 | RUN wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null \ 36 | | gpg --dearmor - | tee /etc/apt/trusted.gpg.d/kitware.gpg >/dev/null && \ 37 | apt-add-repository 'deb https://apt.kitware.com/ubuntu/ bionic main' && \ 38 | apt-get update -qq && \ 39 | apt-get install -y --no-install-recommends cmake cmake-curses-gui 40 | 41 | # Set the default clang-tidy, so CMake can find it 42 | RUN update-alternatives --install /usr/bin/clang-tidy clang-tidy $(which clang-tidy-${LLVM_VER}) 1 43 | 44 | # Install include-what-you-use 45 | ENV IWYU /home/iwyu 46 | ENV IWYU_BUILD ${IWYU}/build 47 | ENV IWYU_SRC ${IWYU}/include-what-you-use 48 | RUN mkdir -p ${IWYU_BUILD} && \ 49 | git clone --branch clang_${LLVM_VER} \ 50 | https://github.com/include-what-you-use/include-what-you-use.git \ 51 | ${IWYU_SRC} 52 | RUN CC=clang-${LLVM_VER} CXX=clang++-${LLVM_VER} cmake -S ${IWYU_SRC} \ 53 | -B ${IWYU_BUILD} \ 54 | -G "Unix Makefiles" -DCMAKE_PREFIX_PATH=/usr/lib/llvm-${LLVM_VER} && \ 55 | cmake --build ${IWYU_BUILD} -j && \ 56 | cmake --install ${IWYU_BUILD} 57 | 58 | # Per https://github.com/include-what-you-use/include-what-you-use#how-to-install: 59 | # `You need to copy the Clang include directory to the expected location before 60 | # running (similarly, use include-what-you-use -print-resource-dir to learn 61 | # exactly where IWYU wants the headers).` 62 | RUN mkdir -p $(include-what-you-use -print-resource-dir 2>/dev/null) 63 | RUN ln -s $(readlink -f /usr/lib/clang/${LLVM_VER}/include) \ 64 | $(include-what-you-use -print-resource-dir 2>/dev/null)/include 65 | 66 | ## Cleanup cached apt data we don't need anymore 67 | #RUN apt-get clean && \ 68 | # rm -rf /var/lib/apt/lists/* 69 | 70 | # Set gcc-${GCC_VER} as default gcc 71 | RUN update-alternatives --install /usr/bin/gcc gcc $(which gcc-${GCC_VER}) 100 72 | RUN update-alternatives --install /usr/bin/g++ g++ $(which g++-${GCC_VER}) 100 73 | 74 | # Set clang-${LLVM_VER} as default clang 75 | RUN update-alternatives --install /usr/bin/clang clang $(which clang-${LLVM_VER}) 100 76 | RUN update-alternatives --install /usr/bin/clang++ clang++ $(which clang++-${LLVM_VER}) 100 77 | 78 | # Allow the user to set compiler defaults 79 | ARG USE_CLANG 80 | # if --build-arg USE_CLANG=1, set CC to 'clang' or set to null otherwise. 81 | ENV CC=${USE_CLANG:+"clang"} 82 | ENV CXX=${USE_CLANG:+"clang++"} 83 | # if CC is null, set it to 'gcc' (or leave as is otherwise). 84 | ENV CC=${CC:-"gcc"} 85 | ENV CXX=${CXX:-"g++"} 86 | 87 | # By default, anything you run in Docker is done as superuser. 88 | # Conan runs some install commands as superuser, and will prepend `sudo` to 89 | # these commands, unless `CONAN_SYSREQUIRES_SUDO=0` is in your env variables. 90 | ENV CONAN_SYSREQUIRES_SUDO 0 91 | # Some packages request that Conan use the system package manager to install 92 | # a few dependencies. This flag allows Conan to proceed with these installations; 93 | # leaving this flag undefined can cause some installation failures. 94 | ENV CONAN_SYSREQUIRES_MODE enabled 95 | 96 | # Include project 97 | ADD . /starter_project 98 | WORKDIR /starter_project 99 | 100 | CMD ["/bin/bash"] 101 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Jason Turner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ProjectOptions.cmake: -------------------------------------------------------------------------------- 1 | include(cmake/SystemLink.cmake) 2 | include(cmake/LibFuzzer.cmake) 3 | include(CMakeDependentOption) 4 | include(CheckCXXCompilerFlag) 5 | 6 | 7 | macro(json2cpp_setup_options) 8 | option(json2cpp_ENABLE_LARGE_TESTS ON) 9 | 10 | option(json2cpp_ENABLE_HARDENING "Enable hardening" ON) 11 | option(json2cpp_ENABLE_COVERAGE "Enable coverage reporting" OFF) 12 | cmake_dependent_option( 13 | json2cpp_ENABLE_GLOBAL_HARDENING 14 | "Attempt to push hardening options to built dependencies" 15 | ON 16 | json2cpp_ENABLE_HARDENING 17 | OFF) 18 | 19 | 20 | if((CMAKE_CXX_COMPILER_ID MATCHES ".*Clang.*" OR CMAKE_CXX_COMPILER_ID MATCHES ".*GNU.*") AND NOT WIN32) 21 | set(SUPPORTS_UBSAN ON) 22 | else() 23 | set(SUPPORTS_UBSAN OFF) 24 | endif() 25 | 26 | if((CMAKE_CXX_COMPILER_ID MATCHES ".*Clang.*" OR CMAKE_CXX_COMPILER_ID MATCHES ".*GNU.*") AND WIN32) 27 | set(SUPPORTS_ASAN OFF) 28 | else() 29 | set(SUPPORTS_ASAN ON) 30 | endif() 31 | 32 | json2cpp_check_libfuzzer_support(LIBFUZZER_SUPPORTED) 33 | option(json2cpp_BUILD_FUZZ_TESTS "Enable fuzz testing executable" ${LIBFUZZER_SUPPORTED}) 34 | 35 | 36 | if(NOT PROJECT_IS_TOP_LEVEL OR json2cpp_PACKAGING_MAINTAINER_MODE) 37 | option(json2cpp_ENABLE_IPO "Enable IPO/LTO" OFF) 38 | option(json2cpp_WARNINGS_AS_ERRORS "Treat Warnings As Errors" OFF) 39 | option(json2cpp_ENABLE_USER_LINKER "Enable user-selected linker" OFF) 40 | option(json2cpp_ENABLE_SANITIZER_ADDRESS "Enable address sanitizer" OFF) 41 | option(json2cpp_ENABLE_SANITIZER_LEAK "Enable leak sanitizer" OFF) 42 | option(json2cpp_ENABLE_SANITIZER_UNDEFINED "Enable undefined sanitizer" OFF) 43 | option(json2cpp_ENABLE_SANITIZER_THREAD "Enable thread sanitizer" OFF) 44 | option(json2cpp_ENABLE_SANITIZER_MEMORY "Enable memory sanitizer" OFF) 45 | option(json2cpp_ENABLE_UNITY_BUILD "Enable unity builds" OFF) 46 | option(json2cpp_ENABLE_CLANG_TIDY "Enable clang-tidy" OFF) 47 | option(json2cpp_ENABLE_CPPCHECK "Enable cpp-check analysis" OFF) 48 | option(json2cpp_ENABLE_PCH "Enable precompiled headers" OFF) 49 | option(json2cpp_ENABLE_CACHE "Enable ccache" OFF) 50 | else() 51 | option(json2cpp_ENABLE_IPO "Enable IPO/LTO" ON) 52 | option(json2cpp_WARNINGS_AS_ERRORS "Treat Warnings As Errors" ON) 53 | option(json2cpp_ENABLE_USER_LINKER "Enable user-selected linker" OFF) 54 | option(json2cpp_ENABLE_SANITIZER_ADDRESS "Enable address sanitizer" ${SUPPORTS_ASAN}) 55 | option(json2cpp_ENABLE_SANITIZER_LEAK "Enable leak sanitizer" OFF) 56 | option(json2cpp_ENABLE_SANITIZER_UNDEFINED "Enable undefined sanitizer" ${SUPPORTS_UBSAN}) 57 | option(json2cpp_ENABLE_SANITIZER_THREAD "Enable thread sanitizer" OFF) 58 | option(json2cpp_ENABLE_SANITIZER_MEMORY "Enable memory sanitizer" OFF) 59 | option(json2cpp_ENABLE_UNITY_BUILD "Enable unity builds" OFF) 60 | option(json2cpp_ENABLE_CLANG_TIDY "Enable clang-tidy" ON) 61 | option(json2cpp_ENABLE_CPPCHECK "Enable cpp-check analysis" ON) 62 | option(json2cpp_ENABLE_PCH "Enable precompiled headers" OFF) 63 | option(json2cpp_ENABLE_CACHE "Enable ccache" ON) 64 | endif() 65 | 66 | if(NOT PROJECT_IS_TOP_LEVEL) 67 | mark_as_advanced( 68 | json2cpp_ENABLE_IPO 69 | json2cpp_WARNINGS_AS_ERRORS 70 | json2cpp_ENABLE_USER_LINKER 71 | json2cpp_ENABLE_SANITIZER_ADDRESS 72 | json2cpp_ENABLE_SANITIZER_LEAK 73 | json2cpp_ENABLE_SANITIZER_UNDEFINED 74 | json2cpp_ENABLE_SANITIZER_THREAD 75 | json2cpp_ENABLE_SANITIZER_MEMORY 76 | json2cpp_ENABLE_UNITY_BUILD 77 | json2cpp_ENABLE_CLANG_TIDY 78 | json2cpp_ENABLE_CPPCHECK 79 | json2cpp_ENABLE_COVERAGE 80 | json2cpp_ENABLE_PCH 81 | json2cpp_ENABLE_CACHE) 82 | endif() 83 | endmacro() 84 | 85 | macro(json2cpp_global_options) 86 | if(json2cpp_ENABLE_IPO) 87 | include(cmake/InterproceduralOptimization.cmake) 88 | json2cpp_enable_ipo() 89 | endif() 90 | 91 | if(json2cpp_ENABLE_HARDENING AND json2cpp_ENABLE_GLOBAL_HARDENING) 92 | include(cmake/Hardening.cmake) 93 | set(ENABLE_UBSAN_MINIMAL_RUNTIME NOT json2cpp_ENABLE_SANITIZER_UNDEFINED) 94 | json2cpp_enable_hardening(json2cpp_options ON ${ENABLE_UBSAN_MINIMAL_RUNTIME}) 95 | endif() 96 | endmacro() 97 | 98 | macro(json2cpp_local_options) 99 | if (PROJECT_IS_TOP_LEVEL) 100 | include(cmake/StandardProjectSettings.cmake) 101 | endif() 102 | 103 | add_library(json2cpp_warnings INTERFACE) 104 | add_library(json2cpp_options INTERFACE) 105 | 106 | include(cmake/CompilerWarnings.cmake) 107 | json2cpp_set_project_warnings( 108 | json2cpp_warnings 109 | ${json2cpp_WARNINGS_AS_ERRORS} 110 | "" 111 | "" 112 | "" 113 | "") 114 | 115 | if(json2cpp_ENABLE_USER_LINKER) 116 | include(cmake/Linker.cmake) 117 | configure_linker(json2cpp_options) 118 | endif() 119 | 120 | include(cmake/Sanitizers.cmake) 121 | json2cpp_enable_sanitizers( 122 | json2cpp_options 123 | ${json2cpp_ENABLE_SANITIZER_ADDRESS} 124 | ${json2cpp_ENABLE_SANITIZER_LEAK} 125 | ${json2cpp_ENABLE_SANITIZER_UNDEFINED} 126 | ${json2cpp_ENABLE_SANITIZER_THREAD} 127 | ${json2cpp_ENABLE_SANITIZER_MEMORY}) 128 | 129 | set_target_properties(json2cpp_options PROPERTIES UNITY_BUILD ${json2cpp_ENABLE_UNITY_BUILD}) 130 | 131 | if(json2cpp_ENABLE_PCH) 132 | target_precompile_headers( 133 | json2cpp_options 134 | INTERFACE 135 | 136 | 137 | ) 138 | endif() 139 | 140 | if(json2cpp_ENABLE_CACHE) 141 | include(cmake/Cache.cmake) 142 | json2cpp_enable_cache() 143 | endif() 144 | 145 | include(cmake/StaticAnalyzers.cmake) 146 | if(json2cpp_ENABLE_CLANG_TIDY) 147 | json2cpp_enable_clang_tidy(json2cpp_options ${json2cpp_WARNINGS_AS_ERRORS}) 148 | endif() 149 | 150 | if(json2cpp_ENABLE_CPPCHECK) 151 | json2cpp_enable_cppcheck(${json2cpp_WARNINGS_AS_ERRORS} "" # override cppcheck options 152 | ) 153 | endif() 154 | 155 | if(json2cpp_ENABLE_COVERAGE) 156 | include(cmake/Tests.cmake) 157 | json2cpp_enable_coverage(json2cpp_options) 158 | endif() 159 | 160 | if(json2cpp_WARNINGS_AS_ERRORS) 161 | check_cxx_compiler_flag("-Wl,--fatal-warnings" LINKER_FATAL_WARNINGS) 162 | if(LINKER_FATAL_WARNINGS) 163 | # This is not working consistently, so disabling for now 164 | # target_link_options(json2cpp_options INTERFACE -Wl,--fatal-warnings) 165 | endif() 166 | endif() 167 | 168 | if(json2cpp_ENABLE_HARDENING AND NOT json2cpp_ENABLE_GLOBAL_HARDENING) 169 | include(cmake/Hardening.cmake) 170 | set(ENABLE_UBSAN_MINIMAL_RUNTIME NOT json2cpp_ENABLE_SANITIZER_UNDEFINED) 171 | json2cpp_enable_hardening(json2cpp_options OFF ${ENABLE_UBSAN_MINIMAL_RUNTIME}) 172 | endif() 173 | 174 | endmacro() 175 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # json2cpp 2 | 3 | ![CI](https://github.com/lefticus/json2cpp/workflows/ci/badge.svg) 4 | [![codecov](https://codecov.io/gh/lefticus/json2cpp/branch/main/graph/badge.svg)](https://codecov.io/gh/lefticus/json2cpp) 5 | [![Language grade: C++](https://img.shields.io/lgtm/grade/cpp/github/lefticus/json2cpp)](https://lgtm.com/projects/g/lefticus/json2cpp/context:cpp) 6 | 7 | **json2cpp** compiles a json file into `static constexpr` data structures that can be used at compile time or runtime. 8 | 9 | Features 10 | 11 | * Literally 0 runtime overhead for loading the statically compiled JSON resource 12 | * Fully constexpr capable if you want to make compile-time decisions based on the JSON resource file 13 | * A `.cpp` firewall file is provided for you, if you have a large resource and don't want to pay the cost of compiling it more than once (but for normal size files it is VERY fast to compile, they are just data structures) 14 | * [nlohmann::json](https://github.com/nlohmann/json) compatible API (should be a drop-in replacement, some features might still be missing) 15 | * [valijson](https://github.com/tristanpenman/valijson) adapter file provided 16 | 17 | 18 | See the [test](test) folder for examples for building resources, using the valijson adapter, constexpr usage of resources, and firewalled usage of resources. 19 | -------------------------------------------------------------------------------- /cmake/CPM.cmake: -------------------------------------------------------------------------------- 1 | set(CPM_DOWNLOAD_VERSION 0.38.1) 2 | 3 | if(CPM_SOURCE_CACHE) 4 | set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 5 | elseif(DEFINED ENV{CPM_SOURCE_CACHE}) 6 | set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 7 | else() 8 | set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 9 | endif() 10 | 11 | # Expand relative path. This is important if the provided path contains a tilde (~) 12 | get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE) 13 | 14 | function(download_cpm) 15 | message(STATUS "Downloading CPM.cmake to ${CPM_DOWNLOAD_LOCATION}") 16 | file(DOWNLOAD 17 | https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake 18 | ${CPM_DOWNLOAD_LOCATION} 19 | ) 20 | endfunction() 21 | 22 | if(NOT (EXISTS ${CPM_DOWNLOAD_LOCATION})) 23 | download_cpm() 24 | else() 25 | # resume download if it previously failed 26 | file(READ ${CPM_DOWNLOAD_LOCATION} check) 27 | if("${check}" STREQUAL "") 28 | download_cpm() 29 | endif() 30 | unset(check) 31 | endif() 32 | 33 | include(${CPM_DOWNLOAD_LOCATION}) 34 | -------------------------------------------------------------------------------- /cmake/Cache.cmake: -------------------------------------------------------------------------------- 1 | # Enable cache if available 2 | function(json2cpp_enable_cache) 3 | set(CACHE_OPTION 4 | "ccache" 5 | CACHE STRING "Compiler cache to be used") 6 | set(CACHE_OPTION_VALUES "ccache" "sccache") 7 | set_property(CACHE CACHE_OPTION PROPERTY STRINGS ${CACHE_OPTION_VALUES}) 8 | list( 9 | FIND 10 | CACHE_OPTION_VALUES 11 | ${CACHE_OPTION} 12 | CACHE_OPTION_INDEX) 13 | 14 | if(${CACHE_OPTION_INDEX} EQUAL -1) 15 | message( 16 | STATUS 17 | "Using custom compiler cache system: '${CACHE_OPTION}', explicitly supported entries are ${CACHE_OPTION_VALUES}" 18 | ) 19 | endif() 20 | 21 | find_program(CACHE_BINARY NAMES ${CACHE_OPTION_VALUES}) 22 | if(CACHE_BINARY) 23 | message(STATUS "${CACHE_BINARY} found and enabled") 24 | set(CMAKE_CXX_COMPILER_LAUNCHER 25 | ${CACHE_BINARY} 26 | CACHE FILEPATH "CXX compiler cache used") 27 | set(CMAKE_C_COMPILER_LAUNCHER 28 | ${CACHE_BINARY} 29 | CACHE FILEPATH "C compiler cache used") 30 | else() 31 | message(WARNING "${CACHE_OPTION} is enabled but was not found. Not using it") 32 | endif() 33 | endfunction() 34 | -------------------------------------------------------------------------------- /cmake/CompilerWarnings.cmake: -------------------------------------------------------------------------------- 1 | # from here: 2 | # 3 | # https://github.com/lefticus/cppbestpractices/blob/master/02-Use_the_Tools_Available.md 4 | 5 | function( 6 | json2cpp_set_project_warnings 7 | project_name 8 | WARNINGS_AS_ERRORS 9 | MSVC_WARNINGS 10 | CLANG_WARNINGS 11 | GCC_WARNINGS 12 | CUDA_WARNINGS) 13 | if("${MSVC_WARNINGS}" STREQUAL "") 14 | set(MSVC_WARNINGS 15 | /W4 # Baseline reasonable warnings 16 | /w14242 # 'identifier': conversion from 'type1' to 'type1', possible loss of data 17 | /w14254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data 18 | /w14263 # 'function': member function does not override any base class virtual member function 19 | /w14265 # 'classname': class has virtual functions, but destructor is not virtual instances of this class may not 20 | # be destructed correctly 21 | /w14287 # 'operator': unsigned/negative constant mismatch 22 | /we4289 # nonstandard extension used: 'variable': loop control variable declared in the for-loop is used outside 23 | # the for-loop scope 24 | /w14296 # 'operator': expression is always 'boolean_value' 25 | /w14311 # 'variable': pointer truncation from 'type1' to 'type2' 26 | /w14545 # expression before comma evaluates to a function which is missing an argument list 27 | /w14546 # function call before comma missing argument list 28 | /w14547 # 'operator': operator before comma has no effect; expected operator with side-effect 29 | /w14549 # 'operator': operator before comma has no effect; did you intend 'operator'? 30 | /w14555 # expression has no effect; expected expression with side- effect 31 | /w14619 # pragma warning: there is no warning number 'number' 32 | /w14640 # Enable warning on thread un-safe static member initialization 33 | /w14826 # Conversion from 'type1' to 'type_2' is sign-extended. This may cause unexpected runtime behavior. 34 | /w14905 # wide string literal cast to 'LPSTR' 35 | /w14906 # string literal cast to 'LPWSTR' 36 | /w14928 # illegal copy-initialization; more than one user-defined conversion has been implicitly applied 37 | /permissive- # standards conformance mode for MSVC compiler. 38 | ) 39 | endif() 40 | 41 | if("${CLANG_WARNINGS}" STREQUAL "") 42 | set(CLANG_WARNINGS 43 | -Wall 44 | -Wextra # reasonable and standard 45 | -Wshadow # warn the user if a variable declaration shadows one from a parent context 46 | -Wnon-virtual-dtor # warn the user if a class with virtual functions has a non-virtual destructor. This helps 47 | # catch hard to track down memory errors 48 | -Wold-style-cast # warn for c-style casts 49 | -Wcast-align # warn for potential performance problem casts 50 | -Wunused # warn on anything being unused 51 | -Woverloaded-virtual # warn if you overload (not override) a virtual function 52 | -Wpedantic # warn if non-standard C++ is used 53 | -Wconversion # warn on type conversions that may lose data 54 | -Wsign-conversion # warn on sign conversions 55 | -Wnull-dereference # warn if a null dereference is detected 56 | -Wdouble-promotion # warn if float is implicit promoted to double 57 | -Wformat=2 # warn on security issues around functions that format output (ie printf) 58 | -Wimplicit-fallthrough # warn on statements that fallthrough without an explicit annotation 59 | ) 60 | endif() 61 | 62 | if("${GCC_WARNINGS}" STREQUAL "") 63 | set(GCC_WARNINGS 64 | ${CLANG_WARNINGS} 65 | -Wmisleading-indentation # warn if indentation implies blocks where blocks do not exist 66 | -Wduplicated-cond # warn if if / else chain has duplicated conditions 67 | -Wduplicated-branches # warn if if / else branches have duplicated code 68 | -Wlogical-op # warn about logical operations being used where bitwise were probably wanted 69 | -Wuseless-cast # warn if you perform a cast to the same type 70 | ) 71 | endif() 72 | 73 | if("${CUDA_WARNINGS}" STREQUAL "") 74 | set(CUDA_WARNINGS 75 | -Wall 76 | -Wextra 77 | -Wunused 78 | -Wconversion 79 | -Wshadow 80 | # TODO add more Cuda warnings 81 | ) 82 | endif() 83 | 84 | if(WARNINGS_AS_ERRORS) 85 | message(TRACE "Warnings are treated as errors") 86 | list(APPEND CLANG_WARNINGS -Werror) 87 | list(APPEND GCC_WARNINGS -Werror) 88 | list(APPEND MSVC_WARNINGS /WX) 89 | endif() 90 | 91 | if(MSVC) 92 | set(PROJECT_WARNINGS_CXX ${MSVC_WARNINGS}) 93 | elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 94 | set(PROJECT_WARNINGS_CXX ${CLANG_WARNINGS}) 95 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 96 | set(PROJECT_WARNINGS_CXX ${GCC_WARNINGS}) 97 | else() 98 | message(AUTHOR_WARNING "No compiler warnings set for CXX compiler: '${CMAKE_CXX_COMPILER_ID}'") 99 | # TODO support Intel compiler 100 | endif() 101 | 102 | # use the same warning flags for C 103 | set(PROJECT_WARNINGS_C "${PROJECT_WARNINGS_CXX}") 104 | 105 | set(PROJECT_WARNINGS_CUDA "${CUDA_WARNINGS}") 106 | 107 | target_compile_options( 108 | ${project_name} 109 | INTERFACE # C++ warnings 110 | $<$:${PROJECT_WARNINGS_CXX}> 111 | # C warnings 112 | $<$:${PROJECT_WARNINGS_C}> 113 | # Cuda warnings 114 | $<$:${PROJECT_WARNINGS_CUDA}>) 115 | endfunction() 116 | -------------------------------------------------------------------------------- /cmake/Cuda.cmake: -------------------------------------------------------------------------------- 1 | # ! target_link_cuda 2 | # A function that links Cuda to the given target 3 | # 4 | # # Example 5 | # add_executable(main_cuda main.cu) 6 | # target_compile_features(main_cuda PRIVATE cxx_std_17) 7 | # target_link_libraries(main_cuda PRIVATE project_options project_warnings) 8 | # target_link_cuda(main_cuda) 9 | # 10 | macro(json2cpp_target_link_cuda target) 11 | # optional named CUDA_WARNINGS 12 | set(oneValueArgs CUDA_WARNINGS) 13 | cmake_parse_arguments( 14 | _cuda_args 15 | "" 16 | "${oneValueArgs}" 17 | "" 18 | ${ARGN}) 19 | 20 | # add CUDA to cmake language 21 | enable_language(CUDA) 22 | 23 | # use the same C++ standard if not specified 24 | if("${CMAKE_CUDA_STANDARD}" STREQUAL "") 25 | set(CMAKE_CUDA_STANDARD "${CMAKE_CXX_STANDARD}") 26 | endif() 27 | 28 | # -fPIC 29 | set_target_properties(${target} PROPERTIES POSITION_INDEPENDENT_CODE ON) 30 | 31 | # We need to explicitly state that we need all CUDA files in the 32 | # ${target} library to be built with -dc as the member functions 33 | # could be called by other libraries and executables 34 | set_target_properties(${target} PROPERTIES CUDA_SEPARABLE_COMPILATION ON) 35 | 36 | if(APPLE) 37 | # We need to add the path to the driver (libcuda.dylib) as an rpath, 38 | # so that the static cuda runtime can find it at runtime. 39 | set_property(TARGET ${target} PROPERTY BUILD_RPATH ${CMAKE_CUDA_IMPLICIT_LINK_DIRECTORIES}) 40 | endif() 41 | 42 | if(WIN32 AND "$ENV{VSCMD_VER}" STREQUAL "") 43 | message( 44 | WARNING 45 | "Compiling Cuda on Windows outside the Visual Studio Commant prompt or without running `vcvarsall.bat x64` probably fails" 46 | ) 47 | endif() 48 | endmacro() 49 | -------------------------------------------------------------------------------- /cmake/Doxygen.cmake: -------------------------------------------------------------------------------- 1 | # Enable doxygen doc builds of source 2 | function(json2cpp_enable_doxygen DOXYGEN_THEME) 3 | # If not specified, use the top readme file as the first page 4 | if((NOT DOXYGEN_USE_MDFILE_AS_MAINPAGE) AND EXISTS "${PROJECT_SOURCE_DIR}/README.md") 5 | set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "${PROJECT_SOURCE_DIR}/README.md") 6 | endif() 7 | 8 | # set better defaults for doxygen 9 | is_verbose(_is_verbose) 10 | if(NOT ${_is_verbose}) 11 | set(DOXYGEN_QUIET YES) 12 | endif() 13 | set(DOXYGEN_CALLER_GRAPH YES) 14 | set(DOXYGEN_CALL_GRAPH YES) 15 | set(DOXYGEN_EXTRACT_ALL YES) 16 | set(DOXYGEN_GENERATE_TREEVIEW YES) 17 | # svg files are much smaller than jpeg and png, and yet they have higher quality 18 | set(DOXYGEN_DOT_IMAGE_FORMAT svg) 19 | set(DOXYGEN_DOT_TRANSPARENT YES) 20 | 21 | # If not specified, exclude the vcpkg files and the files CMake downloads under _deps (like project_options) 22 | if(NOT DOXYGEN_EXCLUDE_PATTERNS) 23 | set(DOXYGEN_EXCLUDE_PATTERNS "${CMAKE_CURRENT_BINARY_DIR}/vcpkg_installed/*" "${CMAKE_CURRENT_BINARY_DIR}/_deps/*") 24 | endif() 25 | 26 | if("${DOXYGEN_THEME}" STREQUAL "") 27 | set(DOXYGEN_THEME "awesome-sidebar") 28 | endif() 29 | 30 | if("${DOXYGEN_THEME}" STREQUAL "awesome" OR "${DOXYGEN_THEME}" STREQUAL "awesome-sidebar") 31 | # use a modern doxygen theme 32 | # https://github.com/jothepro/doxygen-awesome-css v1.6.1 33 | FetchContent_Declare(_doxygen_theme 34 | URL https://github.com/jothepro/doxygen-awesome-css/archive/refs/tags/v1.6.1.zip) 35 | FetchContent_MakeAvailable(_doxygen_theme) 36 | if("${DOXYGEN_THEME}" STREQUAL "awesome" OR "${DOXYGEN_THEME}" STREQUAL "awesome-sidebar") 37 | set(DOXYGEN_HTML_EXTRA_STYLESHEET "${_doxygen_theme_SOURCE_DIR}/doxygen-awesome.css") 38 | endif() 39 | if("${DOXYGEN_THEME}" STREQUAL "awesome-sidebar") 40 | set(DOXYGEN_HTML_EXTRA_STYLESHEET ${DOXYGEN_HTML_EXTRA_STYLESHEET} 41 | "${_doxygen_theme_SOURCE_DIR}/doxygen-awesome-sidebar-only.css") 42 | endif() 43 | else() 44 | # use the original doxygen theme 45 | endif() 46 | 47 | # find doxygen and dot if available 48 | find_package(Doxygen REQUIRED OPTIONAL_COMPONENTS dot) 49 | 50 | # add doxygen-docs target 51 | message(STATUS "Adding `doxygen-docs` target that builds the documentation.") 52 | doxygen_add_docs(doxygen-docs ALL ${PROJECT_SOURCE_DIR} 53 | COMMENT "Generating documentation - entry file: ${CMAKE_CURRENT_BINARY_DIR}/html/index.html") 54 | endfunction() 55 | -------------------------------------------------------------------------------- /cmake/Hardening.cmake: -------------------------------------------------------------------------------- 1 | include(CheckCXXCompilerFlag) 2 | 3 | macro( 4 | json2cpp_enable_hardening 5 | target 6 | global 7 | ubsan_minimal_runtime) 8 | 9 | message(STATUS "** Enabling Hardening (Target ${target}) **") 10 | 11 | if(MSVC) 12 | set(NEW_COMPILE_OPTIONS 13 | "${NEW_COMPILE_OPTIONS} /sdl /DYNAMICBASE /guard:cf /NXCOMPAT") 14 | message(STATUS "*** MSVC flags: /sdl /DYNAIMCBASE /guard:cf /NXCOMPAT") 15 | 16 | elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang|GNU") 17 | set(NEW_CXX_DEFINITIONS "${NEW_CXX_DEFINITIONS} -D_GLIBCXX_ASSERTIONS") 18 | message(STATUS "*** GLIBC++ Assertions (vector[], string[], ...) enabled") 19 | 20 | set(NEW_COMPILE_OPTIONS 21 | "${NEW_COMPILE_OPTIONS} -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3") 22 | message(STATUS "*** g++/clang _FORTIFY_SOURCE=3 enabled") 23 | 24 | # check_cxx_compiler_flag(-fpie PIE) 25 | #if(PIE) 26 | # set(NEW_COMPILE_OPTIONS ${NEW_COMPILE_OPTIONS} -fpie) 27 | # set(NEW_LINK_OPTIONS ${NEW_LINK_OPTIONS} -pie) 28 | # 29 | # message(STATUS "*** g++/clang PIE mode enabled") 30 | #else() 31 | # message(STATUS "*** g++/clang PIE mode NOT enabled (not supported)") 32 | #endif() 33 | 34 | check_cxx_compiler_flag(-fstack-protector-strong STACK_PROTECTOR) 35 | if(STACK_PROTECTOR) 36 | set(NEW_COMPILE_OPTIONS "${NEW_COMPILE_OPTIONS} -fstack-protector-strong") 37 | message(STATUS "*** g++/clang -fstack-protector-strong enabled") 38 | else() 39 | message(STATUS "*** g++/clang -fstack-protector-strong NOT enabled (not supported)") 40 | endif() 41 | 42 | check_cxx_compiler_flag(-fcf-protection CF_PROTECTION) 43 | if(CF_PROTECTION) 44 | set(NEW_COMPILE_OPTIONS "${NEW_COMPILE_OPTIONS} -fcf-protection") 45 | message(STATUS "*** g++/clang -fcf-protection enabled") 46 | else() 47 | message(STATUS "*** g++/clang -fcf-protection NOT enabled (not supported)") 48 | endif() 49 | 50 | check_cxx_compiler_flag(-fstack-clash-protection CLASH_PROTECTION) 51 | if(CLASH_PROTECTION) 52 | if(LINUX OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") 53 | set(NEW_COMPILE_OPTIONS "${NEW_COMPILE_OPTIONS} -fstack-clash-protection") 54 | message(STATUS "*** g++/clang -fstack-clash-protection enabled") 55 | else() 56 | message(STATUS "*** g++/clang -fstack-clash-protection NOT enabled (clang on non-Linux)") 57 | endif() 58 | else() 59 | message(STATUS "*** g++/clang -fstack-clash-protection NOT enabled (not supported)") 60 | endif() 61 | endif() 62 | 63 | if(${ubsan_minimal_runtime}) 64 | check_cxx_compiler_flag("-fsanitize=undefined -fno-sanitize-recover=undefined -fsanitize-minimal-runtime" 65 | MINIMAL_RUNTIME) 66 | if(MINIMAL_RUNTIME) 67 | set(NEW_COMPILE_OPTIONS 68 | "${NEW_COMPILE_OPTIONS} -fsanitize=undefined -fno-sanitize-recover=undefined -fsanitize-minimal-runtime") 69 | message(STATUS "*** ubsan minimal runtime enabled") 70 | else() 71 | message(STATUS "*** ubsan minimal runtime NOT enabled (not supported)") 72 | endif() 73 | else() 74 | message(STATUS "*** ubsan minimal runtime NOT enabled (not requested)") 75 | endif() 76 | 77 | message(STATUS "** Hardening Compiler Flags: ${NEW_COMPILE_OPTIONS}") 78 | message(STATUS "** Hardening Linker Flags: ${NEW_LINK_OPTIONS}") 79 | message(STATUS "** Hardening Compiler Defines: ${NEW_CXX_DEFINITIONS}") 80 | 81 | if(${global}) 82 | message(STATUS "** Setting hardening options globally for all dependencies") 83 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${NEW_COMPILE_OPTIONS}") 84 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${NEW_LINK_OPTIONS}") 85 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${NEW_CXX_DEFINITIONS}") 86 | else() 87 | target_compile_options(${target} INTERFACE ${NEW_COMPILE_OPTIONS}) 88 | target_link_options(${target} INTERFACE ${NEW_LINK_OPTIONS}) 89 | target_compile_definitions(${target} INTERFACE ${NEW_CXX_DEFINITIONS}) 90 | endif() 91 | endmacro() 92 | -------------------------------------------------------------------------------- /cmake/InterproceduralOptimization.cmake: -------------------------------------------------------------------------------- 1 | macro(json2cpp_enable_ipo) 2 | include(CheckIPOSupported) 3 | check_ipo_supported(RESULT result OUTPUT output) 4 | if(result) 5 | set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) 6 | else() 7 | message(SEND_ERROR "IPO is not supported: ${output}") 8 | endif() 9 | endmacro() 10 | -------------------------------------------------------------------------------- /cmake/LibFuzzer.cmake: -------------------------------------------------------------------------------- 1 | function(json2cpp_check_libfuzzer_support var_name) 2 | set(LibFuzzerTestSource 3 | " 4 | #include 5 | 6 | extern \"C\" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) { 7 | return 0; 8 | } 9 | ") 10 | 11 | include(CheckCXXSourceCompiles) 12 | 13 | set(CMAKE_REQUIRED_FLAGS "-fsanitize=fuzzer") 14 | set(CMAKE_REQUIRED_LINK_OPTIONS "-fsanitize=fuzzer") 15 | check_cxx_source_compiles("${LibFuzzerTestSource}" ${var_name}) 16 | 17 | endfunction() 18 | -------------------------------------------------------------------------------- /cmake/Linker.cmake: -------------------------------------------------------------------------------- 1 | macro(json2cpp_configure_linker project_name) 2 | include(CheckCXXCompilerFlag) 3 | 4 | set(USER_LINKER_OPTION 5 | "lld" 6 | CACHE STRING "Linker to be used") 7 | set(USER_LINKER_OPTION_VALUES "lld" "gold" "bfd" "mold") 8 | set_property(CACHE USER_LINKER_OPTION PROPERTY STRINGS ${USER_LINKER_OPTION_VALUES}) 9 | list( 10 | FIND 11 | USER_LINKER_OPTION_VALUES 12 | ${USER_LINKER_OPTION} 13 | USER_LINKER_OPTION_INDEX) 14 | 15 | if(${USER_LINKER_OPTION_INDEX} EQUAL -1) 16 | message( 17 | STATUS 18 | "Using custom linker: '${USER_LINKER_OPTION}', explicitly supported entries are ${USER_LINKER_OPTION_VALUES}") 19 | endif() 20 | 21 | if(NOT ENABLE_USER_LINKER) 22 | return() 23 | endif() 24 | 25 | set(LINKER_FLAG "-fuse-ld=${USER_LINKER_OPTION}") 26 | 27 | check_cxx_compiler_flag(${LINKER_FLAG} CXX_SUPPORTS_USER_LINKER) 28 | if(CXX_SUPPORTS_USER_LINKER) 29 | target_compile_options(${project_name} INTERFACE ${LINKER_FLAG}) 30 | endif() 31 | endmacro() 32 | -------------------------------------------------------------------------------- /cmake/PackageProject.cmake: -------------------------------------------------------------------------------- 1 | # Uses ycm (permissive BSD-3-Clause license) and ForwardArguments (permissive MIT license) 2 | 3 | function(json2cpp_package_project) 4 | cmake_policy(SET CMP0103 NEW) # disallow multiple calls with the same NAME 5 | 6 | set(_options ARCH_INDEPENDENT # default to false 7 | ) 8 | set(_oneValueArgs 9 | # default to the project_name: 10 | NAME 11 | COMPONENT 12 | # default to project version: 13 | VERSION 14 | # default to semver 15 | COMPATIBILITY 16 | # default to ${CMAKE_BINARY_DIR} 17 | CONFIG_EXPORT_DESTINATION 18 | # default to ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/${NAME} suitable for vcpkg, etc. 19 | CONFIG_INSTALL_DESTINATION) 20 | set(_multiValueArgs 21 | # recursively found for the current folder if not specified 22 | TARGETS 23 | # a list of public/interface include directories or files 24 | PUBLIC_INCLUDES 25 | # the names of the INTERFACE/PUBLIC dependencies that are found using `CONFIG` 26 | PUBLIC_DEPENDENCIES_CONFIGURED 27 | # the INTERFACE/PUBLIC dependencies that are found by any means using `find_dependency`. 28 | # the arguments must be specified within double quotes (e.g. " 1.0.0 EXACT" or " CONFIG"). 29 | PUBLIC_DEPENDENCIES 30 | # the names of the PRIVATE dependencies that are found using `CONFIG`. Only included when BUILD_SHARED_LIBS is OFF. 31 | PRIVATE_DEPENDENCIES_CONFIGURED 32 | # PRIVATE dependencies that are only included when BUILD_SHARED_LIBS is OFF 33 | PRIVATE_DEPENDENCIES) 34 | 35 | cmake_parse_arguments( 36 | _PackageProject 37 | "${_options}" 38 | "${_oneValueArgs}" 39 | "${_multiValueArgs}" 40 | "${ARGN}") 41 | 42 | # Set default options 43 | include(GNUInstallDirs) # Define GNU standard installation directories such as CMAKE_INSTALL_DATADIR 44 | 45 | # set default packaged targets 46 | if(NOT _PackageProject_TARGETS) 47 | get_all_installable_targets(_PackageProject_TARGETS) 48 | message(STATUS "package_project: considering ${_PackageProject_TARGETS} as the exported targets") 49 | endif() 50 | 51 | # default to the name of the project or the given name 52 | if("${_PackageProject_NAME}" STREQUAL "") 53 | set(_PackageProject_NAME ${PROJECT_NAME}) 54 | endif() 55 | # ycm args 56 | set(_PackageProject_NAMESPACE "${_PackageProject_NAME}::") 57 | set(_PackageProject_VARS_PREFIX ${_PackageProject_NAME}) 58 | set(_PackageProject_EXPORT ${_PackageProject_NAME}) 59 | 60 | # default version to the project version 61 | if("${_PackageProject_VERSION}" STREQUAL "") 62 | set(_PackageProject_VERSION ${PROJECT_VERSION}) 63 | endif() 64 | 65 | # default compatibility to SameMajorVersion 66 | if("${_PackageProject_COMPATIBILITY}" STREQUAL "") 67 | set(_PackageProject_COMPATIBILITY "SameMajorVersion") 68 | endif() 69 | 70 | # default to the build directory 71 | if("${_PackageProject_CONFIG_EXPORT_DESTINATION}" STREQUAL "") 72 | set(_PackageProject_CONFIG_EXPORT_DESTINATION "${CMAKE_BINARY_DIR}") 73 | endif() 74 | set(_PackageProject_EXPORT_DESTINATION "${_PackageProject_CONFIG_EXPORT_DESTINATION}") 75 | 76 | # use datadir (works better with vcpkg, etc) 77 | if("${_PackageProject_CONFIG_INSTALL_DESTINATION}" STREQUAL "") 78 | set(_PackageProject_CONFIG_INSTALL_DESTINATION "${CMAKE_INSTALL_DATADIR}/${_PackageProject_NAME}") 79 | endif() 80 | # ycm args 81 | set(_PackageProject_INSTALL_DESTINATION "${_PackageProject_CONFIG_INSTALL_DESTINATION}") 82 | 83 | # Installation of the public/interface includes 84 | if(NOT 85 | "${_PackageProject_PUBLIC_INCLUDES}" 86 | STREQUAL 87 | "") 88 | foreach(_INC ${_PackageProject_PUBLIC_INCLUDES}) 89 | # make include absolute 90 | if(NOT IS_ABSOLUTE ${_INC}) 91 | set(_INC "${CMAKE_CURRENT_SOURCE_DIR}/${_INC}") 92 | endif() 93 | # install include 94 | if(IS_DIRECTORY ${_INC}) 95 | # the include directories are directly installed to the install destination. If you want an `include` folder in the install destination, name your include directory as `include` (or install it manually using `install()` command). 96 | install(DIRECTORY ${_INC} DESTINATION "./") 97 | else() 98 | install(FILES ${_INC} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") 99 | endif() 100 | endforeach() 101 | endif() 102 | 103 | # Append the configured public dependencies 104 | if(NOT 105 | "${_PackageProject_PUBLIC_DEPENDENCIES_CONFIGURED}" 106 | STREQUAL 107 | "") 108 | set(_PUBLIC_DEPENDENCIES_CONFIG) 109 | foreach(DEP ${_PackageProject_PUBLIC_DEPENDENCIES_CONFIGURED}) 110 | list(APPEND _PUBLIC_DEPENDENCIES_CONFIG "${DEP} CONFIG") 111 | endforeach() 112 | endif() 113 | list(APPEND _PackageProject_PUBLIC_DEPENDENCIES ${_PUBLIC_DEPENDENCIES_CONFIG}) 114 | # ycm arg 115 | set(_PackageProject_DEPENDENCIES ${_PackageProject_PUBLIC_DEPENDENCIES}) 116 | 117 | # Append the configured private dependencies 118 | if(NOT 119 | "${_PackageProject_PRIVATE_DEPENDENCIES_CONFIGURED}" 120 | STREQUAL 121 | "") 122 | set(_PRIVATE_DEPENDENCIES_CONFIG) 123 | foreach(DEP ${_PackageProject_PRIVATE_DEPENDENCIES_CONFIGURED}) 124 | list(APPEND _PRIVATE_DEPENDENCIES_CONFIG "${DEP} CONFIG") 125 | endforeach() 126 | endif() 127 | # ycm arg 128 | list(APPEND _PackageProject_PRIVATE_DEPENDENCIES ${_PRIVATE_DEPENDENCIES_CONFIG}) 129 | 130 | # Installation of package (compatible with vcpkg, etc) 131 | install( 132 | TARGETS ${_PackageProject_TARGETS} 133 | EXPORT ${_PackageProject_EXPORT} 134 | LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT shlib 135 | ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT lib 136 | RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT bin 137 | PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${_PackageProject_NAME}" COMPONENT dev) 138 | 139 | # install the usage file 140 | set(_targets_str "") 141 | foreach(_target ${_PackageProject_TARGETS}) 142 | set(_targets_str "${_targets_str} ${_PackageProject_NAMESPACE}${_target}") 143 | endforeach() 144 | set(USAGE_FILE_CONTENT 145 | "The package ${_PackageProject_NAME} provides CMake targets: 146 | 147 | find_package(${_PackageProject_NAME} CONFIG REQUIRED) 148 | target_link_libraries(main PRIVATE ${_targets_str}) 149 | ") 150 | install(CODE "MESSAGE(STATUS \"${USAGE_FILE_CONTENT}\")") 151 | file(WRITE "${_PackageProject_EXPORT_DESTINATION}/usage" "${USAGE_FILE_CONTENT}") 152 | install(FILES "${_PackageProject_EXPORT_DESTINATION}/usage" 153 | DESTINATION "${_PackageProject_CONFIG_INSTALL_DESTINATION}") 154 | 155 | unset(_PackageProject_TARGETS) 156 | 157 | # download ForwardArguments 158 | FetchContent_Declare( 159 | _fargs 160 | URL https://github.com/polysquare/cmake-forward-arguments/archive/8c50d1f956172edb34e95efa52a2d5cb1f686ed2.zip) 161 | FetchContent_GetProperties(_fargs) 162 | if(NOT _fargs_POPULATED) 163 | FetchContent_Populate(_fargs) 164 | endif() 165 | include("${_fargs_SOURCE_DIR}/ForwardArguments.cmake") 166 | 167 | # prepare the forward arguments for ycm 168 | set(_FARGS_LIST) 169 | cmake_forward_arguments( 170 | _PackageProject 171 | _FARGS_LIST 172 | OPTION_ARGS 173 | "${_options};" 174 | SINGLEVAR_ARGS 175 | "${_oneValueArgs};EXPORT_DESTINATION;INSTALL_DESTINATION;NAMESPACE;VARS_PREFIX;EXPORT" 176 | MULTIVAR_ARGS 177 | "${_multiValueArgs};DEPENDENCIES;PRIVATE_DEPENDENCIES") 178 | 179 | # download ycm 180 | FetchContent_Declare(_ycm URL https://github.com/robotology/ycm/archive/refs/tags/v0.13.0.zip) 181 | FetchContent_GetProperties(_ycm) 182 | if(NOT _ycm_POPULATED) 183 | FetchContent_Populate(_ycm) 184 | endif() 185 | include("${_ycm_SOURCE_DIR}/modules/InstallBasicPackageFiles.cmake") 186 | 187 | install_basic_package_files(${_PackageProject_NAME} "${_FARGS_LIST}") 188 | 189 | include("${_ycm_SOURCE_DIR}/modules/AddUninstallTarget.cmake") 190 | endfunction() 191 | -------------------------------------------------------------------------------- /cmake/PreventInSourceBuilds.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # This function will prevent in-source builds 3 | # 4 | function(json2cpp_assure_out_of_source_builds) 5 | # make sure the user doesn't play dirty with symlinks 6 | get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH) 7 | get_filename_component(bindir "${CMAKE_BINARY_DIR}" REALPATH) 8 | 9 | # disallow in-source builds 10 | if("${srcdir}" STREQUAL "${bindir}") 11 | message("######################################################") 12 | message("Warning: in-source builds are disabled") 13 | message("Please create a separate build directory and run cmake from there") 14 | message("######################################################") 15 | message(FATAL_ERROR "Quitting configuration") 16 | endif() 17 | endfunction() 18 | 19 | json2cpp_assure_out_of_source_builds() 20 | -------------------------------------------------------------------------------- /cmake/Sanitizers.cmake: -------------------------------------------------------------------------------- 1 | function( 2 | json2cpp_enable_sanitizers 3 | project_name 4 | ENABLE_SANITIZER_ADDRESS 5 | ENABLE_SANITIZER_LEAK 6 | ENABLE_SANITIZER_UNDEFINED_BEHAVIOR 7 | ENABLE_SANITIZER_THREAD 8 | ENABLE_SANITIZER_MEMORY) 9 | 10 | if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 11 | set(SANITIZERS "") 12 | 13 | if(${ENABLE_SANITIZER_ADDRESS}) 14 | list(APPEND SANITIZERS "address") 15 | endif() 16 | 17 | if(${ENABLE_SANITIZER_LEAK}) 18 | list(APPEND SANITIZERS "leak") 19 | endif() 20 | 21 | if(${ENABLE_SANITIZER_UNDEFINED_BEHAVIOR}) 22 | list(APPEND SANITIZERS "undefined") 23 | endif() 24 | 25 | if(${ENABLE_SANITIZER_THREAD}) 26 | if("address" IN_LIST SANITIZERS OR "leak" IN_LIST SANITIZERS) 27 | message(WARNING "Thread sanitizer does not work with Address and Leak sanitizer enabled") 28 | else() 29 | list(APPEND SANITIZERS "thread") 30 | endif() 31 | endif() 32 | 33 | if(${ENABLE_SANITIZER_MEMORY} AND CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 34 | message( 35 | WARNING 36 | "Memory sanitizer requires all the code (including libc++) to be MSan-instrumented otherwise it reports false positives" 37 | ) 38 | if("address" IN_LIST SANITIZERS 39 | OR "thread" IN_LIST SANITIZERS 40 | OR "leak" IN_LIST SANITIZERS) 41 | message(WARNING "Memory sanitizer does not work with Address, Thread or Leak sanitizer enabled") 42 | else() 43 | list(APPEND SANITIZERS "memory") 44 | endif() 45 | endif() 46 | elseif(MSVC) 47 | if(${ENABLE_SANITIZER_ADDRESS}) 48 | list(APPEND SANITIZERS "address") 49 | endif() 50 | if(${ENABLE_SANITIZER_LEAK} 51 | OR ${ENABLE_SANITIZER_UNDEFINED_BEHAVIOR} 52 | OR ${ENABLE_SANITIZER_THREAD} 53 | OR ${ENABLE_SANITIZER_MEMORY}) 54 | message(WARNING "MSVC only supports address sanitizer") 55 | endif() 56 | endif() 57 | 58 | list( 59 | JOIN 60 | SANITIZERS 61 | "," 62 | LIST_OF_SANITIZERS) 63 | 64 | if(LIST_OF_SANITIZERS) 65 | if(NOT 66 | "${LIST_OF_SANITIZERS}" 67 | STREQUAL 68 | "") 69 | if(NOT MSVC) 70 | target_compile_options(${project_name} INTERFACE -fsanitize=${LIST_OF_SANITIZERS}) 71 | target_link_options(${project_name} INTERFACE -fsanitize=${LIST_OF_SANITIZERS}) 72 | else() 73 | string(FIND "$ENV{PATH}" "$ENV{VSINSTALLDIR}" index_of_vs_install_dir) 74 | if("${index_of_vs_install_dir}" STREQUAL "-1") 75 | message( 76 | SEND_ERROR 77 | "Using MSVC sanitizers requires setting the MSVC environment before building the project. Please manually open the MSVC command prompt and rebuild the project." 78 | ) 79 | endif() 80 | target_compile_options(${project_name} INTERFACE /fsanitize=${LIST_OF_SANITIZERS} /Zi /INCREMENTAL:NO) 81 | target_compile_definitions(${project_name} INTERFACE _DISABLE_VECTOR_ANNOTATION _DISABLE_STRING_ANNOTATION) 82 | target_link_options(${project_name} INTERFACE /INCREMENTAL:NO) 83 | endif() 84 | endif() 85 | endif() 86 | 87 | endfunction() 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /cmake/StandardProjectSettings.cmake: -------------------------------------------------------------------------------- 1 | # Set a default build type if none was specified 2 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 3 | message(STATUS "Setting build type to 'RelWithDebInfo' as none was specified.") 4 | set(CMAKE_BUILD_TYPE 5 | RelWithDebInfo 6 | CACHE STRING "Choose the type of build." FORCE) 7 | # Set the possible values of build type for cmake-gui, ccmake 8 | set_property( 9 | CACHE CMAKE_BUILD_TYPE 10 | PROPERTY STRINGS 11 | "Debug" 12 | "Release" 13 | "MinSizeRel" 14 | "RelWithDebInfo") 15 | endif() 16 | 17 | # Generate compile_commands.json to make it easier to work with clang based tools 18 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 19 | 20 | # Enhance error reporting and compiler messages 21 | if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 22 | if(WIN32) 23 | # On Windows cuda nvcc uses cl and not clang 24 | add_compile_options($<$:-fcolor-diagnostics> $<$:-fcolor-diagnostics>) 25 | else() 26 | add_compile_options(-fcolor-diagnostics) 27 | endif() 28 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 29 | if(WIN32) 30 | # On Windows cuda nvcc uses cl and not gcc 31 | add_compile_options($<$:-fdiagnostics-color=always> 32 | $<$:-fdiagnostics-color=always>) 33 | else() 34 | add_compile_options(-fdiagnostics-color=always) 35 | endif() 36 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND MSVC_VERSION GREATER 1900) 37 | add_compile_options(/diagnostics:column) 38 | else() 39 | message(STATUS "No colored compiler diagnostic set for '${CMAKE_CXX_COMPILER_ID}' compiler.") 40 | endif() 41 | 42 | 43 | # run vcvarsall when msvc is used 44 | include("${CMAKE_CURRENT_LIST_DIR}/VCEnvironment.cmake") 45 | run_vcvarsall() 46 | -------------------------------------------------------------------------------- /cmake/StaticAnalyzers.cmake: -------------------------------------------------------------------------------- 1 | macro(json2cpp_enable_cppcheck WARNINGS_AS_ERRORS CPPCHECK_OPTIONS) 2 | find_program(CPPCHECK cppcheck) 3 | if(CPPCHECK) 4 | 5 | if(CMAKE_GENERATOR MATCHES ".*Visual Studio.*") 6 | set(CPPCHECK_TEMPLATE "vs") 7 | else() 8 | set(CPPCHECK_TEMPLATE "gcc") 9 | endif() 10 | 11 | if("${CPPCHECK_OPTIONS}" STREQUAL "") 12 | # Enable all warnings that are actionable by the user of this toolset 13 | # style should enable the other 3, but we'll be explicit just in case 14 | set(CMAKE_CXX_CPPCHECK 15 | ${CPPCHECK} 16 | --template=${CPPCHECK_TEMPLATE} 17 | --enable=style,performance,warning,portability 18 | --inline-suppr 19 | # We cannot act on a bug/missing feature of cppcheck 20 | --suppress=cppcheckError 21 | --suppress=internalAstError 22 | # if a file does not have an internalAstError, we get an unmatchedSuppression error 23 | --suppress=unmatchedSuppression 24 | # noisy and incorrect sometimes 25 | --suppress=passedByValue 26 | # ignores code that cppcheck thinks is invalid C++ 27 | --suppress=syntaxError 28 | --suppress=preprocessorErrorDirective 29 | --inconclusive) 30 | else() 31 | # if the user provides a CPPCHECK_OPTIONS with a template specified, it will override this template 32 | set(CMAKE_CXX_CPPCHECK ${CPPCHECK} --template=${CPPCHECK_TEMPLATE} ${CPPCHECK_OPTIONS}) 33 | endif() 34 | 35 | if(NOT 36 | "${CMAKE_CXX_STANDARD}" 37 | STREQUAL 38 | "") 39 | set(CMAKE_CXX_CPPCHECK ${CMAKE_CXX_CPPCHECK} --std=c++${CMAKE_CXX_STANDARD}) 40 | endif() 41 | if(${WARNINGS_AS_ERRORS}) 42 | list(APPEND CMAKE_CXX_CPPCHECK --error-exitcode=2) 43 | endif() 44 | else() 45 | message(${WARNING_MESSAGE} "cppcheck requested but executable not found") 46 | endif() 47 | endmacro() 48 | 49 | macro(json2cpp_enable_clang_tidy target WARNINGS_AS_ERRORS) 50 | 51 | find_program(CLANGTIDY clang-tidy) 52 | if(CLANGTIDY) 53 | if(NOT 54 | CMAKE_CXX_COMPILER_ID 55 | MATCHES 56 | ".*Clang") 57 | 58 | get_target_property(TARGET_PCH ${target} INTERFACE_PRECOMPILE_HEADERS) 59 | 60 | if("${TARGET_PCH}" STREQUAL "TARGET_PCH-NOTFOUND") 61 | get_target_property(TARGET_PCH ${target} PRECOMPILE_HEADERS) 62 | endif() 63 | 64 | if(NOT ("${TARGET_PCH}" STREQUAL "TARGET_PCH-NOTFOUND")) 65 | message( 66 | SEND_ERROR 67 | "clang-tidy cannot be enabled with non-clang compiler and PCH, clang-tidy fails to handle gcc's PCH file") 68 | endif() 69 | endif() 70 | 71 | # construct the clang-tidy command line 72 | set(CLANG_TIDY_OPTIONS 73 | ${CLANGTIDY} 74 | -extra-arg=-Wno-unknown-warning-option 75 | -extra-arg=-Wno-ignored-optimization-argument 76 | -extra-arg=-Wno-unused-command-line-argument 77 | -p) 78 | # set standard 79 | if(NOT 80 | "${CMAKE_CXX_STANDARD}" 81 | STREQUAL 82 | "") 83 | if("${CLANG_TIDY_OPTIONS_DRIVER_MODE}" STREQUAL "cl") 84 | set(CLANG_TIDY_OPTIONS ${CLANG_TIDY_OPTIONS} -extra-arg=/std:c++${CMAKE_CXX_STANDARD}) 85 | else() 86 | set(CLANG_TIDY_OPTIONS ${CLANG_TIDY_OPTIONS} -extra-arg=-std=c++${CMAKE_CXX_STANDARD}) 87 | endif() 88 | endif() 89 | 90 | # set warnings as errors 91 | if(${WARNINGS_AS_ERRORS}) 92 | list(APPEND CLANG_TIDY_OPTIONS -warnings-as-errors=*) 93 | endif() 94 | 95 | message("Also setting clang-tidy globally") 96 | set(CMAKE_CXX_CLANG_TIDY ${CLANG_TIDY_OPTIONS}) 97 | else() 98 | message(${WARNING_MESSAGE} "clang-tidy requested but executable not found") 99 | endif() 100 | endmacro() 101 | 102 | macro(json2cpp_enable_include_what_you_use) 103 | find_program(INCLUDE_WHAT_YOU_USE include-what-you-use) 104 | if(INCLUDE_WHAT_YOU_USE) 105 | set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE ${INCLUDE_WHAT_YOU_USE}) 106 | else() 107 | message(${WARNING_MESSAGE} "include-what-you-use requested but executable not found") 108 | endif() 109 | endmacro() 110 | -------------------------------------------------------------------------------- /cmake/SystemLink.cmake: -------------------------------------------------------------------------------- 1 | # Include a system directory (which suppresses its warnings). 2 | function(target_include_system_directories target) 3 | set(multiValueArgs INTERFACE PUBLIC PRIVATE) 4 | cmake_parse_arguments( 5 | ARG 6 | "" 7 | "" 8 | "${multiValueArgs}" 9 | ${ARGN}) 10 | 11 | foreach(scope IN ITEMS INTERFACE PUBLIC PRIVATE) 12 | foreach(lib_include_dirs IN LISTS ARG_${scope}) 13 | if(NOT MSVC) 14 | # system includes do not work in MSVC 15 | # awaiting https://gitlab.kitware.com/cmake/cmake/-/issues/18272# 16 | # awaiting https://gitlab.kitware.com/cmake/cmake/-/issues/17904 17 | set(_SYSTEM SYSTEM) 18 | endif() 19 | if(${scope} STREQUAL "INTERFACE" OR ${scope} STREQUAL "PUBLIC") 20 | target_include_directories( 21 | ${target} 22 | ${_SYSTEM} 23 | ${scope} 24 | "$" 25 | "$") 26 | else() 27 | target_include_directories( 28 | ${target} 29 | ${_SYSTEM} 30 | ${scope} 31 | ${lib_include_dirs}) 32 | endif() 33 | endforeach() 34 | endforeach() 35 | 36 | endfunction() 37 | 38 | # Include the directories of a library target as system directories (which suppresses their warnings). 39 | function( 40 | target_include_system_library 41 | target 42 | scope 43 | lib) 44 | # check if this is a target 45 | if(TARGET ${lib}) 46 | get_target_property(lib_include_dirs ${lib} INTERFACE_INCLUDE_DIRECTORIES) 47 | if(lib_include_dirs) 48 | target_include_system_directories(${target} ${scope} ${lib_include_dirs}) 49 | else() 50 | message(TRACE "${lib} library does not have the INTERFACE_INCLUDE_DIRECTORIES property.") 51 | endif() 52 | endif() 53 | endfunction() 54 | 55 | # Link a library target as a system library (which suppresses its warnings). 56 | function( 57 | target_link_system_library 58 | target 59 | scope 60 | lib) 61 | # Include the directories in the library 62 | target_include_system_library(${target} ${scope} ${lib}) 63 | 64 | # Link the library 65 | target_link_libraries(${target} ${scope} ${lib}) 66 | endfunction() 67 | 68 | # Link multiple library targets as system libraries (which suppresses their warnings). 69 | function(target_link_system_libraries target) 70 | set(multiValueArgs INTERFACE PUBLIC PRIVATE) 71 | cmake_parse_arguments( 72 | ARG 73 | "" 74 | "" 75 | "${multiValueArgs}" 76 | ${ARGN}) 77 | 78 | foreach(scope IN ITEMS INTERFACE PUBLIC PRIVATE) 79 | foreach(lib IN LISTS ARG_${scope}) 80 | target_link_system_library(${target} ${scope} ${lib}) 81 | endforeach() 82 | endforeach() 83 | endfunction() 84 | -------------------------------------------------------------------------------- /cmake/Tests.cmake: -------------------------------------------------------------------------------- 1 | function(json2cpp_enable_coverage project_name) 2 | if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 3 | target_compile_options(${project_name} INTERFACE --coverage -O0 -g) 4 | target_link_libraries(${project_name} INTERFACE --coverage) 5 | endif() 6 | endfunction() 7 | -------------------------------------------------------------------------------- /cmake/Utilities.cmake: -------------------------------------------------------------------------------- 1 | # find a subtring from a string by a given prefix such as VCVARSALL_ENV_START 2 | function( 3 | find_substring_by_prefix 4 | output 5 | prefix 6 | input) 7 | # find the prefix 8 | string(FIND "${input}" "${prefix}" prefix_index) 9 | if("${prefix_index}" STREQUAL "-1") 10 | message(SEND_ERROR "Could not find ${prefix} in ${input}") 11 | endif() 12 | # find the start index 13 | string(LENGTH "${prefix}" prefix_length) 14 | math(EXPR start_index "${prefix_index} + ${prefix_length}") 15 | 16 | string( 17 | SUBSTRING "${input}" 18 | "${start_index}" 19 | "-1" 20 | _output) 21 | set("${output}" 22 | "${_output}" 23 | PARENT_SCOPE) 24 | endfunction() 25 | 26 | # A function to set environment variables of CMake from the output of `cmd /c set` 27 | function(set_env_from_string env_string) 28 | # replace ; in paths with __sep__ so we can split on ; 29 | string( 30 | REGEX 31 | REPLACE ";" 32 | "__sep__" 33 | env_string_sep_added 34 | "${env_string}") 35 | 36 | # the variables are separated by \r?\n 37 | string( 38 | REGEX 39 | REPLACE "\r?\n" 40 | ";" 41 | env_list 42 | "${env_string_sep_added}") 43 | 44 | foreach(env_var ${env_list}) 45 | # split by = 46 | string( 47 | REGEX 48 | REPLACE "=" 49 | ";" 50 | env_parts 51 | "${env_var}") 52 | 53 | list(LENGTH env_parts env_parts_length) 54 | if("${env_parts_length}" EQUAL "2") 55 | # get the variable name and value 56 | list( 57 | GET 58 | env_parts 59 | 0 60 | env_name) 61 | list( 62 | GET 63 | env_parts 64 | 1 65 | env_value) 66 | 67 | # recover ; in paths 68 | string( 69 | REGEX 70 | REPLACE "__sep__" 71 | ";" 72 | env_value 73 | "${env_value}") 74 | 75 | # set env_name to env_value 76 | set(ENV{${env_name}} "${env_value}") 77 | 78 | # update cmake program path 79 | if("${env_name}" EQUAL "PATH") 80 | list(APPEND CMAKE_PROGRAM_PATH ${env_value}) 81 | endif() 82 | endif() 83 | endforeach() 84 | endfunction() 85 | 86 | function(get_all_targets var) 87 | set(targets) 88 | get_all_targets_recursive(targets ${CMAKE_CURRENT_SOURCE_DIR}) 89 | set(${var} 90 | ${targets} 91 | PARENT_SCOPE) 92 | endfunction() 93 | 94 | function(get_all_installable_targets var) 95 | set(targets) 96 | get_all_targets(targets) 97 | foreach(_target ${targets}) 98 | get_target_property(_target_type ${_target} TYPE) 99 | if(NOT 100 | ${_target_type} 101 | MATCHES 102 | ".*LIBRARY|EXECUTABLE") 103 | list(REMOVE_ITEM targets ${_target}) 104 | endif() 105 | endforeach() 106 | set(${var} 107 | ${targets} 108 | PARENT_SCOPE) 109 | endfunction() 110 | 111 | macro(get_all_targets_recursive targets dir) 112 | get_property( 113 | subdirectories 114 | DIRECTORY ${dir} 115 | PROPERTY SUBDIRECTORIES) 116 | foreach(subdir ${subdirectories}) 117 | get_all_targets_recursive(${targets} ${subdir}) 118 | endforeach() 119 | 120 | get_property( 121 | current_targets 122 | DIRECTORY ${dir} 123 | PROPERTY BUILDSYSTEM_TARGETS) 124 | list(APPEND ${targets} ${current_targets}) 125 | endmacro() 126 | 127 | function(is_verbose var) 128 | if("CMAKE_MESSAGE_LOG_LEVEL" STREQUAL "VERBOSE" 129 | OR "CMAKE_MESSAGE_LOG_LEVEL" STREQUAL "DEBUG" 130 | OR "CMAKE_MESSAGE_LOG_LEVEL" STREQUAL "TRACE") 131 | set(${var} 132 | ON 133 | PARENT_SCOPE) 134 | else() 135 | set(${var} 136 | OFF 137 | PARENT_SCOPE) 138 | endif() 139 | endfunction() 140 | -------------------------------------------------------------------------------- /cmake/VCEnvironment.cmake: -------------------------------------------------------------------------------- 1 | include("${CMAKE_CURRENT_LIST_DIR}/Utilities.cmake") 2 | 3 | macro(detect_architecture) 4 | # detect the architecture 5 | string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" CMAKE_SYSTEM_PROCESSOR_LOWER) 6 | if(CMAKE_SYSTEM_PROCESSOR_LOWER STREQUAL x86 OR CMAKE_SYSTEM_PROCESSOR_LOWER MATCHES "^i[3456]86$") 7 | set(VCVARSALL_ARCH x86) 8 | elseif( 9 | CMAKE_SYSTEM_PROCESSOR_LOWER STREQUAL x64 10 | OR CMAKE_SYSTEM_PROCESSOR_LOWER STREQUAL x86_64 11 | OR CMAKE_SYSTEM_PROCESSOR_LOWER STREQUAL amd64) 12 | set(VCVARSALL_ARCH x64) 13 | elseif(CMAKE_SYSTEM_PROCESSOR_LOWER STREQUAL arm) 14 | set(VCVARSALL_ARCH arm) 15 | elseif(CMAKE_SYSTEM_PROCESSOR_LOWER STREQUAL arm64 OR CMAKE_SYSTEM_PROCESSOR_LOWER STREQUAL aarch64) 16 | set(VCVARSALL_ARCH arm64) 17 | else() 18 | if(CMAKE_HOST_SYSTEM_PROCESSOR) 19 | set(VCVARSALL_ARCH ${CMAKE_HOST_SYSTEM_PROCESSOR}) 20 | else() 21 | set(VCVARSALL_ARCH x64) 22 | message(STATUS "Unkown architecture CMAKE_SYSTEM_PROCESSOR: ${CMAKE_SYSTEM_PROCESSOR_LOWER} - using x64") 23 | endif() 24 | endif() 25 | endmacro() 26 | 27 | # Run vcvarsall.bat and set CMake environment variables 28 | function(run_vcvarsall) 29 | # if MSVC but VSCMD_VER is not set, which means vcvarsall has not run 30 | if(MSVC AND "$ENV{VSCMD_VER}" STREQUAL "") 31 | 32 | # find vcvarsall.bat 33 | get_filename_component(MSVC_DIR ${CMAKE_CXX_COMPILER} DIRECTORY) 34 | find_file( 35 | VCVARSALL_FILE 36 | NAMES vcvarsall.bat 37 | PATHS "${MSVC_DIR}" 38 | "${MSVC_DIR}/.." 39 | "${MSVC_DIR}/../.." 40 | "${MSVC_DIR}/../../../../../../../.." 41 | "${MSVC_DIR}/../../../../../../.." 42 | PATH_SUFFIXES "VC/Auxiliary/Build" "Common7/Tools" "Tools") 43 | 44 | if(EXISTS ${VCVARSALL_FILE}) 45 | # detect the architecture 46 | detect_architecture() 47 | 48 | # run vcvarsall and print the environment variables 49 | message(STATUS "Running `${VCVARSALL_FILE} ${VCVARSALL_ARCH}` to set up the MSVC environment") 50 | execute_process( 51 | COMMAND 52 | "cmd" "/c" ${VCVARSALL_FILE} ${VCVARSALL_ARCH} # 53 | "&&" "call" "echo" "VCVARSALL_ENV_START" # 54 | "&" "set" # 55 | OUTPUT_VARIABLE VCVARSALL_OUTPUT 56 | OUTPUT_STRIP_TRAILING_WHITESPACE) 57 | 58 | # parse the output and get the environment variables string 59 | find_substring_by_prefix(VCVARSALL_ENV "VCVARSALL_ENV_START" "${VCVARSALL_OUTPUT}") 60 | 61 | # set the environment variables 62 | set_env_from_string("${VCVARSALL_ENV}") 63 | 64 | else() 65 | message( 66 | WARNING 67 | "Could not find `vcvarsall.bat` for automatic MSVC environment preparation. Please manually open the MSVC command prompt and rebuild the project. 68 | ") 69 | endif() 70 | endif() 71 | endfunction() 72 | -------------------------------------------------------------------------------- /cmake/_FORTIFY_SOURCE.hpp: -------------------------------------------------------------------------------- 1 | #ifdef _FORTIFY_SOURCE 2 | #if _FORTIFY_SOURCE < 3 3 | #undef _FORTIFY_SOURCE 4 | #define _FORTIFY_SOURCE 3 5 | #endif 6 | #else 7 | #define _FORTIFY_SOURCE 3 8 | #endif 9 | -------------------------------------------------------------------------------- /examples/allof_integers_and_numbers.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment": "Document must contain an array of integers, all unique.", 3 | "allOf": [ 4 | { 5 | "items": { 6 | "type": "integer" 7 | }, 8 | "additionalItems": false, 9 | "type": "array" 10 | }, 11 | { 12 | "items": { 13 | "type": "number" 14 | }, 15 | "additionalItems": false, 16 | "type": "array", 17 | "uniqueItems": true 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /examples/array_doubles_10_20_30_40.json: -------------------------------------------------------------------------------- 1 | [10.0, 20.0, 30.0, 40.0] -------------------------------------------------------------------------------- /examples/array_integers_10_20_30_40.json: -------------------------------------------------------------------------------- 1 | [10, 20, 30, 40] -------------------------------------------------------------------------------- /examples/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "glossary": { 3 | "title": "example glossary", 4 | "GlossDiv": { 5 | "title": "S", 6 | "subtitle": null, 7 | "GlossList": { 8 | "GlossEntry": { 9 | "ID": "SGML", 10 | "SortAs": "SGML", 11 | "GlossTerm": "Standard Generalized Markup Language", 12 | "Acronym": "SGML", 13 | "Abbrev": "ISO 8879:1986", 14 | "GlossDef": { 15 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 16 | "GlossSeeAlso": [ 17 | "GML", 18 | "XML" 19 | ] 20 | }, 21 | "GlossSee": "markup" 22 | } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /fuzz_test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # A fuzz test runs until it finds an error. This particular one is going to rely on libFuzzer. 2 | # 3 | 4 | find_package(fmt) 5 | 6 | add_executable(fuzz_tester fuzz_tester.cpp) 7 | target_link_libraries( 8 | fuzz_tester 9 | PRIVATE json2cpp::json2cpp_options 10 | json2cpp::json2cpp_warnings 11 | fmt::fmt 12 | -coverage 13 | -fsanitize=fuzzer,undefined,address) 14 | target_compile_options(fuzz_tester PRIVATE -fsanitize=fuzzer,undefined,address) 15 | 16 | # Allow short runs during automated testing to see if something new breaks 17 | set(FUZZ_RUNTIME 18 | 10 19 | CACHE STRING "Number of seconds to run fuzz tests during ctest run") # Default of 10 seconds 20 | 21 | add_test(NAME fuzz_tester_run COMMAND fuzz_tester -max_total_time=${FUZZ_RUNTIME}) 22 | -------------------------------------------------------------------------------- /fuzz_test/fuzz_tester.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | [[nodiscard]] auto sum_values(const uint8_t *Data, size_t Size) 6 | { 7 | constexpr auto scale = 1000; 8 | 9 | int value = 0; 10 | for (std::size_t offset = 0; offset < Size; ++offset) { 11 | value += static_cast(*std::next(Data, static_cast(offset))) * scale; 12 | } 13 | return value; 14 | } 15 | 16 | // Fuzzer that attempts to invoke undefined behavior for signed integer overflow 17 | // cppcheck-suppress unusedFunction symbolName=LLVMFuzzerTestOneInput 18 | extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) 19 | { 20 | fmt::print("Value sum: {}, len{}\n", sum_values(Data, Size), Size); 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /include/json2cpp/json2cpp.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2022 Jason Turner 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | // Important note: the types in this file are only intended for compile-time construction 26 | // but consteval doesn't exist in C++17, and we're targeting C++17 27 | 28 | #ifndef CONSTEXPR_JSON_HPP_INCLUDED 29 | #define CONSTEXPR_JSON_HPP_INCLUDED 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | // simple pair to speed up compilation a bit compared to std::pair 38 | namespace json2cpp { 39 | template struct pair 40 | { 41 | First first; 42 | Second second; 43 | }; 44 | 45 | // simple span because std::span is not in C++17 46 | template struct span 47 | { 48 | template 49 | constexpr explicit span(const std::array &input) 50 | : begin_{ input.data() }, end_{ std::next(input.data(), Size) } 51 | {} 52 | 53 | constexpr span() : begin_{ nullptr }, end_{ nullptr } {} 54 | 55 | [[nodiscard]] constexpr const T *begin() const noexcept { return begin_; } 56 | 57 | [[nodiscard]] constexpr const T *end() const noexcept { return end_; } 58 | 59 | [[nodiscard]] constexpr std::size_t size() const noexcept 60 | { 61 | return static_cast(std::distance(begin_, end_)); 62 | } 63 | 64 | const T *begin_; 65 | const T *end_; 66 | }; 67 | 68 | template struct basic_json; 69 | template using basic_array_t = span>; 70 | template using basic_value_pair_t = pair, basic_json>; 71 | template using basic_object_t = span>; 72 | 73 | using binary_t = span; 74 | 75 | template struct data_variant 76 | { 77 | struct monostate 78 | { 79 | }; 80 | 81 | union value_t { 82 | monostate empty_; 83 | bool bool_; 84 | binary_t binary_; 85 | basic_array_t array_; 86 | basic_object_t object_; 87 | std::int64_t int64_t_; 88 | std::uint64_t uint64_t_; 89 | double double_; 90 | std::basic_string_view string_view_; 91 | std::nullptr_t null_; 92 | 93 | constexpr explicit value_t() : empty_{} {} 94 | constexpr explicit value_t(monostate) : value_t() {} 95 | constexpr explicit value_t(bool b) : bool_{ b } {} 96 | constexpr explicit value_t(binary_t b) : binary_{ b } {} 97 | constexpr explicit value_t(basic_array_t a) : array_{ a } {} 98 | constexpr explicit value_t(basic_object_t o) : object_{ o } {} 99 | constexpr explicit value_t(std::int64_t i) : int64_t_{ i } {} 100 | constexpr explicit value_t(std::uint64_t i) : uint64_t_{ i } {} 101 | constexpr explicit value_t(double d) : double_{ d } {} 102 | constexpr explicit value_t(std::basic_string_view s) : string_view_{ s } {} 103 | constexpr explicit value_t(std::nullptr_t) : null_{} {} 104 | }; 105 | 106 | enum struct selected_type { 107 | empty, 108 | boolean, 109 | binary, 110 | array, 111 | object, 112 | integer, 113 | uinteger, 114 | floating_point, 115 | string, 116 | nullish 117 | }; 118 | 119 | value_t value{ monostate{} }; 120 | selected_type selected{ selected_type::empty }; 121 | 122 | // letting these be implicit measurably improves construction performance 123 | 124 | constexpr data_variant() = default; 125 | // cppcheck-suppress noExplicitConstructor 126 | constexpr data_variant(monostate) : data_variant() {} 127 | // cppcheck-suppress noExplicitConstructor 128 | constexpr data_variant(bool b) : value{ b }, selected{ selected_type::boolean } {} 129 | // cppcheck-suppress noExplicitConstructor 130 | constexpr data_variant(basic_array_t a) : value{ a }, selected{ selected_type::array } {} 131 | // cppcheck-suppress noExplicitConstructor 132 | constexpr data_variant(basic_object_t o) : value{ o }, selected{ selected_type::object } {} 133 | // cppcheck-suppress noExplicitConstructor 134 | constexpr data_variant(std::int64_t i) : value{ i }, selected{ selected_type::integer } {} 135 | // cppcheck-suppress noExplicitConstructor 136 | constexpr data_variant(std::uint64_t i) : value{ i }, selected{ selected_type::uinteger } {} 137 | // cppcheck-suppress noExplicitConstructor 138 | constexpr data_variant(double d) : value{ d }, selected{ selected_type::floating_point } {} 139 | // cppcheck-suppress noExplicitConstructor 140 | constexpr data_variant(std::basic_string_view s) : value{ s }, selected{ selected_type::string } {} 141 | // cppcheck-suppress noExplicitConstructor 142 | constexpr data_variant(std::nullptr_t) : value{ nullptr }, selected{ selected_type::nullish } {} 143 | 144 | [[nodiscard]] constexpr bool is_boolean() const noexcept { return selected == selected_type::boolean; } 145 | 146 | [[nodiscard]] constexpr const bool *get_if_boolean() const noexcept 147 | { 148 | if (selected == selected_type::boolean) { 149 | return &value.bool_; 150 | } else { 151 | return nullptr; 152 | } 153 | } 154 | 155 | [[nodiscard]] constexpr bool is_array() const noexcept { return selected == selected_type::array; } 156 | 157 | [[nodiscard]] constexpr const basic_array_t *get_if_array() const noexcept 158 | { 159 | if (is_array()) { 160 | return &value.array_; 161 | } else { 162 | return nullptr; 163 | } 164 | } 165 | 166 | [[nodiscard]] constexpr bool is_object() const noexcept { return selected == selected_type::object; } 167 | 168 | [[nodiscard]] constexpr const basic_object_t *get_if_object() const noexcept 169 | { 170 | if (is_object()) { 171 | return &value.object_; 172 | } else { 173 | return nullptr; 174 | } 175 | } 176 | 177 | [[nodiscard]] constexpr bool is_integer() const noexcept { return selected == selected_type::integer; } 178 | 179 | [[nodiscard]] constexpr const std::int64_t *get_if_integer() const noexcept 180 | { 181 | if (is_integer()) { 182 | return &value.int64_t_; 183 | } else { 184 | return nullptr; 185 | } 186 | } 187 | 188 | [[nodiscard]] constexpr bool is_uinteger() const noexcept { return selected == selected_type::uinteger; } 189 | 190 | [[nodiscard]] constexpr const std::uint64_t *get_if_uinteger() const noexcept 191 | { 192 | if (is_uinteger()) { 193 | return &value.uint64_t_; 194 | } else { 195 | return nullptr; 196 | } 197 | } 198 | 199 | 200 | [[nodiscard]] constexpr bool is_floating_point() const noexcept { return selected == selected_type::floating_point; } 201 | 202 | 203 | [[nodiscard]] constexpr const double *get_if_floating_point() const noexcept 204 | { 205 | if (is_floating_point()) { 206 | return &value.double_; 207 | } else { 208 | return nullptr; 209 | } 210 | } 211 | 212 | [[nodiscard]] constexpr bool is_string() const noexcept { return selected == selected_type::string; } 213 | 214 | [[nodiscard]] constexpr const std::basic_string_view *get_if_string() const noexcept 215 | { 216 | if (is_string()) { 217 | return &value.string_view_; 218 | } else { 219 | return nullptr; 220 | } 221 | } 222 | 223 | [[nodiscard]] constexpr bool is_null() const noexcept { return selected == selected_type::nullish; } 224 | }; 225 | 226 | template struct basic_json 227 | { 228 | using data_t = data_variant; 229 | 230 | struct iterator 231 | { 232 | constexpr iterator() noexcept = default; 233 | 234 | constexpr explicit iterator(const basic_json &value, std::size_t index = 0) noexcept 235 | : parent_value_(&value), index_{ index } 236 | {} 237 | 238 | constexpr const basic_json &operator*() const 239 | { 240 | if (parent_value_->is_array()) { 241 | return (*parent_value_)[index_]; 242 | } else if (parent_value_->is_object()) { 243 | return std::next(parent_value_->object_data().begin(), static_cast(index_))->second; 244 | } else { 245 | return *parent_value_; 246 | } 247 | } 248 | 249 | constexpr const basic_json *operator->() const noexcept { return &(*(*this)); } 250 | 251 | constexpr std::size_t index() const noexcept { return index_; } 252 | 253 | constexpr const basic_json &value() const noexcept { return *(*this); } 254 | 255 | 256 | constexpr std::basic_string_view key() const 257 | { 258 | if (parent_value_->is_object()) { 259 | return std::next(parent_value_->object_data().begin(), static_cast(index_))->first; 260 | } else { 261 | throw std::runtime_error("json value is not an object, it has no key"); 262 | } 263 | } 264 | 265 | constexpr bool operator==(const iterator &other) const noexcept 266 | { 267 | return other.parent_value_ == parent_value_ && other.index_ == index_; 268 | } 269 | constexpr bool operator!=(const iterator &other) const noexcept { return !(*this == other); } 270 | 271 | 272 | constexpr bool operator<(const iterator &other) const noexcept 273 | { 274 | return other.parent_value_ == parent_value_ && index_ < other.index_; 275 | } 276 | 277 | constexpr iterator &operator--() noexcept 278 | { 279 | --index_; 280 | return *this; 281 | } 282 | 283 | [[nodiscard]] constexpr iterator operator--(int) noexcept 284 | { 285 | iterator result{ *this }; 286 | index_--; 287 | return result; 288 | } 289 | 290 | constexpr iterator &operator++() noexcept 291 | { 292 | ++index_; 293 | return *this; 294 | } 295 | 296 | [[nodiscard]] constexpr iterator operator++(int) noexcept 297 | { 298 | iterator result{ *this }; 299 | index_++; 300 | return result; 301 | } 302 | 303 | constexpr iterator &operator+=(const std::ptrdiff_t value) noexcept 304 | { 305 | index_ = static_cast(static_cast(index_) + value); 306 | return *this; 307 | } 308 | 309 | 310 | constexpr iterator &operator+=(const std::size_t value) noexcept 311 | { 312 | index_ += value; 313 | return *this; 314 | } 315 | 316 | const basic_json *parent_value_{ nullptr }; 317 | std::size_t index_{ 0 }; 318 | }; 319 | 320 | using const_iterator = iterator; 321 | 322 | [[nodiscard]] constexpr iterator begin() const noexcept { return iterator{ *this }; } 323 | 324 | [[nodiscard]] constexpr iterator end() const noexcept { return iterator{ *this, size() }; } 325 | 326 | [[nodiscard]] constexpr iterator cbegin() const noexcept { return begin(); } 327 | 328 | [[nodiscard]] constexpr iterator cend() const noexcept { return end(); } 329 | 330 | [[nodiscard]] constexpr std::size_t size() const noexcept { return size_; } 331 | 332 | [[nodiscard]] constexpr bool empty() const noexcept { return size_ == 0; } 333 | 334 | [[nodiscard]] static constexpr std::size_t size(const basic_json &obj) noexcept 335 | { 336 | if (obj.is_null()) { return 0; } 337 | if (obj.is_object()) { return obj.data.get_if_object()->size(); } 338 | if (obj.is_array()) { return obj.data.get_if_array()->size(); } 339 | 340 | return 1; 341 | } 342 | 343 | 344 | [[nodiscard]] constexpr const basic_json &operator[](const std::size_t idx) const 345 | { 346 | if (const auto &children = array_data(); idx < children.size()) { 347 | return *std::next(children.begin(), static_cast(idx)); 348 | } else { 349 | throw std::runtime_error("index out of range"); 350 | } 351 | } 352 | 353 | [[nodiscard]] constexpr const basic_json &at(const std::basic_string_view key) const 354 | { 355 | const auto &children = object_data(); 356 | 357 | // find_if is not constexpr in C++17, so we rolled our own, 358 | // and this helps us work around bugs in older versions of GCC 359 | // and constexpr 360 | const auto finder = [&]() { 361 | auto itr = children.begin(); 362 | 363 | for (; itr != children.end(); ++itr) { 364 | if (itr->first == key) { return itr; } 365 | } 366 | 367 | return itr; 368 | }; 369 | 370 | const auto obj = finder(); 371 | 372 | if (obj != children.end()) { 373 | return obj->second; 374 | } else { 375 | throw std::runtime_error("Key not found"); 376 | } 377 | } 378 | 379 | template [[nodiscard]] constexpr std::size_t count(const Key &key) const 380 | { 381 | if (is_object()) { 382 | const auto found = find(key); 383 | if (found == end()) { 384 | return 0; 385 | } else { 386 | return 1; 387 | } 388 | } 389 | return 0; 390 | } 391 | 392 | [[nodiscard]] constexpr iterator find(const std::basic_string_view key) const 393 | { 394 | for (auto itr = begin(); itr != end(); ++itr) { 395 | if (itr.key() == key) { return itr; } 396 | } 397 | 398 | return end(); 399 | } 400 | 401 | [[nodiscard]] constexpr const basic_json &operator[](const std::basic_string_view key) const 402 | { 403 | return at(key); 404 | } 405 | 406 | constexpr const auto &array_data() const 407 | { 408 | if (data.is_array()) { 409 | return *data.get_if_array(); 410 | } else { 411 | throw std::runtime_error("value is not an array type"); 412 | } 413 | } 414 | 415 | constexpr const auto &object_data() const 416 | { 417 | if (data.is_object()) { 418 | return *data.get_if_object(); 419 | } else { 420 | throw std::runtime_error("value is not an object type"); 421 | } 422 | } 423 | 424 | constexpr static basic_json object() { return basic_json{ data_t{ basic_object_t{} } }; } 425 | constexpr static basic_json array() { return basic_json{ data_t{ basic_array_t{} } }; } 426 | 427 | template [[nodiscard]] constexpr auto get() const 428 | { 429 | // I don't like this level of implicit conversions in the `get()` function, 430 | // but it's necessary for API compatibility with nlohmann::json 431 | if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) { 433 | if (data.is_uinteger()) { 434 | return Type(*data.get_if_uinteger()); 435 | } else if (data.is_integer()) { 436 | return Type(*data.get_if_integer()); 437 | } else if (data.is_floating_point()) { 438 | return Type(*data.get_if_floating_point()); 439 | } else { 440 | throw std::runtime_error("Unexpected type: number requested");// + ss.str() ); 441 | } 442 | } else if constexpr (std::is_same_v> || std::is_same_v>) { 444 | if (data.is_string()) { 445 | return *data.get_if_string(); 446 | } else { 447 | throw std::runtime_error("Unexpected type: string-like requested"); 448 | } 449 | } else if constexpr (std::is_same_v) { 450 | if (data.is_boolean()) { 451 | return *data.get_if_boolean(); 452 | } else { 453 | throw std::runtime_error("Unexpected type: bool requested"); 454 | } 455 | } else if constexpr (std::is_same_v) { 456 | if (data.is_null()) { 457 | return nullptr; 458 | } else { 459 | throw std::runtime_error("Unexpected type: null requested"); 460 | } 461 | } else { 462 | throw std::runtime_error("Unexpected type for get()"); 463 | } 464 | } 465 | 466 | [[nodiscard]] constexpr bool is_object() const noexcept { return data.selected == data_t::selected_type::object; } 467 | [[nodiscard]] constexpr bool is_array() const noexcept { return data.selected == data_t::selected_type::array; } 468 | [[nodiscard]] constexpr bool is_string() const noexcept { return data.selected == data_t::selected_type::string; } 469 | [[nodiscard]] constexpr bool is_boolean() const noexcept { return data.selected == data_t::selected_type::boolean; } 470 | [[nodiscard]] constexpr bool is_structured() const noexcept { return is_object() || is_array(); } 471 | [[nodiscard]] constexpr bool is_number() const noexcept { return is_number_integer() || is_number_float(); } 472 | [[nodiscard]] constexpr bool is_number_integer() const noexcept { return is_number_signed() || is_number_unsigned(); } 473 | [[nodiscard]] constexpr bool is_null() const noexcept { return data.selected == data_t::selected_type::nullish; } 474 | [[nodiscard]] constexpr bool is_binary() const noexcept { return data.selected == data_t::selected_type::binary; } 475 | 476 | [[nodiscard]] constexpr bool is_number_signed() const noexcept 477 | { 478 | return data.selected == data_t::selected_type::integer; 479 | } 480 | 481 | [[nodiscard]] constexpr bool is_number_unsigned() const noexcept 482 | { 483 | return data.selected == data_t::selected_type::uinteger; 484 | } 485 | 486 | [[nodiscard]] constexpr bool is_number_float() const noexcept 487 | { 488 | return data.selected == data_t::selected_type::floating_point; 489 | } 490 | 491 | [[nodiscard]] constexpr bool is_primitive() const noexcept 492 | { 493 | return is_null() || is_string() || is_boolean() || is_number() || is_binary(); 494 | } 495 | 496 | 497 | data_t data; 498 | std::size_t size_{ basic_json::size(*this) }; 499 | }; 500 | 501 | using json = basic_json; 502 | using object_t = basic_object_t; 503 | using value_pair_t = basic_value_pair_t; 504 | using array_t = basic_array_t; 505 | }// namespace json2cpp 506 | 507 | #endif 508 | -------------------------------------------------------------------------------- /include/json2cpp/json2cpp_adapter.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2022 Jason Turner 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | /** 26 | * @file 27 | * 28 | * @brief Adapter implementation for the json2cpp json parser library. 29 | * 30 | * Include this file in your program to enable support for json2cpp json. 31 | * 32 | * This file defines the following classes (not in this order): 33 | * - json2cppJsonAdapter 34 | * - json2cppJsonArray 35 | * - json2cppJsonValueIterator 36 | * - json2cppJsonFrozenValue 37 | * - json2cppJsonObject 38 | * - json2cppJsonObjectMember 39 | * - json2cppJsonObjectMemberIterator 40 | * - json2cppJsonValue 41 | * 42 | * Due to the dependencies that exist between these classes, the ordering of 43 | * class declarations and definitions may be a bit confusing. The best place to 44 | * start is json2cppJsonAdapter. This class definition is actually very small, 45 | * since most of the functionality is inherited from the BasicAdapter class. 46 | * Most of the classes in this file are provided as template arguments to the 47 | * inherited BasicAdapter class. 48 | */ 49 | 50 | #pragma once 51 | 52 | #include "json2cpp.hpp" 53 | #include 54 | 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | 61 | namespace valijson { 62 | namespace adapters { 63 | 64 | class json2cppJsonAdapter; 65 | class json2cppJsonArrayValueIterator; 66 | class json2cppJsonObjectMemberIterator; 67 | 68 | typedef std::pair json2cppJsonObjectMember; 69 | 70 | /** 71 | * @brief Light weight wrapper for a json2cppJson array value. 72 | * 73 | * This class is light weight wrapper for a json2cppJson array. It provides a 74 | * minimum set of container functions and typedefs that allow it to be used as 75 | * an iterable container. 76 | * 77 | * An instance of this class contains a single reference to the underlying 78 | * json2cppJson value, assumed to be an array, so there is very little overhead 79 | * associated with copy construction and passing by value. 80 | */ 81 | class json2cppJsonArray 82 | { 83 | public: 84 | typedef json2cppJsonArrayValueIterator const_iterator; 85 | typedef json2cppJsonArrayValueIterator iterator; 86 | 87 | /// Construct a json2cppJsonArray referencing an empty array. 88 | json2cppJsonArray() : m_value(emptyArray()) {} 89 | 90 | /** 91 | * @brief Construct a json2cppJsonArray referencing a specific json2cppJson 92 | * value. 93 | * 94 | * @param value reference to a json2cppJson value 95 | * 96 | * Note that this constructor will throw an exception if the value is not 97 | * an array. 98 | */ 99 | explicit json2cppJsonArray(const json2cpp::json &value) : m_value(value) 100 | { 101 | if (!value.is_array()) { throwRuntimeError("Value is not an array."); } 102 | } 103 | 104 | /** 105 | * @brief Return an iterator for the first element of the array. 106 | * 107 | * The iterator return by this function is effectively the iterator 108 | * returned by the underlying json2cppJson implementation. 109 | */ 110 | json2cppJsonArrayValueIterator begin() const; 111 | 112 | /** 113 | * @brief Return an iterator for one-past the last element of the array. 114 | * 115 | * The iterator return by this function is effectively the iterator 116 | * returned by the underlying json2cppJson implementation. 117 | */ 118 | json2cppJsonArrayValueIterator end() const; 119 | 120 | /// Return the number of elements in the array 121 | size_t size() const { return m_value.size(); } 122 | 123 | private: 124 | /** 125 | * @brief Return a reference to a json2cppJson value that is an empty array. 126 | * 127 | * Note that the value returned by this function is a singleton. 128 | */ 129 | static const json2cpp::json &emptyArray() 130 | { 131 | static const json2cpp::json array = json2cpp::json::array(); 132 | return array; 133 | } 134 | 135 | /// Reference to the contained value 136 | const json2cpp::json &m_value; 137 | }; 138 | 139 | /** 140 | * @brief Light weight wrapper for a json2cppJson object. 141 | * 142 | * This class is light weight wrapper for a json2cppJson object. It provides a 143 | * minimum set of container functions and typedefs that allow it to be used as 144 | * an iterable container. 145 | * 146 | * An instance of this class contains a single reference to the underlying 147 | * json2cppJson value, assumed to be an object, so there is very little overhead 148 | * associated with copy construction and passing by value. 149 | */ 150 | class json2cppJsonObject 151 | { 152 | public: 153 | typedef json2cppJsonObjectMemberIterator const_iterator; 154 | typedef json2cppJsonObjectMemberIterator iterator; 155 | 156 | /// Construct a json2cppJsonObject referencing an empty object singleton. 157 | json2cppJsonObject() : m_value(emptyObject()) {} 158 | 159 | /** 160 | * @brief Construct a json2cppJsonObject referencing a specific json2cppJson 161 | * value. 162 | * 163 | * @param value reference to a json2cppJson value 164 | * 165 | * Note that this constructor will throw an exception if the value is not 166 | * an object. 167 | */ 168 | explicit json2cppJsonObject(const json2cpp::json &value) : m_value(value) 169 | { 170 | if (!value.is_object()) { throwRuntimeError("Value is not an object."); } 171 | } 172 | 173 | /** 174 | * @brief Return an iterator for this first object member 175 | * 176 | * The iterator return by this function is effectively a wrapper around 177 | * the iterator value returned by the underlying json2cppJson implementation. 178 | */ 179 | json2cppJsonObjectMemberIterator begin() const; 180 | 181 | /** 182 | * @brief Return an iterator for an invalid object member that indicates 183 | * the end of the collection. 184 | * 185 | * The iterator return by this function is effectively a wrapper around 186 | * the iterator value returned by the underlying json2cppJson implementation. 187 | */ 188 | json2cppJsonObjectMemberIterator end() const; 189 | 190 | /** 191 | * @brief Return an iterator for the object member with the specified 192 | * property name. 193 | * 194 | * If an object member with the specified name does not exist, the iterator 195 | * returned will be the same as the iterator returned by the end() function. 196 | * 197 | * @param propertyName property name to search for 198 | */ 199 | json2cppJsonObjectMemberIterator find(const std::string_view propertyName) const; 200 | 201 | /// Returns the number of members belonging to this object. 202 | size_t size() const { return m_value.size(); } 203 | 204 | private: 205 | /** 206 | * @brief Return a reference to a json2cppJson value that is empty object. 207 | * 208 | * Note that the value returned by this function is a singleton. 209 | */ 210 | static const json2cpp::json &emptyObject() 211 | { 212 | static const json2cpp::json object = json2cpp::json::object(); 213 | return object; 214 | } 215 | 216 | /// Reference to the contained object 217 | const json2cpp::json &m_value; 218 | }; 219 | 220 | 221 | /** 222 | * @brief Stores an independent copy of a json2cppJson value. 223 | * 224 | * This class allows a json2cppJson value to be stored independent of its original 225 | * document. json2cppJson makes this easy to do, as it does not perform any 226 | * custom memory management. 227 | * 228 | * @see FrozenValue 229 | */ 230 | class json2cppJsonFrozenValue : public FrozenValue 231 | { 232 | public: 233 | /** 234 | * @brief Make a copy of a json2cppJson value 235 | * 236 | * @param source the json2cppJson value to be copied 237 | */ 238 | explicit json2cppJsonFrozenValue(json2cpp::json source) : m_value(std::move(source)) {} 239 | 240 | FrozenValue *clone() const override { return new json2cppJsonFrozenValue(m_value); } 241 | 242 | bool equalTo(const Adapter &other, bool strict) const override; 243 | 244 | private: 245 | /// Stored json2cppJson value 246 | json2cpp::json m_value; 247 | }; 248 | 249 | 250 | /** 251 | * @brief Light weight wrapper for a json2cppJson value. 252 | * 253 | * This class is passed as an argument to the BasicAdapter template class, 254 | * and is used to provide access to a json2cppJson value. This class is responsible 255 | * for the mechanics of actually reading a json2cppJson value, whereas the 256 | * BasicAdapter class is responsible for the semantics of type comparisons 257 | * and conversions. 258 | * 259 | * The functions that need to be provided by this class are defined implicitly 260 | * by the implementation of the BasicAdapter template class. 261 | * 262 | * @see BasicAdapter 263 | */ 264 | class json2cppJsonValue 265 | { 266 | public: 267 | /// Construct a wrapper for the empty object singleton 268 | json2cppJsonValue() : m_value(emptyObject()) {} 269 | 270 | /// Construct a wrapper for a specific json2cppJson value 271 | explicit json2cppJsonValue(const json2cpp::json &value) : m_value(value) {} 272 | 273 | /** 274 | * @brief Create a new json2cppJsonFrozenValue instance that contains the 275 | * value referenced by this json2cppJsonValue instance. 276 | * 277 | * @returns pointer to a new json2cppJsonFrozenValue instance, belonging to the 278 | * caller. 279 | */ 280 | FrozenValue *freeze() const { return new json2cppJsonFrozenValue(m_value); } 281 | 282 | /** 283 | * @brief Optionally return a json2cppJsonArray instance. 284 | * 285 | * If the referenced json2cppJson value is an array, this function will return 286 | * a std::optional containing a json2cppJsonArray instance referencing the 287 | * array. 288 | * 289 | * Otherwise it will return an empty optional. 290 | */ 291 | opt::optional getArrayOptional() const 292 | { 293 | if (m_value.is_array()) { return opt::make_optional(json2cppJsonArray(m_value)); } 294 | 295 | return {}; 296 | } 297 | 298 | /** 299 | * @brief Retrieve the number of elements in the array 300 | * 301 | * If the referenced json2cppJson value is an array, this function will 302 | * retrieve the number of elements in the array and store it in the output 303 | * variable provided. 304 | * 305 | * @param result reference to size_t to set with result 306 | * 307 | * @returns true if the number of elements was retrieved, false otherwise. 308 | */ 309 | bool getArraySize(size_t &result) const 310 | { 311 | if (m_value.is_array()) { 312 | result = m_value.size(); 313 | return true; 314 | } 315 | 316 | return false; 317 | } 318 | 319 | bool getBool(bool &result) const 320 | { 321 | if (m_value.is_boolean()) { 322 | result = m_value.get(); 323 | return true; 324 | } 325 | 326 | return false; 327 | } 328 | 329 | bool getDouble(double &result) const 330 | { 331 | if (m_value.is_number_float()) { 332 | result = m_value.get(); 333 | return true; 334 | } 335 | 336 | return false; 337 | } 338 | 339 | bool getInteger(int64_t &result) const 340 | { 341 | if (m_value.is_number_integer()) { 342 | result = m_value.get(); 343 | return true; 344 | } 345 | return false; 346 | } 347 | 348 | /** 349 | * @brief Optionally return a json2cppJsonObject instance. 350 | * 351 | * If the referenced json2cppJson value is an object, this function will return a 352 | * std::optional containing a json2cppJsonObject instance referencing the 353 | * object. 354 | * 355 | * Otherwise it will return an empty optional. 356 | */ 357 | opt::optional getObjectOptional() const 358 | { 359 | if (m_value.is_object()) { return opt::make_optional(json2cppJsonObject(m_value)); } 360 | 361 | opt::optional emptyreturn{}; 362 | return emptyreturn; 363 | } 364 | 365 | /** 366 | * @brief Retrieve the number of members in the object 367 | * 368 | * If the referenced json2cppJson value is an object, this function will 369 | * retrieve the number of members in the object and store it in the output 370 | * variable provided. 371 | * 372 | * @param result reference to size_t to set with result 373 | * 374 | * @returns true if the number of members was retrieved, false otherwise. 375 | */ 376 | bool getObjectSize(size_t &result) const 377 | { 378 | if (m_value.is_object()) { 379 | result = m_value.size(); 380 | return true; 381 | } 382 | 383 | return false; 384 | } 385 | 386 | bool getString(std::string &result) const 387 | { 388 | if (m_value.is_string()) { 389 | result = m_value.get(); 390 | return true; 391 | } 392 | 393 | return false; 394 | } 395 | 396 | static bool hasStrictTypes() { return true; } 397 | 398 | bool isArray() const { return m_value.is_array(); } 399 | 400 | bool isBool() const { return m_value.is_boolean(); } 401 | 402 | bool isDouble() const { return m_value.is_number_float(); } 403 | 404 | bool isInteger() const { return m_value.is_number_integer(); } 405 | 406 | bool isNull() const { return m_value.is_null(); } 407 | 408 | bool isNumber() const { return m_value.is_number(); } 409 | 410 | bool isObject() const { return m_value.is_object(); } 411 | 412 | bool isString() const { return m_value.is_string(); } 413 | 414 | private: 415 | /// Return a reference to an empty object singleton 416 | static const json2cpp::json &emptyObject() 417 | { 418 | static const json2cpp::json object = json2cpp::json::object(); 419 | return object; 420 | } 421 | 422 | /// Reference to the contained json2cppJson value. 423 | const json2cpp::json &m_value; 424 | }; 425 | 426 | /** 427 | * @brief An implementation of the Adapter interface supporting json2cppJson. 428 | * 429 | * This class is defined in terms of the BasicAdapter template class, which 430 | * helps to ensure that all of the Adapter implementations behave consistently. 431 | * 432 | * @see Adapter 433 | * @see BasicAdapter 434 | */ 435 | class json2cppJsonAdapter 436 | : public BasicAdapter 441 | { 442 | public: 443 | /// Construct a json2cppJsonAdapter that contains an empty object 444 | json2cppJsonAdapter() : BasicAdapter() {} 445 | 446 | /// Construct a json2cppJsonAdapter containing a specific json2cpp Json object 447 | explicit json2cppJsonAdapter(const json2cpp::json &value) : BasicAdapter(json2cppJsonValue{ value }) {} 448 | }; 449 | 450 | /** 451 | * @brief Class for iterating over values held in a JSON array. 452 | * 453 | * This class provides a JSON array iterator that dereferences as an instance of 454 | * json2cppJsonAdapter representing a value stored in the array. It has been 455 | * implemented using the boost iterator_facade template. 456 | * 457 | * @see json2cppJsonArray 458 | */ 459 | class json2cppJsonArrayValueIterator 460 | { 461 | public: 462 | using iterator_category = std::bidirectional_iterator_tag; 463 | using value_type = json2cppJsonAdapter; 464 | using difference_type = json2cppJsonAdapter; 465 | using pointer = json2cppJsonAdapter *; 466 | using reference = json2cppJsonAdapter &; 467 | 468 | /** 469 | * @brief Construct a new json2cppJsonArrayValueIterator using an existing 470 | * json2cppJson iterator. 471 | * 472 | * @param itr json2cppJson iterator to store 473 | */ 474 | explicit json2cppJsonArrayValueIterator(const json2cpp::json::const_iterator &itr) : m_itr(itr) {} 475 | 476 | /// Returns a json2cppJsonAdapter that contains the value of the current 477 | /// element. 478 | json2cppJsonAdapter operator*() const { return json2cppJsonAdapter(*m_itr); } 479 | 480 | DerefProxy operator->() const { return DerefProxy(**this); } 481 | 482 | /** 483 | * @brief Compare this iterator against another iterator. 484 | * 485 | * Note that this directly compares the iterators, not the underlying 486 | * values, and assumes that two identical iterators will point to the same 487 | * underlying object. 488 | * 489 | * @param other iterator to compare against 490 | * 491 | * @returns true if the iterators are equal, false otherwise. 492 | */ 493 | bool operator==(const json2cppJsonArrayValueIterator &other) const { return m_itr == other.m_itr; } 494 | 495 | bool operator!=(const json2cppJsonArrayValueIterator &other) const { return !(m_itr == other.m_itr); } 496 | 497 | // cppcheck-suppress functionConst 498 | const json2cppJsonArrayValueIterator &operator++() 499 | { 500 | ++m_itr; 501 | 502 | return *this; 503 | } 504 | 505 | // cppcheck-suppress functionConst 506 | json2cppJsonArrayValueIterator operator++(int) 507 | { 508 | json2cppJsonArrayValueIterator iterator_pre(m_itr); 509 | ++(*this); 510 | return iterator_pre; 511 | } 512 | 513 | // cppcheck-suppress functionConst 514 | const json2cppJsonArrayValueIterator &operator--() 515 | { 516 | --m_itr; 517 | 518 | return *this; 519 | } 520 | 521 | // cppcheck-suppress functionStatic 522 | void advance(std::ptrdiff_t n) { m_itr += n; } 523 | 524 | private: 525 | json2cpp::json::const_iterator m_itr; 526 | }; 527 | 528 | 529 | /** 530 | * @brief Class for iterating over the members belonging to a JSON object. 531 | * 532 | * This class provides a JSON object iterator that dereferences as an instance 533 | * of json2cppJsonObjectMember representing one of the members of the object. It 534 | * has been implemented using the boost iterator_facade template. 535 | * 536 | * @see json2cppJsonObject 537 | * @see json2cppJsonObjectMember 538 | */ 539 | class json2cppJsonObjectMemberIterator 540 | { 541 | public: 542 | using iterator_category = std::bidirectional_iterator_tag; 543 | using value_type = json2cppJsonObjectMember; 544 | using difference_type = json2cppJsonObjectMember; 545 | using pointer = json2cppJsonObjectMember *; 546 | using reference = json2cppJsonObjectMember &; 547 | 548 | /** 549 | * @brief Construct an iterator from a json2cppJson iterator. 550 | * 551 | * @param itr json2cppJson iterator to store 552 | */ 553 | explicit json2cppJsonObjectMemberIterator(const json2cpp::json::const_iterator &itr) : m_itr(itr) {} 554 | 555 | /** 556 | * @brief Returns a json2cppJsonObjectMember that contains the key and value 557 | * belonging to the object member identified by the iterator. 558 | */ 559 | json2cppJsonObjectMember operator*() const { return json2cppJsonObjectMember(m_itr.key(), m_itr.value()); } 560 | 561 | DerefProxy operator->() const { return DerefProxy(**this); } 562 | 563 | /** 564 | * @brief Compare this iterator with another iterator. 565 | * 566 | * Note that this directly compares the iterators, not the underlying 567 | * values, and assumes that two identical iterators will point to the same 568 | * underlying object. 569 | * 570 | * @param other Iterator to compare with 571 | * 572 | * @returns true if the underlying iterators are equal, false otherwise 573 | */ 574 | bool operator==(const json2cppJsonObjectMemberIterator &other) const { return m_itr == other.m_itr; } 575 | 576 | bool operator!=(const json2cppJsonObjectMemberIterator &other) const { return !(m_itr == other.m_itr); } 577 | 578 | // cppcheck-suppress functionConst 579 | const json2cppJsonObjectMemberIterator &operator++() 580 | { 581 | ++m_itr; 582 | 583 | return *this; 584 | } 585 | 586 | // cppcheck-suppress functionConst 587 | json2cppJsonObjectMemberIterator operator++(int) 588 | { 589 | json2cppJsonObjectMemberIterator iterator_pre(m_itr); 590 | ++(*this); 591 | return iterator_pre; 592 | } 593 | 594 | // cppcheck-suppress functionConst 595 | const json2cppJsonObjectMemberIterator &operator--() 596 | { 597 | --m_itr; 598 | 599 | return *this; 600 | } 601 | 602 | private: 603 | /// Iternal copy of the original json2cppJson iterator 604 | json2cpp::json::const_iterator m_itr; 605 | }; 606 | 607 | /// Specialisation of the AdapterTraits template struct for json2cppJsonAdapter. 608 | template<> struct AdapterTraits 609 | { 610 | typedef json2cpp::json DocumentType; 611 | 612 | static std::string adapterName() { return "json2cppJsonAdapter"; } 613 | }; 614 | 615 | inline bool json2cppJsonFrozenValue::equalTo(const Adapter &other, bool strict) const 616 | { 617 | return json2cppJsonAdapter(m_value).equalTo(other, strict); 618 | } 619 | 620 | inline json2cppJsonArrayValueIterator json2cppJsonArray::begin() const 621 | { 622 | return json2cppJsonArrayValueIterator{ m_value.begin() }; 623 | } 624 | 625 | inline json2cppJsonArrayValueIterator json2cppJsonArray::end() const 626 | { 627 | return json2cppJsonArrayValueIterator{ m_value.end() }; 628 | } 629 | 630 | inline json2cppJsonObjectMemberIterator json2cppJsonObject::begin() const 631 | { 632 | return json2cppJsonObjectMemberIterator{ m_value.begin() }; 633 | } 634 | 635 | inline json2cppJsonObjectMemberIterator json2cppJsonObject::end() const 636 | { 637 | return json2cppJsonObjectMemberIterator{ m_value.end() }; 638 | } 639 | 640 | inline json2cppJsonObjectMemberIterator json2cppJsonObject::find(const std::string_view propertyName) const 641 | { 642 | return json2cppJsonObjectMemberIterator{ m_value.find(propertyName) }; 643 | } 644 | 645 | }// namespace adapters 646 | }// namespace valijson 647 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Generic test that uses conan libs 2 | add_executable(json2cpp main.cpp json2cpp.cpp) 3 | add_executable(json2cpp::json2cpp ALIAS json2cpp) 4 | target_link_libraries(json2cpp PRIVATE json2cpp_options json2cpp_warnings) 5 | 6 | target_link_system_libraries( 7 | json2cpp 8 | PRIVATE 9 | CLI11::CLI11 10 | fmt::fmt 11 | spdlog::spdlog 12 | nlohmann_json::nlohmann_json) 13 | 14 | install(TARGETS json2cpp) 15 | install(DIRECTORY ../include DESTINATION .) 16 | 17 | if(json2cpp_ENABLE_LARGE_TESTS) 18 | set(BASE_NAME "${CMAKE_CURRENT_BINARY_DIR}/schema") 19 | add_custom_command( 20 | DEPENDS json2cpp 21 | OUTPUT "${BASE_NAME}_impl.hpp" "${BASE_NAME}.hpp" "${BASE_NAME}.cpp" 22 | COMMAND json2cpp "energyplus_schema" "${CMAKE_SOURCE_DIR}/examples/Energy+.schema.epJSON" "${BASE_NAME}" 23 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") 24 | 25 | add_executable(schema_validator schema_validator.cpp "${BASE_NAME}.cpp") 26 | add_executable(json2cpp::schema_validator ALIAS schema_validator) 27 | target_link_libraries(schema_validator PRIVATE json2cpp_options json2cpp_warnings) 28 | target_link_system_libraries( 29 | schema_validator 30 | PRIVATE 31 | CLI11::CLI11 32 | fmt::fmt 33 | spdlog::spdlog 34 | ValiJSON::valijson 35 | nlohmann_json::nlohmann_json) 36 | 37 | target_include_directories(schema_validator PRIVATE "${CMAKE_SOURCE_DIR}/include") 38 | target_include_directories(schema_validator PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") 39 | 40 | if(MSVC) 41 | target_compile_options(schema_validator PRIVATE "/bigobj") 42 | endif() 43 | 44 | # disable analysis for these very large generated bits of code 45 | set_target_properties(schema_validator PROPERTIES CXX_CPPCHECK "" CXX_CLANG_TIDY "") 46 | endif() 47 | -------------------------------------------------------------------------------- /src/json2cpp.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2022 Jason Turner 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | 26 | #include "json2cpp.hpp" 27 | #include 28 | 29 | std::string compile(const nlohmann::json &value, std::size_t &obj_count, std::vector &lines) 30 | { 31 | const auto current_object_number = obj_count++; 32 | 33 | const auto json_string = [](const auto &str) { return fmt::format("R\"string({})string\"", str); }; 34 | 35 | if (value.is_object()) { 36 | std::vector pairs; 37 | for (auto itr = value.begin(); itr != value.end(); ++itr) { 38 | pairs.push_back( 39 | fmt::format("value_pair_t{{{}, {{{}}}}},", json_string(itr.key()), compile(*itr, obj_count, lines))); 40 | } 41 | 42 | lines.push_back(fmt::format( 43 | "inline constexpr std::array object_data_{} = {{", pairs.size(), current_object_number)); 44 | 45 | std::transform(pairs.begin(), pairs.end(), std::back_inserter(lines), [](const auto &pair) { 46 | return fmt::format(" {}", pair); 47 | }); 48 | 49 | lines.emplace_back("};"); 50 | 51 | return fmt::format("object_t{{object_data_{}}}", current_object_number); 52 | } else if (value.is_array()) { 53 | std::vector entries; 54 | std::transform(value.begin(), value.end(), std::back_inserter(entries), [&](const auto &child) { 55 | return fmt::format("{{{}}},", compile(child, obj_count, lines)); 56 | }); 57 | 58 | 59 | lines.push_back(fmt::format( 60 | "inline constexpr std::array object_data_{} = {{{{", entries.size(), current_object_number)); 61 | 62 | std::transform(entries.begin(), entries.end(), std::back_inserter(lines), [](const auto &entry) { 63 | return fmt::format(" {}", entry); 64 | }); 65 | 66 | lines.emplace_back("}};"); 67 | 68 | return fmt::format("array_t{{object_data_{}}}", current_object_number); 69 | 70 | 71 | } else if (value.is_number_float()) { 72 | return fmt::format("double{{{}}}", value.get()); 73 | } else if (value.is_number_unsigned()) { 74 | return fmt::format("std::uint64_t{{{}}}", value.get()); 75 | } else if (value.is_number() && !value.is_number_unsigned()) { 76 | return fmt::format("std::int64_t{{{}}}", value.get()); 77 | } else if (value.is_boolean()) { 78 | return fmt::format("bool{{{}}}", value.get()); 79 | } else if (value.is_string()) { 80 | return fmt::format("string_view{{{}}}", json_string(value.get())); 81 | } else if (value.is_null()) { 82 | return "std::nullptr_t{}"; 83 | } 84 | 85 | return "unhandled"; 86 | } 87 | 88 | compile_results compile(const std::string_view document_name, const nlohmann::json &json) 89 | { 90 | 91 | std::size_t obj_count{ 0 }; 92 | 93 | compile_results results; 94 | 95 | results.hpp.push_back(fmt::format("#ifndef {}_COMPILED_JSON", document_name)); 96 | results.hpp.push_back(fmt::format("#define {}_COMPILED_JSON", document_name)); 97 | 98 | results.hpp.emplace_back("#include "); 99 | 100 | results.hpp.push_back(fmt::format("namespace compiled_json::{} {{", document_name)); 101 | results.hpp.push_back(fmt::format(" const json2cpp::json &get();", document_name)); 102 | results.hpp.emplace_back("}"); 103 | 104 | results.hpp.emplace_back("#endif"); 105 | 106 | 107 | results.impl.push_back(fmt::format( 108 | "// Just in case the user wants to use the entire document in a constexpr context, it can be included safely")); 109 | results.impl.push_back(fmt::format("#ifndef {}_COMPILED_JSON_IMPL", document_name)); 110 | results.impl.push_back(fmt::format("#define {}_COMPILED_JSON_IMPL", document_name)); 111 | 112 | results.impl.emplace_back("#include "); 113 | 114 | results.impl.push_back(fmt::format(R"( 115 | namespace compiled_json::{}::impl {{ 116 | 117 | using json = json2cpp::basic_json; 118 | using data_t=json2cpp::data_variant; 119 | using string_view=std::basic_string_view; 120 | using array_t=json2cpp::basic_array_t; 121 | using object_t=json2cpp::basic_object_t; 122 | using value_pair_t=json2cpp::basic_value_pair_t; 123 | 124 | )", 125 | document_name)); 126 | 127 | 128 | const auto last_obj_name = compile(json, obj_count, results.impl); 129 | 130 | results.impl.push_back(fmt::format(R"( 131 | inline constexpr auto document = json{{{{{}}}}}; 132 | 133 | 134 | }} 135 | 136 | #endif 137 | 138 | )", 139 | last_obj_name)); 140 | 141 | 142 | spdlog::info("{} JSON objects processed.", obj_count); 143 | 144 | return results; 145 | } 146 | 147 | 148 | compile_results compile(const std::string_view document_name, const std::filesystem::path &filename) 149 | { 150 | spdlog::info("Loading file: '{}'", filename.string()); 151 | 152 | std::ifstream input(filename); 153 | nlohmann::json document; 154 | input >> document; 155 | 156 | spdlog::info("File loaded"); 157 | 158 | return compile(document_name, document); 159 | } 160 | 161 | void write_compilation([[maybe_unused]] std::string_view document_name, 162 | const compile_results &results, 163 | const std::filesystem::path &base_output) 164 | { 165 | 166 | const auto append_extension = [](std::filesystem::path name, std::string_view ext) { return name += ext; }; 167 | 168 | 169 | const auto hpp_name = append_extension(base_output, ".hpp"); 170 | const auto cpp_name = append_extension(base_output, ".cpp"); 171 | const auto impl_name = append_extension(base_output, "_impl.hpp"); 172 | 173 | std::ofstream hpp(hpp_name); 174 | for (const auto &line : results.hpp) { hpp << line << '\n'; } 175 | 176 | std::ofstream impl(impl_name); 177 | for (const auto &line : results.impl) { impl << line << '\n'; } 178 | 179 | std::ofstream cpp(cpp_name); 180 | cpp << fmt::format("#include \"{}\"\n", impl_name.filename().string()); 181 | cpp << fmt::format( 182 | "namespace compiled_json::{} {{\nconst json2cpp::json &get() {{ return compiled_json::{}::impl::document; }}\n}}\n", 183 | document_name, 184 | document_name); 185 | } 186 | 187 | void compile_to(const std::string_view document_name, 188 | const nlohmann::json &json, 189 | const std::filesystem::path &base_output) 190 | { 191 | write_compilation(document_name, compile(document_name, json), base_output); 192 | } 193 | 194 | 195 | void compile_to(const std::string_view document_name, 196 | const std::filesystem::path &filename, 197 | const std::filesystem::path &base_output) 198 | { 199 | write_compilation(document_name, compile(document_name, filename), base_output); 200 | } 201 | -------------------------------------------------------------------------------- /src/json2cpp.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2022 Jason Turner 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | #ifndef JSON2CPP_HPP 26 | #define JSON2CPP_HPP 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | struct compile_results 35 | { 36 | std::vector hpp; 37 | std::vector impl; 38 | }; 39 | 40 | 41 | std::string compile(const nlohmann::json &value, std::size_t &obj_count, std::vector &lines); 42 | 43 | 44 | compile_results compile(const std::string_view document_name, const nlohmann::json &json); 45 | 46 | compile_results compile(const std::string_view document_name, const std::filesystem::path &filename); 47 | 48 | 49 | void write_compilation(std::string_view document_name, 50 | const compile_results &results, 51 | const std::filesystem::path &base_output); 52 | 53 | 54 | void compile_to(const std::string_view document_name, 55 | const nlohmann::json &json, 56 | const std::filesystem::path &base_output); 57 | 58 | void compile_to(const std::string_view document_name, 59 | const std::filesystem::path &filename, 60 | const std::filesystem::path &base_output); 61 | 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2022 Jason Turner 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include "json2cpp.hpp" 31 | #include 32 | #include 33 | 34 | 35 | int main(int argc, const char **argv) 36 | { 37 | try { 38 | CLI::App app("json2cpp version 0.0.1"); 39 | 40 | std::string document_name; 41 | std::filesystem::path input_file_name; 42 | std::filesystem::path output_base_name; 43 | 44 | bool show_version = false; 45 | app.add_flag("--version", show_version, "Show version information"); 46 | app.add_option("", document_name); 47 | app.add_option("", input_file_name); 48 | app.add_option("", output_base_name); 49 | CLI11_PARSE(app, argc, argv); 50 | 51 | compile_to(document_name, input_file_name, output_base_name); 52 | } catch (const std::exception &e) { 53 | spdlog::error("Unhandled exception in main: {}", e.what()); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/schema_validator.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2022 Jason Turner 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | #ifdef __GNUC__ 28 | #pragma GCC diagnostic push 29 | #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" 30 | #endif 31 | #include 32 | #ifdef __GNUC__ 33 | #pragma GCC diagnostic pop 34 | #endif 35 | #include 36 | 37 | #include 38 | #include 39 | 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | 46 | #include "schema.hpp" 47 | 48 | 49 | bool validate(const std::filesystem::path &schema_file_name, const std::filesystem::path &file_to_validate) 50 | { 51 | using valijson::Schema; 52 | using valijson::SchemaParser; 53 | using valijson::Validator; 54 | using valijson::adapters::json2cppJsonAdapter; 55 | using valijson::adapters::NlohmannJsonAdapter; 56 | 57 | // Parse JSON schema content using valijson 58 | spdlog::info("Creating Schema object"); 59 | Schema mySchema; 60 | spdlog::info("Creating SchemaParser object"); 61 | SchemaParser parser; 62 | 63 | 64 | spdlog::info("Creating nlohmann::json object"); 65 | nlohmann::json schema; 66 | spdlog::info("Opening json file"); 67 | std::ifstream schema_file(schema_file_name); 68 | spdlog::info("Loading json file"); 69 | schema_file >> schema; 70 | spdlog::info("Creating NlohmannJsonAdapter object"); 71 | NlohmannJsonAdapter mySchemaAdapter(schema); 72 | spdlog::info("parser.populateSchema object"); 73 | parser.populateSchema(mySchemaAdapter, mySchema); 74 | 75 | spdlog::info("Creating Validator object"); 76 | Validator validator; 77 | spdlog::info("Creating nlohmann::json object"); 78 | nlohmann::json document; 79 | spdlog::info("Opening json file"); 80 | std::ifstream input_file(file_to_validate); 81 | spdlog::info("Loading json file"); 82 | input_file >> document; 83 | spdlog::info("Creating NlohmannJsonAdapter object"); 84 | NlohmannJsonAdapter myTargetAdapter(document); 85 | 86 | spdlog::info("validator.validate"); 87 | const auto result = validator.validate(mySchema, myTargetAdapter, nullptr); 88 | spdlog::info("returning result {}", result); 89 | 90 | return result; 91 | } 92 | 93 | bool validate_internal(const std::filesystem::path &file_to_validate) 94 | { 95 | using valijson::Schema; 96 | using valijson::SchemaParser; 97 | using valijson::Validator; 98 | using valijson::adapters::json2cppJsonAdapter; 99 | using valijson::adapters::NlohmannJsonAdapter; 100 | 101 | // Parse JSON schema content using valijson 102 | spdlog::info("Creating Schema object"); 103 | Schema mySchema; 104 | spdlog::info("Creating SchemaParser object"); 105 | SchemaParser parser; 106 | spdlog::info("Creating json2cppJsonAdapter object"); 107 | json2cppJsonAdapter mySchemaAdapter(compiled_json::energyplus_schema::get()); 108 | spdlog::info("parser.populateSchema object"); 109 | parser.populateSchema(mySchemaAdapter, mySchema); 110 | 111 | spdlog::info("Creating Validator object"); 112 | Validator validator; 113 | spdlog::info("Creating nlohmann::json object"); 114 | nlohmann::json document; 115 | spdlog::info("Opening json file"); 116 | std::ifstream input_file(file_to_validate); 117 | spdlog::info("Loading json file"); 118 | input_file >> document; 119 | spdlog::info("Creating NlohmannJsonAdapter object"); 120 | NlohmannJsonAdapter myTargetAdapter(document); 121 | 122 | spdlog::info("validator.validate"); 123 | const auto result = validator.validate(mySchema, myTargetAdapter, nullptr); 124 | spdlog::info("returning result {}", result); 125 | 126 | return result; 127 | } 128 | 129 | template 130 | void walk_internal(std::int64_t &int_sum, 131 | double &double_sum, 132 | std::size_t &string_sizes, 133 | int &null_count, 134 | int &array_count, 135 | int &object_count, 136 | const JSON &obj) 137 | { 138 | if (obj.is_number_integer()) { 139 | int_sum += obj.template get(); 140 | } else if (obj.is_number_float()) { 141 | double_sum += obj.template get(); 142 | } else if (obj.is_string()) { 143 | string_sizes += obj.template get().size(); 144 | } else if (obj.is_null()) { 145 | ++null_count; 146 | } else if (obj.is_array()) { 147 | ++array_count; 148 | for (const auto &child : obj) { 149 | walk_internal(int_sum, double_sum, string_sizes, null_count, array_count, object_count, child); 150 | } 151 | } else if (obj.is_object()) { 152 | ++object_count; 153 | for (const auto &child : obj) { 154 | walk_internal(int_sum, double_sum, string_sizes, null_count, array_count, object_count, child); 155 | } 156 | } 157 | } 158 | 159 | template void walk(const JSON &objects) 160 | { 161 | std::int64_t int_sum{}; 162 | double double_sum{}; 163 | std::size_t string_sizes{}; 164 | int null_count{}; 165 | int array_count{}; 166 | int object_count{}; 167 | 168 | spdlog::info("Starting tree walk"); 169 | 170 | walk_internal(int_sum, double_sum, string_sizes, null_count, array_count, object_count, objects); 171 | 172 | spdlog::info("{} {} {} {} {} {}", int_sum, double_sum, string_sizes, null_count, array_count, object_count); 173 | } 174 | 175 | int main(int argc, const char **argv) 176 | { 177 | try { 178 | CLI::App app("schema_validator version 0.0.1"); 179 | 180 | std::string document_name; 181 | std::filesystem::path schema_file_name; 182 | std::filesystem::path document_to_validate; 183 | 184 | bool do_walk = false; 185 | bool internal = false; 186 | bool show_version = false; 187 | app.add_option("", schema_file_name); 188 | auto *doc = app.add_option("", document_to_validate); 189 | app.add_flag("--version", show_version, "Show version information"); 190 | app.add_flag("--walk", do_walk, "Just walk the schema and count objects (perf test)")->excludes(doc); 191 | app.add_flag("--internal", internal, "Use internal schema"); 192 | 193 | CLI11_PARSE(app, argc, argv); 194 | 195 | if (do_walk) { 196 | if (internal) { 197 | walk(compiled_json::energyplus_schema::get()); 198 | } else { 199 | spdlog::info("Creating nlohmann::json object"); 200 | nlohmann::json schema; 201 | spdlog::info("Opening json file"); 202 | std::ifstream schema_file(schema_file_name); 203 | spdlog::info("Loading json file"); 204 | schema_file >> schema; 205 | walk(schema); 206 | } 207 | return EXIT_SUCCESS; 208 | } 209 | 210 | if (internal) { 211 | validate_internal(document_to_validate); 212 | } else { 213 | validate(schema_file_name, document_to_validate); 214 | } 215 | 216 | } catch (const std::exception &e) { 217 | spdlog::error("Unhandled exception in main: {}", e.what()); 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15...3.23) 2 | 3 | project(CmakeConfigPackageTests LANGUAGES CXX) 4 | 5 | # ---- Test as standalone project the exported config package ---- 6 | 7 | if(PROJECT_IS_TOP_LEVEL OR TEST_INSTALLED_VERSION) 8 | enable_testing() 9 | 10 | find_package(myproject CONFIG REQUIRED) # for intro, json2cpp_options, ... 11 | 12 | if(NOT TARGET myjson2cpp_options) 13 | message(FATAL_ERROR "Requiered config package not found!") 14 | return() # be strictly paranoid for Template Janitor github action! CK 15 | endif() 16 | endif() 17 | 18 | # ---- Dependencies ---- 19 | 20 | include(${Catch2_SOURCE_DIR}/extras/Catch.cmake) 21 | 22 | 23 | set(BASE_NAME "${CMAKE_CURRENT_BINARY_DIR}/test_json") 24 | add_custom_command( 25 | DEPENDS json2cpp 26 | OUTPUT "${BASE_NAME}_impl.hpp" "${BASE_NAME}.hpp" "${BASE_NAME}.cpp" 27 | COMMAND json2cpp "test_json" "${CMAKE_SOURCE_DIR}/examples/test.json" "${BASE_NAME}" 28 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") 29 | 30 | add_executable(tests tests.cpp "${BASE_NAME}.cpp") 31 | target_include_directories(tests PRIVATE "${CMAKE_SOURCE_DIR}/include") 32 | target_include_directories(tests PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") 33 | 34 | target_link_libraries(tests PRIVATE json2cpp_warnings json2cpp_options Catch2::Catch2WithMain) 35 | 36 | # automatically discover tests that are defined in catch based test files you can modify the unittests. Set TEST_PREFIX 37 | # to whatever you want, or use different for different binaries 38 | catch_discover_tests( 39 | tests 40 | TEST_PREFIX 41 | "unittests." 42 | REPORTER 43 | XML 44 | OUTPUT_DIR 45 | . 46 | OUTPUT_PREFIX 47 | "unittests." 48 | OUTPUT_SUFFIX 49 | .xml) 50 | 51 | set(SCHEMA_BASE_NAME "${CMAKE_CURRENT_BINARY_DIR}/allof_integers_and_numbers.schema") 52 | add_custom_command( 53 | DEPENDS json2cpp 54 | OUTPUT "${SCHEMA_BASE_NAME}_impl.hpp" "${SCHEMA_BASE_NAME}.hpp" "${SCHEMA_BASE_NAME}.cpp" 55 | COMMAND json2cpp "allof_integers_and_numbers_schema" 56 | "${CMAKE_SOURCE_DIR}/examples/allof_integers_and_numbers.schema.json" "${SCHEMA_BASE_NAME}" 57 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") 58 | 59 | set(INT_BASE_NAME "${CMAKE_CURRENT_BINARY_DIR}/array_integers_10_20_30_40") 60 | add_custom_command( 61 | DEPENDS json2cpp 62 | OUTPUT "${INT_BASE_NAME}_impl.hpp" "${INT_BASE_NAME}.hpp" "${INT_BASE_NAME}.cpp" 63 | COMMAND json2cpp "array_integers_10_20_30_40" "${CMAKE_SOURCE_DIR}/examples/array_integers_10_20_30_40.json" 64 | "${INT_BASE_NAME}" 65 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") 66 | 67 | set(DOUBLE_BASE_NAME "${CMAKE_CURRENT_BINARY_DIR}/array_doubles_10_20_30_40") 68 | add_custom_command( 69 | DEPENDS json2cpp 70 | OUTPUT "${DOUBLE_BASE_NAME}_impl.hpp" "${DOUBLE_BASE_NAME}.hpp" "${DOUBLE_BASE_NAME}.cpp" 71 | COMMAND json2cpp "array_doubles_10_20_30_40" "${CMAKE_SOURCE_DIR}/examples/array_doubles_10_20_30_40.json" 72 | "${DOUBLE_BASE_NAME}" 73 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") 74 | 75 | add_executable( 76 | valijson_tests 77 | valijson_tests.cpp 78 | ${SCHEMA_BASE_NAME}.cpp 79 | ${DOUBLE_BASE_NAME}.cpp 80 | ${INT_BASE_NAME}.cpp) 81 | target_include_directories(valijson_tests PRIVATE "${CMAKE_SOURCE_DIR}/include") 82 | target_include_directories(valijson_tests PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") 83 | 84 | target_link_libraries( 85 | valijson_tests 86 | PRIVATE json2cpp_warnings 87 | json2cpp_options) 88 | 89 | target_link_system_libraries( 90 | valijson_tests 91 | PRIVATE Catch2::Catch2WithMain 92 | ValiJSON::valijson) 93 | 94 | # automatically discover tests that are defined in catch based test files you can modify the unittests. Set TEST_PREFIX 95 | # to whatever you want, or use different for different binaries 96 | catch_discover_tests( 97 | valijson_tests 98 | TEST_PREFIX 99 | "unittests." 100 | REPORTER 101 | XML 102 | OUTPUT_DIR 103 | . 104 | OUTPUT_PREFIX 105 | "unittests." 106 | OUTPUT_SUFFIX 107 | .xml) 108 | 109 | # Add a file containing a set of constexpr tests 110 | add_executable(constexpr_tests constexpr_tests.cpp "${BASE_NAME}_impl.hpp") 111 | target_link_libraries(constexpr_tests PRIVATE json2cpp_options json2cpp_warnings Catch2::Catch2WithMain) 112 | 113 | target_include_directories(constexpr_tests PRIVATE "${CMAKE_SOURCE_DIR}/include") 114 | target_include_directories(constexpr_tests PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") 115 | 116 | catch_discover_tests( 117 | constexpr_tests 118 | TEST_PREFIX 119 | "constexpr." 120 | REPORTER 121 | XML 122 | OUTPUT_DIR 123 | . 124 | OUTPUT_PREFIX 125 | "constexpr." 126 | OUTPUT_SUFFIX 127 | .xml) 128 | 129 | # Disable the constexpr portion of the test, and build again this allows us to have an executable that we can debug when 130 | # things go wrong with the constexpr testing 131 | add_executable(relaxed_constexpr_tests constexpr_tests.cpp "${BASE_NAME}_impl.hpp") 132 | target_link_libraries(relaxed_constexpr_tests PRIVATE json2cpp_options json2cpp_warnings Catch2::Catch2WithMain) 133 | target_compile_definitions(relaxed_constexpr_tests PRIVATE -DCATCH_CONFIG_RUNTIME_STATIC_REQUIRE) 134 | target_include_directories(relaxed_constexpr_tests PRIVATE "${CMAKE_SOURCE_DIR}/include") 135 | target_include_directories(relaxed_constexpr_tests PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") 136 | 137 | catch_discover_tests( 138 | relaxed_constexpr_tests 139 | TEST_PREFIX 140 | "relaxed_constexpr." 141 | REPORTER 142 | XML 143 | OUTPUT_DIR 144 | . 145 | OUTPUT_PREFIX 146 | "relaxed_constexpr." 147 | OUTPUT_SUFFIX 148 | .xml) 149 | 150 | if(json2cpp_ENABLE_LARGE_TESTS) 151 | set(BASE_NAME "${CMAKE_CURRENT_BINARY_DIR}/schema") 152 | add_custom_command( 153 | DEPENDS json2cpp 154 | OUTPUT "${BASE_NAME}_impl.hpp" "${BASE_NAME}.hpp" "${BASE_NAME}.cpp" 155 | COMMAND json2cpp "schema" "${CMAKE_SOURCE_DIR}/examples/Energy+.schema.epJSON" "${BASE_NAME}" 156 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") 157 | 158 | # Add a file containing a set of constexpr_schema tests 159 | add_executable(constexpr_schema_tests constexpr_schema_tests.cpp "${BASE_NAME}_impl.hpp") 160 | target_link_libraries(constexpr_schema_tests PRIVATE json2cpp_options json2cpp_warnings Catch2::Catch2WithMain) 161 | target_include_directories(constexpr_schema_tests PRIVATE "${CMAKE_SOURCE_DIR}/include") 162 | target_include_directories(constexpr_schema_tests PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") 163 | 164 | if(MSVC) 165 | target_compile_options(constexpr_schema_tests PRIVATE "/bigobj") 166 | endif() 167 | 168 | # disable analysis for these very large generated bits of code 169 | set_target_properties(constexpr_schema_tests PROPERTIES CXX_CPPCHECK "" CXX_CLANG_TIDY "") 170 | 171 | catch_discover_tests( 172 | constexpr_schema_tests 173 | TEST_PREFIX 174 | "constexpr_schema." 175 | REPORTER 176 | XML 177 | OUTPUT_DIR 178 | . 179 | OUTPUT_PREFIX 180 | "constexpr_schema." 181 | OUTPUT_SUFFIX 182 | .xml) 183 | 184 | # Disable the constexpr_schema portion of the test, and build again this allows us to have an executable that we can debug when 185 | # things go wrong with the constexpr_schema testing 186 | add_executable(relaxed_constexpr_schema_tests constexpr_schema_tests.cpp "${BASE_NAME}_impl.hpp") 187 | target_link_libraries(relaxed_constexpr_schema_tests PRIVATE json2cpp_options json2cpp_warnings Catch2::Catch2WithMain) 188 | target_compile_definitions(relaxed_constexpr_schema_tests PRIVATE -DCATCH_CONFIG_RUNTIME_STATIC_REQUIRE) 189 | target_include_directories(relaxed_constexpr_schema_tests PRIVATE "${CMAKE_SOURCE_DIR}/include") 190 | target_include_directories(relaxed_constexpr_schema_tests PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") 191 | 192 | if(MSVC) 193 | target_compile_options(relaxed_constexpr_schema_tests PRIVATE "/bigobj") 194 | endif() 195 | 196 | # disable analysis for these very large generated bits of code 197 | set_target_properties(relaxed_constexpr_schema_tests PROPERTIES CXX_CPPCHECK "" CXX_CLANG_TIDY "") 198 | 199 | catch_discover_tests( 200 | relaxed_constexpr_schema_tests 201 | TEST_PREFIX 202 | "relaxed_constexpr_schema." 203 | REPORTER 204 | XML 205 | OUTPUT_DIR 206 | . 207 | OUTPUT_PREFIX 208 | "relaxed_constexpr_schema." 209 | OUTPUT_SUFFIX 210 | .xml) 211 | 212 | endif() 213 | -------------------------------------------------------------------------------- /test/catch_main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN// This tells the catch header to generate a main 2 | 3 | #include 4 | -------------------------------------------------------------------------------- /test/constexpr_schema_tests.cpp: -------------------------------------------------------------------------------- 1 | #include "schema_impl.hpp" 2 | #include 3 | #include 4 | 5 | TEST_CASE("Basic test of very large energyplus JSON schema") 6 | { 7 | constexpr auto &document = compiled_json::schema::impl::document;// NOLINT No, I'm not going to mark this `const` 8 | 9 | using namespace std::literals::string_view_literals; 10 | 11 | STATIC_REQUIRE(document["properties"sv]["Version"sv]["patternProperties"sv][".*"sv]["properties"sv] 12 | ["version_identifier"sv]["default"sv] 13 | .get() 14 | == "22.1"sv); 15 | } 16 | -------------------------------------------------------------------------------- /test/constexpr_tests.cpp: -------------------------------------------------------------------------------- 1 | #include "test_json_impl.hpp" 2 | #include 3 | 4 | 5 | TEST_CASE("Can read object size") 6 | { 7 | constexpr auto &document = compiled_json::test_json::impl::document;// NOLINT No, I'm not going to mark this `const` 8 | 9 | STATIC_REQUIRE(document.size() == 1); 10 | } 11 | 12 | constexpr auto count_elements() 13 | { 14 | constexpr auto &document = compiled_json::test_json::impl::document;// NOLINT No, I'm not going to mark this `const` 15 | 16 | std::size_t elements = 0; 17 | for (const auto &json : document) { 18 | // count_if is not constexpr in C++17 19 | // cppcheck-suppress useStlAlgorithm 20 | if (!json.is_null()) { ++elements; } 21 | } 22 | 23 | return elements; 24 | } 25 | 26 | TEST_CASE("Can iterate object") { STATIC_REQUIRE(count_elements() == compiled_json::test_json::impl::document.size()); } 27 | 28 | TEST_CASE("Can read iterator key") 29 | { 30 | constexpr auto &document = compiled_json::test_json::impl::document;// NOLINT No, I'm not going to mark this `const` 31 | 32 | STATIC_REQUIRE(document.begin().key() == "glossary"); 33 | } 34 | -------------------------------------------------------------------------------- /test/tests.cpp: -------------------------------------------------------------------------------- 1 | #include "test_json.hpp" 2 | #include 3 | 4 | TEST_CASE("Can read object size") 5 | { 6 | const auto &document = compiled_json::test_json::get(); 7 | 8 | REQUIRE(document.size() == 1); 9 | } 10 | 11 | 12 | TEST_CASE("Can iterate object") 13 | { 14 | const auto &document = compiled_json::test_json::get(); 15 | 16 | std::size_t elements = 0; 17 | for (const auto &json : document) { 18 | REQUIRE(!json.is_null()); 19 | ++elements; 20 | } 21 | 22 | REQUIRE(elements == document.size()); 23 | } 24 | 25 | TEST_CASE("Can read iterator key") 26 | { 27 | const auto &document = compiled_json::test_json::get(); 28 | REQUIRE(document.begin().key() == "glossary"); 29 | } 30 | -------------------------------------------------------------------------------- /test/valijson_tests.cpp: -------------------------------------------------------------------------------- 1 | #include "allof_integers_and_numbers.schema.hpp" 2 | #include "array_doubles_10_20_30_40.hpp" 3 | #include "array_integers_10_20_30_40.hpp" 4 | #if defined(__GNUC__) && !defined(__clang__) 5 | #pragma GCC diagnostic push 6 | #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" 7 | #endif 8 | #include 9 | #if defined(__GNUC__) && !defined(__clang__) 10 | #pragma GCC diagnostic pop 11 | #endif 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | 18 | TEST_CASE("Can load a valijson schema") 19 | { 20 | using valijson::Schema; 21 | using valijson::SchemaParser; 22 | using valijson::Validator; 23 | using valijson::adapters::json2cppJsonAdapter; 24 | 25 | 26 | // Parse JSON schema content using valijson 27 | Schema mySchema; 28 | SchemaParser parser; 29 | const json2cppJsonAdapter mySchemaAdapter(compiled_json::allof_integers_and_numbers_schema::get()); 30 | CHECK_NOTHROW(parser.populateSchema(mySchemaAdapter, mySchema)); 31 | } 32 | 33 | 34 | TEST_CASE("Validation fails where expected") 35 | { 36 | using valijson::Schema; 37 | using valijson::SchemaParser; 38 | using valijson::Validator; 39 | using valijson::adapters::json2cppJsonAdapter; 40 | 41 | 42 | // Parse JSON schema content using valijson 43 | Schema mySchema; 44 | SchemaParser parser; 45 | const json2cppJsonAdapter mySchemaAdapter(compiled_json::allof_integers_and_numbers_schema::get()); 46 | CHECK_NOTHROW(parser.populateSchema(mySchemaAdapter, mySchema)); 47 | 48 | Validator validator; 49 | const json2cppJsonAdapter myTargetAdapter(compiled_json::array_doubles_10_20_30_40::get()); 50 | 51 | REQUIRE_FALSE(validator.validate(mySchema, myTargetAdapter, nullptr)); 52 | } 53 | 54 | 55 | TEST_CASE("Can validate a document") 56 | { 57 | using valijson::Schema; 58 | using valijson::SchemaParser; 59 | using valijson::Validator; 60 | using valijson::adapters::json2cppJsonAdapter; 61 | 62 | 63 | // Parse JSON schema content using valijson 64 | Schema mySchema; 65 | SchemaParser parser; 66 | const json2cppJsonAdapter mySchemaAdapter(compiled_json::allof_integers_and_numbers_schema::get()); 67 | CHECK_NOTHROW(parser.populateSchema(mySchemaAdapter, mySchema)); 68 | 69 | Validator validator; 70 | const json2cppJsonAdapter myTargetAdapter(compiled_json::array_integers_10_20_30_40::get()); 71 | 72 | REQUIRE(validator.validate(mySchema, myTargetAdapter, nullptr)); 73 | } 74 | --------------------------------------------------------------------------------