Repository navigation
Avoid allocating temporary basic_json for cbor and msgpack object keys - #5328
alexprabhat99 wants to merge 2 commits into
Conversation
This comment was marked as outdated.
This comment was marked as outdated.
38a5640 to
d10514d
Compare
|
Hi @nlohmann |
|
Thanks for tackling this! Overall this looks good and does exactly what #5318 asked for. A few minor comments below. The claimed win is realI compiled the allocation-counting program from #5318 against
CBOR and MessagePack now sit at the same allocation level as UBJSON and BSON. The extracted helper bodies are byte-for-byte the originals, and the Comments1. Missing SPDX header on the new test file
2. It was added in d10514d ("fixes for failing ci"), but nothing on the CBOR/MessagePack path calls it — the template overload routes through 3. The unconstrained template overloads may not be needed, and are undocumented
So the templates' only added capability (key types with an explicit-only conversion to 4. Worth noting the source-compatibility nuance in the PR description Previously keys went through I think the new behavior is the correct one, but since it's the convention here to state API impact: no public API break ( 5. Nit Double blank line after the OptionalThere's no regression test guarding the allocation count, so this could silently regress later. Given it needs a global Reviewed with the help of Claude Code. |
|
Hi @nlohmann |
This comment was marked as resolved.
This comment was marked as resolved.
e6a0fbf to
f6de4f0
Compare
|
Hi @nlohmann |
|
Hi @nlohmann |
nlohmann
left a comment
There was a problem hiding this comment.
Thanks a lot, and sorry for the delay.
Two issues I would like you to address:
- A
static_assertonstd::is_convertible<object_t::key_type, string_t>in the two key loops, so a rejected key type produces one readable line instead of a template wall. - One sentence in
docs/mkdocs/docs/api/basic_json/object_t.mdstating thatobject_t::key_typemust be convertible tostring_t. The page already implies it by describing the type asObjectType<StringType, ...>, but the new tests make it a contract, so let's write it down.
Minor: the key in tests/src/custom_object_key_type.hpp carries a c_str() that no CBOR/MsgPack test uses. That's what UBJSON would need - it calls el.first.size() and .c_str() on the key directly - and in fact this key type still doesn't compile with to_ubjson. Pre-existing and out of scope here, but it means "custom key types are supported" isn't uniformly true yet. Either drop the unused c_str() or leave a comment saying why it's there; the UBJSON gap can be a follow-up issue.
This comment was marked as resolved.
This comment was marked as resolved.
| } | ||
| // LCOV_EXCL_STOP | ||
|
|
||
| static_assert( |
There was a problem hiding this comment.
Looks like you added this manually and only to the single_include file.
nlohmann
left a comment
There was a problem hiding this comment.
Automated review findings — posted via Claude Code on behalf of @nlohmann.
Two issues found with high confidence:
- The
static_assertadded tosingle_include/nlohmann/json.hppguardingobject_t::key_typeconvertibility isn't mirrored in the modularinclude/header it's generated from. Sincesingle_includeis produced frominclude/viamake amalgamate, this diff will fail thecheck_amalgamationCI job, and the modular-header build silently loses the static_assert. - The new
custom_object_key_type.hpptest fixture is missing asize()method to match itsc_str()shim, making it incompatible with UBJSON's key-writing path (verified by compilingto_ubjsonagainst it) despite the comment claiming broader serializer compatibility.
See inline comments below for exact locations.
| } | ||
|
|
||
| // Kept for compatibility with serializers that access object keys as C strings. | ||
| const char* c_str() const noexcept |
There was a problem hiding this comment.
The comment says this is "kept for compatibility with serializers that access object keys as C strings," but there's no matching size(). UBJSON's write_ubjson accesses object keys via el.first.size() and el.first.c_str() directly (not through the string_t conversion operator CBOR/MsgPack/BSON use), so custom_object_key_test::json::to_ubjson(...) fails to compile with "no member named 'size' in 'custom_object_key_test::key'" if this shared fixture is ever reused for a UBJSON test. Not exercised by this PR (CBOR/MsgPack only), so it's latent for now.
There was a problem hiding this comment.
Posted via Claude Code on behalf of @nlohmann.
Still applies to the reworded comment: UBJSON needs both size() and c_str() on the key, so this type still does not work with to_ubjson, and "serialization paths that access object keys through c_str()" suggests otherwise. Since no test here needs it, I would simply drop c_str(); the UBJSON gap can be a separate follow-up.
abba9c3 to
61a73f2
Compare
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
2222a44 to
ce5b4cb
Compare
ce5b4cb to
3cf92d8
Compare
|
Please rebase to the latest develop to resolve conflicts. |
|
@alexprabhat99 Are you willing to continue working on this? |
|
@nlohmann Apologies. I will resolve the conflicts as soon as I get time. |
29c5ed0 to
f3984d7
Compare
ff849d1 to
a1bd525
Compare
… keys Signed-off-by: alexprabhat99 <alexpbara@gmail.com>
a1bd525 to
9dc4a85
Compare
nlohmann
left a comment
There was a problem hiding this comment.
Automated review — posted via Claude Code on behalf of @nlohmann.
Thanks for the rebase! All points from the previous rounds are addressed: the static_asserts are in both include/ and single_include/, object_t.md documents the convertibility requirement, the test header has the SPDX banner, and the template overloads are gone in favor of const string_t& (consistent with the BSON writer). make amalgamate produces no diff, and unit-cbor / unit-msgpack pass locally (C++11 and C++20 for the new test cases, full suites under C++11).
Only nits remain, see inline. One more: please mention the last_child() change in the PR description, since it fixes a develop build break (unit-custom-object-type.cpp fails to compile since #5762 because no_key_compare_map has no rbegin()) that is unrelated to #5318.
| return v.m_data.m_value.object->rbegin()->second; | ||
| // a custom object_t need not provide rbegin(), so | ||
| // std::prev(end()) is used instead (as in pop_last_child() below) | ||
| return std::prev(v.m_data.m_value.object->end())->second; |
There was a problem hiding this comment.
This is right — develop currently fails to compile unit-custom-object-type.cpp since #5762 because no_key_compare_map has no rbegin(), so thanks for fixing it along the way.
Nit: the comment in pop_last_child() below still says std::prev(end()) is used "rather than rbegin() (see last_child() above)", which is now circular since last_child() no longer uses rbegin() either. Could you reword one of the two, e.g. say in one place that std::prev(end()) is used in both because a custom object_t need not provide rbegin() and erase() needs a forward iterator?
|
FYI: I opened #5767 to fix develop. |
d81b0e7 to
9907208
Compare
UBJSON and BJData access object keys through size() and c_str() directly, so the key type now provides both and the comment says why. Signed-off-by: alexprabhat99 <alexpbara@gmail.com>
This PR optimizes CBOR and MessagePack serialization by avoiding the construction of a temporary basic_json object for every object key during serialization.
Previously, object keys were passed through write_cbor()/write_msgpack(), which expected a BasicJsonType and therefore implicitly constructed a temporary basic_json for each key. This introduced unnecessary heap allocations during serialization.
This change factors out the string serialization logic into dedicated helper functions and serializes object keys directly. The implementation preserves compatibility with custom object key types while eliminating the extra basic_json allocations for the common case where object_t::key_type is string_t.
A regression test has been added to verify compatibility with custom object key types.
Fixes #5318
Compatibility note: This does not change the public API (detail::binary_writer is internal). For custom object_t::key_type implementations, CBOR and MessagePack object keys are now required to be implicitly convertible to string_t. Previously, a custom key type handled only through to_json could be serialized through the generic JSON serializer, potentially producing non-string map keys that the library's own CBOR/MessagePack readers would reject.