Merge branch 'develop' into json-view/02b-float-parser

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

# Conflicts:
#	tests/src/unit-class_lexer.cpp
diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml
index 1fa2f9f..3c94bc1 100644
--- a/.github/workflows/ubuntu.yml
+++ b/.github/workflows/ubuntu.yml
@@ -31,6 +31,51 @@
       - 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 fed9e31..19f5dca 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -189,7 +189,39 @@
     )
 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 861fdf6..fbb5781 100644
--- a/FILES.md
+++ b/FILES.md
@@ -249,9 +249,31 @@
 
 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 11b9227..093fbb7 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 @@
 	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 @@
 # We use `-X` to make the resulting ZIP file reproducible, see
 # <https://content.pivotal.io/blog/barriers-to-deterministic-reproducible-zip-files>.
 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 0000000..b2e57a5
--- /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 21a91a3..70407f0 100644
--- a/cmake/pkg-config.pc.in
+++ b/cmake/pkg-config.pc.in
@@ -4,4 +4,4 @@
 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/api/macros/json_use_implicit_conversions.md b/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md
index 675a8ac..2e77813 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 @@
     `JSON_USE_IMPLICIT_CONVERSIONS` is defined to `0`:
 
     ```cpp
-    using wjson = nlohmann::basic_json<std::map, std::vector, std::wstring>;
+    using wjson = nlohmann::json::with_string_t<std::wstring>;
 
     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 63651ca..9765c51 100644
--- a/docs/mkdocs/docs/examples/custom_array_type.cpp
+++ b/docs/mkdocs/docs/examples/custom_array_type.cpp
@@ -1,11 +1,10 @@
 #include <iostream>
-#include <map>
 
 #include <nlohmann/json.hpp>
 
 #include "custom_array_type.hpp"
 
-using custom_json = nlohmann::basic_json<std::map, custom_array_type>;
+using custom_json = nlohmann::json::with_array_t<custom_array_type>;
 
 int main()
 {
diff --git a/docs/mkdocs/docs/examples/custom_binary_type.cpp b/docs/mkdocs/docs/examples/custom_binary_type.cpp
index cea34ea..0a835d4 100644
--- a/docs/mkdocs/docs/examples/custom_binary_type.cpp
+++ b/docs/mkdocs/docs/examples/custom_binary_type.cpp
@@ -1,16 +1,10 @@
-#include <cstdint>
 #include <iostream>
-#include <map>
-#include <string>
-#include <vector>
 
 #include <nlohmann/json.hpp>
 
 #include "custom_binary_type.hpp"
 
-using custom_json = nlohmann::basic_json<std::map, std::vector, std::string, bool,
-      std::int64_t, std::uint64_t, double, std::allocator,
-      nlohmann::adl_serializer, custom_binary_type>;
+using custom_json = nlohmann::json::with_binary_t<custom_binary_type>;
 
 int main()
 {
diff --git a/docs/mkdocs/docs/examples/custom_object_type.cpp b/docs/mkdocs/docs/examples/custom_object_type.cpp
index d4f99cc..9d4b43d 100644
--- a/docs/mkdocs/docs/examples/custom_object_type.cpp
+++ b/docs/mkdocs/docs/examples/custom_object_type.cpp
@@ -1,12 +1,11 @@
 #include <iostream>
 #include <type_traits>
-#include <vector>
 
 #include <nlohmann/json.hpp>
 
 #include "custom_object_type.hpp"
 
-using custom_json = nlohmann::basic_json<custom_object_type, std::vector>;
+using custom_json = nlohmann::json::with_object_t<custom_object_type>;
 
 int main()
 {
diff --git a/docs/mkdocs/docs/examples/custom_string_type.cpp b/docs/mkdocs/docs/examples/custom_string_type.cpp
index 63b798f..2e71c93 100644
--- a/docs/mkdocs/docs/examples/custom_string_type.cpp
+++ b/docs/mkdocs/docs/examples/custom_string_type.cpp
@@ -1,12 +1,10 @@
 #include <iostream>
-#include <map>
-#include <vector>
 
 #include <nlohmann/json.hpp>
 
 #include "custom_string_type.hpp"
 
-using custom_json = nlohmann::basic_json<std::map, std::vector, custom_string_type>;
+using custom_json = nlohmann::json::with_string_t<custom_string_type>;
 
 int main()
 {
diff --git a/docs/mkdocs/docs/features/types/number_handling.md b/docs/mkdocs/docs/features/types/number_handling.md
index 8bcf2dd..7a87352 100644
--- a/docs/mkdocs/docs/features/types/number_handling.md
+++ b/docs/mkdocs/docs/features/types/number_handling.md
@@ -352,9 +352,8 @@
 
     A `basic_json` type that uses `#!c long double` as floating-point type.
 
