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 值
QJsonDocument提供了读写Json文档的方法。QJsonDocument是一个包含了完整JSON文档的类,支持以UTF-8编码的文本和QT自身的二进制格式来读写JSON文档。JSON文档可以使用QJsonDocument::fromJson()将基于JSON文档的文本形式转换为QJsonDocument对象,toJSON()可以将QJsonDocument转换回文本形式。解析文档的有效性可以使用 !isNull() 进行查询。使用isArray()和isObject()可以分别查询一个文档是否包含了一个数组或一个object。使用array()或object()可以将包含在文档中的数组或object提取出来。使用fromBinaryData()或fromRawData()也可以从一个二进制形式创建一个QJsonDocument对象。
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
解析的文档在末尾处包含额外的乱码
QJsonValue类封装了JSON中的值。JSON中的值有6种基本类型:
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 。XML的相关类有:
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
从类的功能可以看出,Qt解析XML有三种方法:
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

    推荐阅读