[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