-    ```cpp hl_lines="2"
-    using json_ld = nlohmann::basic_json<std::map, std::vector, std::string, bool,
-                                         std::int64_t, std::uint64_t, long double>;
+    ```cpp hl_lines="1"
+    using json_ld = nlohmann::json::with_float_t<long double>;
     ```
 
     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 591d49c..3a24d74 100644
--- a/docs/mkdocs/docs/features/types/template_parameters.md
+++ b/docs/mkdocs/docs/features/types/template_parameters.md
@@ -8,6 +8,10 @@
 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<long double>` 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 @@
     using base_t::base_t;
 };
 
-using unordered_json = nlohmann::basic_json<unordered_map_object>;
+using unordered_json = nlohmann::json::with_object_t<unordered_map_object>;
 ```
 
 Whether `#!cpp std::unordered_map` can be instantiated at all depends on the standard library: `object_t` is formed
@@ -176,7 +180,7 @@
     using base_t::base_t;
 };
 
-using flat_hash_json = nlohmann::basic_json<flat_hash_object>;
+using flat_hash_json = nlohmann::json::with_object_t<flat_hash_object>;
 ```
 
 `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 17b2eb3..bfbf850 100644
--- a/docs/mkdocs/docs/integration/migration_guide.md
+++ b/docs/mkdocs/docs/integration/migration_guide.md
@@ -116,7 +116,7 @@
     === "Deprecated"
   
           ```cpp
-          using my_json = nlohmann::basic_json<std::map, std::vector, my_string_type>;
+          using my_json = nlohmann::json::with_string_t<my_string_type>;
           nlohmann::json_pointer<my_json> ptr("/foo/bar/1");
           ```
   
diff --git a/docs/mkdocs/docs/integration/package_managers.md b/docs/mkdocs/docs/integration/package_managers.md
index d5357b8..bfa9943 100644
--- a/docs/mkdocs/docs/integration/package_managers.md
+++ b/docs/mkdocs/docs/integration/package_managers.md
@@ -125,10 +125,24 @@
 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/include/nlohmann/detail/bit_ops.hpp b/include/nlohmann/detail/bit_ops.hpp
index 48c5eda..c570814 100644
--- a/include/nlohmann/detail/bit_ops.hpp
+++ b/include/nlohmann/detail/bit_ops.hpp
@@ -9,12 +9,15 @@
 #pragma once
 
 #include <cstdint> // uint64_t
+#if !defined(__SIZEOF_INT128__) && defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64))
+    #include <intrin0.h> // __umulh, _umul128
+#endif
 
 #include <nlohmann/detail/abi_macros.hpp>
 
 // 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
