From 1df02b5f980b01a4e42f32061f1cba696b6a22e9 Mon Sep 17 00:00:00 2001 From: Sona Kurazyan Date: Fri, 7 Aug 2020 14:57:11 +0200 Subject: Fix conversions to JSON from QVariant After reimplementing Qt JSON support on top of CBOR, there were unintended behavior changes when converting QVariant{, List, Map} to QJson{Value, Array, List} due to reusing the code for converting QVariant* types to CBOR types, and from CBOR types to corresponding JSON types. In particular, conversions from QVariant containing QByteArray to JSON has been affected: according to RFC 7049, when converting from CBOR to JSON, raw byte array data must be encoded in base64url when converting to a JSON string. As a result QVariant* types containing QByteArray data ended up base64url-encoded when converted to JSON, instead of converting using QString::fromUtf8() as before. There were also differences when converting QRegularExpression. Reverted the behavior changes by adding a flag to internal methods for converting CBOR to JSON, to distinguish whether the conversion is done from QVariant* or CBOR types. These methods now will fall back to the old behavior, if the conversion is done using QJson*::fromVariant*(). Additionally fixed QJsonValue::fromVariant conversion for NaN and infinities: they should always convert to QJsonValue::Null. This works correctly when converting from variant to QJsonArray/QJsonObject, but has been wrong for QJsonValue. Added more tests to verify the expected behavior. [ChangeLog][Important Behavior Changes] Restored pre-5.15.0 behavior when converting from QVariant* to QJson* types. Unforeseen consequences of changes in 5.15.0 caused QByteArray data to be base64url-encoded; the handling of QRegularExpression was also unintentionally changed. These conversions are now reverted to the prior behavior. Additionally fixed QJsonValue::fromVariant conversions for NaN and infinities: they should always convert to QJsonValue::Null. Fixes: QTBUG-84739 Change-Id: Iaee667d00e5363906eedbb67948b7b39c9d0bc78 Reviewed-by: Thiago Macieira --- .../auto/corelib/serialization/json/tst_qtjson.cpp | 153 +++++++++++++++++++++ 1 file changed, 153 insertions(+) (limited to 'tests/auto/corelib/serialization/json') diff --git a/tests/auto/corelib/serialization/json/tst_qtjson.cpp b/tests/auto/corelib/serialization/json/tst_qtjson.cpp index b11d8c996d..ac1ef76f8c 100644 --- a/tests/auto/corelib/serialization/json/tst_qtjson.cpp +++ b/tests/auto/corelib/serialization/json/tst_qtjson.cpp @@ -163,6 +163,10 @@ private Q_SLOTS: void streamVariantSerialization(); void escapeSurrogateCodePoints_data(); void escapeSurrogateCodePoints(); + + void fromToVariantConversions_data(); + void fromToVariantConversions(); + private: QString testDataDir; }; @@ -3301,5 +3305,154 @@ void tst_QtJson::escapeSurrogateCodePoints() QVERIFY(buffer.contains(escStr)); } +void tst_QtJson::fromToVariantConversions_data() +{ + QTest::addColumn("variant"); + QTest::addColumn("json"); + QTest::addColumn("jsonToVariant"); + + QByteArray utf8("\xC4\x90\xC4\x81\xC5\xA3\xC3\xA2"); // Đāţâ + QDateTime dt = QDateTime::currentDateTimeUtc(); + QUuid uuid = QUuid::createUuid(); + + constexpr qlonglong maxInt = std::numeric_limits::max(); + constexpr qlonglong minInt = std::numeric_limits::min(); + constexpr double maxDouble = std::numeric_limits::max(); + constexpr double minDouble = std::numeric_limits::min(); + + QTest::newRow("default") << QVariant() << QJsonValue(QJsonValue::Null) + << QVariant::fromValue(nullptr); + QTest::newRow("nullptr") << QVariant::fromValue(nullptr) << QJsonValue(QJsonValue::Null) + << QVariant::fromValue(nullptr); + QTest::newRow("bool") << QVariant(true) << QJsonValue(true) << QVariant(true); + QTest::newRow("int pos") << QVariant(123) << QJsonValue(123) << QVariant(qlonglong(123)); + QTest::newRow("int neg") << QVariant(-123) << QJsonValue(-123) << QVariant(qlonglong(-123)); + QTest::newRow("int big pos") << QVariant((1ll << 55) +1) << QJsonValue((1ll << 55) + 1) + << QVariant(qlonglong((1ll << 55) + 1)); + QTest::newRow("int big neg") << QVariant(-(1ll << 55) + 1) << QJsonValue(-(1ll << 55) + 1) + << QVariant(qlonglong(-(1ll << 55) + 1)); + QTest::newRow("int max") << QVariant(maxInt) << QJsonValue(maxInt) << QVariant(maxInt); + QTest::newRow("int min") << QVariant(minInt) << QJsonValue(minInt) << QVariant(minInt); + QTest::newRow("double pos") << QVariant(123.) << QJsonValue(123.) << QVariant(qlonglong(123.)); + QTest::newRow("double neg") << QVariant(-123.) << QJsonValue(-123.) + << QVariant(qlonglong(-123.)); + QTest::newRow("double big") << QVariant(maxDouble - 1000) << QJsonValue(maxDouble - 1000) + << QVariant(maxDouble - 1000); + QTest::newRow("double max") << QVariant(maxDouble) << QJsonValue(maxDouble) + << QVariant(maxDouble); + QTest::newRow("double min") << QVariant(minDouble) << QJsonValue(minDouble) + << QVariant(minDouble); + QTest::newRow("double big neg") << QVariant(1000 - maxDouble) << QJsonValue(1000 - maxDouble) + << QVariant(1000 - maxDouble); + QTest::newRow("double max neg") << QVariant(-maxDouble) << QJsonValue(-maxDouble) + << QVariant(-maxDouble); + QTest::newRow("double min neg") << QVariant(-minDouble) << QJsonValue(-minDouble) + << QVariant(-minDouble); + + QTest::newRow("string null") << QVariant(QString()) << QJsonValue(QString()) + << QVariant(QString()); + QTest::newRow("string empty") << QVariant(QString("")) << QJsonValue(QString("")) + << QVariant(QString("")); + QTest::newRow("string ascii") << QVariant(QString("Data")) << QJsonValue(QString("Data")) + << QVariant(QString("Data")); + QTest::newRow("string utf8") << QVariant(QString(utf8)) << QJsonValue(QString(utf8)) + << QVariant(QString(utf8)); + + QTest::newRow("bytearray null") << QVariant(QByteArray()) << QJsonValue(QJsonValue::Null) + << QVariant::fromValue(nullptr); + QTest::newRow("bytearray empty") << QVariant(QByteArray()) << QJsonValue(QJsonValue::Null) + << QVariant::fromValue(nullptr); + QTest::newRow("bytearray ascii") << QVariant(QByteArray("Data")) << QJsonValue(QString("Data")) + << QVariant(QString("Data")); + QTest::newRow("bytearray utf8") << QVariant(utf8) << QJsonValue(QString(utf8)) + << QVariant(QString(utf8)); + + QTest::newRow("datetime") << QVariant(dt) << QJsonValue(dt.toString(Qt::ISODateWithMs)) + << QVariant(dt.toString(Qt::ISODateWithMs)); + QTest::newRow("url") << QVariant(QUrl("http://example.com/{q}")) + << QJsonValue("http://example.com/%7Bq%7D") + << QVariant(QString("http://example.com/%7Bq%7D")); + QTest::newRow("uuid") << QVariant(QUuid(uuid)) + << QJsonValue(uuid.toString(QUuid::WithoutBraces)) + << QVariant(uuid.toString(QUuid::WithoutBraces)); + QTest::newRow("regexp") << QVariant(QRegularExpression(".")) << QJsonValue(QJsonValue::Null) + << QVariant::fromValue(nullptr); + + QTest::newRow("inf") << QVariant(qInf()) << QJsonValue(QJsonValue::Null) + << QVariant::fromValue(nullptr); + QTest::newRow("-inf") << QVariant(-qInf()) << QJsonValue(QJsonValue::Null) + << QVariant::fromValue(nullptr); + QTest::newRow("NaN") << QVariant(qQNaN()) << QJsonValue(QJsonValue::Null) + << QVariant::fromValue(nullptr); +} + +void tst_QtJson::fromToVariantConversions() +{ + QFETCH(QVariant, variant); + QFETCH(QJsonValue, json); + QFETCH(QVariant, jsonToVariant); + + QVariant variantFromJson(json); + QVariant variantFromJsonArray(QJsonArray { json }); + QVariant variantFromJsonObject(QVariantMap { { "foo", variant } }); + + QJsonObject object { QPair("foo", json) }; + + // QJsonValue <> QVariant + { + QCOMPARE(QJsonValue::fromVariant(variant), json); + + // test the same for QVariant from QJsonValue/QJsonArray/QJsonObject + QCOMPARE(QJsonValue::fromVariant(variantFromJson), json); + QCOMPARE(QJsonValue::fromVariant(variantFromJsonArray), QJsonArray { json }); + QCOMPARE(QJsonValue::fromVariant(variantFromJsonObject), object); + + // QJsonValue to variant + QCOMPARE(json.toVariant(), jsonToVariant); + QCOMPARE(json.toVariant().userType(), jsonToVariant.userType()); + + // variant to QJsonValue + QCOMPARE(QVariant(json).toJsonValue(), json); + } + + // QJsonArray <> QVariantList + { + QCOMPARE(QJsonArray::fromVariantList(QVariantList { variant }), QJsonArray { json }); + + // test the same for QVariantList from QJsonValue/QJsonArray/QJsonObject + QCOMPARE(QJsonArray::fromVariantList(QVariantList { variantFromJson }), + QJsonArray { json }); + QCOMPARE(QJsonArray::fromVariantList(QVariantList { variantFromJsonArray }), + QJsonArray {{ QJsonArray { json } }}); + QCOMPARE(QJsonArray::fromVariantList(QVariantList { variantFromJsonObject }), + QJsonArray { object }); + + // QJsonArray to variant + QCOMPARE(QJsonArray { json }.toVariantList(), QVariantList { jsonToVariant }); + // variant to QJsonArray + QCOMPARE(QVariant(QJsonArray { json }).toJsonArray(), QJsonArray { json }); + } + + // QJsonObject <> QVariantMap + { + QCOMPARE(QJsonObject::fromVariantMap(QVariantMap { { "foo", variant } }), object); + + // test the same for QVariantMap from QJsonValue/QJsonArray/QJsonObject + QCOMPARE(QJsonObject::fromVariantMap(QVariantMap { { "foo", variantFromJson } }), object); + + QJsonObject nestedArray { QPair("bar", QJsonArray { json }) }; + QJsonObject nestedObject { QPair("bar", object) }; + QCOMPARE(QJsonObject::fromVariantMap(QVariantMap { { "bar", variantFromJsonArray } }), + nestedArray); + QCOMPARE(QJsonObject::fromVariantMap(QVariantMap { { "bar", variantFromJsonObject } }), + nestedObject); + + // QJsonObject to variant + QCOMPARE(object.toVariantMap(), QVariantMap({ { "foo", jsonToVariant } })); + // variant to QJsonObject + QCOMPARE(QVariant(object).toJsonObject(), object); + } +} + QTEST_MAIN(tst_QtJson) #include "tst_qtjson.moc" -- cgit v1.2.3