LINUX.ORG.RU

Передача ассоциативного массива из QML в C++. Преобразование QJSValue в QVariantMap

 , , , ,


0

1

Имеется Qt 5.11.

Вызываю функцию из QML через JavaScript. В качестве параметра передается ассоциативный массив (индексы - строки и значения - строки).

var a;
a["one"]=odin;
a["two']=dva;

ourExemplar.ourMethod(a);

В документации сказано, что такой массив должен автоматически преобразовываться в QVariantMap.

https://doc.qt.io/qt-5/qtqml-cppintegration-data.html

Однако все, что я могу вытащить из QJSValue - это QVariantList (это алиас QList<QVariant>). Да и то этот список будет нуливого размера.

Вот код:
void OurClass::ourMethod(QJSValue jsVal)
{
    qDebug() << Q_FUNC_INFO << "Слот ourMethod()";

    qDebug() << "Результат isObject(): "  << jsVal.isObject();
    qDebug() << "Результат isQObject(): " << jsVal.isQObject();
    qDebug() << "Результат isArray(): "   << jsVal.isArray();
    qDebug() << "Результат isVariant(): " << jsVal.isVariant();

    qDebug() << "QJSValue содержит тип " << jsVal.toVariant().userType();
    qDebug() << "Тип QVariant "     << qMetaTypeId<QVariant>();
    qDebug() << "Тип QVariantList " << qMetaTypeId<QVariantList>();
    qDebug() << "Тип QVariantMap "  << qMetaTypeId<QVariantMap>();
    qDebug() << "Тип QJSValue: "    << qMetaTypeId<QJSValue>();

    QVariantList list=jsVal.toVariant().toList();
    qDebug() << "Длинна list: " << list.size();

Вот результат:
void OurClass::ourMethod(QJSValue) Слот ourMethod()
Результат isObject():  true
Результат isQObject():  false
Результат isArray():  true
Результат isVariant():  false
QJSValue содержит тип  9
Тип QVariant  41
Тип QVariantList  9
Тип QVariantMap  8
Тип QJSValue:  1056
Длинна list:  0

Здесь видно, что isObject() и isArray() одновременно выдают true. Не знаю, должно ли так быть.

Узнать какой тип содержится в QVariant элемента списка я не могу придумать как, так как нет даже первого элемента, ведь список пустой.

В общем, задача - передать ассоциативный массив из QML в C++, но я не могу разобраться как это сделать. Везде пишется как конвертить из C++ в QML, а мне надо наоборот. У буржуев нашел вот такое:

https://lists.qt-project.org/pipermail/development/2014-September/018513.html

Но тут тоже направление из C++ в QML. Как будто из QML в C++ никто ничего структурированного не передает.

★★★★★

Ага, что-то нащупал. Можно пройтись итератором, вот так:

    QJSValueIterator it(jsVal);

    while (it.hasNext()) {
        it.next();
        qDebug() << "Свойство " << it.name() << ": " << it.value().toString();
    }

Свойство  "length" :  "0"
Свойство  "МПО1" :  "bbb"
Свойство  "МПО" :   "aaa"
Свойство  "МПО2" :  "ccc"
Непонятно правда теперь как отделять автоматически добавляемое свойство length от элемента массива с ключем «length».

Xintrea ★★★★★
() автор топика
 Q_INVOKABLE void ourMethod(QJSValue jsVal) const {
        qDebug() << Q_FUNC_INFO << "Слот ourMethod()";

        qDebug() << "Результат isObject(): "  << jsVal.isObject();
        qDebug() << "Результат isQObject(): " << jsVal.isQObject();
        qDebug() << "Результат isArray(): "   << jsVal.isArray();
        qDebug() << "Результат isVariant(): " << jsVal.isVariant();

        qDebug() << "QJSValue содержит тип " << jsVal.toVariant().userType();
        qDebug() << "Тип QVariant "     << qMetaTypeId<QVariant>();
        qDebug() << "Тип QVariantList " << qMetaTypeId<QVariantList>();
        qDebug() << "Тип QVariantMap "  << qMetaTypeId<QVariantMap>();
        qDebug() << "Тип QJSValue: "    << qMetaTypeId<QJSValue>();

        QVariantMap map=jsVal.toVariant().toMap();
        qDebug() << map;

    }

Метод сам по себе норм, и документация верная, это приводится к QVariantMap.

Но в js коде ошибка.

Надо так:

var a = {};
a["one"] = 1;
a["two"] = 2;

ourExemplar.ourMethod(a);

Так как при просто var a; значение a будет undefined, типа nullptr если переводить на С++.

qDebug() << map; выводит у меня:

QMap(("one", QVariant(int, 1))("two", QVariant(int, 2)))
fsb4000 ★★★★★
()
Последнее исправление: fsb4000 (всего исправлений: 1)
Ответ на: комментарий от Xintrea

Для свойства «length» значение it.value().isNumber() равно true, а для остальных - false.

Однако если в JS добавить элемент как a[«hundert»]=100, то у такого элемента it.value().isNumber() будет true.

Пока что вырисовывается только пропускать первый элемент, если его имя «length» и он содержит число 0.

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от fsb4000

Благодарю, заработало.

Только я не понял:


Так как var a; это a = undefined, типа nullptr по С++.


Я проверил с var a = []; - ведь с такой инициализацией a уже не undefined? Но с квадратными скобками не работает.

Да и после добавления хотя бы одного элемента объект какой бы он не был он же перестает быть undefined?

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Xintrea

Я проверил с var a = []; - ведь с такой инициализацией a уже не undefined? Но с квадратными скобками не работает.

var a = []; это инициализация обычного массива.

Видимо дальше QML не понимает, что у него массив или объект/ассоциотивный массив и выбирает обычный массив, но в него мы элементов не добавляли, поэтому длина 0 и прочее.

a["one"]=odin; - добавление атрибута one к массиву a.

то есть тоже самое что и a.one = odin

если a=undefined, то мы и не можем добавить элемент к undefined

Вот 2 ссылки про массивы в Javascript:

https://stackoverflow.com/a/37516003/4544798

https://xkoder.com/blog/2008/07/10/javascript-associative-arrays-demystified/

fsb4000 ★★★★★
()

Ещё, если у тебя всегда именно такой тип будет приходить на вход, то можно сразу QVariantMap принимать в методе, а не QJSValue.

QJSValue нужен если могут быть переданы разные типы…

Q_INVOKABLE void ourMethod(QVariantMap map) const {
    qDebug() << map;
}
fsb4000 ★★★★★
()
Ответ на: комментарий от fsb4000

В общем я поэкспериментировал и понял вот что.

Видимо, JS движок в QML имеет свои особенности представления значений.

Если проинитить var a={};, то a будет считаться объектом, и добавление элементов a[«one»]=odin - это на самом деле добавлением свойств объекту. Может казаться что идет работа с ассоциативным массивом, но на самом деле это работа со свойствами объекта: у ассоциативного массива может быть только один элемент с указанным ключем в виде строки, и у объекта может быть только одно свойство с указанным именем. И если проинициализированный через {} протолкнуть в C++, то он нормально сконвертится в QVariantMap.

Если проинитить var a=[];, то a будет считаться числовым массивом, пока что нуливой длинны. Если добавлять элементы a[«one»]=odin, то (основной?) тип a не поменяется, он так и останется числовым массивом, причем нуливой длинны. Но так же эта переменная будет считаться и объектом, которому добавили свойство «one».

Если такой «двойной» объект протолкнуть в C++, то он не будет конвертиться в QVariantMap, а будет преобразовываться только в QVariantList. Причем длинна у такого списка будет нулевой, ведь числовой массив не наполнялся и был нуливого размера. А если пробежаться по объекту итератором, то можно увидеть свойство «length», равное 0, и ассоциативные свойства с ключами «one», «two».

Но и это еще не все, если такому «двойному» объекту, проинициализированному через [] добавить один числовой элемент массива a[5]=500, тогда полученный QVariantList будет длинной 6, причем элементы от 0 до 4 будут представлены как QVariant(Invalid). А если пробежаться по объекту итератором, то вытаскиваются следующие свойства:

"5" - 500
"length" - 6
"one" - "odin"
"two" - "dva"

Причем элементов 0-4 через итератор не видно, их просто нет.

В общем, как-то так.

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Xintrea

Это потому что кто-то плохо знает JavaScript. В нём нет специального типа для ассоциативного массива (на самом деле есть - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Obje..., но поддерживается не всеми движками). Просто к свойствам любого объекта можно обращаться через квадратные скобки, а наименованием свойства может быть произвольная строка. Также JS позволяет добавлять свойства экземляру обьекта в рантайме ибо динамическая типизация. Массивы в JS это полноправные объекты. Соответственно, при обращении через строковые индексы ты работаешь не с массивом, а с объектом массива. И просто добавляешь ему новые свойства.

Типа как в плюсах можно создать класс наследника std::vector и надобавлять ему полей и методов.

Если тебе нужен ассоциативный массив в JS, то нужно использовать фигурные скобки, либо Map, если движок поддерживает и хочется красиво.

KivApple ★★★★★
()
Последнее исправление: KivApple (всего исправлений: 1)
Ответ на: комментарий от KivApple

Это потому что кто-то плохо знает JavaScript. В нём нет специального типа для ассоциативного массива (на самом деле есть - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Obje..., но поддерживается не всеми движками)

Значит, зависит от движка, а не потому что кто-то плохо знает JavaScript? Я ковырял JavaScript еще когда NetScape в ходу был, и тогда было достаточно объяснения «все массивы в JS являются ассоциативными, не парьте себе мозг, вы ничего численными массивами не заоптимизируете».

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от fsb4000

Видимо дальше QML не понимает, что у него массив или объект/ассоциотивный массив и выбирает обычный массив

Массив - это объект.

var arr = [1,2,3];
arr.legnth
> 3
arr.x = 5;
arr
> [1, 2, 3, x : 5]
arr.length
> 3
arr.join(", ");
> "1, 2, 3"
Т.е. ты можешь добавлять произвольные свойства.

crutch_master ★★★★★
()
Ответ на: комментарий от Xintrea

Нет.

Используя строковые ключи на массиве вы именно расширяете его объект. И это имеет два последствия - у вас в мапе будут лишние ключи типа length и с точки зрения всяких биндингов к другим языкам у вас будет в первую очередь именно массив, а доступ к кастомным полям может оказаться через попу, потому что массив с кастомными полями не самый популярный use-case.

А ключевое отличие Map от просто объекта с произвольным набором полей - возможность использовать нестроковые ключи.

А авторы «не парьте себе мозг» типичные быдлокодеры, пораждающие неподдерживаемый код с непредсказуемыми побочными эффектами при смене или обновлении JS движка. Не надо быть одним из них. Впрочем, возможно, во времена NetScape на JS нельзя было иначе, но за годы язык сильно эволюционировал и на нём стало больше возможностей писать адекватный код.

Массивы - для числовых индексов.

Объекты, Map - для строковых индексов, причём второе предпочтительнее.

Map - для нестроковых индексов.

Отклонение от этих правил - верный способ выстрелить себе в ногу в самый неожиданный момент.

KivApple ★★★★★
()
Последнее исправление: KivApple (всего исправлений: 1)
Ответ на: комментарий от crutch_master

В JS ты можешь добавлять с этим никто не спорит.

Но когда ты передаешь в С++, то QML однозначен:

qDebug() << "QJSValue содержит тип " << jsVal.toVariant().userType(); // тут говорится о типе массив.

и

jsVal.toVariant().toMap(); // возвращает пустую QVariantMap
fsb4000 ★★★★★
()
Ответ на: комментарий от Xintrea

Видимо, JS движок в QML имеет свои особенности представления значений.

Это не особенности, всё правильно он делает по спецификации. В жс ассоциотивный массив - это Map. {} - объект, массив - тоже объект и обладает всеми свойствами объекта. У объекта нет счётчика свойств. У Map - есть. Если qml умеет в Map мб лучше использовать его.

Если проинитить var a=[];, то a будет считаться числовым массивом

Это не числовой массив. В массив можно напихать чего угодно, хоть функции, массивы и объекты.

crutch_master ★★★★★
()
Ответ на: комментарий от fsb4000

Но когда ты передаешь в С++, то QML однозначен

Да он забивает хер просто на то, что там какие-то свойства были. Видит массив и работает с той частью, где это массив.

crutch_master ★★★★★
()
Ответ на: комментарий от KivApple

Map - для нестроковых индексов.

Map - это когда тебе надо часто добавлять/удалять свойства, потому что Map - это действительно Map (ну, по крайней мере в V8). Map нельзя легко и просто перегнать в json. С другой стороны удалять свойства из объекта - медленно, черевато деоптимизацией. А кому вообще нужны нестроковые ключи? Где это может понадобится?
Объекты вообще желательно иметь с одинаковым составом полей, которые добавляются в одинаковом порядке.

crutch_master ★★★★★
()
Последнее исправление: crutch_master (всего исправлений: 4)
Ответ на: комментарий от crutch_master

Это не числовой массив. В массив можно напихать чего угодно, хоть функции, массивы и объекты.

Я имел в виду что индексы числовые. Значения, естественно, могут быть любыми, в JS же не строгая типизация.

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от crutch_master

Объект при удалении ключей превращается в hash map под капотом в v8.

Идея в том, что в v8 есть ряд оптимизаций для объектов, из которых не удаляют ключи, а добавляют в одинаковом порядке. Это позволяет им работать быстрее обычной hash map.

Но если использовать объект вместо Map, эти оптимизации отключатся и он по производительности станет равен Map.

Map если чем-то и выиграет, то только тем, что для его элементов эти оптимизации исходно не применяются.

KivApple ★★★★★
()
Последнее исправление: KivApple (всего исправлений: 1)
Ответ на: комментарий от crutch_master

Создание объекта и дроп ключа из него скорее всего будет немного дольше, чем создание Map. Во-вторых, это особенность движка V8, на других движках могут быть какие-то более значительные просадки производительности. В-третьих, стилистически может быть удобно разделение между объектами и ассоциативными массивами. В-четвертых, нестроковые ключи.

Но самое главное - речь вообще о том, что ТСу нужно {} вместо []. Map был упомянут лишь как альтернатива {}. Это не отменяет того, что ТС удаляет гланды через задний проход и на этом и подорвался (импровизированная мапа передаётся в C++ код как пустой массив).

KivApple ★★★★★
()
Ответ на: комментарий от KivApple

Создание объекта и дроп ключа из него скорее всего будет немного дольше, чем создание Map. Во-вторых, это особенность движка V8, на других движках могут быть какие-то более значительные просадки производительности. В-третьих, стилистически может быть удобно разделение между объектами и ассоциативными массивами.

Тут согласен.

В-четвертых, нестроковые ключи.

А есть хоть один кейс, где это нужно? Если ключ - объект, не проще ли ему добавить свойство?

Но самое главное - речь вообще о том, что ТСу нужно {} вместо [].

Да с ТСом всё понятно.

crutch_master ★★★★★
()
Последнее исправление: crutch_master (всего исправлений: 1)