@@ -71,6 +74,12 @@
     __extension__ using uint128 = unsigned __int128;
     const uint128 r = static_cast<uint128>(a) * b;
     return {static_cast<std::uint64_t>(r), static_cast<std::uint64_t>(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/meson.build b/meson.build
index 2800957..19f7347 100644
--- a/meson.build
+++ b/meson.build
@@ -2,24 +2,120 @@
     '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 0000000..d4bfb20
--- /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/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp
index 64fa63b..93ffdd2 100644
--- a/single_include/nlohmann/json.hpp
+++ b/single_include/nlohmann/json.hpp
@@ -8793,13 +8793,16 @@
 
 
 #include <cstdint> // uint64_t
+#if !defined(__SIZEOF_INT128__) && defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64))
+    #include <intrin0.h> // __umulh, _umul128
+#endif
 
 // #include <nlohmann/detail/abi_macros.hpp>
 
 
 // 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
@@ -8856,6 +8859,12 @@
     __extension__ using uint128 = unsigned __int128;
     const uint128 r = static_cast<uint128>(a) * b;
     return {static_cast<std::uint64_t>(r), static_cast<std::uint64_t>(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-allocator.cpp b/tests/src/unit-allocator.cpp
index 07eb21c..4062870 100644
--- a/tests/src/unit-allocator.cpp
+++ b/tests/src/unit-allocator.cpp
@@ -370,14 +370,7 @@
 #if !(defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0)
     SECTION("std::map-backed object_t")
     {
-        using bad_alloc_json = nlohmann::basic_json<std::map,
-              std::vector,
-              std::string,
-              bool,
-              std::int64_t,
-              std::uint64_t,
-              double,
-              nth_alloc_fails_allocator>;
+        using bad_alloc_json = nlohmann::json::with_allocator_t<nth_alloc_fails_allocator>;
 
         check_deep_copy_survives_failing_allocation<bad_alloc_json>(false);
         check_deep_copy_survives_failing_allocation<bad_alloc_json>(true);
@@ -385,14 +378,7 @@
 
     SECTION("ordered_map-backed object_t")
     {
-        using bad_alloc_ordered_json = nlohmann::basic_json<nlohmann::ordered_map,
-              std::vector,
-              std::string,
-              bool,
-              std::int64_t,
-              std::uint64_t,
-              double,
-              nth_alloc_fails_allocator>;
+        using bad_alloc_ordered_json = nlohmann::ordered_json::with_allocator_t<nth_alloc_fails_allocator>;
 
         check_deep_copy_survives_failing_allocation<bad_alloc_ordered_json>(false);
         check_deep_copy_survives_failing_allocation<bad_alloc_ordered_json>(true);
@@ -450,14 +436,7 @@
 
 TEST_CASE("deep copy uses the provided allocator")
 {
-    using counting_json = nlohmann::basic_json<std::map,
-          std::vector,
-          std::string,
-          bool,
-          std::int64_t,
-          std::uint64_t,
-          double,
-          scratch_counting_allocator>;
+    using counting_json = nlohmann::json::with_allocator_t<scratch_counting_allocator>;
 
     // 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 @@
     // 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<std::map,
-          std::vector,
-          std::string,
-          bool,
-          std::int64_t,
-          std::uint64_t,
-          double,
-          countdown_allocator>;
+    using countdown_json = nlohmann::json::with_allocator_t<countdown_allocator>;
 
     // 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 @@
     // 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<std::map,
-          std::vector,
-          std::string,
-          bool,
-          std::int64_t,
-          std::uint64_t,
-          double,
-          counting_allocator>;
+    using counting_json = nlohmann::json::with_allocator_t<counting_allocator>;
 
     SECTION("array")
     {
@@ -683,14 +648,7 @@
 TEST_CASE("a failed allocation leaves the value unchanged")
 {
     // create JSON type using the throwing allocator
-    using my_json = nlohmann::basic_json<std::map,
-          std::vector,
-          std::string,
-          bool,
-          std::int64_t,
-          std::uint64_t,
-          double,
-          my_allocator>;
+    using my_json = nlohmann::json::with_allocator_t<my_allocator>;
 
     // 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 3e5edf7..03dbc94 100644
--- a/tests/src/unit-binary_formats.cpp
+++ b/tests/src/unit-binary_formats.cpp
@@ -237,7 +237,7 @@
 // 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<std::map, std::vector, std::string, bool, std::int32_t, std::uint32_t, float>;
+using narrow_json = nlohmann::json::with_integers_t<std::int32_t, std::uint32_t>::with_float_t<float>;
 using bytes = std::vector<std::uint8_t>;
 
 bytes encode_cbor(const json& j)
diff --git a/tests/src/unit-class_lexer.cpp b/tests/src/unit-class_lexer.cpp
index 3a84208..82690a6 100644
--- a/tests/src/unit-class_lexer.cpp
+++ b/tests/src/unit-class_lexer.cpp
@@ -17,6 +17,7 @@
 #include <cstdio> // snprintf
 #include <cstdlib> // strtod
 #include <cstring> // memcpy
+#include <limits> // numeric_limits
 #include <map> // map
 #include <random> // mt19937
 #include <sstream> // stringstream
@@ -1136,8 +1137,39 @@
 
     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<std::uint64_t>::max)();
+        const std::array<std::pair<std::uint64_t, std::uint64_t>, 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)
         {
@@ -1146,8 +1178,7 @@
             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));
diff --git a/tests/src/unit-comparison.cpp b/tests/src/unit-comparison.cpp
index b9257cb..1e409bf 100644
--- a/tests/src/unit-comparison.cpp
+++ b/tests/src/unit-comparison.cpp
@@ -824,7 +824,7 @@
         return !(lhs == rhs);
     }
 };
-using unordered_json = nlohmann::basic_json<unordered_object_t>;
+using unordered_json = nlohmann::json::with_object_t<unordered_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 @@
 
 template<class Key, class Value, class /*Compare*/, class Allocator>
 using key_case_map = std::map<Key, Value, key_case_less, Allocator>;
-using key_case_json = nlohmann::basic_json<key_case_map>;
+using key_case_json = nlohmann::json::with_object_t<key_case_map>;
 
 // the innermost value of a chain of single-element arrays
 template<typename Json>
@@ -905,7 +905,7 @@
 
 template<class Key, class Value, class /*Compare*/, class Allocator>
 using case_insensitive_map = std::map<Key, Value, case_insensitive_less, Allocator>;
-using ci_json = nlohmann::basic_json<case_insensitive_map>;
+using ci_json = nlohmann::json::with_object_t<case_insensitive_map>;
 } // 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 7a374b6..6f015d2 100644
--- a/tests/src/unit-custom-array-type.cpp
+++ b/tests/src/unit-custom-array-type.cpp
@@ -22,7 +22,7 @@
 
 // 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<std::map, std::deque>;
+using deque_json = nlohmann::json::with_array_t<std::deque>;
 
 // 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 @@
     void at() = delete;
 };
 
-using no_at_json = nlohmann::basic_json<std::map, vector_without_at>;
+using no_at_json = nlohmann::json::with_array_t<vector_without_at>;
 
 } // namespace
 
diff --git a/tests/src/unit-custom-object-type.cpp b/tests/src/unit-custom-object-type.cpp
index 0eaaf2d..fdd0632 100644
--- a/tests/src/unit-custom-object-type.cpp
+++ b/tests/src/unit-custom-object-type.cpp
@@ -179,7 +179,7 @@
     }
 };
 
