qvariantToNlohmann has always owned LogosResult -> {success,value,error}. The
way back did not exist: nlohmannToQVariant turns that object into a plain
QVariantMap, and a qvariant_cast<LogosResult> of a QVariantMap yields a
default-constructed, silently-failed result. So every consumer that received a
`result` over the canonical JSON wire either re-derived the decode or lost it.
The pair is now symmetric, and both fields recurse through the canonical
decoder — so a `value` carrying bytes / 64-bit integers / containers comes back
with the shape the encoder sent, and a null `error` stays an INVALID QVariant
rather than becoming an empty QString. That last state is the point: it is what
the Qt transport delivers for "no error", and no std::string-typed intermediate
can carry it.
Tests pin the round trip, the absent-error state, bytes + uint64 inside `value`,
and the non-object input.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>