From ed8ba0201fe9ee3c6dcc97c92c93bab38faec2a1 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Wed, 7 Oct 2026 22:20:05 +0200 Subject: [PATCH 1/3] Install CMake package config files with Meson, and add Meson options (#5587) * Install CMake package config files with Meson, and add Meson options Squashed onto develop from: - Install CMake package config files with Meson - meson: Indent code inside an if block - meson: set a minimum Meson version - meson: use `override_dependency()` to set dependencies - meson: use `install_subdir` for headers - meson: set the C++ standard to C++11 - meson: handle single header and multiheader the same way CMake does - meson: add support for the GlobalUDLs option - meson: add support for the ImplictConversions option - Add meson information to FILES.md - Complete the Meson options and match CMake's compile definitions - Add the JSON_* definitions to the CMake pkg-config file - Document the Meson options and check them in CI - Fix the Meson CMake target for includedir or datadir outside the prefix - Avoid //include in Meson-generated CMake target for prefix / - Add DisableTupleReferenceConversion to Meson and the CMake pkg-config file - Check in CI that Meson and pkg-config offer the CMake options - Remove accidentally committed Python bytecode Co-authored-by: Dylan Baker Signed-off-by: Dylan Baker Signed-off-by: Niels Lohmann * Add the StrictBinaryUTF8 and DeleteDeprecatedFunctions Meson options JSON_StrictBinaryUTF8 (#5741) and JSON_DeleteDeprecatedFunctions (#5755) arrived with develop but were missing from the Meson build and the pkg-config file, so check_build_options failed. Both Meson options default to false and add JSON_STRICT_BINARY_UTF8=1 and JSON_DELETE_DEPRECATED_FUNCTIONS=1 to the dependency, the pkg-config file, and the generated CMake target; CMake's pkg-config file now carries both definitions too. The ci_meson_install job sets and checks them in its non-default install. Signed-off-by: Niels Lohmann --------- Signed-off-by: Dylan Baker Signed-off-by: Niels Lohmann Co-authored-by: Dylan Baker --- .github/workflows/ubuntu.yml | 45 ++++++ CMakeLists.txt | 34 ++++- FILES.md | 26 +++- Makefile | 8 +- cmake/nlohmann_jsonTargets.cmake.in | 42 +++++ cmake/pkg-config.pc.in | 2 +- .../docs/integration/package_managers.md | 22 ++- meson.build | 114 ++++++++++++-- meson_options.txt | 66 ++++++++ tools/check_build_options/README.md | 21 +++ .../check_build_options.py | 144 ++++++++++++++++++ 11 files changed, 505 insertions(+), 19 deletions(-) create mode 100644 cmake/nlohmann_jsonTargets.cmake.in create mode 100644 meson_options.txt create mode 100644 tools/check_build_options/README.md create mode 100755 tools/check_build_options/check_build_options.py diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 1fa2f9fc6..3c94bc1e4 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -31,6 +31,51 @@ jobs: - name: Build run: cmake --build build --target ci_test_gcc + ci_meson_install: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Get latest CMake and ninja + uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2 + - name: Check that Meson and pkg-config offer the CMake options + run: make check_build_options + - name: Install Meson + run: pip install meson + - name: Install with Meson + run: | + meson setup build-meson --prefix=${{ github.workspace }}/install + meson install -C build-meson + - name: Use the installed package with find_package + run: | + cmake -S tests/cmake_import/project -B build-import -DCMAKE_PREFIX_PATH=${{ github.workspace }}/install + cmake --build build-import + - name: Install with Meson and non-default options + run: | + meson setup build-meson-options --prefix=${{ github.workspace }}/install-options -DMultipleHeaders=true -DDiagnostics=true -DGlobalUDLs=false -DDisableTupleReferenceConversion=true -DStrictBinaryUTF8=true -DDeleteDeprecatedFunctions=true + meson install -C build-meson-options + - name: Check that the options reach the installed files + run: | + test -d install-options/include/nlohmann/detail + cflags=$(PKG_CONFIG_PATH=${{ github.workspace }}/install-options/share/pkgconfig pkg-config --cflags nlohmann_json) + echo "$cflags" + echo "$cflags" | grep -q -- '-DJSON_DIAGNOSTICS=1' + echo "$cflags" | grep -q -- '-DJSON_USE_GLOBAL_UDLS=0' + echo "$cflags" | grep -q -- '-DJSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1' + echo "$cflags" | grep -q -- '-DJSON_STRICT_BINARY_UTF8=1' + echo "$cflags" | grep -q -- '-DJSON_DELETE_DEPRECATED_FUNCTIONS=1' + grep -q 'JSON_USE_GLOBAL_UDLS=0;JSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1;JSON_DIAGNOSTICS=1' install-options/share/cmake/nlohmann_json/nlohmann_jsonTargets.cmake + grep -q 'JSON_STRICT_BINARY_UTF8=1;JSON_DELETE_DEPRECATED_FUNCTIONS=1' install-options/share/cmake/nlohmann_json/nlohmann_jsonTargets.cmake + cmake -S tests/cmake_import/project -B build-import-options -DCMAKE_PREFIX_PATH=${{ github.workspace }}/install-options + cmake --build build-import-options + - name: Install with Meson and the include directory outside the prefix + run: | + meson setup build-meson-split --prefix=${{ github.workspace }}/install-split --includedir=${{ github.workspace }}/install-split-dev/include + meson install -C build-meson-split + cmake -S tests/cmake_import/project -B build-import-split -DCMAKE_PREFIX_PATH=${{ github.workspace }}/install-split + cmake --build build-import-split + ci_infer: runs-on: ubuntu-latest steps: diff --git a/CMakeLists.txt b/CMakeLists.txt index fed9e3171..19f5dca30 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -189,7 +189,39 @@ if (MSVC) ) endif() -# Install a pkg-config file, so other tools can find this. +# Install a pkg-config file, so other tools can find this. It carries the same +# compile definitions as the target above. +set(NLOHMANN_JSON_PKGCONFIG_CFLAGS "") +if (NOT JSON_GlobalUDLs) + string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_USE_GLOBAL_UDLS=0") +endif() +if (NOT JSON_ImplicitConversions) + string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_USE_IMPLICIT_CONVERSIONS=0") +endif() +if (JSON_DisableEnumSerialization) + string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_DISABLE_ENUM_SERIALIZATION=1") +endif() +if (JSON_DisableTupleReferenceConversion) + string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1") +endif() +if (JSON_Diagnostics) + string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_DIAGNOSTICS=1") +endif() +if (JSON_Diagnostic_Positions) + string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_DIAGNOSTIC_POSITIONS=1") +endif() +if (JSON_LegacyDiscardedValueComparison) + string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1") +endif() +if (JSON_StrictNulHandling) + string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_STRICT_NUL_HANDLING=1") +endif() +if (JSON_StrictBinaryUTF8) + string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_STRICT_BINARY_UTF8=1") +endif() +if (JSON_DeleteDeprecatedFunctions) + string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_DELETE_DEPRECATED_FUNCTIONS=1") +endif() configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/cmake/pkg-config.pc.in" "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc" diff --git a/FILES.md b/FILES.md index 861fdf610..fbb57815a 100644 --- a/FILES.md +++ b/FILES.md @@ -249,9 +249,31 @@ make BUILD.bazel The "Check amalgamation" workflow fails if the file is out of date. -### `meson.build` +### `meson.build` and `meson_options.txt` -The build definition for the [Meson](https://mesonbuild.com) build system. +Meson build definitions suitable for use as a subproject ("wrap" in Meson terminology). + +Projects wishing to use the wrap can execute: +```sh +meson wrap install nlohmann_json +``` + +Which allows Meson to build from source when a system provided dependency isn't available. + +To build directly: +```sh +meson setup builddir +ninja -C builddir +``` + +`meson_options.txt` defines the options, which mirror the CMake options that change the library's target (for example, +`-DDiagnostics=true`). Meson requires this file next to `meson.build`, so it is also part of `include.zip`. `make check_build_options` +([`tools/check_build_options`](tools/check_build_options/README.md)) checks in CI that both files and the pkg-config files +stay in sync with the CMake options. + +When installing, `meson.build` installs the headers, a pkg-config file, and the CMake package config files, so that +`find_package(nlohmann_json)` works. As Meson cannot generate `nlohmann_jsonTargets.cmake` itself, it is created from +the template `cmake/nlohmann_jsonTargets.cmake.in`, which is only used by Meson. ### `Package.swift` diff --git a/Makefile b/Makefile index 11b9227a4..093fbb7b5 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: pretty clean ChangeLog.md release update_hedley update_hedley_undef BUILD.bazel natvis macro_builder_check +.PHONY: pretty clean ChangeLog.md release update_hedley update_hedley_undef BUILD.bazel natvis macro_builder_check check_build_options ########################################################################## # configuration @@ -127,6 +127,10 @@ macro_builder_check: diff "$$TMPDIR/paste.hpp" "$$TMPDIR/paste_actual.hpp" || (echo "===================================================================\n $(MACRO_SCOPE_HPP) (NLOHMANN_JSON_EXPAND..NLOHMANN_JSON_DOUBLE_PASTE63) is out of date!\n Regenerate it, see tools/macro_builder/README.md.\n===================================================================" ; exit 1); \ diff "$$TMPDIR/type_body.hpp" "$$TMPDIR/type_body_actual.hpp" || (echo "===================================================================\n $(MACRO_SCOPE_HPP) (NLOHMANN_JSON_TYPE_BODY) is out of date!\n Regenerate it, see tools/macro_builder/README.md.\n===================================================================" ; exit 1) +# check that the Meson build and the pkg-config files offer the options of the CMake target +check_build_options: + python3 tools/check_build_options/check_build_options.py . + # check if file single_include/nlohmann/json.hpp has been amalgamated from the nlohmann sources check-amalgamation: @mv $(AMALGAMATED_FILE) $(AMALGAMATED_FILE)~ @@ -184,7 +188,7 @@ json.tar.xz: # We use `-X` to make the resulting ZIP file reproducible, see # . include.zip: BUILD.bazel - zip -9 --recurse-paths -X include.zip $(SRCS) $(AMALGAMATED_FILE) $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_LITERALS_FILE) BUILD.bazel MODULE.bazel meson.build LICENSE.MIT + zip -9 --recurse-paths -X include.zip $(SRCS) $(AMALGAMATED_FILE) $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_LITERALS_FILE) BUILD.bazel MODULE.bazel meson.build meson_options.txt LICENSE.MIT # Create the files for a release and add signatures and hashes. release: include.zip json.tar.xz diff --git a/cmake/nlohmann_jsonTargets.cmake.in b/cmake/nlohmann_jsonTargets.cmake.in new file mode 100644 index 000000000..b2e57a526 --- /dev/null +++ b/cmake/nlohmann_jsonTargets.cmake.in @@ -0,0 +1,42 @@ +# Imported target for installations made with Meson (see meson.build). +# +# CMake installations generate this file with install(EXPORT ...). Meson cannot +# do that, but as the library is header-only, the target only needs an include +# directory, the C++ standard, and the compile definitions of the options that +# differ from their defaults. Paths are computed relative to this file so that +# the installation can be relocated (e.g., into a sysroot), unless includedir or +# datadir is outside the prefix. + +if(TARGET @PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@) + return() +endif() + +get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_DIR}/@NLOHMANN_JSON_CONFIG_TO_PREFIX@" ABSOLUTE) +# As in CMake's generated file: avoid "//include" for an installation to "/". +if(_IMPORT_PREFIX STREQUAL "/") + set(_IMPORT_PREFIX "") +endif() + +add_library(@PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@ INTERFACE IMPORTED) +set_target_properties(@PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@ PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "@NLOHMANN_JSON_INCLUDE_DIR@" +) +if(CMAKE_VERSION VERSION_LESS 3.8) + set_target_properties(@PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@ PROPERTIES + INTERFACE_COMPILE_FEATURES cxx_range_for + ) +else() + set_target_properties(@PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@ PROPERTIES + INTERFACE_COMPILE_FEATURES cxx_std_11 + ) +endif() + +set(_NLOHMANN_JSON_COMPILE_DEFINITIONS "@NLOHMANN_JSON_COMPILE_DEFINITIONS@") +if(_NLOHMANN_JSON_COMPILE_DEFINITIONS) + set_target_properties(@PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@ PROPERTIES + INTERFACE_COMPILE_DEFINITIONS "${_NLOHMANN_JSON_COMPILE_DEFINITIONS}" + ) +endif() + +unset(_NLOHMANN_JSON_COMPILE_DEFINITIONS) +unset(_IMPORT_PREFIX) diff --git a/cmake/pkg-config.pc.in b/cmake/pkg-config.pc.in index 21a91a3cf..70407f09b 100644 --- a/cmake/pkg-config.pc.in +++ b/cmake/pkg-config.pc.in @@ -4,4 +4,4 @@ includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ Name: @PROJECT_NAME@ Description: JSON for Modern C++ Version: @PROJECT_VERSION@ -Cflags: -I${includedir} +Cflags: -I${includedir}@NLOHMANN_JSON_PKGCONFIG_CFLAGS@ diff --git a/docs/mkdocs/docs/integration/package_managers.md b/docs/mkdocs/docs/integration/package_managers.md index d5357b8ff..bfa994340 100644 --- a/docs/mkdocs/docs/integration/package_managers.md +++ b/docs/mkdocs/docs/integration/package_managers.md @@ -125,10 +125,24 @@ meson wrap install nlohmann_json Please see the Meson project for any issues regarding the packaging. The provided `meson.build` can also be used as an alternative to CMake for installing `nlohmann_json` system-wide in -which case a [pkg-config](pkg-config.md) file is installed. To use it, have your build system require the -`nlohmann_json` pkg-config dependency. In Meson, it is preferred to use the -[`dependency()`](https://mesonbuild.com/Reference-manual.html#dependency) object with a subproject fallback, rather than -using the subproject directly. +which case a [pkg-config](pkg-config.md) file and the CMake package config files are installed. To use it, have your build system require +the `nlohmann_json` pkg-config dependency, or use [`find_package(nlohmann_json)`](cmake.md#external) in CMake. In Meson, +it is preferred to use the [`dependency()`](https://mesonbuild.com/Reference-manual.html#dependency) object with a +subproject fallback, rather than using the subproject directly. + +The options that change the library's configuration are available in Meson as well, named like the +[CMake options](cmake.md#cmake-options) without the `JSON_` prefix: `MultipleHeaders`, `GlobalUDLs`, +`ImplicitConversions`, `DisableEnumSerialization`, `DisableTupleReferenceConversion`, `Diagnostics`, +`Diagnostic_Positions`, `LegacyDiscardedValueComparison`, `StrictNulHandling`, `StrictBinaryUTF8`, and +`DeleteDeprecatedFunctions`. They have the same defaults as in CMake, except that +`MultipleHeaders` is `false`. Set them with `-D` when setting up the build, or with the subproject name as prefix when +the library is used as a subproject: + +```shell +meson setup build -Dnlohmann_json:Diagnostics=true +``` + +The resulting compile definitions are part of the Meson dependency, the pkg-config file, and the CMake target. ??? example "Example: Wrap" diff --git a/meson.build b/meson.build index 2800957ec..19f734784 100644 --- a/meson.build +++ b/meson.build @@ -2,24 +2,120 @@ project('nlohmann_json', 'cpp', version : '3.12.0', license : 'MIT', + meson_version : '>= 0.64', + default_options: ['cpp_std=c++11'], ) +if get_option('MultipleHeaders') + incdir = 'include' +else + incdir = 'single_include' +endif + +# The same compile definitions as the CMake target (see target_compile_definitions +# in CMakeLists.txt): only an option that differs from its default adds one. +json_defines = [] +if not get_option('GlobalUDLs') + json_defines += 'JSON_USE_GLOBAL_UDLS=0' +endif +if not get_option('ImplicitConversions') + json_defines += 'JSON_USE_IMPLICIT_CONVERSIONS=0' +endif +if get_option('DisableEnumSerialization') + json_defines += 'JSON_DISABLE_ENUM_SERIALIZATION=1' +endif +if get_option('DisableTupleReferenceConversion') + json_defines += 'JSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1' +endif +if get_option('Diagnostics') + json_defines += 'JSON_DIAGNOSTICS=1' +endif +if get_option('Diagnostic_Positions') + json_defines += 'JSON_DIAGNOSTIC_POSITIONS=1' +endif +if get_option('LegacyDiscardedValueComparison') + json_defines += 'JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1' +endif +if get_option('StrictNulHandling') + json_defines += 'JSON_STRICT_NUL_HANDLING=1' +endif +if get_option('StrictBinaryUTF8') + json_defines += 'JSON_STRICT_BINARY_UTF8=1' +endif +if get_option('DeleteDeprecatedFunctions') + json_defines += 'JSON_DELETE_DEPRECATED_FUNCTIONS=1' +endif + +cpp_args = [] +foreach define : json_defines + cpp_args += '-D' + define +endforeach + nlohmann_json_dep = declare_dependency( - include_directories: include_directories('single_include') + compile_args: cpp_args, + include_directories: include_directories(incdir) ) +meson.override_dependency('nlohmann_json', nlohmann_json_dep) +# The multi-header version under the name earlier versions of this file used nlohmann_json_multiple_headers = declare_dependency( + compile_args: cpp_args, include_directories: include_directories('include') ) if not meson.is_subproject() -install_headers('single_include/nlohmann/json.hpp', subdir: 'nlohmann') -install_headers('single_include/nlohmann/json_fwd.hpp', subdir: 'nlohmann') -install_headers('single_include/nlohmann/json_literals.hpp', subdir: 'nlohmann') + install_subdir( + incdir / 'nlohmann', + install_dir: get_option('includedir'), + install_tag: 'devel', + ) -pkgc = import('pkgconfig') -pkgc.generate(name: 'nlohmann_json', - version: meson.project_version(), - description: 'JSON for Modern C++' -) + pkgc = import('pkgconfig') + pkgc.generate(name: 'nlohmann_json', + version: meson.project_version(), + description: 'JSON for Modern C++', + extra_cflags: cpp_args, + install_dir: get_option('datadir') / 'pkgconfig', + ) + + # CMake package config files, so that find_package(nlohmann_json) works. The + # include directory is given relative to the config files, so that the + # installation can be relocated. This is not possible if includedir or datadir + # is an absolute path outside the prefix (e.g., with the separate outputs of + # Nix); then the absolute include directory is used, as CMake does. + fs = import('fs') + cmake_install_dir = get_option('datadir') / 'cmake' / meson.project_name() + cmake_to_prefix = [] + if fs.is_absolute(get_option('includedir')) or fs.is_absolute(cmake_install_dir) + cmake_include_dir = (get_option('prefix') / get_option('includedir')).replace('\\', '/') + else + foreach component : cmake_install_dir.split('/') + cmake_to_prefix += '..' + endforeach + cmake_include_dir = '${_IMPORT_PREFIX}/' + get_option('includedir') + endif + + cmake_conf = configuration_data() + cmake_conf.set('PROJECT_NAME', meson.project_name()) + cmake_conf.set('PROJECT_VERSION', meson.project_version()) + cmake_conf.set('PROJECT_VERSION_MAJOR', meson.project_version().split('.')[0]) + cmake_conf.set('NLOHMANN_JSON_TARGET_NAME', meson.project_name()) + cmake_conf.set('NLOHMANN_JSON_TARGETS_EXPORT_NAME', meson.project_name() + 'Targets') + cmake_conf.set('NLOHMANN_JSON_INCLUDE_DIR', cmake_include_dir) + cmake_conf.set('NLOHMANN_JSON_CONFIG_TO_PREFIX', '/'.join(cmake_to_prefix)) + cmake_conf.set('NLOHMANN_JSON_COMPILE_DEFINITIONS', ';'.join(json_defines)) + + foreach cmake_file : [ + ['cmake/config.cmake.in', 'nlohmann_jsonConfig.cmake'], + ['cmake/nlohmann_jsonConfigVersion.cmake.in', 'nlohmann_jsonConfigVersion.cmake'], + ['cmake/nlohmann_jsonTargets.cmake.in', 'nlohmann_jsonTargets.cmake'], + ] + configure_file( + input: cmake_file[0], + output: cmake_file[1], + configuration: cmake_conf, + format: 'cmake@', + install_dir: cmake_install_dir, + ) + endforeach endif diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 000000000..d4bfb201f --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,66 @@ +option( + 'MultipleHeaders', + type: 'boolean', + value: false, + description: 'Use non-amalgamated version of the library', +) +option( + 'GlobalUDLs', + type: 'boolean', + value: true, + description: 'Place user-defined string literals in the global namespace', +) +option( + 'ImplicitConversions', + type: 'boolean', + value: true, + description: 'Enable implicit conversions', +) +option( + 'DisableEnumSerialization', + type: 'boolean', + value: false, + description: 'Disable default integer enum serialization', +) +option( + 'DisableTupleReferenceConversion', + type: 'boolean', + value: false, + description: 'Disable conversion from a one-element tuple of a JSON reference', +) +option( + 'Diagnostics', + type: 'boolean', + value: false, + description: 'Use extended diagnostic messages', +) +option( + 'Diagnostic_Positions', + type: 'boolean', + value: false, + description: 'Enable diagnostic positions', +) +option( + 'LegacyDiscardedValueComparison', + type: 'boolean', + value: false, + description: 'Enable legacy discarded value comparison', +) +option( + 'StrictNulHandling', + type: 'boolean', + value: false, + description: 'Enable strict NUL-byte handling', +) +option( + 'StrictBinaryUTF8', + type: 'boolean', + value: false, + description: 'Enable UTF-8 checks in the CBOR, UBJSON, BJData, and BSON writers', +) +option( + 'DeleteDeprecatedFunctions', + type: 'boolean', + value: false, + description: 'Delete the deprecated functions instead of only deprecating them', +) diff --git a/tools/check_build_options/README.md b/tools/check_build_options/README.md new file mode 100644 index 000000000..1b24a55ff --- /dev/null +++ b/tools/check_build_options/README.md @@ -0,0 +1,21 @@ +# check_build_options + +Checks that the Meson build and the pkg-config files offer the same options as the CMake target, so that a new CMake +option is not forgotten in one of them. + +The compile definitions of the CMake target (`target_compile_definitions` in [`CMakeLists.txt`](../../CMakeLists.txt)) +are the reference. When you add an option there, also add it to + +- the pkg-config block in `CMakeLists.txt` (`NLOHMANN_JSON_PKGCONFIG_CFLAGS`), +- [`meson_options.txt`](../../meson_options.txt), named without the `JSON_` prefix and with the same default, +- [`meson.build`](../../meson.build) (`json_defines`), and +- the list of Meson options in + [`docs/mkdocs/docs/integration/package_managers.md`](../../docs/mkdocs/docs/integration/package_managers.md). + +Run the check with + +```shell +make check_build_options +``` + +It needs only Python 3 and runs in the `ci_meson_install` CI job. diff --git a/tools/check_build_options/check_build_options.py b/tools/check_build_options/check_build_options.py new file mode 100755 index 000000000..9b9debfdc --- /dev/null +++ b/tools/check_build_options/check_build_options.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +"""Check that the Meson build and the pkg-config files offer the CMake options. + +The compile definitions of the CMake target (target_compile_definitions in +CMakeLists.txt) are the reference. For every option used there, the script +checks that + +- the CMake pkg-config file adds the same definition under the same condition, +- meson_options.txt has a boolean option of the same name without the "JSON_" + prefix and with the same default, +- meson.build adds the same definition under the same condition, and +- the Meson section of the package manager documentation lists the option. + +Meson's MultipleHeaders option selects the include directory and adds no +definition; it is the only Meson option without a definition. +""" + +import argparse +import os +import re +import sys + +REPO_ROOT = os.path.normpath(os.path.join(sys.path[0], '..', '..')) +DOCS = os.path.join('docs', 'mkdocs', 'docs', 'integration', 'package_managers.md') + +# Meson options that add no compile definition, with their default +MESON_ONLY = {'MultipleHeaders': 'false'} + + +def read(root, path): + with open(os.path.join(root, path), encoding='utf-8') as f: + return f.read() + + +def cmake_target_definitions(cmake): + """Return {option: (definition, add_if_on)} from target_compile_definitions.""" + block = re.search(r'target_compile_definitions\(\s*\$\{NLOHMANN_JSON_TARGET_NAME\}\s*INTERFACE(.*?)\n\)', cmake, re.S) + if not block: + sys.exit('CMakeLists.txt: target_compile_definitions of the target not found') + result = {} + for line in block.group(1).split('\n'): + line = line.strip() + if not line: + continue + m = re.fullmatch(r'\$<\$>:(\w+=\w+)>', line) + if m: + result[m.group(1)] = (m.group(2), False) + continue + m = re.fullmatch(r'\$<\$:(\w+=\w+)>', line) + if m: + result[m.group(1)] = (m.group(2), True) + continue + sys.exit(f'CMakeLists.txt: unexpected line in target_compile_definitions: {line}') + return result + + +def cmake_defaults(cmake): + """Return {option: 'true'/'false'} for the option() calls of the form JSON_.""" + return {m.group(1): 'true' if m.group(2) == 'ON' else 'false' + for m in re.finditer(r'^option\(JSON_(\w+)\s+"[^"]*"\s+(ON|OFF)\)', cmake, re.M)} + + +def cmake_pkgconfig_definitions(cmake): + """Return {option: (definition, add_if_on)} from the pkg-config block.""" + return {m.group(2): (m.group(3), m.group(1) is None) + for m in re.finditer(r'if \((NOT )?JSON_(\w+)\)\s*\n\s*string\(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -D(\w+=\w+)"\)', cmake)} + + +def meson_options(options): + """Return {option: default} for the boolean options in meson_options.txt.""" + result = {} + for block in re.findall(r'option\((.*?)\)', options, re.S): + name = re.search(r"'(\w+)'", block).group(1) + kind = re.search(r"type\s*:\s*'(\w+)'", block) + value = re.search(r'value\s*:\s*(\w+)', block) + result[name] = value.group(1) if kind and kind.group(1) == 'boolean' and value else None + return result + + +def meson_definitions(meson): + """Return {option: (definition, add_if_on)} from meson.build.""" + return {m.group(2): (m.group(3), m.group(1) is None) + for m in re.finditer(r"if (not )?get_option\('(\w+)'\)\s*\n\s*json_defines \+= '(\w+=\w+)'", meson)} + + +def describe(definition): + name, add_if_on = definition + return f'{name} if {"enabled" if add_if_on else "disabled"}' + + +def compare(errors, where, expected, actual): + for option, definition in expected.items(): + if option not in actual: + errors.append(f'{where}: no definition for option {option} (expected {describe(definition)})') + elif actual[option] != definition: + errors.append(f'{where}: option {option} adds {describe(actual[option])}, expected {describe(definition)}') + for option in actual.keys() - expected.keys(): + errors.append(f'{where}: definition for option {option}, which the CMake target does not have') + + +def main(): + parser = argparse.ArgumentParser(description=__doc__.split('\n')[0]) + parser.add_argument('root', nargs='?', default=REPO_ROOT, help='repository root (default: %(default)s)') + root = parser.parse_args().root + + cmake = read(root, 'CMakeLists.txt') + reference = cmake_target_definitions(cmake) + defaults = cmake_defaults(cmake) + errors = [] + + compare(errors, 'CMakeLists.txt (pkg-config)', reference, cmake_pkgconfig_definitions(cmake)) + compare(errors, 'meson.build', reference, meson_definitions(read(root, 'meson.build'))) + + options = meson_options(read(root, 'meson_options.txt')) + for option in reference: + if option not in defaults: + errors.append(f'CMakeLists.txt: no option(JSON_{option} ... ON|OFF)') + elif option not in options: + errors.append(f'meson_options.txt: option {option} missing') + elif options[option] != defaults[option]: + errors.append(f'meson_options.txt: option {option} must be boolean with value {defaults[option]} as in CMake') + for option, default in MESON_ONLY.items(): + if options.get(option) != default: + errors.append(f'meson_options.txt: option {option} must be boolean with value {default}') + for option in options.keys() - reference.keys() - MESON_ONLY.keys(): + errors.append(f'meson_options.txt: option {option} has no counterpart in the CMake target') + + docs = read(root, DOCS) + for option in sorted(set(options) & (reference.keys() | MESON_ONLY.keys())): + if f'`{option}`' not in docs: + errors.append(f'{DOCS}: Meson option {option} not listed') + + for error in errors: + print(error, file=sys.stderr) + if errors: + print('The Meson build and the pkg-config files must offer the options of the CMake target; see ' + 'tools/check_build_options/README.md.', file=sys.stderr) + return 1 + print(f'OK: {len(reference)} options with compile definitions agree between CMake, pkg-config, and Meson.') + return 0 + + +if __name__ == '__main__': + sys.exit(main()) From a269794db7ce14879dc07b0ad510e878b593d4d2 Mon Sep 17 00:00:00 2001 From: Suyog Verma Date: Thu, 8 Oct 2026 12:19:32 +0530 Subject: [PATCH 2/3] Use MSVC intrinsics for full multiplication (#5782) * Use MSVC intrinsics for full multiplication Signed-off-by: Suyog Verma * Fix formatting in unit-class_lexer Signed-off-by: Suyog Verma * Address review feedback Signed-off-by: Suyog Verma --------- Signed-off-by: Suyog Verma --- include/nlohmann/detail/bit_ops.hpp | 13 ++++++++-- single_include/nlohmann/json.hpp | 13 ++++++++-- tests/src/unit-class_lexer.cpp | 37 ++++++++++++++++++++++++++--- 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/include/nlohmann/detail/bit_ops.hpp b/include/nlohmann/detail/bit_ops.hpp index 9655ff18b..9ccb82c66 100644 --- a/include/nlohmann/detail/bit_ops.hpp +++ b/include/nlohmann/detail/bit_ops.hpp @@ -9,12 +9,15 @@ #pragma once #include // uint64_t +#if !defined(__SIZEOF_INT128__) && defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)) + #include // __umulh, _umul128 +#endif #include // Portable bit-level helpers for the number and string scanners. They use -// compiler builtins where available and plain C++ otherwise, so they need no -// platform headers and work regardless of byte order. +// compiler builtins or platform-specific intrinsics where available and plain +// C++ otherwise, so they work regardless of byte order. NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail @@ -52,6 +55,12 @@ inline uint128_parts full_multiplication(std::uint64_t a, std::uint64_t b) noexc __extension__ using uint128 = unsigned __int128; const uint128 r = static_cast(a) * b; return {static_cast(r), static_cast(r >> 64u)}; +#elif defined(_MSC_VER) && defined(_M_X64) + std::uint64_t high = 0; + const std::uint64_t low = _umul128(a, b, &high); + return {low, high}; +#elif defined(_MSC_VER) && defined(_M_ARM64) + return {a * b, __umulh(a, b)}; #else const std::uint64_t a_lo = a & 0xFFFFFFFFu; const std::uint64_t a_hi = a >> 32u; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 4f1ded530..72c95feea 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -8791,13 +8791,16 @@ NLOHMANN_JSON_NAMESPACE_END #include // uint64_t +#if !defined(__SIZEOF_INT128__) && defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)) + #include // __umulh, _umul128 +#endif // #include // Portable bit-level helpers for the number and string scanners. They use -// compiler builtins where available and plain C++ otherwise, so they need no -// platform headers and work regardless of byte order. +// compiler builtins or platform-specific intrinsics where available and plain +// C++ otherwise, so they work regardless of byte order. NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail @@ -8835,6 +8838,12 @@ inline uint128_parts full_multiplication(std::uint64_t a, std::uint64_t b) noexc __extension__ using uint128 = unsigned __int128; const uint128 r = static_cast(a) * b; return {static_cast(r), static_cast(r >> 64u)}; +#elif defined(_MSC_VER) && defined(_M_X64) + std::uint64_t high = 0; + const std::uint64_t low = _umul128(a, b, &high); + return {low, high}; +#elif defined(_MSC_VER) && defined(_M_ARM64) + return {a * b, __umulh(a, b)}; #else const std::uint64_t a_lo = a & 0xFFFFFFFFu; const std::uint64_t a_hi = a >> 32u; diff --git a/tests/src/unit-class_lexer.cpp b/tests/src/unit-class_lexer.cpp index 1d20901d6..860af81d6 100644 --- a/tests/src/unit-class_lexer.cpp +++ b/tests/src/unit-class_lexer.cpp @@ -17,6 +17,7 @@ using nlohmann::json; #include // uint32_t, uint64_t #include // strtod #include // memcpy +#include // numeric_limits #include // stringstream #include // string #include // pair @@ -891,8 +892,39 @@ TEST_CASE("Eisel-Lemire float conversion") SECTION("128-bit products and leading zeros") { + const auto check_product = [](std::uint64_t a, std::uint64_t b) + { + const auto product = nlohmann::detail::full_multiplication(a, b); + CHECK(big_from(product.high, product.low) == big_mul(big_from(0, a), big_from(0, b))); + }; + + const std::uint64_t max = (std::numeric_limits::max)(); + const std::array, 13> edge_cases = + { + { + {0, 0}, + {0, 1}, + {1, 1}, + {1, max}, + {0xFFFFFFFFu, 0x100000000u}, + {0x100000000u, 0x100000000u}, + {0x100000001u, 0x100000001u}, + {max, max}, + {max, 2}, + {0xFFFFFFFF00000000u, 0x100000001u}, + {0x100000001u, 0xFFFFFFFF00000000u}, + {max, 1}, + {2, max}, + } + }; + + for (const auto& test : edge_cases) + { + check_product(test.first, test.second); + } + // whichever implementation the compiler gets (with or without a - // 128-bit integer type or a builtin) + // 128-bit integer type or a builtin / intrinsic) std::uint64_t state = 42; for (int i = 0; i < 10000; ++i) { @@ -901,8 +933,7 @@ TEST_CASE("Eisel-Lemire float conversion") state ^= state << 17u; const std::uint64_t a = state; const std::uint64_t b = (state * 0x9E3779B97F4A7C15u) >> (i % 64); - const auto product = nlohmann::detail::full_multiplication(a, b); - CHECK(big_from(product.high, product.low) == big_mul(big_from(0, a), big_from(0, b))); + check_product(a, b); const int k = i % 64; const std::uint64_t x = (std::uint64_t{1} << k) | (a & ((std::uint64_t{1} << k) - 1)); From 3d7f554927d597b8f45927803eaadb0c267e1998 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Thu, 8 Oct 2026 16:11:16 +0200 Subject: [PATCH 3/3] Use the with_*_t aliases in tests, examples, and docs (#5787) * Use the with_*_t aliases in tests, examples, and docs Replace spelled-out basic_json<...> instantiations that only change one or two template parameters with nlohmann::json::with_*_t (or ordered_json::with_*_t when the object type is ordered_map). Types that change all three number types chain with_integers_t and with_float_t. The raw basic_json<...> spelling stays where the template parameter list itself is the subject: the alias tests in unit-udt.cpp, explicit instantiations, and the ordered_json/compile-time docs. Signed-off-by: Niels Lohmann * Fix unit-large_json for clang and JSON_DIAGNOSTICS Two test problems from #5781 broke CI on develop: CAPTURE(depth); trips clang's -Wextra-semi-stmt, and the type_error.321 messages did not account for the diagnostics path prefix. Signed-off-by: Niels Lohmann * Use static_cast in unit-hash for clang-tidy #5772 added functional casts that clang-tidy reports as C-style casts (google-readability-casting). Also append a char instead of a one-character string in unit-large_json. Signed-off-by: Niels Lohmann * Declare the expected message prefix const in unit-large_json Without JSON_DIAGNOSTICS the prefix was never modified, which clang-tidy reports (misc-const-correctness). Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- .../macros/json_use_implicit_conversions.md | 2 +- .../docs/examples/custom_array_type.cpp | 3 +- .../docs/examples/custom_binary_type.cpp | 8 +-- .../docs/examples/custom_object_type.cpp | 3 +- .../docs/examples/custom_string_type.cpp | 4 +- .../docs/features/types/number_handling.md | 5 +- .../features/types/template_parameters.md | 8 ++- .../docs/integration/migration_guide.md | 2 +- tests/src/unit-allocator.cpp | 54 +++---------------- tests/src/unit-binary_formats.cpp | 2 +- tests/src/unit-comparison.cpp | 6 +-- tests/src/unit-custom-array-type.cpp | 4 +- tests/src/unit-custom-object-type.cpp | 6 +-- tests/src/unit-hash.cpp | 6 +-- tests/src/unit-large_json.cpp | 24 ++++++--- tests/src/unit-locale-cpp.cpp | 6 +-- tests/src/unit-msgpack.cpp | 4 +- tests/src/unit-regression1.cpp | 14 +++-- tests/src/unit-regression2.cpp | 21 ++------ tests/src/unit-regression3.cpp | 15 +----- tests/src/unit-serialization.cpp | 3 +- 21 files changed, 68 insertions(+), 132 deletions(-) diff --git a/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md b/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md index 675a8acee..2e7781378 100644 --- a/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md +++ b/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md @@ -67,7 +67,7 @@ By default, implicit conversions are enabled. `JSON_USE_IMPLICIT_CONVERSIONS` is defined to `0`: ```cpp - using wjson = nlohmann::basic_json; + using wjson = nlohmann::json::with_string_t; void load(const nlohmann::json& j); diff --git a/docs/mkdocs/docs/examples/custom_array_type.cpp b/docs/mkdocs/docs/examples/custom_array_type.cpp index 63651cadf..9765c51e8 100644 --- a/docs/mkdocs/docs/examples/custom_array_type.cpp +++ b/docs/mkdocs/docs/examples/custom_array_type.cpp @@ -1,11 +1,10 @@ #include -#include #include #include "custom_array_type.hpp" -using custom_json = nlohmann::basic_json; +using custom_json = nlohmann::json::with_array_t; int main() { diff --git a/docs/mkdocs/docs/examples/custom_binary_type.cpp b/docs/mkdocs/docs/examples/custom_binary_type.cpp index cea34ea38..0a835d475 100644 --- a/docs/mkdocs/docs/examples/custom_binary_type.cpp +++ b/docs/mkdocs/docs/examples/custom_binary_type.cpp @@ -1,16 +1,10 @@ -#include #include -#include -#include -#include #include #include "custom_binary_type.hpp" -using custom_json = nlohmann::basic_json; +using custom_json = nlohmann::json::with_binary_t; int main() { diff --git a/docs/mkdocs/docs/examples/custom_object_type.cpp b/docs/mkdocs/docs/examples/custom_object_type.cpp index d4f99ccf0..9d4b43daf 100644 --- a/docs/mkdocs/docs/examples/custom_object_type.cpp +++ b/docs/mkdocs/docs/examples/custom_object_type.cpp @@ -1,12 +1,11 @@ #include #include -#include #include #include "custom_object_type.hpp" -using custom_json = nlohmann::basic_json; +using custom_json = nlohmann::json::with_object_t; int main() { diff --git a/docs/mkdocs/docs/examples/custom_string_type.cpp b/docs/mkdocs/docs/examples/custom_string_type.cpp index 63b798fc6..2e71c9378 100644 --- a/docs/mkdocs/docs/examples/custom_string_type.cpp +++ b/docs/mkdocs/docs/examples/custom_string_type.cpp @@ -1,12 +1,10 @@ #include -#include -#include #include #include "custom_string_type.hpp" -using custom_json = nlohmann::basic_json; +using custom_json = nlohmann::json::with_string_t; int main() { diff --git a/docs/mkdocs/docs/features/types/number_handling.md b/docs/mkdocs/docs/features/types/number_handling.md index bae315160..13b6dfd43 100644 --- a/docs/mkdocs/docs/features/types/number_handling.md +++ b/docs/mkdocs/docs/features/types/number_handling.md @@ -351,9 +351,8 @@ The number types can be changed with template parameters. A `basic_json` type that uses `#!c long double` as floating-point type. - ```cpp hl_lines="2" - using json_ld = nlohmann::basic_json; + ```cpp hl_lines="1" + using json_ld = nlohmann::json::with_float_t; ``` Note values should then be parsed with `json_ld::parse` rather than `json::parse` as the latter would parse diff --git a/docs/mkdocs/docs/features/types/template_parameters.md b/docs/mkdocs/docs/features/types/template_parameters.md index a83b7a56a..f20102878 100644 --- a/docs/mkdocs/docs/features/types/template_parameters.md +++ b/docs/mkdocs/docs/features/types/template_parameters.md @@ -8,6 +8,10 @@ these requirements so they do not have to be discovered by trial and error. Each that are known to work for that parameter and the ones that do not, checked against Boost 1.83, Abseil 20250127.0, Folly, EASTL 3.21, `ankerl::unordered_dense`, `phmap`, `gtl`, `robin_hood`, `tsl::ordered_map`, and Qt 6. +To change a single template parameter and keep the others, use the member alias templates +[`with_*_t`](../../api/basic_json/with_t.md); for instance, `nlohmann::json::with_float_t` is `json` with +`#!cpp long double` as [`number_float_t`](../../api/basic_json/number_float_t.md). + ## How to read this page Requirements are split into two groups: @@ -143,7 +147,7 @@ struct unordered_map_object using base_t::base_t; }; -using unordered_json = nlohmann::basic_json; +using unordered_json = nlohmann::json::with_object_t; ``` Whether `#!cpp std::unordered_map` can be instantiated at all depends on the standard library: `object_t` is formed @@ -176,7 +180,7 @@ struct flat_hash_object using base_t::base_t; }; -using flat_hash_json = nlohmann::basic_json; +using flat_hash_json = nlohmann::json::with_object_t; ``` `absl::node_hash_map` keeps references to the mapped values valid across insertions; `absl::flat_hash_map` does not, diff --git a/docs/mkdocs/docs/integration/migration_guide.md b/docs/mkdocs/docs/integration/migration_guide.md index 17b2eb360..bfbf850e0 100644 --- a/docs/mkdocs/docs/integration/migration_guide.md +++ b/docs/mkdocs/docs/integration/migration_guide.md @@ -116,7 +116,7 @@ function to use instead. === "Deprecated" ```cpp - using my_json = nlohmann::basic_json; + using my_json = nlohmann::json::with_string_t; nlohmann::json_pointer ptr("/foo/bar/1"); ``` diff --git a/tests/src/unit-allocator.cpp b/tests/src/unit-allocator.cpp index 07eb21c4e..4062870d5 100644 --- a/tests/src/unit-allocator.cpp +++ b/tests/src/unit-allocator.cpp @@ -370,14 +370,7 @@ TEST_CASE("copy of a deeply nested value survives a failing allocation (#5640)") #if !(defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0) SECTION("std::map-backed object_t") { - using bad_alloc_json = nlohmann::basic_json; + using bad_alloc_json = nlohmann::json::with_allocator_t; check_deep_copy_survives_failing_allocation(false); check_deep_copy_survives_failing_allocation(true); @@ -385,14 +378,7 @@ TEST_CASE("copy of a deeply nested value survives a failing allocation (#5640)") SECTION("ordered_map-backed object_t") { - using bad_alloc_ordered_json = nlohmann::basic_json; + using bad_alloc_ordered_json = nlohmann::ordered_json::with_allocator_t; check_deep_copy_survives_failing_allocation(false); check_deep_copy_survives_failing_allocation(true); @@ -450,14 +436,7 @@ struct scratch_counting_allocator : std::allocator TEST_CASE("deep copy uses the provided allocator") { - using counting_json = nlohmann::basic_json; + using counting_json = nlohmann::json::with_allocator_t; // deeper than the 128 levels the copy constructor descends into, so the // innermost objects are copied by the iterative deep copy @@ -516,14 +495,7 @@ TEST_CASE("converting a deeply nested value from another specialization fails cl // the allocator in noexcept constructors, so a failing construction crashes // the program there instead of throwing std::bad_alloc. Nothing to check. #if !(defined(_MSC_VER) && _MSC_VER < 1910 && defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0) - using countdown_json = nlohmann::basic_json; + using countdown_json = nlohmann::json::with_allocator_t; // deeper than the 128 levels the converting constructor descends into, so // that failures land on both sides of the bound - or, built with @@ -631,14 +603,7 @@ TEST_CASE("destructor performs no allocation, only deallocation") // Since that stack could itself throw bad_alloc from inside the // noexcept destructor (#5135), destroy() no longer allocates anything: // it only ever frees what is already there. - using counting_json = nlohmann::basic_json; + using counting_json = nlohmann::json::with_allocator_t; SECTION("array") { @@ -683,14 +648,7 @@ TEST_CASE("destructor performs no allocation, only deallocation") TEST_CASE("a failed allocation leaves the value unchanged") { // create JSON type using the throwing allocator - using my_json = nlohmann::basic_json; + using my_json = nlohmann::json::with_allocator_t; // Each of these creates a string, array, object, or binary value. The // value must be created before the type is changed: otherwise, a failed diff --git a/tests/src/unit-binary_formats.cpp b/tests/src/unit-binary_formats.cpp index 3e5edf765..03dbc94da 100644 --- a/tests/src/unit-binary_formats.cpp +++ b/tests/src/unit-binary_formats.cpp @@ -237,7 +237,7 @@ namespace // the binary formats as function pointers for "Binary formats with narrow number types"; // named functions rather than lambdas, because clang 3.5 cannot convert a lambda // to a function pointer in the braced initializer of the format table -using narrow_json = nlohmann::basic_json; +using narrow_json = nlohmann::json::with_integers_t::with_float_t; using bytes = std::vector; bytes encode_cbor(const json& j) diff --git a/tests/src/unit-comparison.cpp b/tests/src/unit-comparison.cpp index b9257cb17..1e409bfb0 100644 --- a/tests/src/unit-comparison.cpp +++ b/tests/src/unit-comparison.cpp @@ -824,7 +824,7 @@ struct unordered_object_t : std::map, Allocator> return !(lhs == rhs); } }; -using unordered_json = nlohmann::basic_json; +using unordered_json = nlohmann::json::with_object_t; // the entries "0" to "9", enumerated in ascending or in descending order unordered_json make_unordered_object(const bool descending) @@ -875,7 +875,7 @@ struct key_case_less template using key_case_map = std::map; -using key_case_json = nlohmann::basic_json; +using key_case_json = nlohmann::json::with_object_t; // the innermost value of a chain of single-element arrays template @@ -905,7 +905,7 @@ struct case_insensitive_less template using case_insensitive_map = std::map; -using ci_json = nlohmann::basic_json; +using ci_json = nlohmann::json::with_object_t; } // namespace TEST_CASE("equality of objects whose entries have no fixed order") diff --git a/tests/src/unit-custom-array-type.cpp b/tests/src/unit-custom-array-type.cpp index 7a374b612..6f015d234 100644 --- a/tests/src/unit-custom-array-type.cpp +++ b/tests/src/unit-custom-array-type.cpp @@ -22,7 +22,7 @@ namespace // std::deque has no capacity() member function, which the library only needs // to detect a reallocation for JSON_DIAGNOSTICS -using deque_json = nlohmann::basic_json; +using deque_json = nlohmann::json::with_array_t; // a std::vector whose at() is hidden: the library performs its own bounds // check and must not fall back to the container's checked accessor @@ -39,7 +39,7 @@ class vector_without_at : public std::vector void at() = delete; }; -using no_at_json = nlohmann::basic_json; +using no_at_json = nlohmann::json::with_array_t; } // namespace diff --git a/tests/src/unit-custom-object-type.cpp b/tests/src/unit-custom-object-type.cpp index 0eaaf2d3c..fdd063292 100644 --- a/tests/src/unit-custom-object-type.cpp +++ b/tests/src/unit-custom-object-type.cpp @@ -179,7 +179,7 @@ class no_key_compare_map } }; -using no_key_compare_json = nlohmann::basic_json; +using no_key_compare_json = nlohmann::json::with_object_t; // An ObjectType whose erase(iterator) returns void rather than the following // iterator, as for instance Abseil's hash maps do @@ -196,7 +196,7 @@ struct void_erase_map : std::map } }; -using void_erase_json = nlohmann::basic_json; +using void_erase_json = nlohmann::json::with_object_t; // wraps an iterator, but only offers the LegacyForwardIterator operations, // like the iterators of std::unordered_map and other hash maps @@ -388,7 +388,7 @@ class forward_only_map } }; -using forward_only_json = nlohmann::basic_json; +using forward_only_json = nlohmann::json::with_object_t; } // namespace diff --git a/tests/src/unit-hash.cpp b/tests/src/unit-hash.cpp index e70b39b5a..117bc0151 100644 --- a/tests/src/unit-hash.cpp +++ b/tests/src/unit-hash.cpp @@ -157,13 +157,13 @@ TEST_CASE("hash") // the ends of the integer ranges, which equal floats exactly const auto int_min = (std::numeric_limits::min)(); const auto int_max = (std::numeric_limits::max)(); - const auto two_63 = json::number_unsigned_t(1) << 63U; + const auto two_63 = static_cast(1) << 63U; CHECK(json(int_min) == json(-9223372036854775808.0)); CHECK(std::hash {}(json(int_min)) == std::hash {}(json(-9223372036854775808.0))); CHECK(json(two_63) == json(9223372036854775808.0)); CHECK(std::hash {}(json(two_63)) == std::hash {}(json(9223372036854775808.0))); - CHECK(json(json::number_unsigned_t(int_max)) == json(int_max)); - CHECK(std::hash {}(json(json::number_unsigned_t(int_max))) == std::hash {}(json(int_max))); + CHECK(json(static_cast(int_max)) == json(int_max)); + CHECK(std::hash {}(json(static_cast(int_max))) == std::hash {}(json(int_max))); } TEST_CASE("hash") diff --git a/tests/src/unit-large_json.cpp b/tests/src/unit-large_json.cpp index 6ce5626d8..cd615f4c3 100644 --- a/tests/src/unit-large_json.cpp +++ b/tests/src/unit-large_json.cpp @@ -426,7 +426,7 @@ TEST_CASE("issue #5392 - binary writers on deeply nested values") { for (std::size_t depth = 120; depth <= 140; ++depth) { - CAPTURE(depth); + CAPTURE(depth) const json array = nested_array(depth, json(7)); CHECK(json::from_cbor(json::to_cbor(array)) == array); @@ -464,7 +464,7 @@ TEST_CASE("issue #5392 - binary writers on deeply nested values") nlohmann::detail::recursion_depth_limit() + 1, nlohmann::detail::recursion_depth_limit() + 2 }) { - CAPTURE(depth); + CAPTURE(depth) const json array = nested_array(depth, json(0)); std::vector expected_cbor(depth, 0x81); @@ -505,10 +505,22 @@ TEST_CASE("issue #5392 - binary writers on deeply nested values") const json discarded_leaf(json::value_t::discarded); const json deep_discarded = nested_array(depth, discarded_leaf); - CHECK_THROWS_WITH_AS(json::to_cbor(deep_discarded), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error); - CHECK_THROWS_WITH_AS(json::to_msgpack(deep_discarded), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error); - CHECK_THROWS_WITH_AS(json::to_ubjson(deep_discarded), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error); - CHECK_THROWS_WITH_AS(json::to_bjdata(deep_discarded), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error); + // with diagnostics, the message names the path to the discarded leaf +#if JSON_DIAGNOSTICS + std::string path; + for (std::size_t i = 0; i < depth; ++i) + { + path += "/0"; + } + const std::string prefix = "[json.exception.type_error.321] (" + path + ") "; +#else + const std::string prefix = "[json.exception.type_error.321] "; +#endif + + CHECK_THROWS_WITH_AS(json::to_cbor(deep_discarded), (prefix + "cannot serialize discarded value to CBOR").c_str(), json::type_error); + CHECK_THROWS_WITH_AS(json::to_msgpack(deep_discarded), (prefix + "cannot serialize discarded value to MessagePack").c_str(), json::type_error); + CHECK_THROWS_WITH_AS(json::to_ubjson(deep_discarded), (prefix + "cannot serialize discarded value to UBJSON").c_str(), json::type_error); + CHECK_THROWS_WITH_AS(json::to_bjdata(deep_discarded), (prefix + "cannot serialize discarded value to BJData").c_str(), json::type_error); } SECTION("does not overflow the C++ stack") diff --git a/tests/src/unit-locale-cpp.cpp b/tests/src/unit-locale-cpp.cpp index 626296825..4865e5687 100644 --- a/tests/src/unit-locale-cpp.cpp +++ b/tests/src/unit-locale-cpp.cpp @@ -172,7 +172,7 @@ TEST_CASE("locale-dependent test (LC_NUMERIC=de_DE)") // a floating-point type that is not a float or a double is written // with snprintf, whose locale-specific decimal point and thousands // separator are undone afterwards - using long_double_json = nlohmann::basic_json; + using long_double_json = nlohmann::json::with_float_t; CHECK(long_double_json(12345.5L).dump() == "12345.5"); CHECK(long_double_json(1.0L).dump() == "1.0"); CHECK(long_double_json(-0.25L).dump() == "-0.25"); @@ -272,7 +272,7 @@ TEST_CASE("locale changes between lexer construction and number conversion (#519 } text += "]"; - using long_double_json = nlohmann::basic_json; + using long_double_json = nlohmann::json::with_float_t; // reference values, parsed without a locale switch REQUIRE(std::setlocale(LC_NUMERIC, "C") != nullptr); @@ -432,7 +432,7 @@ TEST_CASE("locale changes during a single dump() (#5709 item 3)") // long double on 64-bit Arm, where it is IEEE-754 double) takes the // locale-independent to_chars() path instead, and this test is a no-op // there. - using long_double_json = nlohmann::basic_json; + using long_double_json = nlohmann::json::with_float_t; using ld_limits = std::numeric_limits; const bool is_ieee_single_or_double = (ld_limits::is_iec559 && ld_limits::digits == 24 && ld_limits::max_exponent == 128) || diff --git a/tests/src/unit-msgpack.cpp b/tests/src/unit-msgpack.cpp index b705f94b7..d29769738 100644 --- a/tests/src/unit-msgpack.cpp +++ b/tests/src/unit-msgpack.cpp @@ -2432,8 +2432,8 @@ TEST_CASE("MessagePack numbers use the active union member (see #5644)") // used to read the union member that was not the active one, writing // wrong bytes for some values; std::int64_t/std::uint64_t (the default // types, where both members have the same width) were not affected - using int32_json = nlohmann::basic_json; - using int16_json = nlohmann::basic_json; + using int32_json = nlohmann::json::with_integers_t; + using int16_json = nlohmann::json::with_integers_t; SECTION("number_integer_t = std::int32_t") { diff --git a/tests/src/unit-regression1.cpp b/tests/src/unit-regression1.cpp index 8f0d4c747..084eb79fd 100644 --- a/tests/src/unit-regression1.cpp +++ b/tests/src/unit-regression1.cpp @@ -39,7 +39,7 @@ using nlohmann::json; template using my_workaround_fifo_map = nlohmann::fifo_map, A>; -using my_json = nlohmann::basic_json; +using my_json = nlohmann::json::with_object_t; ///////////////////////////////////////////////////////////////////// // for #977 @@ -86,8 +86,7 @@ struct foo_serializer < T, typename std::enable_if < !std::is_same::valu }; } // namespace ns -using foo_json = nlohmann::basic_json>; +using foo_json = nlohmann::json::with_json_serializer_t; ///////////////////////////////////////////////////////////////////// // for #805 @@ -254,7 +253,7 @@ TEST_CASE("regression tests 1") { // create JSON class with nonstandard integer number type using custom_json = - nlohmann::basic_json; + nlohmann::json::with_integers_t::with_float_t; custom_json j; j["int_1"] = 1; CHECK(j["int_1"] == 1); @@ -470,18 +469,17 @@ TEST_CASE("regression tests 1") // create JSON class with nonstandard float number type // float - nlohmann::basic_json const j_float = + nlohmann::json::with_integers_t::with_float_t const j_float = 1.23e25f; CHECK(j_float.get() == 1.23e25f); // double - nlohmann::basic_json const j_double = + nlohmann::json const j_double = 1.23e35; CHECK(j_double.get() == 1.23e35); // long double - nlohmann::basic_json - const j_long_double = 1.23e45L; + nlohmann::json::with_float_t const j_long_double = 1.23e45L; CHECK(j_long_double.get() == 1.23e45L); } diff --git a/tests/src/unit-regression2.cpp b/tests/src/unit-regression2.cpp index d084e6432..b532e75ae 100644 --- a/tests/src/unit-regression2.cpp +++ b/tests/src/unit-regression2.cpp @@ -64,18 +64,7 @@ using ordered_json = nlohmann::ordered_json; ///////////////////////////////////////////////////////////////////// // for #4804 ///////////////////////////////////////////////////////////////////// - using json_4804 = nlohmann::basic_json, // BinaryType - void // CustomBaseClass - >; + using json_4804 = nlohmann::json::with_binary_t>; #endif #ifdef JSON_HAS_CPP_20 @@ -107,7 +96,7 @@ DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors") // for #1021 ///////////////////////////////////////////////////////////////////// -using float_json = nlohmann::basic_json; +using float_json = nlohmann::json::with_float_t; #if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) namespace @@ -155,10 +144,8 @@ struct failing_allocator : std::allocator }; }; -using failing_json = nlohmann::basic_json; -using failing_ordered_json = nlohmann::basic_json; +using failing_json = nlohmann::json::with_allocator_t; +using failing_ordered_json = nlohmann::ordered_json::with_allocator_t; // builds `depth` levels of nesting around a scalar, iteratively (never // recursing: each wrap only moves the previous, already-built value, which diff --git a/tests/src/unit-regression3.cpp b/tests/src/unit-regression3.cpp index 69a95fb9b..f8d9740fa 100644 --- a/tests/src/unit-regression3.cpp +++ b/tests/src/unit-regression3.cpp @@ -62,18 +62,7 @@ using ordered_json = nlohmann::ordered_json; ///////////////////////////////////////////////////////////////////// // for #4804 ///////////////////////////////////////////////////////////////////// - using json_4804 = nlohmann::basic_json, // BinaryType - void // CustomBaseClass - >; + using json_4804 = nlohmann::json::with_binary_t>; #endif #ifdef JSON_HAS_CPP_20 @@ -930,7 +919,7 @@ TEST_CASE("regression test #5476 - array type without reserve()") { // the capacity reserved for definite-length arrays must not require the // array type to have a reserve() member function - using deque_json = nlohmann::basic_json; + using deque_json = nlohmann::json::with_array_t; SECTION("std::deque") { diff --git a/tests/src/unit-serialization.cpp b/tests/src/unit-serialization.cpp index 1aa2111e7..c56c4f5c1 100644 --- a/tests/src/unit-serialization.cpp +++ b/tests/src/unit-serialization.cpp @@ -367,8 +367,7 @@ TEST_CASE("dump for basic_json with long double number_float_t") // serializer::dump_float(x, std::false_type). That branch must use the // "%.*Lg" format specifier; using "%.*g" with a long double argument is // undefined behavior and corrupts the output. - using long_double_json = nlohmann::basic_json; + using long_double_json = nlohmann::json::with_float_t; SECTION("round-trip dump/parse") {