-using no_key_compare_json = nlohmann::basic_json<no_key_compare_map>;
+using no_key_compare_json = nlohmann::json::with_object_t<no_key_compare_map>;
 
 // An ObjectType whose erase(iterator) returns void rather than the following
 // iterator, as for instance Abseil's hash maps do
@@ -196,7 +196,7 @@
     }
 };
 
-using void_erase_json = nlohmann::basic_json<void_erase_map>;
+using void_erase_json = nlohmann::json::with_object_t<void_erase_map>;
 
 // wraps an iterator, but only offers the LegacyForwardIterator operations,
 // like the iterators of std::unordered_map and other hash maps
@@ -388,7 +388,7 @@
     }
 };
 
-using forward_only_json = nlohmann::basic_json<forward_only_map>;
+using forward_only_json = nlohmann::json::with_object_t<forward_only_map>;
 
 } // namespace
 
diff --git a/tests/src/unit-hash.cpp b/tests/src/unit-hash.cpp
index e70b39b..117bc01 100644
--- a/tests/src/unit-hash.cpp
+++ b/tests/src/unit-hash.cpp
@@ -157,13 +157,13 @@
     // the ends of the integer ranges, which equal floats exactly
     const auto int_min = (std::numeric_limits<json::number_integer_t>::min)();
     const auto int_max = (std::numeric_limits<json::number_integer_t>::max)();
-    const auto two_63 = json::number_unsigned_t(1) << 63U;
+    const auto two_63 = static_cast<json::number_unsigned_t>(1) << 63U;
     CHECK(json(int_min) == json(-9223372036854775808.0));
     CHECK(std::hash<json> {}(json(int_min)) == std::hash<json> {}(json(-9223372036854775808.0)));
     CHECK(json(two_63) == json(9223372036854775808.0));
     CHECK(std::hash<json> {}(json(two_63)) == std::hash<json> {}(json(9223372036854775808.0)));
-    CHECK(json(json::number_unsigned_t(int_max)) == json(int_max));
-    CHECK(std::hash<json> {}(json(json::number_unsigned_t(int_max))) == std::hash<json> {}(json(int_max)));
+    CHECK(json(static_cast<json::number_unsigned_t>(int_max)) == json(int_max));
+    CHECK(std::hash<json> {}(json(static_cast<json::number_unsigned_t>(int_max))) == std::hash<json> {}(json(int_max)));
 }
 
 TEST_CASE("hash<nlohmann::ordered_json>")
