Search code examples
c++qtserializationqvariantqsettings

Save custom QMap template instantiation in QSettings in human-readable form


In my code, I use the QSettings mechanism to save and load my own class MyClass inside a QMap<unsigned int, MyClass> into config files.

I know how to register my own types to QMetaObject so I can use them with QVariant. This allows to save them with QSettings. See working code below.

However, the representation of those types in the actual config file I'm writing to is by no means human-readable. Is there any method to make the custom map more readable in a text editor, so I could potentially manually alter the configs outside of Qt?

The code that saves my custom types to the config:

struct MyClass
{
    unsigned int id;
    QString name;
    QString value;

    friend QDataStream & operator<< (QDataStream &arch, const MyClass& c)
    {
        return arch << c.id << c.name << c.value;
    }
    friend QDataStream & operator>> (QDataStream &arch, MyClass& c)
    {
        return arch >> c.id >> c.name >> c.value;
    }
};
Q_DECLARE_METATYPE(MyClass)
typedef QMap<unsigned int, MyClass> MyMap;

int main(int argc, char *argv[])
{
   MyMap map;
    map.insert(100, {100, "name1", "value1"});
    map.insert(101, {101, "name2", "value2"});
    map.insert(200, {200, "name3", "value3"});

    qRegisterMetaTypeStreamOperators<MyMap>("MyMap");
    QSettings conf("/home/dave/temp/myfile.conf", QSettings::IniFormat);
    conf.setValue("myMapping", QVariant::fromValue(map));
    conf.sync();
}

The written config file:

[General]
myMapping="@Variant(\0\0\0\x7f\0\0\0\x13QMap<uint,MyClass>\0\0\0\0\x3\0\0\0\xc8\0\0\0\xc8\0\0\0\n\0n\0\x61\0m\0\x65\0\x33\0\0\0\f\0v\0\x61\0l\0u\0\x65\0\x33\0\0\0\x65\0\0\0\x65\0\0\0\n\0n\0\x61\0m\0\x65\0\x32\0\0\0\f\0v\0\x61\0l\0u\0\x65\0\x32\0\0\0\x64\0\0\0\x64\0\0\0\n\0n\0\x61\0m\0\x65\0\x31\0\0\0\f\0v\0\x61\0l\0u\0\x65\0\x31)"

Solution

  • Qt takes care of serializing/deserializing your map - unfortunately that does not take human-readability into account.

    You would need to manually handle the serializing/deserializing of the entire map and write/read QString (or something like that), e.g.

    QString serialize(const QMap<int, MyClass>& mapToSerialize)
    {
        QStringList result;
        for(int key : mapToSerialize.keys())
        {
            result.append("%1%2%3).arg(QString::number(i), SEPARATOR_TOKEN, serialize(mapToSerialize.value(i));
        }
        return result.join(MAP_SEPARATOR_TOKEN);
    }
    

    This of course has the disadvantage that you 1) need to write the serialize/deserialize functions for the map and your class and 2) you need to handle all the special cases (escape special characters, parsing bad data, etc.)