[Development] Qml/JS QVariantMap conversion problem
Simon Hausmann
simon.hausmann at digia.com
Thu Sep 25 07:55:50 CEST 2014
On Wednesday, September 24, 2014 07:02:32 PM Nils Jeisecke wrote:
> Hi!
>
> While porting to Qt5 I've stumbled over a very strange problem.
>
> QVariantMap objects mutate when transported from C++ via Qml back to C++.
>
> QVariant as constructed on the C++ side:
> QVariant(QVariantMap, QMap(("x", QVariant(QStringList, ("a", "b")) ) ) )
>
> QVariant when gone through Qml and handled back to C++:
> QVariant(QVariantMap, QMap(("x", QVariant(QVariantMap, QMap(("0",
> QVariant(QString, "a") ) ( "1" , QVariant(QString, "b") ) ) ) ) ) )
>
> So what used to be a QStringList now is a QMap?
>
> This is:
> a) not the behavior of Qt4/QtQuick 1
> b) pretty weird
>
> This does not happen if a QVariant directly wrapping a QStringList is
> used, only when a QVariantMap is involved.
>
> Testcase:
>
> --main.cpp--
>
> #include <QApplication>
> #include <QQmlApplicationEngine>
> #include <QQmlContext>
> #include <QDebug>
>
> class Dummy : public QObject
> {
> Q_OBJECT
> public:
> Dummy(QObject *parent = 0) : QObject(parent) {}
>
> Q_INVOKABLE void setMap(const QVariant &map) {
> qDebug() << "setMap" << map;
> }
>
> Q_INVOKABLE QVariant buildMap() const {
> QVariantMap map;
> map["x"] = QStringList() << "a" << "b";
> qDebug() << "buildMap" << QVariant(map);
> return map;
> }
> };
>
> int main(int argc, char *argv[])
> {
> QApplication app(argc, argv);
> QQmlApplicationEngine engine;
>
> engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
>
> Dummy *dummy = new Dummy;
> engine.rootContext()->setContextProperty("dummy", dummy);
>
> return app.exec();
> }
> #include "main.moc"
>
> --main.qml--
>
> import QtQuick 2.3
> import QtQuick.Controls 1.2
>
> ApplicationWindow {
> visible: true
> width: 640
> height: 480
>
> Button {
> text: "Mutate!"
> onClicked: dummy.setMap(dummy.buildMap());
> }
> }
>
> --------------------
>
> Any ideas?
Yeah, the variant conversions were iffy. When you put a QVariantMap or
QVariantList into QML, we'll try to convert it to a JavaScript object. So a
map["x"] = QStringList() << "a" << "b"
becomes
map = { x: [ "a", "b" ] }
So I think that part is fine.
The conversion back is something that's been lossy in the past, like in your
case and many other reported bugs. We've changed this in Qt 5.4 slightly: When
converting a JavaScript object back to a QVariant, we won't try to "destroy"
it but instead we'll give you a QVariant that holds a (strong) reference to
the JavaScript object, it'll be a QJSValue in a QVariant. Note how somebody
could call setMap with the return value of buildMap() but also with a JS
object literal: setMap( { x: ["a", "b" ] })
So in Qt 5.4 you'll get a QVariant with a QJSValue inside, and if you extract
that and _explicitly_ convert it to a QVariant, then you'll get what you
expect:
QVariant(QVariantMap, QMap(("x", QVariant(QStringList, ("a", "b")) ) ) )
You can do the conversion like this:
QVariant map = ...
if (map.userType() == qMetaTypeId<QJSValue>())
map = qvariant_cast<QJSValue>(map).toVariant();
Hope this helps,
Simon
More information about the Development
mailing list