fix(json): keep nested bytes/ints tagged in qvariantToNlohmann containers#23
Merged
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes lossless JSON conversion for QVariant containers so nested QByteArray (and integer) values remain type-preserved when converting through qvariantToNlohmann, which is critical for the Qt↔Rust cdylib bridge and other C-ABI consumers.
Changes:
- Reordered
qvariantToNlohmannto runQStringList/QVariantList/QVariantMaprecursion before theQJsonObject/QJsonArrayconversion fallbacks, preventing nested bytes/ints from being coerced by Qt JSON. - Added unit tests covering nested
QByteArrayinside lists/maps and a list-shaped argument case to ensure round-trip preservation vianlohmannArgsToQVariantList.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
cpp/logos_json_convert.cpp |
Moves Qt JSON fallbacks after container recursion to preserve nested byte/int types during conversion. |
tests/protocol/test_json_convert_bytes.cpp |
Adds regression tests verifying nested bytes remain tagged and can round-trip back to QByteArray. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+100
to
+108
| // Nested bytes inside a container must keep the tagged form. Regression for the | ||
| // cdylib method-argument path: LogosProviderObject::callMethodStdBridge feeds | ||
| // every call argument through qvariantToNlohmann, and a bstr argument arrives | ||
| // (over QtRO) as a QByteArray nested inside the QVariantList of call args. | ||
| // Before the fix, the canConvert<QJsonArray>/<QJsonObject> fallbacks caught the | ||
| // container FIRST and routed it through QJson, which has no byte type — the | ||
| // nested QByteArray was flattened to a plain string, so a Rust cdylib's | ||
| // {"_bytes":...} decoder saw "hello" and produced an empty Vec (echoBytes → null). | ||
| TEST(JsonConvertBytes, NestedByteArrayInListStaysTagged) |
Comment on lines
+142
to
+145
| // Exactly the callMethodStdBridge shape: the outer call arg is a | ||
| // QVariantList holding one bstr param as a QByteArray. qvariantToNlohmann | ||
| // must yield [{"_bytes":...}] so the callee (or the next hop's | ||
| // nlohmannArgsToQVariantList) recovers a QByteArray, not the string "hello". |
…ners
qvariantToNlohmann ran its canConvert<QJsonObject>/<QJsonArray> fallbacks BEFORE
the type-preserving QVariantList/QVariantMap recursion. A QVariantList/QVariantMap
also reports canConvert<QJson*>()==true, so a container was routed through QJson —
which has no byte type and degrades numerics to double. A nested QByteArray was
therefore flattened to a plain string, losing the canonical {"_bytes":...} tag.
Concretely this broke bstr method ARGUMENTS to cdylib (Rust) modules:
LogosProviderObject::callMethodStdBridge feeds each call arg through
qvariantToNlohmann, and a bstr arg arrives (over QtRO) as a QByteArray nested in
the QVariantList of call args. It was flattened to "hello", so the cdylib's
{"_bytes":...} decoder produced an empty Vec (e.g. echoBytes returned null). The
QtRO C++ path was unaffected (native QByteArray marshaling) and the plain-lp path
was already correct; only the container-through-QVariant leg dropped the tag.
Fix: move the container recursion (QStringList/QVariantList/QVariantMap) ahead of
the QJson fallbacks so nested elements recurse element-by-element (bytes stay
tagged, integers stay integers); only genuine QJson-typed variants reach the
fallbacks. Adds nested-bytes-in-list/map + bridge-shape regression tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
dlipicar
force-pushed
the
fix/qvariant-nested-bytes
branch
from
July 17, 2026 22:34
417a7b3 to
eced852
Compare
Collaborator
Author
|
Good catch on both — the code was fine but the test comments overstated the shape. Tightened them:
Comment-only amend; tests and behavior unchanged. |
This was referenced Jul 17, 2026
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.
Problem
A
bstrmethod argument never reaches a cdylib (Rust) module — the callee decodes an emptyVec<u8>and, e.g.,echoBytes(v)returnsnull.Root cause is in
qvariantToNlohmann(cpp/logos_json_convert.cpp). It ran thecanConvert<QJsonObject>()/canConvert<QJsonArray>()fallbacks before the type-preservingQVariantList/QVariantMaprecursion. AQVariantList/QVariantMapalso reportscanConvert<QJson…>() == true, so a container was routed throughQJson— which has no byte type and degrades numerics todouble. A nestedQByteArraywas flattened to a plain string, losing the canonical{"_bytes":"<base64url>"}tag. (Nested whole integers happened to survive — they serialize without a decimal point — which is why the existing int-preservation tests passed while bytes silently broke, and why the container recursion added earlier was effectively shadowed/dead for these types.)How it bites
LogosProviderObject::callMethodStdBridgeconverts every call argument viaqvariantToNlohmann. Abstrargument arrives (over QtRO) as aQByteArraynested inside theQVariantListof call args:The QtRO C++ path is unaffected (native
QByteArraymarshaling; Qt auto-convertsQString↔QByteArray), and the plain-lppath was already correct and tested. Only the container-through-QVariantleg dropped the tag.Fix
Move the container recursion (
QStringList/QVariantList/QVariantMap) ahead of theQJsonfallbacks, so nested elements recurse element-by-element —QByteArray→{"_bytes":…}(tagged), integers stay integers, etc. Only genuineQJson-typedQVariants reach the fallbacks now. One-block reorder; no behavior change for scalars or already-QJsonvariants.Tests
JsonConvertBytesunit tests: nested bytes in a list, nested bytes in a map, and the exactcallMethodStdBridgeshape (QVariantList[QByteArray]→[{"_bytes":…}]→ re-decodes toQByteArray). All fail before / pass after.[tstr]tests unaffected).logoscore, the Rusttest_fullapi_rust.echoBytes 'json:{"_bytes":"aGVsbG8"}'now returns the bytes ({"_bytes":"aGVsbG8"}) instead ofnull; instrumentation confirmedcallMethodStdBridgenow yields[{"_bytes":"aGVsbG8"}]instead of["hello"].🤖 Generated with Claude Code