#include "objectconverter_p.h" #include "exception.h" #include "cborserializer.h" #include using namespace QtJsonSerializer; using namespace QtJsonSerializer::TypeConverters; Q_LOGGING_CATEGORY(QtJsonSerializer::TypeConverters::logObjConverter, "qt.jsonserializer.converter.object") bool ObjectConverter::canConvert(int metaTypeId) const { auto flags = QMetaType(metaTypeId).flags(); return flags.testFlag(QMetaType::PointerToQObject); } QList ObjectConverter::allowedCborTags(int metaTypeId) const { Q_UNUSED(metaTypeId) return { NoTag, static_cast(CborSerializer::GenericObject), static_cast(CborSerializer::ConstructedObject) }; } QList ObjectConverter::allowedCborTypes(int metaTypeId, QCborTag tag) const { Q_UNUSED(metaTypeId) switch (static_cast(tag)) { case CborSerializer::GenericObject: return {QCborValue::Array}; case CborSerializer::ConstructedObject: return {QCborValue::Array, QCborValue::Null}; default: return {QCborValue::Map, QCborValue::Null}; } } int ObjectConverter::guessType(QCborTag tag, QCborValue::Type dataType) const { Q_UNUSED(dataType) switch (static_cast(tag)) { case CborSerializer::GenericObject: case CborSerializer::ConstructedObject: return QMetaType::QObjectStar; default: return QMetaType::UnknownType; } } QCborValue ObjectConverter::serialize(int propertyType, const QVariant &value) const { auto object = value.value(); if (!object) return QCborValue::Null; QCborMap cborMap; // get the metaobject, based on polymorphism const QMetaObject *metaObject = nullptr; auto poly = static_cast(helper()->getProperty("polymorphing").toInt()); auto isPoly = false; switch (poly) { case SerializerBase::Polymorphing::Disabled: isPoly = false; break; case SerializerBase::Polymorphing::Enabled: isPoly = polyMetaObject(object); break; case SerializerBase::Polymorphing::Forced: isPoly = true; break; default: Q_UNREACHABLE(); break; } if (isPoly) { metaObject = object->metaObject(); //first: pass the class name cborMap[QStringLiteral("@class")] = QString::fromUtf8(metaObject->className()); } else metaObject = QMetaType(propertyType).metaObject(); if (!metaObject) throw SerializationException(QByteArray("Unable to get metaobject for type ") + QMetaTypeName(propertyType)); //go through all properties and try to serialize them const auto keepObjectName = helper()->getProperty("keepObjectName").toBool(); const auto ignoreStoredAttribute = helper()->getProperty("ignoreStoredAttribute").toBool(); auto i = QObject::staticMetaObject.indexOfProperty("objectName"); if (!keepObjectName) i++; for(; i < metaObject->propertyCount(); i++) { auto property = metaObject->property(i); if (ignoreStoredAttribute || property.isStored()) cborMap[QString::fromUtf8(property.name())] = helper()->serializeSubtype(property, property.read(object)); } return cborMap; } QVariant ObjectConverter::deserializeCbor(int propertyType, const QCborValue &value, QObject *parent) const { if ((value.isTag() ? value.taggedValue() : value).isNull()) return QVariant::fromValue(nullptr); QCborMap cborMap; if (value.isTag()) { if (value.tag() == static_cast(CborSerializer::GenericObject)) return QVariant::fromValue(deserializeGenericObject(value.taggedValue().toArray(), parent)); else if (value.tag() == static_cast(CborSerializer::ConstructedObject)) return QVariant::fromValue(deserializeConstructedObject(value.taggedValue(), parent)); else cborMap = value.taggedValue().toMap(); } else cborMap = value.toMap(); auto poly = static_cast(helper()->getProperty("polymorphing").toInt()); auto metaObject = QMetaType(propertyType).metaObject(); if (!metaObject) throw DeserializationException(QByteArray("Unable to get metaobject for type ") + QMetaTypeName(propertyType)); // try to get the polymorphic metatype (if allowed) auto isPoly = false; if (poly != SerializerBase::Polymorphing::Disabled) { if (cborMap.contains(QStringLiteral("@class"))) { isPoly = true; QByteArray classField = cborMap[QStringLiteral("@class")].toString().toUtf8() + "*"; // add the star #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) auto typeId = QMetaType::type(classField.constData()); auto nMeta = QMetaType(typeId).metaObject(); #else auto metaType = QMetaType::fromName(classField.constData()); auto nMeta = metaType.metaObject(); #endif if (!nMeta) throw DeserializationException("Unable to find class requested from json \"@class\" property: " + classField); if (!nMeta->inherits(metaObject)) { throw DeserializationException("Requested class from \"@class\" field, " + classField + QByteArray(", does not inhert the property type ") + QMetaTypeName(propertyType)); } metaObject = nMeta; } else if (poly == SerializerBase::Polymorphing::Forced) throw DeserializationException("Json does not contain the \"@class\" field, but forced polymorphism requires it"); } // try to construct the object auto object = metaObject->newInstance(Q_ARG(QObject*, parent)); if (!object) { throw DeserializationException(QByteArray("Failed to construct object of type ") + metaObject->className() + QByteArray(" (Does the constructor \"Q_INVOKABLE class(QObject*);\" exist?)")); } deserializeProperties(metaObject, object, cborMap, isPoly); return QVariant::fromValue(object); } bool ObjectConverter::polyMetaObject(QObject *object) const { auto meta = object->metaObject(); //check the internal property if (object->dynamicPropertyNames().contains("__qt_json_serializer_polymorphic")) { auto polyProp = object->property("__qt_json_serializer_polymorphic").toBool(); return polyProp; } //check the class info auto polyIndex = meta->indexOfClassInfo("polymorphic"); if (polyIndex != -1) { auto info = meta->classInfo(polyIndex); if (info.value() == QByteArray("true")) return true;// use the object else if (info.value() == QByteArray("false")) return false;// use the class else qCWarning(logObjConverter) << "Invalid value for polymorphic classinfo on object type" << meta->className() << "ignored"; } //default: the class return false;// use the class } QObject *ObjectConverter::deserializeGenericObject(const QCborArray &value, QObject *parent) const { if (value.size() == 0) throw DeserializationException{"A GenericObject requires at least one argument, specifying the object name"}; // find a meta object QByteArray className = value.first().toString().toUtf8() + "*"; // add the star #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) auto typeId = QMetaType::type(className.constData()); auto metaObject = QMetaType(typeId).metaObject(); #else auto metaType = QMetaType::fromName(className.constData()); auto metaObject = metaType.metaObject(); #endif if (!metaObject) throw DeserializationException("Unable to find class requested from GenericObject tagged array: " + className); // deserialize all arguments QVariantList arguments; arguments.reserve(static_cast(value.size() - 1)); for (auto aIdx = 1ll; aIdx < value.size(); ++aIdx) arguments.append(helper()->deserializeSubtype(QMetaType::UnknownType, value[aIdx], nullptr, className + "[" + QByteArray::number(aIdx - 1) + "]")); // find a matching constructor for (auto cIdx = 0; cIdx < metaObject->constructorCount(); ++cIdx) { const auto constructor = metaObject->constructor(cIdx); // verify same argument count (but allow extra QObject argument for parenting) auto extraObj = false; if (constructor.parameterCount() != arguments.size()) { if (constructor.parameterCount() == arguments.size() + 1 && constructor.parameterType(constructor.parameterCount() - 1) == QMetaType::QObjectStar) extraObj = true; else continue; } // verify each argument can be converted (by converting it) auto argCopy = arguments; auto allOk = true; for (auto pIdx = 0; pIdx < argCopy.size(); ++pIdx) { const auto pType = constructor.parameterType(pIdx); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) if (!argCopy[pIdx].canConvert(pType) || !argCopy[pIdx].convert(pType)) { #else if (!argCopy[pIdx].canConvert(QMetaType(pType)) || !argCopy[pIdx].convert(QMetaType(pType))) { #endif allOk = false; break; } } // if all arguments could be converted -> construct the object if (allOk) { if (extraObj) argCopy.append(QVariant::fromValue(parent)); Q_ASSERT(argCopy.size() <= 10); std::array gArgs; gArgs.fill(QGenericArgument{}); for (auto pIdx = 0; pIdx < argCopy.size(); ++pIdx) gArgs[static_cast(pIdx)] = {argCopy[pIdx].typeName(), argCopy[pIdx].constData()}; auto object = metaObject->newInstance(gArgs[0], gArgs[1], gArgs[2], gArgs[3], gArgs[4], gArgs[5], gArgs[6], gArgs[7], gArgs[8], gArgs[9]); if (!object) { throw DeserializationException(QByteArray("Failed to construct object of type ") + metaObject->className() + QByteArray(" - deserialized constructor arguments are potentially invalid!")); } return object; } } throw DeserializationException{"Unable to find any constructor for " + className + " with " + QByteArray::number(arguments.size()) + " arguments and matching types!"}; } QObject *ObjectConverter::deserializeConstructedObject(const QCborValue &value, QObject *parent) const { if (value.isNull()) return nullptr; const auto cborArray = value.toArray(); if (cborArray.size() != 2) throw DeserializationException{"The ConstructedObject tagged array must have exactly two elements!"}; auto object = deserializeGenericObject(cborArray[0].toArray(), parent); if (!object) return nullptr; if (!cborArray[1].isNull()) deserializeProperties(object->metaObject(), object, cborArray[1].toMap()); return object; } void ObjectConverter::deserializeProperties(const QMetaObject *metaObject, QObject *object, const QCborMap &value, bool isPoly) const { auto validationFlags = helper()->getProperty("validationFlags").value(); auto keepObjectName = helper()->getProperty("keepObjectName").toBool(); // collect required properties, if set QSet reqProps; if (validationFlags.testFlag(SerializerBase::ValidationFlag::AllProperties)) { const auto ignoreStoredAttribute = helper()->getProperty("ignoreStoredAttribute").toBool(); auto i = QObject::staticMetaObject.indexOfProperty("objectName"); if (!keepObjectName) i++; for (; i < metaObject->propertyCount(); i++) { auto property = metaObject->property(i); if(ignoreStoredAttribute || property.isStored()) reqProps.insert(property.name()); } } //now deserialize all json properties for (auto it = value.constBegin(); it != value.constEnd(); it++) { if (isPoly && it.key() == QStringLiteral("@class")) continue; const auto key = it.key().toString().toUtf8(); const auto propIndex = metaObject->indexOfProperty(key); if (propIndex != -1) { const auto property = metaObject->property(propIndex); property.write(object, helper()->deserializeSubtype(property, it.value(), object)); reqProps.remove(property.name()); } else if (validationFlags.testFlag(SerializerBase::ValidationFlag::NoExtraProperties)) { throw DeserializationException("Found extra property " + key + " but extra properties are not allowed"); } else object->setProperty(key, helper()->deserializeSubtype(QMetaType::UnknownType, it.value(), object, key)); } //make shure all required properties have been read if (validationFlags.testFlag(SerializerBase::ValidationFlag::AllProperties) && !reqProps.isEmpty()) { throw DeserializationException(QByteArray("Not all properties for ") + metaObject->className() + QByteArray(" are present in the json object Missing properties: ") + reqProps.values().join(", ")); } }