| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329 |
- #include "objectconverter_p.h"
- #include "exception.h"
- #include "cborserializer.h"
- #include <array>
- 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<QCborTag> ObjectConverter::allowedCborTags(int metaTypeId) const
- {
- Q_UNUSED(metaTypeId)
- return {
- NoTag,
- static_cast<QCborTag>(CborSerializer::GenericObject),
- static_cast<QCborTag>(CborSerializer::ConstructedObject)
- };
- }
- QList<QCborValue::Type> ObjectConverter::allowedCborTypes(int metaTypeId, QCborTag tag) const
- {
- Q_UNUSED(metaTypeId)
- switch (static_cast<quint64>(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<quint64>(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<QObject*>();
- if (!object)
- return QCborValue::Null;
- QCborMap cborMap;
- // get the metaobject, based on polymorphism
- const QMetaObject *metaObject = nullptr;
- auto poly = static_cast<SerializerBase::Polymorphing>(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<QObject*>(nullptr);
- QCborMap cborMap;
- if (value.isTag()) {
- if (value.tag() == static_cast<QCborTag>(CborSerializer::GenericObject))
- return QVariant::fromValue(deserializeGenericObject(value.taggedValue().toArray(), parent));
- else if (value.tag() == static_cast<QCborTag>(CborSerializer::ConstructedObject))
- return QVariant::fromValue(deserializeConstructedObject(value.taggedValue(), parent));
- else
- cborMap = value.taggedValue().toMap();
- } else
- cborMap = value.toMap();
- auto poly = static_cast<SerializerBase::Polymorphing>(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<int>(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<QGenericArgument, 10> gArgs;
- gArgs.fill(QGenericArgument{});
- for (auto pIdx = 0; pIdx < argCopy.size(); ++pIdx)
- gArgs[static_cast<size_t>(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<SerializerBase::ValidationFlags>();
- auto keepObjectName = helper()->getProperty("keepObjectName").toBool();
- // collect required properties, if set
- QSet<QByteArray> 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(", "));
- }
- }
|