Object.keys, Object.values and JSON.stringify give empty results in QML

46 Views Asked by At

I wrote a code which exposes an C++ object to QML and I would like to check declared properties of the object with native JS things, but they does not work as it expected. I wrote FizzBuzzDerived.properties method and it works file as I would expect Object.keys work.

Result in case of calling the functions

Object.keys(FizzBuzz);    // -> []
Object.values(FizzBuzz);  // -> []
JSON.stringify(FizzBuzz); // -> {} 
FizzBuzz.properties();    // -> ["objectName", "fizz", "buzz", "foo", "bar"]

Here is the code.

main.cpp

#include "myplugin.h"
#include <QApplication>
#include <QQmlApplicationEngine>
#include <QQmlEngine>
#include <QQmlExtensionPlugin>

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;

    MyPlugin plugin;
    plugin.registerTypes("ObjectStorage");

    const QUrl url = QStringLiteral("qrc:/main.qml");
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);
    engine.load(url);

    return app.exec();
}

myplugin.h

#ifndef MYPLUGIN_H
#define MYPLUGIN_H

#include "objectstorage.h"
#include <QObject>
#include <QQmlExtensionPlugin>

class MyPlugin : public QQmlExtensionPlugin
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)
public:
    void registerTypes(const char *uri) override
    {
        Q_ASSERT(QLatin1String(uri) == QLatin1String("ObjectStorage"));
        qmlRegisterSingletonType<FizzBuzzDerived>("Model", 1, 0, "FizzBuzz", ObjectStorage::provider);
    }
};

#endif // MYPLUGIN_H

objectstorage.h

#ifndef OBJECTSTORAGE_H
#define OBJECTSTORAGE_H

#include "fizzbuzzderived.h"
#include <QObject>
#include <QQmlEngine>

class ObjectStorage : public QObject
{
    Q_OBJECT
public:
    static QObject *provider(QQmlEngine *qml, QJSEngine *js)
    {
        Q_UNUSED(qml)
        Q_UNUSED(js)

        FizzBuzzDerived *object = new FizzBuzzDerived();
        object->setFizz(45);
        object->setBuzz(24.42);
        object->setFoo(false);
        object->setBar(Bar::B);

        return object;
    }
};

#endif // OBJECTSTORAGE_H

fizzbuzzderived.h

#ifndef FIZZBUZZDERIVED_H
#define FIZZBUZZDERIVED_H

#include "fizzbuzz.h"
#include <QObject>
#include <QDebug>

class FizzBuzzDerived : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int fizz READ fizz WRITE setFizz NOTIFY fizzChanged);
    Q_PROPERTY(double buzz READ buzz WRITE setBuzz NOTIFY buzzChanged)
    Q_PROPERTY(bool foo READ foo WRITE setFoo NOTIFY fooChanged)
    Q_PROPERTY(int bar READ bar WRITE setBar NOTIFY barChanged)

public:
    explicit FizzBuzzDerived(QObject *parent = nullptr);

    Q_INVOKABLE QList<QString> properties() const;

    int fizz() const;
    double buzz() const;
    bool foo() const;
    int bar() const;

public slots:
    void setFizz(int fizz);
    void setBuzz(double buzz);
    void setFoo(bool foo);
    void setBar(int bar);

signals:
    void fizzChanged(int fizz);
    void buzzChanged(double buzz);
    void fooChanged(bool foo);
    void barChanged(int bar);

private:
    FizzBuzz m_object;
};

Q_DECLARE_METATYPE(FizzBuzzDerived *)

#endif // FIZZBUZZDERIVED_H

fizzbuzzderived.cpp

#include "fizzbuzzderived.h"
#include <QMetaProperty>

FizzBuzzDerived::FizzBuzzDerived(QObject *parent)
    : QObject(parent)
{

}

QList<QString> FizzBuzzDerived::properties() const
{
    QList<QString> names;
    const QMetaObject *metaObject = this->metaObject();
    for (int i = 0; i < metaObject->propertyCount(); ++i) {
        QMetaProperty property = metaObject->property(i);
        names << QString(property.name());
    }

    return names;
}

int FizzBuzzDerived::fizz() const
{
    return m_object.fizz;
}

