Repository navigation
Fix ADL leak of nlohmann::detail through basic_json's default base class - #5238
Merged
Merged
Conversation
Fixes #4320. Since #3110 (3.11.3), basic_json unconditionally derives from detail::json_base_class<CustomBaseClass>, which resolves to detail::json_default_base when no custom base class is supplied (the default). Because json_default_base lived in namespace nlohmann::detail, every basic_json specialization gained nlohmann::detail as an associated namespace for argument-dependent lookup (ADL) purposes - even when no custom base class is used. This silently exposed the library's internal, ADL-only generic to_json overloads (e.g. the compatible-array-type overload in detail/conversions/to_json.hpp) to any unqualified to_json()/from_json() call a user makes involving a basic_json argument. In #4320, a user's to_json for one type delegated via an unqualified to_json() call to a base type's to_json (a common pattern for thin wrapper/adapter types). Because the delegated-to argument type also has begin()/end() (as Eigen::Vector3d has since Eigen 3.4), overload resolution now also considers the library's own generic array serializer. That overload is an exact-match template versus the user's overload, which requires a derived-to-base conversion - and an exact match beats a non-exact conversion regardless of template-ness, so the user's serializer got silently bypassed. Move json_default_base out of nlohmann::detail into nlohmann itself. detail::json_base_class<T> keeps its alias-template location (aliases don't affect ADL - only the referenced type's namespace does), so this does not change basic_json's effective base class or any public API; it only removes the accidental ADL exposure of nlohmann::detail for the default (no custom base class) case. Added a regression test reproducing the exact overload-resolution scenario (derived-to-base delegation to a begin()/end()-bearing base type's to_json) using a minimal stand-in for the Eigen type involved in #4320, verified to fail without this fix and pass with it. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
nlohmann
marked this pull request as draft
July 5, 2026 21:17
Address ci_clang_tidy failures on PR #5238: use default member initializer suppression for the C-array member (constructor still needs to reference the parameters, so the check's suggested rewrite doesn't apply), mark the free-function to_json/to_eigen overloads NOLINT(misc-use-internal-linkage) to match this file's existing convention for ADL customization points, and use a braced-init-list return instead of repeating the type name. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
gregmarr
approved these changes
Jul 6, 2026
nlohmann
marked this pull request as ready for review
July 6, 2026 06:25
2 tasks done
jwnimmer-tri
pushed a commit
to jwnimmer-tri/json
that referenced
this pull request
Sep 21, 2026
nlohmann
added a commit
that referenced
this pull request
Sep 27, 2026
Since #5238, json_default_base lives directly in the (inline, ABI-tagged) library namespace, e.g. nlohmann::json_abi_v3_12_0::json_default_base, and no longer in detail. The fallback entries only named <ns>::detail::json_default_base, so they would not match anything built from the current headers or any later release. Emit an entry for both names: the non-detail one for current code, the detail one for users of 3.12.0 (the version in #4972). Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #4320.
Since #3110 (3.11.3),
basic_jsonunconditionally derives fromdetail::json_base_class<CustomBaseClass>, which resolves todetail::json_default_basewhen no custom base class is supplied (the default,CustomBaseClass = void). Becausejson_default_baselived in namespacenlohmann::detail, everybasic_jsonspecialization gainednlohmann::detailas an associated namespace for argument-dependent lookup (ADL) — even for users who never touch the custom-base-class feature.This silently exposes the library's internal, ADL-only generic
to_jsonoverloads (e.g. the compatible-array-type overload indetail/conversions/to_json.hpp) to any unqualifiedto_json()/from_json()call a user makes involving abasic_jsonargument.In #4320, the user's
to_jsonfor one type delegates via an unqualifiedto_json()call to a base type'sto_json— a common pattern for thin wrapper/adapter types (there, a class deriving fromEigen::Vector3d). Because the delegated-to argument's type also hasbegin()/end()(true ofEigen::Vector3dsince Eigen 3.4), overload resolution now also considers the library's own generic array serializer, newly visible via thenlohmann::detailADL leak. That overload is an exact-match template for the derived type, whereas the user's intended overload requires a derived-to-base conversion — and an exact match beats a non-exact conversion regardless of template-ness, so the user's serializer is silently bypassed and the object gets serialized as[x,y,z]instead of{"x":x,"y":y,"z":z}.Fix
Move
json_default_baseout ofnlohmann::detailintonlohmannitself.detail::json_base_class<T>keeps its alias-template location — alias templates don't affect ADL, only the referenced type's namespace does — so this doesn't changebasic_json's effective base class,json_base_class_t's public signature, or any other public API. It only removes the accidental ADL exposure ofnlohmann::detailfor the default (no custom base class) case.Public API is unchanged
Two distinct names are involved here, and it's worth being precise about which one actually moved:
detail::json_base_class<T>— the alias template that implements thevoid → defaultswitch, and thatjson_base_class_tis documented in terms of (using json_base_class_t = detail::json_base_class<CustomBaseClass>;). This stays innlohmann::detail, unchanged.json_default_base— the empty placeholder struct thatjson_base_class<T>resolves to whenT = void. This is the one that moves, fromnlohmann::detail::json_default_basetonlohmann::json_default_base.json_default_basewas never part of the documented API — onlyCustomBaseClassandjson_base_class_tare documented, and neither of those names, namespaces, or signatures change.json_base_class_t's definition still resolves through the samenlohmann::detail::json_base_classalias template it always did; only the value that alias produces for the default (no custom base class) case moves namespace. So no documented name, header path, or template signature changes — the only observable effect is the ADL fix itself.Test plan
unit-regression2.cppreproducing the exact overload-resolution scenario from Behavioral change of serializers in 3.11.3. Name lookup related. #4320 (derived-to-base delegation to abegin()/end()-bearing base type'sto_json), using a minimal stand-in for the Eigen type involved (no Eigen dependency needed).[1.0,2.0,3.0]instead of{"x":1.0,"y":2.0,"z":3.0}) and passes with it.tests/src/unit-custom-base-class.cpp,unit-regression2.cpp,unit-udt.cpp,unit-udt_macro.cppall pass against both the modular headers and the amalgamatedsingle_includeheader, at C++11/14/17/20, with-Wall -Wextra -Werror.make amalgamate(astyle 3.4.13) —single_include/nlohmann/json.hppis up to date.(This PR was prepared by Claude Code on behalf of @nlohmann, following up on the maintainer's own root-cause investigation of #4320.)