第16章 XML与JSON解析:数据交换的左右手
说到XML和JSON,这俩东西在Qt开发里几乎天天见。我个人习惯把XML当成「严谨的老学究」,JSON则是「随性的年轻人」。一个讲究格式规范,一个追求简洁高效。今天咱们就把Qt里解析它们的几种方式捋一遍。
核心知识点速览
- XML解析:QXmlStreamReader(流式)、DOM(文档对象模型)、SAX(事件驱动)
- XML写入:QXmlStreamWriter
- JSON解析:QJsonDocument、QJsonObject、QJsonArray
- JSON写入:QJsonDocument::toJson()
16.1 XML解析:三种武器任你选
XML这东西,说它烦人吧,它确实啰嗦。但很多老系统、配置文件、SVG图形格式都在用。Qt给了我们三种解析方式,各有各的脾气。
16.1.1 QXmlStreamReader —— 我的首选
我个人最常用的是流式解析。为什么?因为它不占内存。你想想看,一个几百兆的XML文件,如果用DOM一次性加载到内存里,程序直接就崩了。流式解析就像读小说一样,一页一页翻,读过的就扔了。
// 流式读取XML
QFile file("config.xml");
if (!file.open(QIODevice::ReadOnly)) {
qWarning() << "文件打开失败";
return;
}
QXmlStreamReader reader(&file);
QString currentTag;
while (!reader.atEnd()) {
reader.readNext();
if (reader.isStartElement()) {
currentTag = reader.name().toString();
if (currentTag == "user") {
QString id = reader.attributes().value("id").toString();
qDebug() << "用户ID:" << id;
}
}
else if (reader.isCharacters() && !reader.isWhitespace()) {
if (currentTag == "name") {
qDebug() << "姓名:" << reader.text().toString();
}
}
}
if (reader.hasError()) {
qWarning() << "解析错误:" << reader.errorString();
}
file.close();
小技巧:流式解析不能回头。如果你需要随机访问XML的某个节点,那就别用流式了。我当年做一个日志分析工具,一开始用了流式,后来发现需要反复查某个时间段的记录,果断换成了DOM。
16.1.2 DOM解析 —— 小文件的王者
DOM解析会把整个XML文档加载到内存里,构建成一棵树。好处是你可以随便在树上爬来爬去,想查哪个节点就查哪个节点。
// DOM方式解析
QDomDocument doc;
QFile file("config.xml");
if (!file.open(QIODevice::ReadOnly)) return;
if (!doc.setContent(&file)) {
qWarning() << "文档格式错误";
file.close();
return;
}
QDomElement root = doc.documentElement();
QDomNodeList users = root.elementsByTagName("user");
for (int i = 0; i < users.size(); ++i) {
QDomElement user = users.at(i).toElement();
QString id = user.attribute("id");
QString name = user.firstChildElement("name").text();
qDebug() << "ID:" << id << "Name:" << name;
}
file.close();
注意:DOM解析有个坑——它会把整个文档都吃掉。我曾经接手过一个项目,用DOM解析一个50MB的XML,结果程序启动要等10秒。后来改成流式解析,瞬间就快了。所以记住:小文件用DOM,大文件用流式。
16.1.3 SAX解析 —— 老古董但依然能打
SAX解析是事件驱动的。说白了就是:你告诉解析器「遇到开始标签叫我,遇到结束标签也叫我」,然后它一边读一边通知你。这种方式比流式更底层一些,但控制力更强。
// SAX解析需要继承QXmlDefaultHandler
class MyHandler : public QXmlDefaultHandler {
public:
bool startElement(const QString &namespaceURI,
const QString &localName,
const QString &qName,
const QXmlAttributes &atts) override {
if (qName == "user") {
qDebug() << "发现用户:" << atts.value("id");
}
return true;
}
bool characters(const QString &ch) override {
// 处理文本内容
return true;
}
};
// 使用SAX解析
QXmlSimpleReader reader;
MyHandler handler;
reader.setContentHandler(&handler);
QXmlInputSource source(&file);
reader.parse(source);
16.2 写入XML:QXmlStreamWriter
写XML比读XML简单多了。QXmlStreamWriter就像一支笔,你告诉它写什么,它就写什么。我习惯用它来生成配置文件。
QFile file("output.xml");
if (!file.open(QIODevice::WriteOnly)) return;
QXmlStreamWriter writer(&file);
writer.setAutoFormatting(true); // 自动缩进,好看!
writer.writeStartDocument();
writer.writeStartElement("config");
writer.writeStartElement("database");
writer.writeAttribute("type", "mysql");
writer.writeTextElement("host", "localhost");
writer.writeTextElement("port", "3306");
writer.writeEndElement(); // 结束database
writer.writeEndElement(); // 结束config
writer.writeEndDocument();
file.close();
经验之谈:setAutoFormatting(true)一定要开。不然生成的XML全挤在一行,你自己都不想看。我刚开始写Qt的时候就没开这个,结果被同事吐槽了好久。
16.3 JSON解析:现代人的选择
JSON现在太流行了。Web API返回的基本都是JSON,Qt也提供了很舒服的接口。说实话,我现在写新项目,能用JSON绝不用XML。
16.3.1 解析JSON字符串
QString jsonStr = R"({
"name": "张三",
"age": 28,
"skills": ["C++", "Qt", "Python"],
"address": {
"city": "北京",
"street": "长安街"
}
})";
QJsonDocument doc = QJsonDocument::fromJson(jsonStr.toUtf8());
if (doc.isNull()) {
qWarning() << "JSON格式错误";
return;
}
QJsonObject obj = doc.object();
QString name = obj["name"].toString();
int age = obj["age"].toInt();
// 处理数组
QJsonArray skills = obj["skills"].toArray();
for (const auto &skill : skills) {
qDebug() << "技能:" << skill.toString();
}
// 处理嵌套对象
QJsonObject addr = obj["address"].toObject();
QString city = addr["city"].toString();
16.3.2 构建JSON数据
QJsonObject user;
user["name"] = "李四";
user["age"] = 32;
QJsonArray tags;
tags.append("工程师");
tags.append("项目经理");
user["tags"] = tags;
QJsonObject config;
config["user"] = user;
config["version"] = "1.0.0";
QJsonDocument doc(config);
QString jsonOutput = doc.toJson(QJsonDocument::Indented);
// 保存到文件
QFile file("user.json");
file.open(QIODevice::WriteOnly);
file.write(jsonOutput.toUtf8());
file.close();
关键区别:JSON解析比XML快得多,代码也更简洁。但XML有Schema验证、命名空间等高级功能,JSON没有。选哪个?看场景。如果是配置文件,我推荐JSON。如果是需要严格数据验证的场合,XML更合适。
16.4 实战对比:XML vs JSON
| 特性 | XML | JSON |
|---|---|---|
| 可读性 | 标签多,略显臃肿 | 简洁明了 |
| 解析速度 | 较慢(尤其DOM) | 快 |
| 内存占用 | 流式低,DOM高 | 整体较低 |
| 数据类型 | 纯文本 | 支持数字、布尔、数组 |
| 验证机制 | 有DTD/Schema | 无内置验证 |
| 适用场景 | 配置文件、文档存储 | Web API、数据交换 |
避坑指南:我曾经在解析JSON时犯过一个低级错误——用QJsonDocument::fromJson()后没检查isNull()。结果程序跑起来一直崩溃,查了半天才发现是JSON字符串里多了一个逗号。记住:任何解析操作都要做错误检查!
嗯,XML和JSON的解析就说这么多。实际开发中,你八成会遇到混合使用的情况——比如从JSON接口拿数据,然后转成XML存到本地。Qt的这套API设计得挺顺手,多用几次就熟练了。
我的建议:新手先从JSON入手,它更直观。等需要处理遗留系统或者做文档格式转换时,再深入研究XML的三种解析方式。别一口气全学,容易消化不良。
公众号:蓝海资料掘金营,微信deep3321