diff options
author | Arno Rehn <a.rehn@menlosystems.com> | 2022-10-26 20:05:28 +0200 |
---|---|---|
committer | Arno Rehn <a.rehn@menlosystems.com> | 2023-02-09 17:19:50 +0100 |
commit | 4b5949bf54aa84eea866da9eb8d0b6834c968282 (patch) | |
tree | 77c18eef7e35b692977bf1dfcd6dc93c7e40c006 | |
parent | 48d2066d6c9b38f29368e5205a5b7f49ab3fa857 (diff) | |
download | qtwebchannel-4b5949bf54aa84eea866da9eb8d0b6834c968282.tar.gz |
Add server-side support for custom converters
Automatically tries to convert server-side values to/from QJsonValue.
Carries on as usual when the conversion fails.
Fixes: QTBUG-92902
Change-Id: I89ae7c3bc8490223c9fab41ca513d9277483692e
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
-rw-r--r-- | src/webchannel/qmetaobjectpublisher.cpp | 33 | ||||
-rw-r--r-- | src/webchannel/qwebchannel.cpp | 9 | ||||
-rw-r--r-- | tests/auto/webchannel/tst_webchannel.cpp | 37 | ||||
-rw-r--r-- | tests/auto/webchannel/tst_webchannel.h | 15 |
4 files changed, 76 insertions, 18 deletions
diff --git a/src/webchannel/qmetaobjectpublisher.cpp b/src/webchannel/qmetaobjectpublisher.cpp index 84c3709..c956676 100644 --- a/src/webchannel/qmetaobjectpublisher.cpp +++ b/src/webchannel/qmetaobjectpublisher.cpp @@ -782,17 +782,8 @@ QVariant QMetaObjectPublisher::unwrapVariant(const QVariant &value) const QVariant QMetaObjectPublisher::toVariant(const QJsonValue &value, int targetType) const { QMetaType target(targetType); - if (targetType == QMetaType::QJsonValue) { - return QVariant::fromValue(value); - } else if (targetType == QMetaType::QJsonArray) { - if (!value.isArray()) - qWarning() << "Cannot not convert non-array argument" << value << "to QJsonArray."; - return QVariant::fromValue(value.toArray()); - } else if (targetType == QMetaType::QJsonObject) { - if (!value.isObject()) - qWarning() << "Cannot not convert non-object argument" << value << "to QJsonObject."; - return QVariant::fromValue(value.toObject()); - } else if (target.flags() & QMetaType::PointerToQObject) { + + if (target.flags() & QMetaType::PointerToQObject) { QObject *unwrappedObject = unwrapObject(value.toObject()[KEY_ID].toString()); if (unwrappedObject == nullptr) qWarning() << "Cannot not convert non-object argument" << value << "to QObject*."; @@ -802,13 +793,18 @@ QVariant QMetaObjectPublisher::toVariant(const QJsonValue &value, int targetType return QVariant(target, reinterpret_cast<const void*>(&flagsValue)); } - // this converts QJsonObjects to QVariantMaps, which is not desired when - // we want to get a QJsonObject or QJsonValue (see above) - QVariant variant = unwrapVariant(value.toVariant()); - if (targetType != QMetaType::QVariant && !variant.convert(target)) { - qWarning() << "Could not convert argument" << value << "to target type" << target.name() << '.'; + QVariant variant = QJsonValue::fromVariant(value); + // Try explicit conversion to the target type first. If that fails, fall + // back to generic conversion + if (auto converted = variant; converted.convert(target)) { + variant = std::move(converted); + } else { + if (targetType != QMetaType::QVariant) { + qWarning() << "Could not convert argument" << value << "to target type" << target.name() << '.'; + } + variant = value.toVariant(); } - return variant; + return unwrapVariant(variant); } int QMetaObjectPublisher::conversionScore(const QJsonValue &value, int targetType) const @@ -981,6 +977,9 @@ QJsonValue QMetaObjectPublisher::wrapResult(const QVariant &result, QWebChannelA if (!map.convert(QMetaType::fromType<QVariantMap>())) map = result; return wrapMap(map.value<QVariantMap>(), transport); + } else if (auto v = result; v.convert(QMetaType::fromType<QJsonValue>())) { + // Support custom converters to QJsonValue + return v.value<QJsonValue>(); } return QJsonValue::fromVariant(result); diff --git a/src/webchannel/qwebchannel.cpp b/src/webchannel/qwebchannel.cpp index 60e49e3..54a5c0b 100644 --- a/src/webchannel/qwebchannel.cpp +++ b/src/webchannel/qwebchannel.cpp @@ -33,6 +33,12 @@ QT_BEGIN_NAMESPACE QWebChannel transparently supports QFuture. When a client calls a method that returns a QFuture, QWebChannel will send a response with the QFuture result only after the QFuture has finished. + Custom conversion of types to and from JSON is supported by defining converters with + QMetaType::registerConverter() to and from QJsonValue. Note that custom converters from QJsonValue to a concrete + type must fail if the QJsonValue does not match the expected format. Otherwise QWebChannel cannot fall back to its + default conversion mechanisms. + Custom converters are also available on \l{Qt WebChannel JavaScript API}{the JavaScript side}. + The C++ QWebChannel API makes it possible to talk to any HTML client, which could run on a local or even remote machine. The only limitation is that the HTML client supports the JavaScript features used by \c{qwebchannel.js}. As such, one can interact @@ -40,7 +46,8 @@ QT_BEGIN_NAMESPACE There also exists a declarative \l{Qt WebChannel QML Types}{WebChannel API}. - \sa {Qt WebChannel Standalone Example}, {Qt WebChannel JavaScript API}{JavaScript API} + \sa {Qt WebChannel Standalone Example}, {Qt WebChannel JavaScript API}{JavaScript API}, + QMetaType::registerConverter() */ /*! diff --git a/tests/auto/webchannel/tst_webchannel.cpp b/tests/auto/webchannel/tst_webchannel.cpp index 7bf04dd..7305929 100644 --- a/tests/auto/webchannel/tst_webchannel.cpp +++ b/tests/auto/webchannel/tst_webchannel.cpp @@ -21,6 +21,7 @@ #endif #include <memory> +#include <optional> #include <vector> QT_USE_NAMESPACE @@ -244,8 +245,34 @@ TestWebChannel::TestWebChannel(QObject *parent) , m_lastDouble(0) { qRegisterMetaType<TestStruct>(); + Q_ASSERT(QMetaType::fromType<TestStruct>().isEqualityComparable()); + Q_ASSERT(QMetaType::fromType<TestStruct>().hasRegisteredDebugStreamOperator()); + qRegisterMetaType<TestStructVector>(); QMetaType::registerConverter<TestStructVector, QVariantList>(convert_to_js); + + QMetaType::registerConverter<TestStruct, QString>(); + + QMetaType::registerConverter<TestStruct, QJsonValue>( + [](const TestStruct &s) { + return QJsonObject { + { "__type__", "TestStruct" }, + { "foo", s.foo }, + { "bar", s.bar }, + }; + }); + + QMetaType::registerConverter<QJsonValue, TestStruct>( + [](const QJsonValue &value) -> std::optional<TestStruct> { + const auto object = value.toObject(); + if (object.value("__type__").toString() != QStringLiteral("TestStruct")) + return std::nullopt; + + return TestStruct { + object.value("foo").toInt(), + object.value("bar").toInt(), + }; + }); } TestWebChannel::~TestWebChannel() @@ -952,6 +979,11 @@ void TestWebChannel::testWrapValues_data() {"Two", 2}}) << QJsonValue(QJsonObject{{"One", 1}, {"Two", 2}}); + + QTest::addRow("customType") << QVariant::fromValue(TestStruct{42, 7}) + << QJsonValue(QJsonObject{{"__type__", "TestStruct"}, + {"foo", 42}, + {"bar", 7}}); } void TestWebChannel::testWrapValues() @@ -993,6 +1025,11 @@ void TestWebChannel::testJsonToVariant_data() const TestObject::TestFlags flags = TestObject::FirstFlag | TestObject::SecondFlag; QTest::addRow("flags") << QJsonValue(static_cast<int>(flags)) << QVariant::fromValue(flags); + + QTest::addRow("customType") << QJsonValue(QJsonObject{{"__type__", "TestStruct"}, + {"foo", 42}, + {"bar", 7}}) + << QVariant::fromValue(TestStruct{42, 7}); } void TestWebChannel::testJsonToVariant() diff --git a/tests/auto/webchannel/tst_webchannel.h b/tests/auto/webchannel/tst_webchannel.h index 9344fdc..290457f 100644 --- a/tests/auto/webchannel/tst_webchannel.h +++ b/tests/auto/webchannel/tst_webchannel.h @@ -15,6 +15,7 @@ #if QT_CONFIG(future) #include <QFuture> #endif +#include <QtDebug> #include <QtWebChannel/QWebChannelAbstractTransport> @@ -26,7 +27,21 @@ struct TestStruct {} int foo; int bar; + + operator QString() const { + return QStringLiteral("TestStruct(foo=%1, bar=%2)").arg(foo).arg(bar); + } }; +inline bool operator==(const TestStruct &a, const TestStruct &b) +{ + return a.foo == b.foo && a.bar == b.bar; +} +inline QDebug operator<<(QDebug &dbg, const TestStruct &ts) +{ + QDebugStateSaver dbgState(dbg); + dbg.noquote() << static_cast<QString>(ts); + return dbg; +} Q_DECLARE_METATYPE(TestStruct) using TestStructVector = std::vector<TestStruct>; Q_DECLARE_METATYPE(TestStructVector) |