I solved this problem for a component which needs to save and restore variants of arbitrary type, without knowing what its clients expect. The solution was to store the variant's typeName()
alongside each value:
void store(QSettings& settings, const QString& key, const QVariant& value)
{
settings.setValue(key+"value", value);
settings.setValue(key+"type", value.typeName());
}
When reading back, we also read the type name, and convert()
the variant if it's not already the correct type, before returning it.
QVariant retrieve(const QSettings& settings, const QString& key)
{
auto value = settings.value(key+"value");
const auto typeName = settings.value(key+"type").toString();
const bool wasNull = value.isNull(); // NOTE 1
const auto t = QMetaType::type(typeName.toUtf8()); // NOTE 2
if (value.userType() != t && !value.convert(t) && !wasNull) {
// restore value that was cleared by the failed convert()
value = settings.value(key+"value");
qWarning() << "Failed to convert value" << value << "to" << typeName;
}
return value;
}
Notes
The wasNull
variable is in there because of this niggle of convert()
:
Warning: For historical reasons, converting a null QVariant
results in a null value of the desired type (e.g., an empty string for QString
) and a result of false
.
In this case, we need to ignore the misleading return value, and keep the successfully-converted null variant of the correct type.
It's not clear that UTF-8 is the correct encoding for QMetaType
names (perhaps local 8-bit is assumed?); my types are all ASCII, so I just use toLatin1()
instead, which might be faster. If it were an issue, I'd use QString::fromLatin1
in the store()
method (instead of implicit char*
to QString
conversion), to ensure a clean round-trip.
If the type name is not found, t
will be QMetaType::UnknownType
; that's okay, because convert()
will then fail, and we'll return the unconverted variant (or a null). It's not great, but it's a corner case that won't happen in normal usage, and my system will recover reasonably quickly.