diff --git a/tests/src/unit-large_json.cpp b/tests/src/unit-large_json.cpp
index 6ce5626..cd615f4 100644
--- a/tests/src/unit-large_json.cpp
+++ b/tests/src/unit-large_json.cpp
@@ -426,7 +426,7 @@
     {
         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 @@
                     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<std::uint8_t> expected_cbor(depth, 0x81);
@@ -505,10 +505,22 @@
         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 5f5e78c..b665b65 100644
--- a/tests/src/unit-locale-cpp.cpp
+++ b/tests/src/unit-locale-cpp.cpp
@@ -172,7 +172,7 @@
             // 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<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, long double>;
+            using long_double_json = nlohmann::json::with_float_t<long double>;
             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");
@@ -273,7 +273,7 @@
     }
     text += "]";
 
-    using long_double_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, long double>;
+    using long_double_json = nlohmann::json::with_float_t<long double>;
 
     // reference values, parsed without a locale switch
     REQUIRE(std::setlocale(LC_NUMERIC, "C") != nullptr);
@@ -449,7 +449,7 @@
     // 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<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, long double>;
+    using long_double_json = nlohmann::json::with_float_t<long double>;
     using ld_limits = std::numeric_limits<long_double_json::number_float_t>;
     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 b705f94..d297697 100644
--- a/tests/src/unit-msgpack.cpp
+++ b/tests/src/unit-msgpack.cpp
@@ -2432,8 +2432,8 @@
     // 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<std::map, std::vector, std::string, bool, std::int32_t, std::uint64_t, double>;
-    using int16_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int16_t, std::uint64_t, double>;
+    using int32_json = nlohmann::json::with_integers_t<std::int32_t, std::uint64_t>;
+    using int16_json = nlohmann::json::with_integers_t<std::int16_t, std::uint64_t>;
 
     SECTION("number_integer_t = std::int32_t")
     {
diff --git a/tests/src/unit-regression1.cpp b/tests/src/unit-regression1.cpp
index 8f0d4c7..084eb79 100644
--- a/tests/src/unit-regression1.cpp
+++ b/tests/src/unit-regression1.cpp
@@ -39,7 +39,7 @@
 
 template<class K, class V, class dummy_compare, class A>
 using my_workaround_fifo_map = nlohmann::fifo_map<K, V, nlohmann::fifo_map_compare<K>, A>;
-using my_json = nlohmann::basic_json<my_workaround_fifo_map>;
+using my_json = nlohmann::json::with_object_t<my_workaround_fifo_map>;
 
 /////////////////////////////////////////////////////////////////////
 // for #977
@@ -86,8 +86,7 @@
 };
 } // namespace ns
 
-using foo_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t,
-      std::uint64_t, double, std::allocator, ns::foo_serializer, std::vector<std::uint8_t>>;
+using foo_json = nlohmann::json::with_json_serializer_t<ns::foo_serializer>;
 
 /////////////////////////////////////////////////////////////////////
 // for #805
@@ -254,7 +253,7 @@
     {
         // create JSON class with nonstandard integer number type
         using custom_json =
-            nlohmann::basic_json<std::map, std::vector, std::string, bool, int32_t, uint32_t, float>;
+            nlohmann::json::with_integers_t<std::int32_t, std::uint32_t>::with_float_t<float>;
         custom_json j;
         j["int_1"] = 1;
         CHECK(j["int_1"] == 1);
@@ -470,18 +469,17 @@
         // create JSON class with nonstandard float number type
 
         // float
-        nlohmann::basic_json<std::map, std::vector, std::string, bool, int32_t, uint32_t, float> const j_float =
+        nlohmann::json::with_integers_t<std::int32_t, std::uint32_t>::with_float_t<float> const j_float =
             1.23e25f;
         CHECK(j_float.get<float>() == 1.23e25f);
 
         // double
-        nlohmann::basic_json<std::map, std::vector, std::string, bool, int64_t, uint64_t, double> const j_double =
+        nlohmann::json const j_double =
             1.23e35;
         CHECK(j_double.get<double>() == 1.23e35);
 
         // long double
-        nlohmann::basic_json<std::map, std::vector, std::string, bool, int64_t, uint64_t, long double>
-        const j_long_double = 1.23e45L;
+        nlohmann::json::with_float_t<long double> const j_long_double = 1.23e45L;
         CHECK(j_long_double.get<long double>() == 1.23e45L);
     }
 
