[Development] [RFO QTBUG-84739] QJsonValue::fromVariant containing QByteArray

Thiago Macieira thiago.macieira at intel.com
Wed Jul 1 23:21:33 CEST 2020

Re: https://bugreports.qt.io/browse/QTBUG-84739
Summary: Qt 5.15 has an unintentional change in behaviour that has broken 
existing applications. We need to decide whether to:
 a) leave as-is
 b) revert permanently
 c) revert for 5.15.x but keep the new behaviour in 6.x

(a) implies one change in behaviour (5.14.2 to 5.15.0)
(b) implies two changes in behaviour (to and from 5.15.0)
(c) implies three changes in behaviour: 5.14.2→5.15.0, 5.15.0→5.15.1, 

    const char binarydata[] = "\0\1\2\3\xff";
    QVariantMap vmap = {
        {"ascii", QByteArray("ASCII text.\n")},
        {"latin1", QByteArray("R\xe9sum\xe9")},
        {"utf8", QByteArray("Résumé")},
        {"binary", QByteArray(binarydata, sizeof(binarydata) - 1)}
    QJsonObject jobj = QJsonObject::fromVariantMap(vmap);
    printf("%s", qPrintable(QJsonDocument(jobj).toJson()));

Because of the switch in QJsonValue/Object/Array to use the CBOR classes as a 
backend, we had an uninteded change in behaviour in how the JSON 
fromVariantXxx functions behave for some variant types that are not part of 
the JSON specification. In the bug report, this happened specifically for 

Qt 5.14 output:
    "ascii": "ASCII text.\n",
    "binary": null,
    "latin1": "R�sum�",
    "utf8": "Résumé"

Qt 5.15 output:
    "ascii": "QVNDSUkgdGV4dC4K",
    "binary": "AAECA_8",
    "latin1": "UulzdW3p",
    "utf8": "UsOpc3Vtw6k"

The Qt 5.15 output is the Base64url encoding of the original byte array data, 
following the CBOR recommendations on how to convert CBOR to JSON (RFC 7049 
section 4.1[1]). This was unintentional and happened only due to code reuse. 
The QtJson unit tests did not test this and do not test how converting from 
any QVariant types except the base ones that apply to JSON, plus QUrl and 
QUuid. But it has broken user code that relied on this feature.

It wasn't entirely undocumented. QJsonValue::fromVariant said:
    For all other QVariant types a conversion to a QString will be attempted.
    If the returned string
    is empty, a Null QJsonValue will be stored, otherwise a String value using
    the returned QString.
[this text is changed in https://codereview.qt-project.org/305996]

Since it depends on QVariant::toString(), it's implicit that the behaviour can 
change from version to version. For Qt 6.0, we're already discussing whether 
conversion between types in QVariant makes sense and toString() is one of 
those cases.

As can be seen in the testcase, the old behaviour is silently lossy, which 
means it's dangerous. The new behaviour is surprising, but it's not lossy, as 
the original byte data is retrievable:

	$ base64 -d <<<QVNDSUkgdGV4dC4K 
	ASCII text.
	$ base64 -d <<<UulzdW3p | od -tc
	0000000   R 351   s   u   m 351
	$ base64 -d <<<UsOpc3Vtw6k=

(the type change is inevitable and is accepted by the user)

Request For Opinion: what do we do now? See options at the beginning of the 

Note also this behaviour is not completely correct, because fromVariant 
converts *empty* strings to null, but it should only convert null QStrings to 
null, leaving empty strings alone (that's the QCborValue::fromVariant 
behaviour). I also don't think the implementation is self-consistent, because 
the conversion code appears more than once.

[1] https://tools.ietf.org/html/rfc7049#section-4.1
Thiago Macieira - thiago.macieira (AT) intel.com
  Software Architect - Intel System Software Products

More information about the Development mailing list