Qt开发总结(26)——解析JSON和XML
之前几篇总结了文件、网络和串口操作等,这些功能或多或少都与IO操作有关,你可能已经发现他们涉及的一些类都是由QIODevice派生而来,这意味着涉及到数据传输,本篇将介绍两类常见的数据格式——JSON和XML,并总结Qt是如何解析他们的。
JSON数据格式 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。JSON采用完全独立于语言的文本格式,这些特性使JSON成为理想的数据交换语言。易于人阅读和编写,同时也易于机器解析和生成。JSON本来是JavaScript 的一种原生格式,但是并不代表别的语言不能使用它。只是说在 JavaScript 中处理 JSON 数据不需要任何特殊的 API 或工具包。
JSON建构于两种结构:
1. “名称/值”对的集合(A collection of name/value pairs)。不同的语言中,它被理解为对象(object),记录(record),结构(struct),字典(dictionary),哈希表(hash table),有键列表(keyed list),或者关联数组 (associative array)。
按照最简单的形式,可以用下面这样的 JSON 表示 "名称/值对" :{ "firstName": "wang" }
这个示例非常基本,而且实际上比等效的纯文本 "名称/值对" 占用更多的空间:firstName=wang
但是,当将多个"名称/值对"串在一起时,JSON就会体现出它的价值了。首先,可以创建包含多个"名称/值对"的 记录,比如:
{ "firstName": "wang", "lastName":"wayne", "email": "wayne@csdn.com" }
从语法方面来看,这与"名称/值对"相比并没有很大的优势,但是在这种情况下 JSON 更容易使用,而且可读性更好。例如,它明确地表示以上三个值都是同一记录的一部分;花括号使这些值有了某种联系。
2. 值的有序列表(An ordered list of values)。在大部分语言中,它被理解为数组(array)。
当需要表示一组值时,JSON 不但能够提高可读性,而且可以减少复杂性。例如,假设您希望表示一个人名列表。在 XML 中,需要许多开始标记和结束标记;如果使用典型的 名称 / 值对(就像在本系列前面文章中看到的那种名称/值对),那么必须建立一种专有的数据格式,或者将键名称修改为 person1-firstName这样的形式。
如果使用 JSON,就只需将多个带花括号的记录分组在一起:
{ "people": [
{ "firstName": "wang", "lastName":"wayne", "email": "aaaa" },
{ "firstName": "zhao", "lastName":"san", "email": "bbbb"},
{ "firstName": "li", "lastName":"si", "email": "cccc" }
]}
这不难理解。在这个示例中,只有一个名为 people的变量,值是包含三个条目的数组,每个条目是一个人的记录,其中包含名、姓和电子邮件地址。上面的示例演示如何用括号将记录组合成一个值。
XML数据格式 XML是一种可扩展标记语言(Extensible Markup Language),是一种标记语言。主要用于描述数据和用作配置文件。
在XML开头要有XML声明,指明所用XML版本,文档编码和文档的独立性信息。如:
具体内容由元素构成。元素由开始标签、元素内容和结束标签构成。其语法为<元素名 属性名="属性值">。注意元素必须正确的嵌套。结束标签可以是换行的,也可以不换行,简写一个反斜杠。另外,一个元素可以包含多个属性,名称中不能包含空格。
另外还可以添加注释:以的形式,对文档中的内容进行说明。
JSON和XML对比 ◆可读性
JSON和XML的可读性可谓不相上下,一边是简易的语法,一边是规范的标签形式,很难分出胜负。
◆可扩展性
XML天生有很好的扩展性,JSON当然也有,没有什么是XML能扩展,而JSON却不能。不过JSON在Javascript主场作战,可以存储Javascript复合对象,有着xml不可比拟的优势。
◆编码难度
XML有丰富的编码工具,比如Dom4j、JDom等,JSON也有提供的工具。无工具的情况下,相信熟练的开发人员一样能很快的写出想要的xml文档和JSON字符串,不过,xml文档要多很多结构上的字符。
◆解码难度
XML的解析方式有两种:
一是通过文档模型解析,也就是通过父标签索引出一组标记。例如:xmlData.getElementsByTagName("tagName"),但是这样是要在预先知道文档结构的情况下使用,无法进行通用的封装。
另外一种方法是遍历节点(document 以及 childNodes)。这个可以通过递归来实现,不过解析出来的数据仍旧是形式各异,往往也不能满足预先的要求。
凡是这样可扩展的结构数据解析起来一定都很困难。
JSON也同样如此。如果预先知道JSON结构的情况下,使用JSON进行数据传递简直是太美妙了,可以写出很实用美观可读性强的代码。如果你是纯粹的前台开发人员,一定会非常喜欢JSON。但是如果你是一个应用开发人员,就不是那么喜欢了,毕竟xml才是真正的结构化标记语言,用于进行数据传递。
而如果不知道JSON的结构而去解析JSON的话,那简直是噩梦。费时费力不说,代码也会变得冗余拖沓,得到的结果也不尽人意。但是这样也不影响众多前台开发人员选择JSON。因为json.js中的toJSONString()就可以看到JSON的字符串结构。当然不是使用这个字符串,这样仍旧是噩梦。常用JSON的人看到这个字符串之后,就对JSON的结构很明了了,就更容易的操作JSON。
以上是在Javascript中仅对于数据传递的xml与JSON的解析。在Javascript地盘内,JSON毕竟是主场作战,其优势当然要远远优越于xml。如果JSON中存储Javascript复合对象,而且不知道其结构的话,我相信很多程序员也一样是哭着解析JSON的。
Qt解析JSON QT4中使用第三方库QJson解析JSON文件。QT5新增加了处理JSON的类,类均以QJson开头,包含在QtCore模块中。QT5新增加六个相关类:
QJsonArray |
封装 JSON 数组 |
QJsonDocument |
读写 JSON 文档 |
QJsonObject |
封装 JSON 对象 |
QJsonObject::iterator |
用于遍历QJsonObject的STL风格的非const遍历器 |
QJsonParseError |
报告 JSON 处理过程中出现的错误 |
QJsonValue |
封装 JSON 值 |
QJsonArray封装了JSON数组。JSON数组是值的链表,可以插入和删除QJsonValue。QJsonArray与QVariantList可以相互转换。QJsonArray可以用size(), insert(), removeAt()进行操作,还可以用标准C++的迭代器模式来迭代其内容。QJsonArray是一个隐式共享的类,只要没有被改变,可以和创建QJsonArray的document共享数据。通过QJsonDocument可以将一个QJsonArray转换成或转换自一个文本形式的JSON。
QJsonObject类用于封装JSON对象。JSON对象是包含键值对的链表,其中键是唯一的字符串,其值由QJsonValue代表。QJsonObject可以与QVariantMap相互转换,可以用size()来获得键值对的数目,insert()、remove()分别用来插入和删除pair。可以用标准C++的迭代器模式(iterator pattern)来迭代其内容。QJsonObject是一个隐式共享的类,只要没有被改变过,QJsonObject会和创建它的document共享数据。可以通过QJsonDocument将QJsonObject和文本格式相互转换。
QJsonParseError类用于在JSON解析中报告错误。
常量 |
值 |
描述 |
QJsonParseError::NoError |
0 |
未发生错误 |
QJsonParseError::UnterminatedObject |
1 |
对象不正确地终止以右花括号结束 |
QJsonParseError::MissingNameSeparator |
2 |
分隔不同项的逗号丢失 |
QJsonParseError::UnterminatedArray |
3 |
数组不正确地终止以右中括号结束 |
QJsonParseError::MissingValueSeparator |
4 |
对象中分割 key/value 的冒号丢失 |
QJsonParseError::IllegalValue |
5 |
值是非法的 |
QJsonParseError::TerminationByNumber |
6 |
在解析数字时,输入流结束 |
QJsonParseError::IllegalNumber |
7 |
数字格式不正确 |
QJsonParseError::IllegalEscapeSequence |
8 |
在输入时,发生一个非法转义序列 |
QJsonParseError::IllegalUTF8String |
9 |
在输入时,发生一个非法 UTF8 序列 |
QJsonParseError::UnterminatedString |
10 |
字符串不是以引号结束 |
QJsonParseError::MissingObject |
11 |
一个对象是预期的,但是不能被发现 |
QJsonParseError::DeepNesting |
12 |
对解析器来说,JSON 文档嵌套太深 |
QJsonParseError::DocumentTooLarge |
13 |
对解析器来说,JSON 文档太大 |
QJsonParseError::GarbageAtEnd |
14 |
解析的文档在末尾处包含额外的乱码 |
boolQJsonValue::Bool
doubleQJsonValue::Double
stringQJsonValue::String
arrayQJsonValue::Array
objectQJsonValue::Object
nullQJsonValue::Null
UndefinedQJsonValue::Undefined
value可以是以上任何一种数据类型。另外,QJsonValue有一个特殊的flag来表示未定义类型。可以用isUndefined()来查询。可以用type()或isBool(),、isString()等来查询value的类型。类似的,可以用toBool()、toString()等将一个value转换成存储在该value内部的类型。
JSON解析的流程如下:
A、将对应的字符串生成QJsonDocument对象
B、判断QJsonDocument对象是QJsonObject还是QJsonArray
C、如果是QJsonObject类型,获取一个QJsonObject对象,然后根据QJsonObject的API函数进行解析
D、如果是QJsonArray类型,获取一个QJsonArray对象,然后根据QJsonArray的API函数进行解析
E、根据获取的QJsonObject或QJsonArray取得QJsonValue类型的数据
F、迭代分解数据获取各个值
要解析的JSON文件:
{
"people": [
{ "firstName": "wang", "lastName":"wayne", "email": "aaaa" },
{ "firstName": "zhao", "lastName":"san", "email": "bbbb"},
{ "firstName": "li", "lastName":"si", "email": "cccc" }
],
"animal": {
"name": "dog",
"weight": 20
}
}
解析代码:
QFile loadFile("../123.json");
if (!loadFile.open(QIODevice::ReadOnly))
{
qWarning("Couldn't open json file.");
return -1;
}
QJsonParseError json_error;
QByteArray saveData = https://www.it610.com/article/loadFile.readAll();
QJsonDocument loadDoc(QJsonDocument::fromJson(saveData, &json_error));
if (json_error.error != QJsonParseError::NoError)
{
qDebug() <<"json error!"<< json_error.error;
return -1;
} QJsonObject rootObj = loadDoc.object();
QStringList keys = rootObj.keys();
for (int i = 0;
i < keys.size();
i++)
{
qDebug() << "key" << i << " is:" << keys.at(i);
}
//对于嵌套节点
if (rootObj.contains("animal"))
{
QJsonValue value = https://www.it610.com/article/rootObj.value("animal");
if (value.isObject())
{// 判断 value 是否为节点
QJsonObject obj = value.toObject();
if (obj.contains("name"))
{
QJsonValue value2 = obj.value("name");
if (value2.isString())
{
QString animal = value2.toString();
qDebug() << "Animal Name : " << animal;
}
}
if (obj.contains("weight"))
{
QJsonValue value2 = obj.value("weight");
if (value2.isDouble())
{
int weight = value2.toInt();
qDebug() << "Animal weight : " << weight;
}
}}
}
//对于数组
if (rootObj.contains("people"))
{
QJsonValue value = https://www.it610.com/article/rootObj.value("people");
if (value.isArray())
{
QJsonArray array = value.toArray();
int nSize = array.size();
for (int i = 0;
i < nSize;
++i)
{
QJsonValue value = https://www.it610.com/article/array.at(i);
if (value.isObject())
{
QJsonObject obj = value.toObject();
if (obj.contains("firstName"))
{
QJsonValue value = https://www.it610.com/article/obj.value("firstName");
if(value.isString())
{
QString peoplename = value.toString();
qDebug() << "people Name : " << peoplename;
}
}
}
}
}
}
Qt解析XML Qt中有独立的XML模块支持,在使用时需要用QT += xml添加支持,并在头文件中加入#include
QDomAttr |
Represents one attribute of a QDomElement |
QDomCDATASection |
Represents an XML CDATA section |
QDomCharacterData |
Represents a generic string in the DOM |
QDomComment |
Represents an XML comment |
QDomDocument |
Represents an XML document |
QDomDocumentFragment |
Tree of QDomNodes which is not usually a complete QDomDocument |
QDomDocumentType |
The representation of the DTD in the document tree |
QDomElement |
Represents one element in the DOM tree |
QDomEntity |
Represents an XML entity |
QDomEntityReference |
Represents an XML entity reference |
QDomImplementation |
Information about the features of the DOM implementation |
QDomNamedNodeMap |
Contains a collection of nodes that can be accessed by name |
QDomNode |
The base class for all the nodes in a DOM tree |
QDomNodeList |
List of QDomNode objects |
QDomNotation |
Represents an XML notation |
QDomProcessingInstruction |
Represents an XML processing instruction |
QDomText |
Represents text data in the parsed XML document |
QXmlAttributes |
XML attributes |
QXmlContentHandler |
Interface to report the logical content of XML data |
QXmlDTDHandler |
Interface to report DTD content of XML data |
QXmlDeclHandler |
Interface to report declaration content of XML data |
QXmlDefaultHandler |
Default implementation of all the XML handler classes |
QXmlEntityResolver |
Interface to resolve external entities contained in XML data |
QXmlErrorHandler |
Interface to report errors in XML data |
QXmlInputSource |
The input data for the QXmlReader subclasses |
QXmlLexicalHandler |
Interface to report the lexical content of XML data |
QXmlLocator |
The XML handler classes with information about the parsing position within a file |
QXmlNamespaceSupport |
Helper class for XML readers which want to include namespace support |
QXmlParseException |
Used to report errors with the QXmlErrorHandler interface |
QXmlReader |
Interface for XML readers (i.e. parsers) |
QXmlSimpleReader |
Implementation of a simple XML parser |
1.通过QXmlStreamReader解析。
2.通过DOM方式。
3.通过QXmlSimpleReader也即回调函数的方式。
下面简单用代码说明这三种方式:
XML文件:
1. QXmlStreamReader解析
QFile file("../123.xml");
if (!file.open(QIODevice::ReadOnly))
{
qWarning("Couldn't open xml file.");
return -1;
} QXmlStreamReader reader(&file);
while (!reader.atEnd()) {
reader.readNext();
if (reader.isStartElement())
{
if (reader.name() == "people")
{
QString str = reader.attributes().value("fisrtname").toString();
QString str1 = reader.attributes().value("email").toString();
qDebug() << "People's Name: "<
我们看到这类似于读文本文件,用while循环,并reader.readNext(),直到遇到符合自己想要查找的项。
2. DOM解析
QDomDocument doc;
QString errStr;
int errLine;
int errColumn;
if (!doc.setContent(&file, false, &errStr, &errLine, &errColumn))
{
qDebug() << "Error:Parse error at: " << errLine << ", " << errColumn << errStr;
return -1;
} QDomElement root = doc.documentElement();
if (root.tagName() != "peoples")
{
qDebug() << "Error:No peoples!";
return -1;
}
QDomNodeList Nodelist = doc.elementsByTagName("people");
for (int i = 0;
i < Nodelist.count();
i++)
{
QDomNode peopleNode = Nodelist.at(i);
QString str = peopleNode.toElement().attribute("fisrtname");
QString str1 = peopleNode.toElement().attribute("email");
qDebug() << "People's Name: " << str << ", email:" << str1;
}
我们看到这个思路比较清晰,更像传统的解析流程,elementsByTagName接口也为我们提供了很大的方便。
3. QXmlSimpleReader回调方式解析
需要添加一个Handle类,继承于QXmlDefaultHandler。重写startElement,endElement,characters等接口。
//handler.h
#pragma once#include class Handler : public QXmlDefaultHandler
{public:
Handler();
~Handler();
bool startElement(const QString &namespaceURI, const QString &localName,
const QString &qName, const QXmlAttributes &attributes) override;
bool endElement(const QString &namespaceURI, const QString &localName,
const QString &qName) override;
bool characters(const QString &str) override;
bool fatalError(const QXmlParseException &exception) override;
QString errorString() const override;
private:
QString currentText;
QString errorStr;
};
//handler.cpp
Handler::Handler() : QXmlDefaultHandler()
{
}Handler::~Handler()
{
}
bool Handler::startElement(const QString & /* namespaceURI */,
const QString & /* localName */,
const QString &qName,
const QXmlAttributes &attributes)
{
if (qName == "people")
{
QString str = attributes.value("fisrtname");
QString str1 = attributes.value("email");
qDebug() << "People First name is "<
通过setContentHandler设置handler,通过QXmlInputSource设置源文件,调用parse(xmlInputSource)开始解析,调用流程是进入Handler类,按照XML内容顺序,遇到开始标签则自动调用startElement,遇到结束标签则自动调用endElement,如果有内容,则调用characters。可以在characters这里合成自己想要的内容,通过信号发送出去(本例中未涉及)。
综上所述,个人觉得DOM的方式比较简单易懂,也比较好操作,但效率较低;回调的方式比较复杂,但是一旦写好结构,针对大的XML解析需要改动的并不多,可扩展性比较强;StreamReader的方式效率比较低,毕竟要循环访问,但也比DOM少遍历一次。对于我来说,小的XML文件,可以用DOM方式解析,如果是大XML文件,可以用QXmlStreamReader解析。
例子 【Qt开发总结(26)——解析JSON和XML】最后附上上述测试代码工程,可以到本人下载频道下载:https://download.csdn.net/download/bjtuwayne/12080233
推荐阅读
- 急于表达——往往欲速则不达
- 慢慢的美丽
- 《真与假的困惑》???|《真与假的困惑》??? ——致良知是一种伟大的力量
- 2019-02-13——今天谈梦想()
- 深入理解Go之generate
- 考研英语阅读终极解决方案——阅读理解如何巧拿高分
- Ⅴ爱阅读,亲子互动——打卡第178天
- 低头思故乡——只是因为睡不着
- 7.9号工作总结~司硕
- 取名——兰