diff --git a/tests/src/unit-regression2.cpp b/tests/src/unit-regression2.cpp
index d084e64..b532e75 100644
--- a/tests/src/unit-regression2.cpp
+++ b/tests/src/unit-regression2.cpp
@@ -64,18 +64,7 @@
     /////////////////////////////////////////////////////////////////////
     // for #4804
     /////////////////////////////////////////////////////////////////////
-    using json_4804 = nlohmann::basic_json<std::map,        // ObjectType
-    std::vector,     // ArrayType
-    std::string,     // StringType
-    bool,            // BooleanType
-    std::int64_t,    // NumberIntegerType
-    std::uint64_t,   // NumberUnsignedType
-    double,          // NumberFloatType
-    std::allocator,  // AllocatorType
-    nlohmann::adl_serializer,  // JSONSerializer
-    std::vector<std::byte>,    // BinaryType
-    void                       // CustomBaseClass
-    >;
+    using json_4804 = nlohmann::json::with_binary_t<std::vector<std::byte>>;
 #endif
 
 #ifdef JSON_HAS_CPP_20
@@ -107,7 +96,7 @@
 // for #1021
 /////////////////////////////////////////////////////////////////////
 
-using float_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>;
+using float_json = nlohmann::json::with_float_t<float>;
 
 #if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
 namespace
@@ -155,10 +144,8 @@
     };
 };
 
-using failing_json = nlohmann::basic_json<std::map, std::vector, std::string, bool,
-      std::int64_t, std::uint64_t, double, failing_allocator>;
-using failing_ordered_json = nlohmann::basic_json<nlohmann::ordered_map, std::vector, std::string, bool,
-      std::int64_t, std::uint64_t, double, failing_allocator>;
+using failing_json = nlohmann::json::with_allocator_t<failing_allocator>;
+using failing_ordered_json = nlohmann::ordered_json::with_allocator_t<failing_allocator>;
 
 // 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 69a95fb..f8d9740 100644
--- a/tests/src/unit-regression3.cpp
+++ b/tests/src/unit-regression3.cpp
@@ -62,18 +62,7 @@
     /////////////////////////////////////////////////////////////////////
     // for #4804
     /////////////////////////////////////////////////////////////////////
-    using json_4804 = nlohmann::basic_json<std::map,        // ObjectType
-    std::vector,     // ArrayType
-    std::string,     // StringType
-    bool,            // BooleanType
-    std::int64_t,    // NumberIntegerType
-    std::uint64_t,   // NumberUnsignedType
-    double,          // NumberFloatType
-    std::allocator,  // AllocatorType
-    nlohmann::adl_serializer,  // JSONSerializer
-    std::vector<std::byte>,    // BinaryType
-    void                       // CustomBaseClass
-    >;
+    using json_4804 = nlohmann::json::with_binary_t<std::vector<std::byte>>;
 #endif
 
 #ifdef JSON_HAS_CPP_20
@@ -930,7 +919,7 @@
 {
     // 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<std::map, std::deque>;
+    using deque_json = nlohmann::json::with_array_t<std::deque>;
 
     SECTION("std::deque")
     {
diff --git a/tests/src/unit-serialization.cpp b/tests/src/unit-serialization.cpp
index 1aa2111..c56c4f5 100644
--- a/tests/src/unit-serialization.cpp
+++ b/tests/src/unit-serialization.cpp
@@ -367,8 +367,7 @@
     // 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<std::map, std::vector, std::string,
-          bool, std::int64_t, std::uint64_t, long double>;
+    using long_double_json = nlohmann::json::with_float_t<long double>;
 
     SECTION("round-trip dump/parse")
     {
diff --git a/tools/check_build_options/README.md b/tools/check_build_options/README.md
new file mode 100644
index 0000000..1b24a55
--- /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 0000000..9b9debf
--- /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'\$<\$<NOT:\$<BOOL:\$\{JSON_(\w+)\}>>:(\w+=\w+)>', line)
+        if m:
+            result[m.group(1)] = (m.group(2), False)
+            continue
+        m = re.fullmatch(r'\$<\$<BOOL:\$\{JSON_(\w+)\}>:(\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_<name>."""
+    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())