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": "[email protected]" }

从语法方面来看,这与"名称/值对"相比并没有很大的优势,但是在这种情况下 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版本,文档编码和文档的独立性信息。如:

<?xml version="1.0" encoding="utf-8" standalone="no"?>

具体内容由元素构成。元素由开始标签、元素内容和结束标签构成。其语法为<元素名 属性名="属性值">。注意元素必须正确的嵌套。结束标签可以是换行的,也可以不换行,简写一个反斜杠。另外,一个元素可以包含多个属性,名称中不能包含空格。

<peoples>

         <people fisrtname="wang" lastName="wayne" email="[email protected]"/>

         <people fisrtname="zhao" lastName="san" email="[email protected]"/>

         <people fisrtname="li" lastName="si" email="[email protected]"/>

</peoples>

另外还可以添加注释:以<! >的形式,对文档中的内容进行说明。

<!--人员信息-->

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种基本类型:

    bool           QJsonValue::Bool

    double         QJsonValue::Double

    string         QJsonValue::String

    array          QJsonValue::Array

    object         QJsonValue::Object

    null           QJsonValue::Null

    Undefined      QJsonValue::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 = 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 = 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 = rootObj.value("people");
		if (value.isArray())
		{
			QJsonArray array = value.toArray();
			int nSize = array.size();
			for (int i = 0; i < nSize; ++i)
			{
				QJsonValue value = array.at(i);
				if (value.isObject())
				{
					QJsonObject obj = value.toObject();
					if (obj.contains("firstName"))
					{
						QJsonValue value = obj.value("firstName");
						if(value.isString())
						{
							QString peoplename = value.toString();
							qDebug() << "people Name : " << peoplename;
						}
					}
				}
			}
		}
	}

Qt解析XML

Qt中有独立的XML模块支持,在使用时需要用QT += xml添加支持,并在头文件中加入#include <QtXml>。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文件:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>

<peoples>

         <people fisrtname="wang" lastName="wayne" email="[email protected]"/>

         <people fisrtname="zhao" lastName="san" email="[email protected]"/>

         <people fisrtname="li" lastName="si" email="[email protected]"/>

</peoples>

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: "<<str <<", email:"<<str1;
			}
				
		}
	}

我们看到这类似于读文本文件,用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 <QXmlDefaultHandler>

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 "<<str << "email is "<< str1;
	}
	return true;
}
bool Handler::endElement(const QString & /* namespaceURI */,
	const QString & /* localName */,
	const QString &qName)
{
	if (qName == "peoples") 
	{
		
	}
	currentText.clear();
	return true;
}
bool Handler::characters(const QString &str)
{
	currentText += str;
	return true;
}
bool Handler::fatalError(const QXmlParseException &exception)
{
	QMessageBox::information(NULL, QObject::tr("SAX Bookmarks"),
		QObject::tr("Parse error at line %1, column %2:\n"
			"%3")
		.arg(exception.lineNumber())
		.arg(exception.columnNumber())
		.arg(exception.message()));
	return false;
}

QString Handler::errorString() const
{
	return errorStr;
}
//main.cpp
Handler *handler = new Handler;
QXmlSimpleReader xmlReader;
xmlReader.setContentHandler(handler);
xmlReader.setErrorHandler(handler);
QXmlInputSource xmlInputSource(&file);
if (!xmlReader.parse(xmlInputSource))
{
	qWarning("Couldn't read xml file.");
}

通过setContentHandler设置handler,通过QXmlInputSource设置源文件,调用parse(xmlInputSource)开始解析,调用流程是进入Handler类,按照XML内容顺序,遇到开始标签则自动调用startElement,遇到结束标签则自动调用endElement,如果有内容,则调用characters。可以在characters这里合成自己想要的内容,通过信号发送出去(本例中未涉及)。

综上所述,个人觉得DOM的方式比较简单易懂,也比较好操作,但效率较低;回调的方式比较复杂,但是一旦写好结构,针对大的XML解析需要改动的并不多,可扩展性比较强;StreamReader的方式效率比较低,毕竟要循环访问,但也比DOM少遍历一次。对于我来说,小的XML文件,可以用DOM方式解析,如果是大XML文件,可以用QXmlStreamReader解析。

例子

最后附上上述测试代码工程,可以到本人下载频道下载:https://download.csdn.net/download/bjtuwayne/12080233

发布了77 篇原创文章 · 获赞 63 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/bjtuwayne/article/details/104734159