├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── QtPropertySerializer.cpp ├── QtPropertySerializer.h ├── README.md └── test ├── CMakeLists.txt ├── test_QtPropertySerializer.cpp ├── test_QtPropertySerializer.h └── test_QtPropertySerializer.pro /.gitignore: -------------------------------------------------------------------------------- 1 | # CMake 2 | CMakeFiles/ 3 | CMakeScripts/ 4 | _deps/ 5 | *_autogen/ 6 | *.build/ 7 | CMakeCache.txt 8 | *.cmake 9 | 10 | # MacOSX 11 | .DS_Store 12 | *.xcodeproj/ 13 | *.build/ 14 | Debug/ 15 | Release/ 16 | build/ 17 | Info.plist 18 | project.pbxproj 19 | project.xcworkspace 20 | xcshareddata/ 21 | 22 | # C++ 23 | *.slo 24 | *.lo 25 | *.o 26 | *.a 27 | *.la 28 | *.lai 29 | *.so 30 | *.dll 31 | *.dylib 32 | 33 | # Qt-es 34 | .qmake.cache 35 | .qmake.stash 36 | *.pro.user 37 | *.pro.user.* 38 | *.qbs.user 39 | *.qbs.user.* 40 | *.moc 41 | moc_*.h 42 | moc_*.cpp 43 | qrc_*.cpp 44 | ui_*.h 45 | Makefile* 46 | *build-* 47 | *.mak 48 | 49 | # QtCreator 50 | *.autosave 51 | 52 | # QtCtreator Qml 53 | *.qmlproject.user 54 | *.qmlproject.user.* 55 | 56 | # QtCtreator CMake 57 | CMakeLists.txt.user 58 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Created by Marcel Paz Goldschen-Ohm 2 | 3 | # !!! Set env variable QT5_DIR to /lib/cmake/Qt5 4 | # For macx xcode project: cmake -G Xcode 5 | 6 | cmake_minimum_required(VERSION 3.11) 7 | 8 | set(PROJECT_NAME QtPropertySerializer) 9 | project(${PROJECT_NAME} LANGUAGES CXX) 10 | 11 | set(CMAKE_CXX_STANDARD 11) # This is equal to QMAKE_CXX_FLAGS += -std=c++0x 12 | set(CMAKE_INCLUDE_CURRENT_DIR ON) # Search in current dir. 13 | set(CMAKE_AUTOMOC ON) # Run moc automatically for Qt. 14 | set(CMAKE_AUTOUIC ON) # Run uic automatically for *.ui files. 15 | set(CMAKE_AUTORCC ON) # Run automatically for *.qrc files. 16 | 17 | if(APPLE AND EXISTS /usr/local/opt/qt5) 18 | # Homebrew installs Qt5 (up to at least 5.9.1) in 19 | # /usr/local/qt5, ensure it can be found by CMake since 20 | # it is not in the default /usr/local prefix. 21 | list(APPEND CMAKE_PREFIX_PATH "/usr/local/opt/qt5") 22 | endif() 23 | 24 | # Find required packages. 25 | find_package(Qt5 COMPONENTS Core REQUIRED) 26 | 27 | # Build project as a static library. 28 | add_library(${PROJECT_NAME} STATIC QtPropertySerializer.cpp QtPropertySerializer.h) 29 | qt5_use_modules(${PROJECT_NAME} Core) 30 | target_link_libraries(${PROJECT_NAME} ${QT_LIBRARIES}) 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Marcel Goldschen-Ohm 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /QtPropertySerializer.cpp: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------- 2 | * Author: Marcel Paz Goldschen-Ohm 3 | * Email: marcel.goldschen@gmail.com 4 | * -------------------------------------------------------------------------------- */ 5 | 6 | #include "QtPropertySerializer.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace QtPropertySerializer 16 | { 17 | QVariantMap serialize(const QObject *object, int childDepth, bool includeReadOnlyProperties) 18 | { 19 | QVariantMap data; 20 | if(!object) 21 | return data; 22 | // Properties. 23 | const QMetaObject *metaObject = object->metaObject(); 24 | int propertyCount = metaObject->propertyCount(); 25 | for(int i = 0; i < propertyCount; ++i) { 26 | const QMetaProperty metaProperty = metaObject->property(i); 27 | if(metaProperty.isReadable() && (includeReadOnlyProperties || metaProperty.isWritable())) { 28 | const QByteArray propertyName = QByteArray(metaProperty.name()); 29 | const QVariant propertyValue = object->property(propertyName.constData()); 30 | addMappedData(data, propertyName, propertyValue); 31 | } 32 | } 33 | foreach(const QByteArray &propertyName, object->dynamicPropertyNames()) { 34 | const QVariant propertyValue = object->property(propertyName.constData()); 35 | addMappedData(data, propertyName, propertyValue); 36 | } 37 | // Children. 38 | if(childDepth == -1 || childDepth > 0) { 39 | if(childDepth > 0) 40 | --childDepth; 41 | foreach(QObject *child, object->children()) { 42 | const QByteArray className(child->metaObject()->className()); 43 | addMappedData(data, className, serialize(child, childDepth, includeReadOnlyProperties)); 44 | } 45 | } 46 | return data; 47 | } 48 | 49 | QVariantList serialize(const QList objects, int childDepth, bool includeReadOnlyProperties) 50 | { 51 | QVariantList data; 52 | for(QObject *object : objects) { 53 | data.append(serialize(object, childDepth, includeReadOnlyProperties)); 54 | } 55 | return data; 56 | } 57 | 58 | void addMappedData(QVariantMap &data, const QByteArray &key, const QVariant &value) 59 | { 60 | if(value.canConvert()) { 61 | // Handle QObject* values. !!! This will be deserialized as a child object! 62 | QObject *object = qvariant_cast(value); 63 | addMappedData(data, key, serialize(object)); 64 | } else if(value.canConvert >()) { 65 | // Handle QList values. !!! These will be deserialized as child objects! 66 | QList objects = qvariant_cast >(value); 67 | QVariantList values; 68 | for(QObject *object : objects) { 69 | values.append(serialize(object)); 70 | } 71 | addMappedData(data, key, values); 72 | } else { 73 | // Handle all other values (i.e. QVariant, QVariantList, QVariantMap, ...). 74 | if(data.contains(key)) { 75 | // If data already contains key, make sure key's value is a list and append the input value. 76 | QVariant &existingData = data[key]; 77 | if(existingData.type() == QVariant::List) { 78 | QVariantList values = existingData.toList(); 79 | values.append(value); 80 | data[key] = values; 81 | } else { 82 | QVariantList values; 83 | values.append(existingData); 84 | values.append(value); 85 | data[key] = values; 86 | } 87 | } else { 88 | data[key] = value; 89 | } 90 | } 91 | } 92 | 93 | void deserialize(QObject *object, const QVariantMap &data, ObjectFactory *factory) 94 | { 95 | if(!object) 96 | return; 97 | for(QVariantMap::const_iterator i = data.constBegin(); i != data.constEnd(); ++i) { 98 | if(i.value().type() == QVariant::Map) { 99 | // Child object. 100 | QByteArray className = i.key().toUtf8(); 101 | const QVariantMap &childData = i.value().toMap(); 102 | bool childFound = false; 103 | if(childData.contains("objectName")) { 104 | // If objectName is specified for the child, find the first existing child with matching objectName and className. 105 | QObjectList children = object->findChildren(childData.value("objectName").toString()); 106 | foreach(QObject *child, children) { 107 | if(className == QByteArray(child->metaObject()->className())) { 108 | deserialize(child, childData, factory); 109 | childFound = true; 110 | break; 111 | } 112 | } 113 | } else { 114 | // If objectName is NOT specified for the child, find the first existing child with matching className. 115 | foreach(QObject *child, object->children()) { 116 | if(className == QByteArray(child->metaObject()->className())) { 117 | deserialize(child, childData, factory); 118 | childFound = true; 119 | break; 120 | } 121 | } 122 | } 123 | // If we still have not found an existing child, attempt to create one dynamically. 124 | if(!childFound) { 125 | QObject *child = NULL; 126 | if(className == QByteArray("QObject")) 127 | child = new QObject; 128 | else if(factory && factory->hasCreator(className)) 129 | child = factory->create(className); 130 | if(child) { 131 | child->setParent(object); 132 | deserialize(child, childData, factory); 133 | } 134 | } 135 | } else if(i.value().type() == QVariant::List) { 136 | // List of child objects and/or properties. 137 | QByteArray className = i.key().toUtf8(); 138 | const QVariantList &childDataList = i.value().toList(); 139 | // Keep track of existing children that have been deserialized. 140 | QObjectList existingChildrenWithClassNameAndObjectName; 141 | QObjectList existingChildrenWithClassName; 142 | foreach(QObject *child, object->children()) { 143 | if(className == QByteArray(child->metaObject()->className())) { 144 | if(!child->objectName().isEmpty()) 145 | existingChildrenWithClassNameAndObjectName.append(child); 146 | else 147 | existingChildrenWithClassName.append(child); 148 | } 149 | } 150 | for(QVariantList::const_iterator j = childDataList.constBegin(); j != childDataList.constEnd(); ++j) { 151 | if(j->type() == QVariant::Map) { 152 | // Child object. 153 | const QVariantMap &childData = j->toMap(); 154 | bool childFound = false; 155 | if(childData.contains("objectName")) { 156 | // If objectName is specified for the child, find the first existing child with matching objectName and className. 157 | foreach(QObject *child, existingChildrenWithClassNameAndObjectName) { 158 | if(child->objectName() == childData.value("objectName").toString()) { 159 | deserialize(child, childData, factory); 160 | existingChildrenWithClassNameAndObjectName.removeOne(child); 161 | childFound = true; 162 | break; 163 | } 164 | } 165 | } 166 | if(!childFound) { 167 | // If objectName is NOT specified for the child or we could NOT find an object with the same name, 168 | // find the first existing child with matching className. 169 | if(!existingChildrenWithClassName.isEmpty()) { 170 | QObject *child = existingChildrenWithClassName.first(); 171 | deserialize(child, childData, factory); 172 | existingChildrenWithClassName.removeOne(child); 173 | childFound = true; 174 | } 175 | } 176 | // If we still havent found an existing child, attempt to create one dynamically. 177 | if(!childFound) { 178 | QObject *child = NULL; 179 | if(className == QByteArray("QObject")) 180 | child = new QObject; 181 | else if(factory && factory->hasCreator(className)) 182 | child = factory->create(className); 183 | if(child) { 184 | child->setParent(object); 185 | deserialize(child, childData, factory); 186 | } 187 | } 188 | } else { 189 | // Property. 190 | const QByteArray &propertyName = className; 191 | const QVariant &propertyValue = *j; 192 | object->setProperty(propertyName.constData(), propertyValue); 193 | } 194 | } 195 | } else { 196 | // Property. 197 | const QByteArray propertyName = i.key().toUtf8(); 198 | const QVariant &propertyValue = i.value(); 199 | object->setProperty(propertyName.constData(), propertyValue); 200 | } 201 | } 202 | } 203 | 204 | void deserialize(QList &objects, const QVariantList &data, ObjectFactory *factory, const QByteArray &objectCreatorKey) 205 | { 206 | int i = 0; 207 | for(QVariantList::const_iterator j = data.constBegin(); j != data.constEnd(); ++j) { 208 | if(j->type() == QVariant::Map) { 209 | // Objects should be maps. 210 | QObject *object = i < objects.size() ? objects[i] : NULL; 211 | if(!object && factory) { 212 | if(!objectCreatorKey.isEmpty()) { 213 | object = factory->create(objectCreatorKey); 214 | } 215 | if(!object && !objects.isEmpty()) { 216 | // Use className of object in the list as the factory creator key. 217 | for(QObject *obj : objects) { 218 | if(obj) { 219 | object = factory->create(obj->metaObject()->className()); 220 | break; 221 | } 222 | } 223 | } 224 | } 225 | if(object) { 226 | deserialize(object, j->toMap(), factory); 227 | if(i < objects.size()) { 228 | objects[i] = object; 229 | } else { 230 | objects.append(object); 231 | } 232 | } else { 233 | // Failed to deserialize map into object. 234 | // Should only happen if we failed to create the object (i.e. mising factory, creator key or preallocated object in list) 235 | if(i >= objects.size()) { 236 | // Since we can't make new objects, give up. 237 | return; 238 | } 239 | } 240 | ++i; 241 | } 242 | } 243 | } 244 | 245 | QVariantMap readJson(const QString &filePath) 246 | { 247 | QFile file(filePath); 248 | if(!file.open(QIODevice::Text | QIODevice::ReadOnly)) 249 | throw std::runtime_error("QtPropertySerializer::readJson: Failed to open file " + filePath.toStdString()); 250 | QString buffer = file.readAll(); 251 | file.close(); 252 | return QJsonDocument::fromJson(buffer.toUtf8()).toVariant().toMap(); 253 | } 254 | 255 | void writeJson(const QVariantMap &data, const QString &filePath) 256 | { 257 | QFile file(filePath); 258 | if(!file.open(QIODevice::Text | QIODevice::WriteOnly)) 259 | throw std::runtime_error("QtPropertySerializer::writeJson: Failed to open file " + filePath.toStdString()); 260 | QTextStream out(&file); 261 | out << QJsonDocument::fromVariant(data).toJson(QJsonDocument::Indented); 262 | file.close(); 263 | } 264 | 265 | void readJson(QObject *object, const QString &filePath, ObjectFactory *factory) 266 | { 267 | QVariantMap data = readJson(filePath); 268 | deserialize(object, data, factory); 269 | } 270 | 271 | void writeJson(QObject *object, const QString &filePath, int childDepth, bool includeReadOnlyProperties) 272 | { 273 | QVariantMap data = serialize(object, childDepth, includeReadOnlyProperties); 274 | writeJson(data, filePath); 275 | } 276 | 277 | } // QtPropertySerializer 278 | -------------------------------------------------------------------------------- /QtPropertySerializer.h: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------- 2 | * Tools for serializing properties in a QObject tree. 3 | * - Serialize/Deserialize to/from a QVariantMap. 4 | * - Read/Write from/to a JSON file. 5 | * 6 | * Author: Marcel Paz Goldschen-Ohm 7 | * Email: marcel.goldschen@gmail.com 8 | * -------------------------------------------------------------------------------- */ 9 | 10 | #ifndef __QtPropertySerializer_H__ 11 | #define __QtPropertySerializer_H__ 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #ifdef DEBUG 25 | #include 26 | #include 27 | #endif 28 | 29 | namespace QtPropertySerializer 30 | { 31 | /* -------------------------------------------------------------------------------- 32 | * Object factory for dynamic object creation during deserialization. 33 | * -------------------------------------------------------------------------------- */ 34 | class ObjectFactory 35 | { 36 | public: 37 | typedef std::function ObjectCreatorFunction; 38 | typedef QMap ObjectCreatorMap; 39 | 40 | // Map of (key,creator) pairs. 41 | ObjectCreatorMap creators; 42 | 43 | public: 44 | // These functions are not absolutely necessary since the creators map is publicly accessible, 45 | // but I've kept them here for backwards compatibility and convenience. 46 | void registerCreator(const QByteArray &key, ObjectCreatorFunction creator) { creators[key] = creator; } 47 | bool hasCreator(const QByteArray &key) const { return creators.contains(key); } 48 | ObjectCreatorFunction getCreator(const QByteArray &key) const { return creators.value(key); } 49 | QList creatorKeys() const { return creators.keys(); } 50 | QObject* create(const QByteArray &key) const { return creators.contains(key) ? creators.value(key)() : 0; } 51 | 52 | // For convenience. e.g. call ObjectFactory::registerCreator("MyClass", ObjectFactory::defaultCreator); 53 | // Requires T to have a default constructor T(). 54 | template 55 | static QObject* defaultCreator() { return new T(); } 56 | template 57 | static QObject* defaultChildCreator(QObject *parent) { T *object = new T(); object->setParent(parent); return object; } 58 | 59 | // Default creators based on className for convenience. 60 | template 61 | void registerClass() { creators[T::staticMetaObject.className()] = defaultCreator; } 62 | template 63 | void registerChildClass(QObject *parent) { creators[T::staticMetaObject.className()] = std::bind(defaultChildCreator, parent); } 64 | }; 65 | 66 | /* -------------------------------------------------------------------------------- 67 | * Serialize QObject --> QVariantMap 68 | * -------------------------------------------------------------------------------- */ 69 | QVariantMap serialize(const QObject *object, int childDepth = -1, bool includeReadOnlyProperties = true); 70 | QVariantList serialize(const QList objects, int childDepth = -1, bool includeReadOnlyProperties = true); 71 | 72 | // Helper function for serialize(). 73 | void addMappedData(QVariantMap &data, const QByteArray &key, const QVariant &value); 74 | 75 | /* -------------------------------------------------------------------------------- 76 | * Deserialize QVariantMap --> QObject 77 | * -------------------------------------------------------------------------------- */ 78 | void deserialize(QObject *object, const QVariantMap &data, ObjectFactory *factory = NULL); 79 | void deserialize(QList &objects, const QVariantList &data, ObjectFactory *factory = NULL, const QByteArray &objectCreatorKey = ""); 80 | 81 | /* -------------------------------------------------------------------------------- 82 | * Read/Write from/to JSON file. 83 | * -------------------------------------------------------------------------------- */ 84 | QVariantMap readJson(const QString &filePath); 85 | void writeJson(const QVariantMap &data, const QString &filePath); 86 | 87 | void readJson(QObject *object, const QString &filePath, ObjectFactory *factory = NULL); 88 | void writeJson(QObject *object, const QString &filePath, int childDepth = -1, bool includeReadOnlyProperties = true); 89 | 90 | } // QtPropertySerializer 91 | 92 | #endif 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QtPropertySerializer 2 | 3 | Simple C++ serialization for a QObject property tree. 4 | 5 | QObject <==> QVariantMap <==> {Json, XML, ...} 6 | 7 | * Recursively serializes child objects. 8 | * ONLY serializes properties (ignores other class members). 9 | * Dynamic runtime creation of child objects during deserialization (requires factory). 10 | 11 | **Author**: Marcel Goldschen-Ohm 12 | **Email**: 13 | **License**: MIT 14 | Copyright (c) 2015 Marcel Goldschen-Ohm 15 | 16 | ## Alternatives 17 | 18 | For JSON serialization, also check out [qjson](https://github.com/flavio/qjson). 19 | 20 | ## INSTALL 21 | 22 | Everything is in: 23 | 24 | * `QtPropertySerializer.h` 25 | * `QtPropertySerializer.cpp` 26 | 27 | ### CMake: 28 | 29 | See `CMakeLists.txt` for example build as a static library. 30 | 31 | :point_right: **This is most likely what you want:** See `test/CMakeLists.txt` for example build of an app that uses QtPropertySerializer. This build uses CMake to automatically download QtPropertySerializer files directly from this GitHub repository, builds QtPropertySerializer as a static library and links it to the app executable. This way you can use QtPropertySerializer in your project without downloading or managing the QtPropertySerializer repository manually. 32 | 33 | ### Requires: 34 | 35 | * [Qt](http://www.qt.io) 36 | 37 | ## QObject <==> QVariantMap 38 | 39 | QVariantMap (key, value) pairs are either: 40 | 41 | 1. (property name, property value) 42 | 2. (child object class name, child QVariantMap) 43 | 3. (child object class name, QVariantList of QVariantMaps for multiple children of the same type) 44 | 45 | ## Quick start code snippets 46 | 47 | See `test.h/cpp` for complete example code. 48 | 49 | ```cpp 50 | #include "QtPropertySerializer.h" 51 | ``` 52 | 53 | #### A QObject tree. 54 | 55 | jane 56 | |-- john 57 | |-- josephine 58 | |-- spot 59 | 60 | ```cpp 61 | class Person : public QObject { ... }; 62 | class Pet : public QObject { ... }; 63 | Person jane; 64 | Person *john = new Person; 65 | Person *josephine = new Person; 66 | Pet *spot = new Pet; 67 | john->setParent(&jane); 68 | josephine->setParent(&jane); 69 | spot->setParent(josephine); 70 | ``` 71 | 72 | #### Serialize QObject ==> QVariantMap. 73 | 74 | ```cpp 75 | QVariantMap janePropertyTree = QtPropertySerializer::serialize(&jane); 76 | ``` 77 | 78 | * Access child QVariantMaps in parent QVariantMap by class name. 79 | * Multiple children of the same type are placed in a QVariantList. 80 | 81 | ```cpp 82 | QVariantList janePersonList = janePropertyTree["Person"].toList(); 83 | QVariantMap johnPropertyTree = janePersonList[0].toMap(); 84 | QVariantMap josephinePropertyTree = janePersonList[1].toMap(); 85 | QVariantMap spotPropertyTree = josephinePropertyTree["Pet"].toMap(); 86 | ``` 87 | 88 | * Access properties in QVariantMap by property name. 89 | 90 | ```cpp 91 | qDebug() << janePropertyTree["objectName"]; 92 | qDebug() << johnPropertyTree["objectName"]; 93 | qDebug() << josephinePropertyTree["objectName"]; 94 | qDebug() << spotPropertyTree["objectName"]; 95 | ``` 96 | 97 | #### Deserialize QVariantMap ==> QObject. 98 | 99 | Deserialize into a pre-existing object tree. 100 | 101 | ```cpp 102 | QtPropertySerializer::deserialize(&jane, janePropertyTree); 103 | ``` 104 | 105 | Deserialize into an object without pre-existing child objects (requires runtime dynamic object creation using a factory). 106 | 107 | ```cpp 108 | QtPropertySerializer::ObjectFactory factory; 109 | factory.registerCreator("Person", factory.defaultCreator); 110 | factory.registerCreator("Pet", factory.defaultCreator); 111 | 112 | // factory.defaultCreator is a static function taking 113 | // no arguments and returning a QObject* for `new T()`. 114 | // You are also free to use your own custom creator function here. 115 | 116 | // Prior to deserialization, bizarroJane has no children. 117 | Person bizarroJane; 118 | 119 | // After deserialization, bizarroJane is identical to Jane 120 | // with children John and Josephine, and Josephine's pet Spot. 121 | QtPropertySerializer::deserialize(&bizarroJane, janePropertyTree, &factory); 122 | ``` 123 | 124 | #### Serialize QObject <==> JSON file. 125 | 126 | ```cpp 127 | QtPropertySerializer::writeJson(&jane, "jane.json"); 128 | QtPropertySerializer::readJson(&jane, "jane.json"); 129 | 130 | Person juniper; 131 | QtPropertySerializer::readJson(&juniper, "jane.json", &factory); 132 | ``` 133 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Created by Marcel Paz Goldschen-Ohm 2 | 3 | # !!! Set env variable QT5_DIR to /lib/cmake/Qt5 4 | # For macx xcode project: cmake -G Xcode 5 | 6 | cmake_minimum_required(VERSION 3.11) 7 | 8 | set(PROJECT_NAME test_QtPropertySerializer) 9 | project(${PROJECT_NAME} LANGUAGES CXX) 10 | 11 | include(FetchContent) 12 | 13 | # Fetch QtPropertySerializer repository from GitHub. 14 | set(REPO QtPropertySerializer) 15 | string(TOLOWER ${REPO} REPOlc) 16 | FetchContent_Declare(${REPO} 17 | GIT_REPOSITORY "https://github.com/marcel-goldschen-ohm/QtPropertySerializer.git" 18 | ) 19 | FetchContent_GetProperties(${REPO}) 20 | if(NOT ${REPOlc}_POPULATED) 21 | FetchContent_Populate(${REPO}) 22 | message(STATUS "${REPO} source dir: ${${REPOlc}_SOURCE_DIR}") 23 | message(STATUS "${REPO} binary dir: ${${REPOlc}_BINARY_DIR}") 24 | endif() 25 | 26 | set(CMAKE_CXX_STANDARD 11) # This is equal to QMAKE_CXX_FLAGS += -std=c++0x 27 | set(CMAKE_INCLUDE_CURRENT_DIR ON) # Search in current dir. 28 | set(CMAKE_AUTOMOC ON) # Run moc automatically for Qt. 29 | set(CMAKE_AUTOUIC ON) # Run uic automatically for *.ui files. 30 | set(CMAKE_AUTORCC ON) # Run automatically for *.qrc files. 31 | 32 | if(APPLE AND EXISTS /usr/local/opt/qt5) 33 | # Homebrew installs Qt5 (up to at least 5.9.1) in 34 | # /usr/local/qt5, ensure it can be found by CMake since 35 | # it is not in the default /usr/local prefix. 36 | list(APPEND CMAKE_PREFIX_PATH "/usr/local/opt/qt5") 37 | endif() 38 | 39 | # Find required packages. 40 | find_package(Qt5 COMPONENTS Core REQUIRED) 41 | 42 | # Build fetched QtPropertySerializer as a static library. 43 | add_library(QtPropertySerializer STATIC ${qtpropertyserializer_SOURCE_DIR}/QtPropertySerializer.cpp ${qtpropertyserializer_SOURCE_DIR}/QtPropertySerializer.h) 44 | qt5_use_modules(QtPropertySerializer Core) 45 | target_link_libraries(QtPropertySerializer ${QT_LIBRARIES}) 46 | 47 | # Build project executable. 48 | add_executable(${PROJECT_NAME} test_QtPropertySerializer.cpp test_QtPropertySerializer.h) 49 | target_include_directories(${PROJECT_NAME} PUBLIC ${qtpropertyserializer_SOURCE_DIR}) 50 | qt5_use_modules(${PROJECT_NAME} Core) 51 | target_link_libraries(${PROJECT_NAME} ${QT_LIBRARIES} QtPropertySerializer) 52 | -------------------------------------------------------------------------------- /test/test_QtPropertySerializer.cpp: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------- 2 | * Example tests for QtObjectPropertySerializer. 3 | * 4 | * Author: Marcel Paz Goldschen-Ohm 5 | * Email: marcel.goldschen@gmail.com 6 | * -------------------------------------------------------------------------------- */ 7 | 8 | #include "test_QtPropertySerializer.h" 9 | 10 | #include 11 | #include 12 | 13 | #include "QtPropertySerializer.h" 14 | 15 | int main(int, char **) 16 | { 17 | std::cout << "Running tests for QtPropertySerializer..." << std::endl; 18 | 19 | // Jane is the root of our family tree. 20 | Person jane("Jane"); 21 | jane.heightInCm = 170; 22 | jane.dateOfBirth = QDate(1969, 7, 20); 23 | 24 | // Jane's child John. 25 | Person *john = new Person("John"); 26 | john->setParent(&jane); 27 | john->heightInCm = 190; 28 | john->dateOfBirth = QDate(1995, 5, 20); 29 | 30 | // Jane's child Josephine. 31 | Person *josephine = new Person("Josephine"); 32 | josephine->setParent(&jane); 33 | josephine->heightInCm = 50; 34 | josephine->dateOfBirth = QDate(2000, 12, 25); 35 | 36 | // Josephine's pet dog Spot. 37 | Pet *spot = new Pet("Spot"); 38 | spot->setParent(josephine); 39 | spot->species = "dog"; 40 | spot->setProperty("vaccinated", true); // Dynamic property. 41 | 42 | //------------------------- 43 | // QObject --> QVariantMap 44 | //------------------------- 45 | 46 | std::cout << "Checking serialization from QObject to QVariantMap... "; 47 | 48 | // Get Jane's property tree. 49 | QVariantMap janeData = QtPropertySerializer::serialize(&jane); 50 | 51 | // Map keys for properties are the property names. 52 | // Map keys for child objects are the child object class names. 53 | assert(janeData["objectName"].toString() == jane.objectName()); 54 | assert(janeData["height"].toInt() == jane.heightInCm); 55 | assert(janeData["dob"].toDate() == jane.dateOfBirth); 56 | // janeData["Person"] is a QVariantList containing QVariantMaps for John and Josephine. 57 | // If Jane had only one child, then janeData["Person"] would instead 58 | // be a QVariantMap for the only child (not a QVariantList of QVariantMaps) 59 | // analogous to josephineData["Pet"] below. 60 | QVariantList janePersonList = janeData["Person"].toList(); 61 | QVariantMap johnData = janePersonList[0].toMap(); 62 | QVariantMap josephineData = janePersonList[1].toMap(); 63 | assert(johnData["objectName"].toString() == john->objectName()); 64 | assert(johnData["height"].toInt() == john->heightInCm); 65 | assert(johnData["dob"].toDate() == john->dateOfBirth); 66 | assert(josephineData["objectName"].toString() == josephine->objectName()); 67 | assert(josephineData["height"].toInt() == josephine->heightInCm); 68 | assert(josephineData["dob"].toDate() == josephine->dateOfBirth); 69 | // josephineData["Pet"] is a QVariantMap for Spot. 70 | // If Josephine had two or more pets, then josephineData["Pet"] would instead 71 | // be a QVariantList of QVariantMaps for each pet analogous to janeData["Person"]. 72 | QVariantMap spotData = josephineData["Pet"].toMap(); 73 | assert(spotData["objectName"].toString() == spot->objectName()); 74 | assert(spotData["species"].toString() == spot->species); 75 | assert(spotData["vaccinated"].toBool() == spot->property("vaccinated").toBool()); 76 | 77 | std::cout << "OK" << std::endl; 78 | 79 | //------------------------- 80 | // QVarinatMap --> QObject 81 | //------------------------- 82 | 83 | std::cout << "Checking deserialization from QVariantMap into QObject with preallocated tree... "; 84 | 85 | // Alter Jane's property tree and then reload it from janeData. 86 | jane.heightInCm = 0; 87 | john->heightInCm = 0; 88 | josephine->heightInCm = 0; 89 | spot->species = "cat"; 90 | 91 | QtPropertySerializer::deserialize(&jane, janeData); 92 | 93 | assert(jane.heightInCm == janeData["height"]); 94 | assert(john->heightInCm == johnData["height"]); 95 | assert(josephine->heightInCm == josephineData["height"]); 96 | assert(spot->species == spotData["species"]); 97 | 98 | std::cout << "OK" << std::endl; 99 | 100 | std::cout << "Checking deserialization from QVariantMap into QObject without preallocated tree... "; 101 | 102 | // Try and load Jane's property tree into a new object without preexisting children. 103 | // This will fail to deserialize the children. 104 | Person bizarroJane; 105 | QtPropertySerializer::deserialize(&bizarroJane, janeData); 106 | 107 | // Bizarro Jane should have Jane's properties, but NO children. 108 | assert(bizarroJane.objectName() == jane.objectName()); 109 | assert(bizarroJane.heightInCm == jane.heightInCm); 110 | assert(bizarroJane.dateOfBirth == jane.dateOfBirth); 111 | assert(bizarroJane.children().size() == 0); 112 | 113 | std::cout << "OK" << std::endl; 114 | 115 | std::cout << "Checking deserialization from QVariantMap into QObject without preallocated tree... "; 116 | 117 | // Use a factory for dynamic creation of Person objects 118 | // and try again to deserialize janeData into bizzaro Jane. 119 | QtPropertySerializer::ObjectFactory factory; 120 | factory.registerCreator("Person", factory.defaultCreator); 121 | factory.registerCreator("Pet", factory.defaultCreator); 122 | QtPropertySerializer::deserialize(&bizarroJane, janeData, &factory); 123 | 124 | // Bizzaro Jane should now be identical to Jane. 125 | assert(bizarroJane.children().size() == 2); 126 | Person *bizarroJohn = qobject_cast(bizarroJane.findChild("John")); 127 | Person *bizarroJosephine = qobject_cast(bizarroJane.findChild("Josephine")); 128 | Pet *bizarroSpot = qobject_cast(bizarroJosephine->findChild("Spot")); 129 | assert(bizarroJane.objectName() == jane.objectName()); 130 | assert(bizarroJane.heightInCm == jane.heightInCm); 131 | assert(bizarroJane.dateOfBirth == jane.dateOfBirth); 132 | assert(bizarroJohn->objectName() == john->objectName()); 133 | assert(bizarroJohn->heightInCm == john->heightInCm); 134 | assert(bizarroJohn->dateOfBirth == john->dateOfBirth); 135 | assert(bizarroJosephine->objectName() == josephine->objectName()); 136 | assert(bizarroJosephine->heightInCm == josephine->heightInCm); 137 | assert(bizarroJosephine->dateOfBirth == josephine->dateOfBirth); 138 | assert(bizarroSpot->objectName() == spot->objectName()); 139 | assert(bizarroSpot->species == spot->species); 140 | assert(bizarroSpot->property("vaccinated").toBool() == spot->property("vaccinated").toBool()); 141 | 142 | std::cout << "OK" << std::endl; 143 | 144 | //------------------------ 145 | // QObject <--> JSON file 146 | //------------------------ 147 | 148 | std::cout << "Checking serialization/deserialization to/from JSON file... "; 149 | 150 | // These convert to/from QVariantMap under the hood. 151 | QtPropertySerializer::writeJson(&jane, "jane.json"); 152 | QtPropertySerializer::readJson(&jane, "jane.json", &factory); 153 | 154 | std::cout << "OK" << std::endl; 155 | 156 | return 0; 157 | } 158 | -------------------------------------------------------------------------------- /test/test_QtPropertySerializer.h: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------- 2 | * Example tests for QtPropertySerializer. 3 | * 4 | * Author: Marcel Paz Goldschen-Ohm 5 | * Email: marcel.goldschen@gmail.com 6 | * -------------------------------------------------------------------------------- */ 7 | 8 | #ifndef __test_QtPropertySerializer_H__ 9 | #define __test_QtPropertySerializer_H__ 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | class Pet : public QObject 16 | { 17 | Q_OBJECT 18 | Q_PROPERTY(QString species MEMBER species) 19 | 20 | public: 21 | QString species; 22 | 23 | Pet(const QString &name = "") { setObjectName(name); } 24 | }; 25 | 26 | class Person : public QObject 27 | { 28 | Q_OBJECT 29 | Q_PROPERTY(int height MEMBER heightInCm) 30 | Q_PROPERTY(QDate dob MEMBER dateOfBirth) 31 | Q_PROPERTY(QStringList list MEMBER list) 32 | Q_PROPERTY(QList pets MEMBER pets) 33 | 34 | public: 35 | int heightInCm; 36 | QDate dateOfBirth; 37 | QStringList list{"1", "2"}; 38 | QList pets{new Pet("pet1"), new Pet("pet2")}; 39 | 40 | // Members that are NOT properties NOR children are NOT serialized. 41 | QString nickName; // NOT a property OR a child object. 42 | QObject something; // Non-child member object. 43 | 44 | Person(const QString &name = "") { setObjectName(name); } 45 | }; 46 | 47 | #endif // __test_QtPropertySerializer_H__ 48 | -------------------------------------------------------------------------------- /test/test_QtPropertySerializer.pro: -------------------------------------------------------------------------------- 1 | TARGET = test_QtPropertySerializer 2 | TEMPLATE = app 3 | QT += core 4 | QT -= gui 5 | CONFIG += c++11 6 | 7 | OBJECTS_DIR = Debug/.obj 8 | MOC_DIR = Debug/.moc 9 | RCC_DIR = Debug/.rcc 10 | UI_DIR = Debug/.ui 11 | 12 | INCLUDEPATH += .. 13 | 14 | HEADERS += ../QtPropertySerializer.h 15 | SOURCES += ../QtPropertySerializer.cpp 16 | 17 | HEADERS += test_QtPropertySerializer.h 18 | SOURCES += test_QtPropertySerializer.cpp 19 | --------------------------------------------------------------------------------