跳转到内容
新建笔记

QVariant 与 QVariantMap:类型、转换和缺失值

QVariant 保存一个带运行时类型信息的值,常用于 Qt 模型、属性和通用数据接口。它不是同时装下很多值的容器;再次赋值会改变当前保存的内容。std::variant 在编译期列出候选类型,QVariant 与 Qt 元类型和转换机制结合,二者适用场景不同,不宜笼统评价谁“更强大”。

QVariantMap 则是 QMap<QString, QVariant> 的别名,用字符串键组合多个 QVariant 值。它适合较灵活的配置或界面数据;字段固定、约束重要的业务数据,普通结构体往往更容易检查。

类型可转换,不代表这次转换成功

跳转到“类型可转换,不代表这次转换成功”
操作应检查什么
isValid()是否保存了有效的类型;不表示内容符合业务规则
metaType()、typeId()Qt 6 中查看实际保存的类型;旧 type() 不作为新示例接口
canConvert<T>()是否存在到目标类型的转换路径;不能保证这个具体值可转换
toInt(&ok)、toDouble(&ok)用 ok 区分转换失败和合法的零;还要另行检查范围或单位
toString()、toList()、toMap()需要明确预期来源类型;空结果不自动意味着原值就是空值
value<T>()按指定类型取值;不要把失败后的默认值当成已成功解析的数据
isNull()与无效值、空字符串和业务上的“缺失”分别处理;Qt 6 的空值规则不能照搬 Qt 5

例如,字符串类型有到整数的转换路径,但 "12abc" 转为整数仍会失败。原示例中的 123、"Hello, QVariant!"、3.14 都保留在下面的可运行程序中。Qt:QVariant

insert(key, value) 添加或覆盖值。value(key) 在缺键时返回默认 QVariant,且不插入;非 const 的 operator[](key) 会创建缺失键。若要区分“缺少字段”和“字段存在但为空”,先用 contains() 或 constFind() 检查。

QVariantMap 按键排序,keys() 与 values() 遵循相同的键顺序,并不记录插入先后。需要稳定的业务展示顺序时,可单独定义字段顺序,或使用明确的列表结构。Qt:QMap

完整示例:转换、配置读取与 JSON

跳转到“完整示例:转换、配置读取与 JSON”
#include <QByteArray>
#include <QJsonDocument>
#include <QJsonParseError>
#include <QMetaType>
#include <QObject>
#include <QString>
#include <QStringList>
#include <QVariant>
#include <QVariantMap>
#include <cassert>
int main() {
const QVariant integer(123);
const QVariant text(QStringLiteral("Hello, QVariant!"));
const QVariant real(3.14);
assert(integer.metaType() == QMetaType::fromType<int>());
assert(text.typeId() == QMetaType::QString);
bool ok = false;
assert(integer.toInt(&ok) == 123 && ok);
assert(text.toString() == QStringLiteral("Hello, QVariant!"));
assert(real.toDouble(&ok) == 3.14 && ok);
const QVariant invalid;
const QVariant emptyText(QStringLiteral(""));
const QVariant nullObject = QVariant::fromValue(static_cast<QObject *>(nullptr));
assert(!invalid.isValid());
assert(emptyText.isValid() && emptyText.toString().isEmpty());
assert(nullObject.isValid() && nullObject.isNull());
const QVariant badInteger(QStringLiteral("12abc"));
assert(badInteger.canConvert<int>());
const int failedValue = badInteger.toInt(&ok);
assert(!ok && failedValue == 0);
const int validZero = QVariant(QStringLiteral("0")).toInt(&ok);
assert(ok && validZero == 0);
QVariantMap map;
map.insert(QStringLiteral("name"), QStringLiteral("Kimi"));
map.insert(QStringLiteral("age"), 25);
map.insert(QStringLiteral("isDeveloper"), true);
assert(map.value(QStringLiteral("name")).toString() == QStringLiteral("Kimi"));
const int age = map.value(QStringLiteral("age")).toInt(&ok);
assert(ok && age == 25);
assert(map.value(QStringLiteral("isDeveloper")).toBool());
const QStringList keys = map.keys();
assert((keys == QStringList{"age", "isDeveloper", "name"}));
const QVariantList values = map.values();
assert(values.size() == keys.size());
for (qsizetype i = 0; i < keys.size(); ++i)
assert(values.at(i) == map.value(keys.at(i)));
const auto originalSize = map.size();
assert(!map.value(QStringLiteral("nickname")).isValid());
assert(map.size() == originalSize);
QVariant &created = map[QStringLiteral("nickname")];
assert(!created.isValid() && map.contains(QStringLiteral("nickname")));
assert(map.size() == originalSize + 1);
map.remove(QStringLiteral("nickname"));
QObject settings;
settings.setProperty("profile", map); // 动态属性;用读取结果验证存储
assert(settings.property("profile").toMap().value("name").toString() == "Kimi");
const QByteArray json = QJsonDocument::fromVariant(map).toJson(QJsonDocument::Compact);
QJsonParseError parseError;
const QJsonDocument document = QJsonDocument::fromJson(json, &parseError);
assert(parseError.error == QJsonParseError::NoError && document.isObject());
const QVariantMap restored = document.toVariant().toMap();
assert(restored.value("name").toString() == "Kimi");
assert(restored.value("age").toInt(&ok) == 25 && ok);
assert(restored.value("isDeveloper").toBool());
}

保存为 main.cpp,在 Debug 构建中运行:

cmake_minimum_required(VERSION 3.20)
project(VariantExample LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt6 REQUIRED COMPONENTS Core)
add_executable(variant_example main.cpp)
target_link_libraries(variant_example PRIVATE Qt6::Core)

动态属性和预先声明的 Q_PROPERTY 不同。给 QObject 新增动态属性时,setProperty() 的返回值不能简单当作“动态属性未写入”的证明;本例读取属性核对结果。Qt:QObject::setProperty

JSON 往返只验证这里使用的字符串、整数和布尔字段。JSON 不是任意 QVariant 的无损序列化格式:自定义结构、对象指针以及精确的原始 C++ 类型身份不能靠它自动保留。文件或网络数据还应校验字段是否存在、类型、数值范围及长度;不能只调用 toInt() 就认为配置有效。Qt:QJsonDocument

自定义值类型的声明、运行时注册及排队传递,见 [[10-knowledge/computing/software-development/application-development/desktop/qt/core/metatypes-and-queued-values|元类型与排队传值]]。