double FizzBuzzDerived::buzz() const
{
    return m_object.buzz;
}

bool FizzBuzzDerived::foo() const
{
    return m_object.foo;
}

int FizzBuzzDerived::bar() const
{
    return m_object.bar;
}

void FizzBuzzDerived::setFizz(int fizz)
{
    if (m_object.fizz != fizz) {
        m_object.fizz = fizz;
        emit fizzChanged(m_object.fizz);
    }
}

void FizzBuzzDerived::setBuzz(double buzz)
{
    if (m_object.buzz != buzz) {
        m_object.buzz = buzz;
        emit buzzChanged(m_object.buzz);
    }
}

void FizzBuzzDerived::setFoo(bool foo)
{
    if (m_object.foo != foo) {
        m_object.foo = foo;
        emit fooChanged(m_object.foo);
    }
}

void FizzBuzzDerived::setBar(int bar)
{
    if (m_object.bar != bar) {
        m_object.bar = static_cast<Bar>(bar);
        emit barChanged(m_object.bar);
    }
}

main.qml

import QtQuick 2.0
import Model 1.0

Item {
    property FizzBuzz item: FizzBuzz

    Component.onCompleted: {
        const object = item;
        const props = object.properties();
        console.info(`object: ${object}`)
        console.info(props.map(prop => `${prop}: ${JSON.stringify(object[prop])}`).join(", "));
    }
}

Tried to get the object keys from C++ side and it works. Don't have a clue why this is happening.

By the way, I use Qt 5.14.2.

1

There are 1 best solutions below

2
Stephen Quan On

I recommend having a QVariantMap or QVariant property. When QML/JS sees this it will marshal it into a proper Javascript object which means all Javascript functions such as JSON.stringify() will now work.

Changing your private FizzBuzz m_object to QVariantMap m_json should do the trick.

Also, for your signal handlers, I do not recommend offering properties since this will make it ambiguous later on whether handlers are accessing properties or arguments since they're both named the same. In fact, you should rely more on your properties and make your signal handlers parameterless.

class FizzBuzzDerived : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int fizz READ fizz WRITE setFizz NOTIFY fizzChanged);
    Q_PROPERTY(double buzz READ buzz WRITE setBuzz NOTIFY buzzChanged)
    Q_PROPERTY(bool foo READ foo WRITE setFoo NOTIFY fooChanged)
    Q_PROPERTY(int bar READ bar WRITE setBar NOTIFY barChanged)
    Q_PROPERTY(QVariant json READ json WRITE setJson NOTIFY jsonChanged)

public:
    explicit FizzBuzzDerived(QObject *parent = nullptr);

    Q_INVOKABLE QList<string> properties() const { return m_json.keys(); }
    int fizz() const { return m_json["fizz"].toInt(); }
    double buzz() const { return m_json["buzz"].toDouble(); }
    bool foo() const { return m_json["foo"].toBool(); }
    int bar() const { return m_json["bar"].toInt(); }
    QVariant json() const { return m_json; }

public slots:
    void setFizz(int value)
    {
        if (value == fizz()) return;
        m_json["fizz"] = value;
        emit fizzChanged();
        emit jsonChanged();
    }
    void setBuzz(double value)
    {
        if (value == buzz()) return;
        m_json["buzz"] = value;
        emit buzzChanged();
        emit jsonChanged();
    }
    void setFoo(bool value)
    {
        if (value == foo()) return;
        m_json["foo"] = value;
        emit fooChanged();
        emit jsonChanged();
    }
    void setBar(int value)
    {
        if (value == bar()) return;
        m_json["bar"] = value;
        emit barChanged();
        emit jsonChanged();
    }
    void setJson(const QVariant& value)
    {
        m_json = value.toMap();
        emit fizzChanged();
        emit buzzChanged();
        emit fooChanged();
        emit barChanged();
        emit jsonChanged();
    }

signals:
    void fizzChanged();
    void buzzChanged();
    void fooChanged();
    void barChanged();
    void jsonChanged();

private:
    QVariantMap m_json;
};

Your QML code should be:

Object.keys(FizzBuzz.json);
Object.values(FizzBuzz.json);
JSON.stringify(FizzBuzz.json);
FizzBuzz.properties();