From dc4a70e7b4af0e28bb4a318ad36e57e6e510a6fc Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Mon, 28 Sep 2026 21:23:52 +0200 Subject: [PATCH] Add JSON_NO_UDLS to leave out the user-defined string literals The bodies of operator""_json and operator""_json_pointer call the parser, so every translation unit including the library instantiates it, even if it never parses anything. Defining JSON_NO_UDLS leaves the literals out entirely, which saves 15-35% compile time for such translation units (#5294). Nothing changes if the macro is not defined. Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/api/macros/index.md | 1 + docs/mkdocs/docs/api/macros/json_no_udls.md | 61 +++++++++++++++ .../docs/api/macros/json_use_global_udls.md | 5 ++ docs/mkdocs/docs/api/operator_literal_json.md | 4 +- .../docs/api/operator_literal_json_pointer.md | 4 +- docs/mkdocs/docs/features/macros.md | 8 ++ docs/mkdocs/mkdocs.yml | 1 + include/nlohmann/json.hpp | 4 +- single_include/nlohmann/json.hpp | 4 +- src/modules/json.cppm | 2 + tests/src/unit-no_udls.cpp | 77 +++++++++++++++++++ 11 files changed, 167 insertions(+), 4 deletions(-) create mode 100644 docs/mkdocs/docs/api/macros/json_no_udls.md create mode 100644 tests/src/unit-no_udls.cpp diff --git a/docs/mkdocs/docs/api/macros/index.md b/docs/mkdocs/docs/api/macros/index.md index bf773b5c4..a59af1eaa 100644 --- a/docs/mkdocs/docs/api/macros/index.md +++ b/docs/mkdocs/docs/api/macros/index.md @@ -30,6 +30,7 @@ header. See also the [macro overview page](../../features/macros.md). - [**JSON_HAS_THREE_WAY_COMPARISON**](json_has_three_way_comparison.md) - control 3-way comparison support - [**JSON_NO_IO**](json_no_io.md) - switch off functions relying on certain C++ I/O headers - [**JSON_NO_THREAD_LOCAL**](json_no_thread_local.md) - switch off the use of `thread_local` storage +- [**JSON_NO_UDLS**](json_no_udls.md) - leave out the user-defined string literals (UDLs) to reduce compile time - [**JSON_SKIP_UNSUPPORTED_COMPILER_CHECK**](json_skip_unsupported_compiler_check.md) - do not warn about unsupported compilers - [**JSON_USE_GLOBAL_UDLS**](json_use_global_udls.md) - place user-defined string literals (UDLs) into the global namespace - [**JSON_USE_SIMDUTF**](json_use_simdutf.md) - use the simdutf library to accelerate UTF-8 validation diff --git a/docs/mkdocs/docs/api/macros/json_no_udls.md b/docs/mkdocs/docs/api/macros/json_no_udls.md new file mode 100644 index 000000000..7e52d302f --- /dev/null +++ b/docs/mkdocs/docs/api/macros/json_no_udls.md @@ -0,0 +1,61 @@ +# JSON_NO_UDLS + +```cpp +#define JSON_NO_UDLS +``` + +When defined, the user-defined string literals [`operator""_json`](../operator_literal_json.md) and +[`operator""_json_pointer`](../operator_literal_json_pointer.md) are not defined, neither in the namespace +`nlohmann::literals::json_literals` nor in the global namespace (regardless of +[`JSON_USE_GLOBAL_UDLS`](json_use_global_udls.md)). + +The literals are ordinary inline functions whose bodies call the parser, so every translation unit that includes the +library instantiates the parser — even if it never parses anything itself. Defining `JSON_NO_UDLS` avoids this and +reduces the compile time of such translation units (e.g., ones that only define types and conversions or pass `json` +values around). Everything else in the library is unaffected; use [`parse`](../basic_json/parse.md) and the +[`json_pointer`](../json_pointer/json_pointer.md) constructor instead of the literals. + +## Default definition + +By default, `#!cpp JSON_NO_UDLS` is not defined. + +```cpp +#undef JSON_NO_UDLS +``` + +## Notes + +!!! info "Per translation unit" + + The macro only removes declarations, so it can be defined for some translation units and not for others. Code that + uses `_json` or `_json_pointer` fails to compile when `JSON_NO_UDLS` is defined. + +## Examples + +??? example + + The code below leaves out the user-defined string literals and uses `parse` and the `json_pointer` constructor + instead. + + ```cpp + #define JSON_NO_UDLS 1 + #include + + int main() + { + // auto j = R"({"foo": 42})"_json; // This line would fail to compile + auto j = nlohmann::json::parse(R"({"foo": 42})"); + auto p = nlohmann::json::json_pointer("/foo"); + return j.at(p) == 42 ? 0 : 1; + } + ``` + +## See also + +- [`operator""_json`](../operator_literal_json.md) +- [`operator""_json_pointer`](../operator_literal_json_pointer.md) +- [`JSON_USE_GLOBAL_UDLS`](json_use_global_udls.md) - place user-defined string literals (UDLs) into the global namespace + +## Version history + +- Added in version 3.13.0. diff --git a/docs/mkdocs/docs/api/macros/json_use_global_udls.md b/docs/mkdocs/docs/api/macros/json_use_global_udls.md index 3110d4662..f7ba822e1 100644 --- a/docs/mkdocs/docs/api/macros/json_use_global_udls.md +++ b/docs/mkdocs/docs/api/macros/json_use_global_udls.md @@ -32,6 +32,10 @@ When the macro is not defined, the library will define it to its default value. [`JSON_GlobalUDLs`](../../integration/cmake.md#json_globaludls) (`ON` by default) which defines `JSON_USE_GLOBAL_UDLS` accordingly. +!!! info "Leaving out the literals" + + If [`JSON_NO_UDLS`](json_no_udls.md) is defined, the literals are not defined at all and this macro has no effect. + ## Examples ??? example "Example 1: Default behavior" @@ -92,6 +96,7 @@ When the macro is not defined, the library will define it to its default value. - [`operator""_json`](../operator_literal_json.md) - [`operator""_json_pointer`](../operator_literal_json_pointer.md) +- [`JSON_NO_UDLS`](json_no_udls.md) - leave out the user-defined string literals entirely - [:simple-cmake: JSON_GlobalUDLs](../../integration/cmake.md#json_globaludls) - CMake option to control the macro ## Version history diff --git a/docs/mkdocs/docs/api/operator_literal_json.md b/docs/mkdocs/docs/api/operator_literal_json.md index babce5799..15a5a606f 100644 --- a/docs/mkdocs/docs/api/operator_literal_json.md +++ b/docs/mkdocs/docs/api/operator_literal_json.md @@ -18,7 +18,8 @@ using namespace nlohmann; ``` This is suggested to ease migration to the next major version release of the library. See -[`JSON_USE_GLOBAL_UDLS`](macros/json_use_global_udls.md#notes) for details. +[`JSON_USE_GLOBAL_UDLS`](macros/json_use_global_udls.md#notes) for details. The operator is not defined if +[`JSON_NO_UDLS`](macros/json_no_udls.md) is defined. ## Parameters @@ -59,6 +60,7 @@ Linear. ## See also - [Creating JSON values](../features/creating_values.md) - the article on creating JSON values +- [JSON_NO_UDLS](macros/json_no_udls.md) - leave out the user-defined string literals ## Version history diff --git a/docs/mkdocs/docs/api/operator_literal_json_pointer.md b/docs/mkdocs/docs/api/operator_literal_json_pointer.md index e1b729467..5817963a4 100644 --- a/docs/mkdocs/docs/api/operator_literal_json_pointer.md +++ b/docs/mkdocs/docs/api/operator_literal_json_pointer.md @@ -17,7 +17,8 @@ using namespace nlohmann::literals::json_literals; using namespace nlohmann; ``` This is suggested to ease migration to the next major version release of the library. See -[`JSON_USE_GLOBAL_UDLS`](macros/json_use_global_udls.md#notes) for details. +[`JSON_USE_GLOBAL_UDLS`](macros/json_use_global_udls.md#notes) for details. The operator is not defined if +[`JSON_NO_UDLS`](macros/json_no_udls.md) is defined. ## Parameters @@ -58,6 +59,7 @@ Linear. ## See also - [json_pointer](json_pointer/index.md) - type to represent JSON Pointers +- [JSON_NO_UDLS](macros/json_no_udls.md) - leave out the user-defined string literals ## Version history diff --git a/docs/mkdocs/docs/features/macros.md b/docs/mkdocs/docs/features/macros.md index 2bc8a4a5b..9a612fd07 100644 --- a/docs/mkdocs/docs/features/macros.md +++ b/docs/mkdocs/docs/features/macros.md @@ -99,6 +99,14 @@ same values and the same comparisons. See [full documentation of `JSON_NO_THREAD_LOCAL`](../api/macros/json_no_thread_local.md). +## `JSON_NO_UDLS` + +When defined, the user-defined string literals `operator""_json` and `operator""_json_pointer` are left out entirely. This +reduces the compile time of translation units that do not use them, because the literals would otherwise instantiate +the parser in every translation unit that includes the library. + +See [full documentation of `JSON_NO_UDLS`](../api/macros/json_no_udls.md). + ## `JSON_PRECISE_STREAM_POSITION` When defined to `1`, [`operator>>`](../api/operator_gtgt.md) and non-strict diff --git a/docs/mkdocs/mkdocs.yml b/docs/mkdocs/mkdocs.yml index 70537bd27..65111c8ca 100644 --- a/docs/mkdocs/mkdocs.yml +++ b/docs/mkdocs/mkdocs.yml @@ -296,6 +296,7 @@ nav: - 'JSON_NOEXCEPTION': api/macros/json_noexception.md - 'JSON_NO_IO': api/macros/json_no_io.md - 'JSON_NO_THREAD_LOCAL': api/macros/json_no_thread_local.md + - 'JSON_NO_UDLS': api/macros/json_no_udls.md - 'JSON_PRECISE_STREAM_POSITION': api/macros/json_precise_stream_position.md - 'JSON_SKIP_LIBRARY_VERSION_CHECK': api/macros/json_skip_library_version_check.md - 'JSON_SKIP_UNSUPPORTED_COMPILER_CHECK': api/macros/json_skip_unsupported_compiler_check.md diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 500fcddf2..d7dcfa6e2 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -6423,6 +6423,7 @@ std::string format_as(const NLOHMANN_BASIC_JSON_TPL& j) return j.dump(); } +#ifndef JSON_NO_UDLS inline namespace literals { inline namespace json_literals @@ -6472,6 +6473,7 @@ inline nlohmann::json::json_pointer operator""_json_pointer(const char8_t* s, st } // namespace json_literals } // namespace literals +#endif // JSON_NO_UDLS NLOHMANN_JSON_NAMESPACE_END /////////////////////// @@ -6606,7 +6608,7 @@ struct formatter // NOLINT(cert-dcl58-c } // namespace std -#if JSON_USE_GLOBAL_UDLS +#if JSON_USE_GLOBAL_UDLS && !defined(JSON_NO_UDLS) #if !defined(JSON_HEDLEY_GCC_VERSION) || JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) using nlohmann::literals::json_literals::operator""_json; // NOLINT(misc-unused-using-decls,google-global-names-in-headers) using nlohmann::literals::json_literals::operator""_json_pointer; //NOLINT(misc-unused-using-decls,google-global-names-in-headers) diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 576498738..e73583e17 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -32504,6 +32504,7 @@ std::string format_as(const NLOHMANN_BASIC_JSON_TPL& j) return j.dump(); } +#ifndef JSON_NO_UDLS inline namespace literals { inline namespace json_literals @@ -32553,6 +32554,7 @@ inline nlohmann::json::json_pointer operator""_json_pointer(const char8_t* s, st } // namespace json_literals } // namespace literals +#endif // JSON_NO_UDLS NLOHMANN_JSON_NAMESPACE_END /////////////////////// @@ -32687,7 +32689,7 @@ struct formatter // NOLINT(cert-dcl58-c } // namespace std -#if JSON_USE_GLOBAL_UDLS +#if JSON_USE_GLOBAL_UDLS && !defined(JSON_NO_UDLS) #if !defined(JSON_HEDLEY_GCC_VERSION) || JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) using nlohmann::literals::json_literals::operator""_json; // NOLINT(misc-unused-using-decls,google-global-names-in-headers) using nlohmann::literals::json_literals::operator""_json_pointer; //NOLINT(misc-unused-using-decls,google-global-names-in-headers) diff --git a/src/modules/json.cppm b/src/modules/json.cppm index 15207535f..480961f6b 100644 --- a/src/modules/json.cppm +++ b/src/modules/json.cppm @@ -32,6 +32,7 @@ using NLOHMANN_JSON_NAMESPACE::ordered_json; using NLOHMANN_JSON_NAMESPACE::ordered_map; using NLOHMANN_JSON_NAMESPACE::to_string; +#ifndef JSON_NO_UDLS inline namespace literals { inline namespace json_literals @@ -40,6 +41,7 @@ inline namespace json_literals using NLOHMANN_JSON_NAMESPACE::literals::json_literals::operator""_json_pointer; } // namespace json_literals } // namespace literals +#endif // Note: the following nlohmann::detail symbols must be exported due to // an MSVC bug failing to compile without these symbols visible (ticket #3970) diff --git a/tests/src/unit-no_udls.cpp b/tests/src/unit-no_udls.cpp new file mode 100644 index 000000000..bf5402b1c --- /dev/null +++ b/tests/src/unit-no_udls.cpp @@ -0,0 +1,77 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ (supporting code) +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +// This translation unit checks JSON_NO_UDLS, which leaves out the user-defined +// string literals operator""_json and operator""_json_pointer entirely (see +// #5294), while the rest of the library keeps working. +#define JSON_NO_UDLS 1 + +#include "doctest_compatibility.h" + +#include +#include + +#include +using json = nlohmann::json; + +// An argument type whose associated namespace is the library namespace, so +// argument-dependent lookup of a literal operator called by its function name +// also searches the inline namespaces nlohmann::literals::json_literals. +NLOHMANN_JSON_NAMESPACE_BEGIN +struct no_udls_probe +{ + operator const char* () const // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + { + return ""; + } +}; +NLOHMANN_JSON_NAMESPACE_END + +namespace +{ +// The calls below use a dependent argument, so a literal operator that is not +// declared at all is a substitution failure rather than a hard error: lookup is +// deferred to the point of instantiation, where it considers the declarations +// visible from here (the global using-declarations of JSON_USE_GLOBAL_UDLS) plus +// argument-dependent lookup (the literals in the library namespace). +template +using json_udl_t = decltype(operator""_json(std::declval(), std::size_t())); + +template +using json_pointer_udl_t = decltype(operator""_json_pointer(std::declval(), std::size_t())); + +template +using has_json_udl = nlohmann::detail::is_detected; + +template +using has_json_pointer_udl = nlohmann::detail::is_detected; +} // namespace + +TEST_CASE("JSON_NO_UDLS") +{ + SECTION("literals are not declared") + { + // global namespace (JSON_USE_GLOBAL_UDLS defaults to 1) + CHECK_FALSE(has_json_udl::value); + CHECK_FALSE(has_json_pointer_udl::value); + + // nlohmann::literals::json_literals + CHECK_FALSE(has_json_udl::value); + CHECK_FALSE(has_json_pointer_udl::value); + } + + SECTION("the rest of the library keeps working") + { + const json j = json::parse(R"({"foo": {"bar": 42}})"); + CHECK(j.dump() == R"({"foo":{"bar":42}})"); + + const json::json_pointer ptr("/foo/bar"); + CHECK(j.at(ptr) == 42); + CHECK(j.contains(ptr)); + } +}