Repository navigation
Restore v3.12.0 support for custom object key types - #5795
Conversation
Custom object_t types whose key_type is not string_t compiled with v3.12.0 for several APIs that unreleased changes broke: - to_bson failed for every custom key type (#5553 kept a const string_t* to the key); the nested entry's header is now written where the entry is found. - Copying deep values (and parse, merge_patch, update, insert) required operator== on keys (#5389); keys without one are now paired via find(). - to_cbor/to_msgpack required an implicit conversion to string_t (#5746, #5328); keys without one go through a temporary basic_json again. - at() required a conversion to string_t for its error message (#5727); other keys are passed to concat() unchanged again. The new unit-custom-object-key-type.cpp covers five key types with different capabilities. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
| { | ||
| nested_name = &index_name; | ||
| write_bson_entry_header(index_name, el.is_object() ? 0x03 : 0x04); | ||
| write_number<std::int32_t>(to_bson_length(nested_sizes[next_size++]), true); |
There was a problem hiding this comment.
In line with your recent deduplication changes, is two times enough duplication to make a helper function?
There was a problem hiding this comment.
Yes, done in b9c46c5: write_bson_nested_head() writes the type, key and size of a nested element. It has to be called inside the loops (not once after them, as before) because an object key may only convert to a temporary string_t, so it can't be kept by pointer past the loop.
(Written by Claude Code.)
GCC with -Werror=float-equal rejects comparing the double value with ==. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
develop (#5798) added tests/src/unit-custom-object-key-type.cpp with the custom object key tests moved out of unit-cbor.cpp and unit-msgpack.cpp. This branch added a file of the same name; keep develop's file and move this branch's tests: - the key types, helpers, and test bodies go to custom_key_test.hpp; - unit-custom-key-type-{full,no-eq,explicit,to-json,c-str}.cpp run the tests for one object type each, as one file with all five specializations failed to link with MinGW ("relocation truncated to fit", more than 65535 sections, see .github/workflows/windows.yml); - the at() test expects the "(bytes 0-7)" prefix with JSON_DIAGNOSTIC_POSITIONS. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
| template<typename KeyType, detail::enable_if_t<std::is_constructible<string_t, const KeyType&>::value, int> = 0> | ||
| static string_t key_for_message(const KeyType& key) | ||
| { | ||
| return string_t(key); |
There was a problem hiding this comment.
Does this copy a string unnecessarily if it has both size() and data() but can also be used to construct string_t? Is that not even a consideration because this is in the exception path? What type even can be used as a key but needs a string_t wrapper?
There was a problem hiding this comment.
Good catch — yes, it did. A miss on a string_t (or string_view) key copied the key only so concat() could copy it again; v3.12.0 passed the key_type overload's key through directly. Fixed in e6aba4b: keys with data() and size() now go to concat() unchanged, and only other keys are converted to string_t. It's only on the throwing path, so the cost was small, but there was no reason for it.
The conversion is still needed for key types that convert to string_t but have no size(). Examples are key_full, key_no_eq and key_explicit in the new tests: they only have data(), for JSON_DIAGNOSTICS, so concat() can't take them directly. String literals are converted too, as in v3.12.0.
(Written by Claude Code.)
The reference is only passed to concat() within the full-expression that holds the key, like the similar helpers in binary_writer.hpp. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
clang-tidy (cppcoreguidelines-non-private-member-variables-in-classes) rejects the protected member; the derived key types use a protected accessor instead. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
key_for_message() converted every key that string_t can be constructed from, so a miss on a string_t or string_view key copied it before concat() copied it again. Keys that concat() can append through data() and size() are now passed through; only other keys (string literals, key types that just convert to string_t) are converted. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Signed-off-by: Niels Lohmann <mail@nlohmann.me> # Conflicts: # include/nlohmann/json.hpp # single_include/nlohmann/json.hpp
dump() instantiates the serializer for the whole value type, which does not compile for object key types it cannot write (e.g., the custom_key_test::key_c_str of #5795), even though the value is always a number here. Produce the decimal digits directly instead; they are generated per digit so that the minimum of a signed type is never negated as a whole. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Custom
object_ttypes whosekey_typeis notstring_t(for example astd::mapkeyed by a user-defined class) compiled with v3.12.0 for several APIs that unreleased changes ondevelopbroke. This PR makes everything that compiled with v3.12.0 compile again, and pins it with a test.Found while reviewing #5328, which documented a new requirement that
object_t::key_typebe implicitly convertible tostring_t. A compile sweep (28 APIs × 5 key types × C++11/C++17, v3.12.0 vsdevelop) found these regressions:to_bsonkey_typeisstring_t(all custom keys, even well-behaved ones)const string_t*to the keyparse,merge_patch,update,insertoperator==on keys (std::maponly needsoperator<)copy_keys_equal()uses==if available, else pairs values viafind()to_cbor,to_msgpackstring_twrite_cbor_key()/write_msgpack_key(): fast path for convertible keys (as in #5328), otherwise a temporarybasic_jsonas in v3.12.0; thestatic_asserts are removedat()(key not found)string_tconstructible from the keykey_for_message()passes non-convertible keys toconcat()unchanged, as v3.12.0 didThe sentence #5328 added to
object_t.mdis reverted. After this PR, the sweep shows no regressions for any key type or standard.The key types in the new
tests/src/unit-custom-object-key-type.cpp:operator std::stringandoperator==operator std::string, nooperator==explicit operator std::stringto_jsonoverloadsize()/c_str()It tests copy (shallow and deeper than
recursion_depth_limit()),parse,merge_patch/update/insert,at(), BSON (the two key types BSON supported in v3.12.0), and byte-identical CBOR/MessagePack output, shallow and deep. The deep cases cover the*_iterativewriters.Verified locally: the new test in C++11, C++20, and with
JSON_DIAGNOSTICS, with the CI's-Weverythingflags; unit-bson, unit-cbor, unit-msgpack, unit-custom-object-type, unit-element_access2, unit-constructor1 in C++11;make amalgamateclean.Breaking changes
None. This removes requirements on
object_t::key_typethat unreleased changes added after v3.12.0, including the compatibility note in #5328. Nothing changes forstring_tkeys: they still take the direct paths, and CBOR/MessagePack still writes them without a temporarybasic_json.This PR was written by Claude Code on behalf of @nlohmann.
🤖 Generated with Claude Code