// SPDX-License-Identifier: LGPL-3.0-or-later /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright (C) 2013 - 2024, nymea GmbH * Copyright (C) 2024 - 2025, chargebyte austria GmbH * * This file is part of nymea. * * nymea is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * nymea is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with nymea. If not, see . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "jsonhandler.h" #include "typeutils.h" #include "loggingcategories.h" #include #include #include "types/param.h" JsonHandler::JsonHandler(QObject *parent) : QObject(parent) { qRegisterMetaType(); registerEnum(); } QHash JsonHandler::cacheHashes() const { return QHash(); } QVariantMap JsonHandler::translateNotification(const QString ¬ification, const QVariantMap ¶ms, const QLocale &locale) { Q_UNUSED(notification) Q_UNUSED(locale) return params; } QVariantMap JsonHandler::jsonEnums() const { return m_enums; } QVariantMap JsonHandler::jsonFlags() const { return m_flags; } QVariantMap JsonHandler::jsonObjects() const { return m_objects; } QVariantMap JsonHandler::jsonMethods() const { return m_methods; } QVariantMap JsonHandler::jsonNotifications() const { return m_notifications; } QString JsonHandler::objectRef(const QString &objectName) { return "$ref:" + objectName; } JsonHandler::BasicType JsonHandler::variantTypeToBasicType(QMetaType::Type variantType) { switch (variantType) { case QMetaType::QUuid: return Uuid; case QMetaType::QString: return String; case QMetaType::QStringList: return StringList; case QMetaType::Int: return Int; case QMetaType::UInt: return Uint; case QMetaType::Double: return Double; case QMetaType::Bool: return Bool; case QMetaType::QColor: return Color; case QMetaType::QTime: return Time; case QMetaType::QVariantMap: return Object; case QMetaType::QDateTime: return Uint; // DateTime is represented as time_t default: return Variant; } } QMetaType::Type JsonHandler::basicTypeToMetaType(JsonHandler::BasicType basicType) { switch (basicType) { case Uuid: return QMetaType::QUuid; case String: return QMetaType::QString; case StringList: return QMetaType::QStringList; case Int: return QMetaType::Int; case Uint: return QMetaType::UInt; case Double: return QMetaType::Double; case Bool: return QMetaType::Bool; case Color: return QMetaType::QColor; case Time: return QMetaType::QTime; case Object: return QMetaType::QVariantMap; case Variant: return QMetaType::UnknownType; } return QMetaType::UnknownType; } void JsonHandler::registerObject(const QString &name, const QVariantMap &object) { m_objects.insert(name, object); } void JsonHandler::registerMethod(const QString &name, const QString &description, const QVariantMap ¶ms, const QVariantMap &returns, Types::PermissionScope permissionScope, const QString &deprecationInfo) { QVariantMap methodData; methodData.insert("description", description); methodData.insert("params", params); methodData.insert("returns", returns); methodData.insert("permissionScope", enumValueName(permissionScope)); if (!deprecationInfo.isEmpty()) { methodData.insert("deprecated", deprecationInfo); } m_methods.insert(name, methodData); } void JsonHandler::registerNotification(const QString &name, const QString &description, const QVariantMap ¶ms, const QString &deprecationInfo) { QVariantMap notificationData; notificationData.insert("description", description); notificationData.insert("params", params); if (!deprecationInfo.isEmpty()) { notificationData.insert("deprecated", deprecationInfo); } m_notifications.insert(name, notificationData); } JsonReply *JsonHandler::createReply(const QVariantMap &data) const { return JsonReply::createReply(const_cast(this), data); } JsonReply *JsonHandler::createAsyncReply(const QString &method) const { return JsonReply::createAsyncReply(const_cast(this), method); } void JsonHandler::registerObject(const QMetaObject &metaObject) { QString className = QString(metaObject.className()).split("::").last(); QVariantMap description; for (int i = 0; i < metaObject.propertyCount(); i++) { QMetaProperty metaProperty = metaObject.property(i); QString name = metaProperty.name(); if (name == "objectName") { continue; // Skip QObject's objectName property } if (metaProperty.isUser()) { name.prepend("o:"); } if (!metaProperty.isWritable()) { name.prepend("r:"); } if (metaProperty.revision() != 0) { name.prepend("d:"); } QVariant typeName; #if QT_VERSION >= QT_VERSION_CHECK(6,0,0) int typeId = metaProperty.typeId(); if (typeId >= QMetaType::User) { if (metaProperty.typeName() == QStringLiteral("QMetaType::Type")) { typeName = QString("$ref:BasicType"); } else if (QString(metaProperty.typeName()).startsWith("QList")) { QString elementType = QString(metaProperty.typeName()).remove("QList<").remove(">"); if (elementType == "ThingId" || elementType == "EventTypeId" || elementType == "StateTypeId" || elementType == "ActionTypeId") { elementType = "QUuid"; } QMetaType::Type variantType = static_cast(QMetaType::fromName(elementType.toUtf8()).id()); typeName = QVariantList() << enumValueName(variantTypeToBasicType(variantType)); } else { QString typeNameRaw = QString(metaProperty.typeName()); // QString propertyNameRaw = QString(metaProperty.name()); // QString metaTypeNameRaw = QString(metaProperty.metaType().name()); if (typeNameRaw.contains("QFlag")) { QString enumType = QString(typeNameRaw).split("::").last().remove('<').remove('>'); typeName = QString("$ref:%1").arg(m_flagsEnums.key(enumType)); } else { typeName = QString("$ref:%1").arg(QString(typeNameRaw).split("::").last().remove('<').remove('>')); } // qCDebug(dcJsonRpc()) << typeNameRaw << propertyNameRaw << metaTypeNameRaw << typeName; } } else if (metaProperty.isEnumType()) { QString typeNameRaw = QString(metaProperty.typeName()); // Note: since Qt6 flags are also enums, and the type name contains // QFlags, therefore we need to remove additionally all < > typeName = QString("$ref:%1").arg(typeNameRaw.split("::").last().remove('<').remove('>')); } else if (metaProperty.isFlagType()) { typeName = QVariantList() << "$ref:" + m_flagsEnums.value(metaProperty.name()); } else if (typeId == QMetaType::QVariantList) { typeName = QVariantList() << enumValueName(Variant); } else { typeName = enumValueName(variantTypeToBasicType(static_cast(typeId))); } #else if (metaProperty.type() == QVariant::UserType) { if (metaProperty.typeName() == QStringLiteral("QMetaType::Type")) { typeName = QString("$ref:BasicType"); } else if (QString(metaProperty.typeName()).startsWith("QList")) { QString elementType = QString(metaProperty.typeName()).remove("QList<").remove(">"); if (elementType == "ThingId" || elementType == "EventTypeId" || elementType == "StateTypeId" || elementType == "ActionTypeId") { elementType = "QUuid"; } QMetaType::Type variantType = static_cast(QVariant::nameToType(elementType.toUtf8())); typeName = QVariantList() << enumValueName(variantTypeToBasicType(variantType)); } else { typeName = QString("$ref:%1").arg(QString(metaProperty.typeName()).split("::").last()); } } else if (metaProperty.isEnumType()) { typeName = QString("$ref:%1").arg(QString(metaProperty.typeName()).split("::").last()); } else if (metaProperty.isFlagType()) { typeName = QVariantList() << "$ref:" + m_flagsEnums.value(metaProperty.name()); } else if (metaProperty.type() == QVariant::List) { typeName = QVariantList() << enumValueName(Variant); } else { typeName = enumValueName(variantTypeToBasicType(static_cast(metaProperty.type()))); } #endif description.insert(name, typeName); } m_objects.insert(className, description); m_metaObjects.insert(className, metaObject); } void JsonHandler::registerList(const QMetaObject &listMetaObject, const QMetaObject &metaObject) { QString listTypeName = QString(listMetaObject.className()).split("::").last(); QString objectTypeName = QString(metaObject.className()).split("::").last(); m_objects.insert(listTypeName, QVariantList() << QVariant(QString("$ref:%1").arg(objectTypeName))); m_metaObjects.insert(listTypeName, listMetaObject); m_listMetaObjects.insert(listTypeName, listMetaObject); m_listEntryTypes.insert(listTypeName, objectTypeName); Q_ASSERT_X(listMetaObject.indexOfProperty("count") >= 0, "JsonHandler", QString("List type %1 does not implement \"count\" property!").arg(listTypeName).toUtf8()); Q_ASSERT_X(listMetaObject.indexOfMethod("get(int)") >= 0, "JsonHandler", QString("List type %1 does not implement \"Q_INVOKABLE QVariant get(int index)\" method!").arg(listTypeName).toUtf8()); Q_ASSERT_X(listMetaObject.indexOfMethod("put(QVariant)") >= 0, "JsonHandler", QString("List type %1 does not implement \"Q_INVOKABLE void put(QVariant variant)\" method!").arg(listTypeName).toUtf8()); } void JsonHandler::registerObject(const QMetaObject &metaObject, const QMetaObject &listMetaObject) { registerObject(metaObject); registerList(listMetaObject, metaObject); } QVariant JsonHandler::pack(const QMetaObject &metaObject, const void *value) const { QString className = QString(metaObject.className()).split("::").last(); if (m_listMetaObjects.contains(className)) { QVariantList ret; QMetaProperty countProperty = metaObject.property(metaObject.indexOfProperty("count")); QMetaObject entryMetaObject = m_metaObjects.value(m_listEntryTypes.value(className)); int count = countProperty.readOnGadget(value).toInt(); QMetaMethod getMethod = metaObject.method(metaObject.indexOfMethod("get(int)")); for (int i = 0; i < count; i++) { QVariant entry; getMethod.invokeOnGadget(const_cast(value), Q_RETURN_ARG(QVariant, entry), Q_ARG(int, i)); ret.append(pack(entryMetaObject, entry.data())); } return ret; } if (m_metaObjects.contains(className)) { QVariantMap ret; for (int i = 0; i < metaObject.propertyCount(); i++) { QMetaProperty metaProperty = metaObject.property(i); // Skip QObject's objectName property if (metaProperty.name() == QStringLiteral("objectName")) { continue; } QVariant propertyValue = metaProperty.readOnGadget(value); // qCDebug(dcJsonRpc()) << metaProperty.name() << "optional:" << metaProperty.isUser() // << "value:" << propertyValue << "valid:" << propertyValue.isValid() << "null:" << propertyValue.isNull(); // If it's optional and empty, we may skip it #if QT_VERSION >= QT_VERSION_CHECK(6,0,0) if (metaProperty.isUser()) { bool isEmpty = false; switch (propertyValue.typeId()) { case QMetaType::QString: isEmpty = propertyValue.toString().isEmpty(); break; case QMetaType::QUuid: isEmpty = propertyValue.toUuid().isNull(); break; default: isEmpty = (!propertyValue.isValid() || propertyValue.isNull()); break; } if (isEmpty) { // Optional and empty...skip this property continue; } } #else if (metaProperty.isUser() && (!propertyValue.isValid() || propertyValue.isNull())) { continue; } #endif #if QT_VERSION >= QT_VERSION_CHECK(6,0,0) // Pack flags if (metaProperty.isFlagType()) { QString enumName = QString(metaProperty.typeName()).split("::").last().remove('<').remove('>'); QString flagName = m_flagsEnums.key(enumName); QMetaEnum metaFlag = m_metaFlags.value(flagName); Q_ASSERT_X(m_metaFlags.contains(flagName), this->metaObject()->className(), QString("Cannot pack %1. %2 is not registered in this handler.").arg(className).arg(flagName).toUtf8()); int flagValue = propertyValue.toInt(); QStringList flags; for (int i = 0; i < metaFlag.keyCount(); i++) { int flag = metaFlag.value(i) & flagValue; if (flag == metaFlag.value(i) && flag > 0) { flags.append(metaFlag.key(i)); } } ret.insert(metaProperty.name(), flags); continue; } // Pack enums if (metaProperty.isEnumType()) { QString enumName = QString(metaProperty.typeName()).split("::").last().remove('<').remove('>'); Q_ASSERT_X(m_metaEnums.contains(enumName), this->metaObject()->className(), QString("Cannot pack %1. %2 is not registered in this handler.").arg(className).arg(metaProperty.typeName()).toUtf8()); QMetaEnum metaEnum = m_metaEnums.value(enumName); ret.insert(metaProperty.name(), metaEnum.key(propertyValue.toInt())); continue; } #else // Pack flags if (metaProperty.isFlagType()) { QString flagName = QString(metaProperty.typeName()).split("::").last(); QMetaEnum metaFlag = m_metaFlags.value(flagName); Q_ASSERT_X(m_metaFlags.contains(flagName), this->metaObject()->className(), QString("Cannot pack %1. %2 is not registered in this handler.").arg(className).arg(flagName).toUtf8()); int flagValue = propertyValue.toInt(); QStringList flags; for (int i = 0; i < metaFlag.keyCount(); i++) { int flag = metaFlag.value(i) & flagValue; if (flag == metaFlag.value(i) && flag > 0) { flags.append(metaFlag.key(i)); } } ret.insert(metaProperty.name(), flags); continue; } // Pack enums if (metaProperty.isEnumType()) { QString enumName = QString(metaProperty.typeName()).split("::").last(); Q_ASSERT_X(m_metaEnums.contains(enumName), this->metaObject()->className(), QString("Cannot pack %1. %2 is not registered in this handler.").arg(className).arg(metaProperty.typeName()).toUtf8()); QMetaEnum metaEnum = m_metaEnums.value(enumName); ret.insert(metaProperty.name(), metaEnum.key(propertyValue.toInt())); continue; } #endif // Basic type/Variant type if (metaProperty.typeName() == QStringLiteral("QMetaType::Type")) { QMetaEnum metaEnum = QMetaEnum::fromType(); ret.insert(metaProperty.name(), metaEnum.valueToKey(propertyValue.template value())); continue; } // Our own objects QString propertyTypeName = QString(metaProperty.typeName()).split("::").last(); #if QT_VERSION >= QT_VERSION_CHECK(6,0,0) int typeId = metaProperty.typeId(); #else int typeId = metaProperty.type(); #endif if (typeId >= QMetaType::User) { if (m_listMetaObjects.contains(propertyTypeName)) { QMetaObject entryMetaObject = m_listMetaObjects.value(propertyTypeName); QVariant packed = pack(entryMetaObject, propertyValue.data()); if (!metaProperty.isUser() || packed.toList().count() > 0) { ret.insert(metaProperty.name(), packed); } continue; } if (m_metaObjects.contains(propertyTypeName)) { QMetaObject entryMetaObject = m_metaObjects.value(propertyTypeName); QVariant packed = pack(entryMetaObject, propertyValue.data()); int isValidIndex = entryMetaObject.indexOfMethod("isValid()"); bool isValid = true; if (isValidIndex >= 0) { QMetaMethod isValidMethod = entryMetaObject.method(isValidIndex); isValidMethod.invokeOnGadget(propertyValue.data(), Q_RETURN_ARG(bool, isValid)); } if (isValid || !metaProperty.isUser()) { ret.insert(metaProperty.name(), packed); } continue; } // Manually converting QList... Only QVariantList is known to the meta system if (propertyTypeName.startsWith("ParamList")) { QVariantList list; foreach (const Param &entry, propertyValue.value()) { list << pack(entry); } ret.insert(metaProperty.name(), list); continue; } // Manually converting QList... Only QVariantList is known to the meta system if (propertyTypeName.startsWith("QList<")) { QVariantList list; if (propertyTypeName == "QList") { foreach (int entry, propertyValue.value>()) { list << entry; } } else if (propertyTypeName == "QList") { foreach (const QUuid &entry, propertyValue.value>()) { list << entry; } } else if (propertyTypeName == "QList") { foreach (const ThingId &entry, propertyValue.value>()) { list << entry; } } else if (propertyTypeName == "QList") { foreach (const EventTypeId &entry, propertyValue.value>()) { list << entry; } } else if (propertyTypeName == "QList") { foreach (const EventTypeId &entry, propertyValue.value>()) { list << entry; } } else if (propertyTypeName == "QList") { foreach (const EventTypeId &entry, propertyValue.value>()) { list << entry; } } else if (propertyTypeName == "QList") { foreach (const QDateTime ×tamp, propertyValue.value>()) { list << timestamp.toSecsSinceEpoch(); } } else { Q_ASSERT_X(false, this->metaObject()->className(), QString("Unhandled list type: %1").arg(propertyTypeName).toUtf8()); qCWarning(dcJsonRpc()) << "Cannot pack property of unhandled list type" << propertyTypeName; } if (!list.isEmpty() || !metaProperty.isUser()) { ret.insert(metaProperty.name(), list); } continue; } Q_ASSERT_X(false, this->metaObject()->className(), QString("Unregistered property type: %1").arg(propertyTypeName).toUtf8()); qCWarning(dcJsonRpc()) << "Cannot pack property of unregistered object type" << propertyTypeName; continue; } // Standard properties, QString, int etc... // Special treatment for QDateTime (converting to time_t) if (typeId == QMetaType::QDateTime) { QDateTime dateTime = propertyValue.toDateTime(); if (metaProperty.isUser() && dateTime.toSecsSinceEpoch() == 0) { continue; } propertyValue = propertyValue.toDateTime().toSecsSinceEpoch(); } else if (typeId == QMetaType::QTime) { propertyValue = propertyValue.toTime().toString("hh:mm"); if (metaProperty.isUser() && propertyValue.toString().isEmpty()) { continue; } } ret.insert(metaProperty.name(), propertyValue); } return ret; } Q_ASSERT_X(false, this->metaObject()->className(), QString("Unregistered object type: %1").arg(className).toUtf8()); qCWarning(dcJsonRpc()) << "Cannot pack object of unregistered type" << className; return QVariant(); } QVariant JsonHandler::unpack(const QMetaObject &metaObject, const QVariant &value) const { QString typeName = QString(metaObject.className()).split("::").last(); // If it's a list object, loop over count if (m_listMetaObjects.contains(typeName)) { #if QT_VERSION >= QT_VERSION_CHECK(6,0,0) if (static_cast(value.typeId()) != QMetaType::QVariantList) { #else if (static_cast(value.type()) != QMetaType::QVariantList) { #endif qCWarning(dcJsonRpc()) << "Cannot unpack" << typeName << ". Value is not in list format:" << value; return QVariant(); } QVariantList list = value.toList(); int typeId = QMetaType::type(metaObject.className()); void* ptr = QMetaType::create(typeId); Q_ASSERT_X(typeId != 0, this->metaObject()->className(), QString("Cannot handle unregistered meta type %1").arg(metaObject.className()).toUtf8()); QMetaObject entryMetaObject = m_metaObjects.value(m_listEntryTypes.value(typeName)); QMetaMethod putMethod = metaObject.method(metaObject.indexOfMethod("put(QVariant)")); foreach (const QVariant &variant, list) { QVariant value = unpack(entryMetaObject, variant); putMethod.invokeOnGadget(ptr, Q_ARG(QVariant, value)); } #if QT_VERSION >= QT_VERSION_CHECK(6,0,0) QVariant ret = QVariant(QMetaType(typeId), ptr); #else QVariant ret = QVariant(static_cast(typeId), ptr); #endif QMetaType::destroy(typeId, ptr); return ret; } // If it's an object, loop over all properties if (m_metaObjects.contains(typeName)) { QVariantMap map = value.toMap(); int typeId = QMetaType::type(metaObject.className()); QMetaType metaType(typeId); Q_ASSERT_X(typeId != 0, this->metaObject()->className(), QString("Cannot handle unregistered meta type %1").arg(typeName).toUtf8()); void* ptr = QMetaType::create(typeId); for (int i = 0; i < metaObject.propertyCount(); i++) { QMetaProperty metaProperty = metaObject.property(i); if (metaProperty.name() == QStringLiteral("objectName")) { continue; } if (!metaProperty.isWritable()) { continue; } if (!metaProperty.isUser()) { Q_ASSERT_X(map.contains(metaProperty.name()), this->metaObject()->className(), QString("Missing property %1 in map.").arg(metaProperty.name()).toUtf8()); } if (map.contains(metaProperty.name())) { QString propertyTypeName = QString(metaProperty.typeName()).split("::").last(); QVariant variant = map.value(metaProperty.name()); // recurse into child lists if (m_listMetaObjects.contains(propertyTypeName)) { QMetaObject propertyMetaObject = m_listMetaObjects.value(propertyTypeName); metaProperty.writeOnGadget(ptr, unpack(propertyMetaObject, variant)); continue; } // recurse into child objects if (m_metaObjects.contains(propertyTypeName)) { QMetaObject propertyMetaObject = m_metaObjects.value(propertyTypeName); metaProperty.writeOnGadget(ptr, unpack(propertyMetaObject, variant)); continue; } if (QString(metaProperty.typeName()).startsWith("QList<")) { if (metaProperty.typeName() == QStringLiteral("QList")) { QList intList; foreach (const QVariant &val, variant.toList()) { intList.append(val.toInt()); } metaProperty.writeOnGadget(ptr, QVariant::fromValue(intList)); } else if (metaProperty.typeName() == QStringLiteral("QList") || metaProperty.typeName() == QStringLiteral("QList") || metaProperty.typeName() == QStringLiteral("QList") || metaProperty.typeName() == QStringLiteral("QList") || metaProperty.typeName() == QStringLiteral("QList")) { QList uuidList; foreach (const QVariant &val, variant.toList()) { uuidList.append(val.toUuid()); } metaProperty.writeOnGadget(ptr, QVariant::fromValue(uuidList)); } continue; } #if QT_VERSION >= QT_VERSION_CHECK(6,0,0) int typeId = metaProperty.typeId(); #else int typeId = metaProperty.type(); #endif // Special treatment for QDateTime (convert from time_t) if (typeId == QMetaType::QDateTime) { variant = QDateTime::fromSecsSinceEpoch(variant.toUInt()); } else if (typeId == QMetaType::QTime) { variant = QTime::fromString(variant.toString(), "hh:mm"); } // For basic properties just write the veriant as is metaProperty.writeOnGadget(ptr, variant); } } #if QT_VERSION >= QT_VERSION_CHECK(6,0,0) QVariant ret = QVariant(QMetaType(typeId), ptr); #else QVariant ret = QVariant(typeId, ptr); #endif QMetaType::destroy(typeId, ptr); return ret; } return QVariant(); }