手把手教你解析XML
期望通过每一次分享,让技术的门槛变低,落地更容易。 —— 周围
本文适用范围
- 实际工作中遇到关于解析和转换xml的问题
- 不懂,想详细了解学习如何解析xml的同学
本文目的
通过本文,你将解决如下问题:
- xml数据结构对应的java实体结构是如何绑定的
- 如何将xml结构数据转换成对象
- 如何将对象转换为xml
原始数据
<RESPONSE>
<ROW>
<id>E37AA69997D0F399E040007F0100484D</id>
<guid>20130809103931E37AA70270E78822E040007F01004851@gd.zg</guid>
<qryid>FR20130809103441000000001054@90001113@gd.zg</qryid>
<todepturi>40001009@jm.gd.cp</todepturi>
<todeptname>中共江门市委办秘书科</todeptname>
<sendamount>1</sendamount>
<status>RC01</status>
<sendtime>2013-08-09 10:39:32</sendtime>
<recvtime>2013-08-09 10:35:21</recvtime>
<appuri>201308071@jm.gd.cp</appuri>
<appname>江门市委办法规文件报备系统</appname>
<senddocstatus>
<status exchid="E201308090300709@11000003@gd.zg">
<site type="NT01" uri="11000003@gd.zg" name="广东省委机要局交换箱" status="PS03" stadesc="已经处理" modify="2013-08-09 10:39:34" errcode="0" final="0"/>
<site type="NT03" uri="201308071@jm.gd.cp" name="江门市委办法规文件报备系统" status="PS03" stadesc="已经处理" modify="2013-08-09 10:39:45" errcode="0" final="0" wsaddr="http://172.17.4.241:8080/fgsys/services/datasynservice"/>
</status>
</senddocstatus>
<printedinfo>
<rcpt>
<exchid>E201310290563707@20000007@jm.gd.cp-print</exchid>
<prints>
<operuri>21800168@jm.gd.cp</operuri>
<opername>测试用户2</opername>
<operdate>2013-10-30 9:19:55</operdate>
<print num="0000016"/> <!--公文顺序号 没有流水号print节点为空-->
<print num="0000017"/>
</prints>
</rcpt>
</printedinfo>
</ROW>
</RESPONSE>
上述的xml格式基本涵盖了标签值、标签属性值、集合等内容,我们了解了针对特殊数据格式的处理,解析起来就没问题了,下面所讲的xml例子全部来源于上面的xml原始数据。
前置环境
你需要导入xstream.jar
的包
需要注意的细节
- xml标签区分大小写,映射的时候不要认为它能够像sql一样,不管大小写
- xml标签对应实体属性,应确保数据类型是一致的,不然拿不到数据或者报错
解释格式
下面将对常见的xml和后端java-model做一个对应格式映射
属性格式
<id>E37AA69997D0F399E040007F0100484D</id>
<guid>20130809103931E37AA70270E78822E040007F01004851@gd.zg</guid>
上述这种xml标签是节点标签名称如 <id>
,并且内容数据是String
类型, 对应到java实体就是下面的声明:
/** E37AA69997D0F399E040007F0100484D */
private String id;
/** 20130809103931E37AA70270E78822E040007F01004851@gd.zg */
private String guid;
对象格式
<prints>
<operuri>21800168@jm.gd.cp</operuri>
<opername>测试用户2</opername>
<operdate>2013-10-30 9:19:55</operdate>
</prints>
如上述的格式,它对应的就是<prints>
标签是一个对象,它下面包含了3个子节点<operuri>
、<opername>
、<operdate>
。我们要将它们转换应该就是对象封装了,如下:
@XStreamAlias("prints")//标签名称是小写的prints,但是java对象声明是首字母大写不一致
@XStreamConverter(value=com.thoughtworks.xstream.converters.extended.JavaClassConverter.class)
public class Prints {
/** 21800168@jm.gd.cp */
private String operuri;
/** 测试用户2 */
private String opername;
/** 2013-10-30 9:19:55 */
//此处的注解也是别名替换,声明绑定xml标签时,使用operdate,而不是属性名operdate1
@XStreamAlias("operdate")
private String operdate1;
... (省略后续字段 及相关get/set方法)
}
接着,可以自己在对象里构建一个main方法做测试:
public static void main(String[] args) {
String xml= "<prints><operuri>21800168@jm.gd.cp</operuri><opername>测试用户2</opername><operdate>2013-10-30 9:19:55</operdate></prints>";
//com.leadal.csa.common.utils.XmlUtil
Prints prints = (Prints) XmlUtil.Xml2Object(xml, Prints.class);
System.out.println(prints);
}
好了,完成了第一个转换测试,接下来要继续介绍另一种对象格式的解析。
<site type="NT03" uri="201308071@jm.gd.cp" name="江门市委办法规文件报备系统" status="PS03" stadesc="已经处理" modify="2013-08-09 10:39:45" errcode="0" final="0" wsaddr="http://172.17.4.241:8080/fgsys/services/datasynservice"/>
按照之前的理解,创建这个标签应该叫<site>
,那我们构造对应对象就可以了,但是你会发现,它没有子节点,只有像html标签一样的标签属性值,同样也可以提取,不过要换一个方式,下面提供2种方法:
@XStreamAlias("site")
@XStreamConverter(value=com.thoughtworks.xstream.converters.extended.JavaClassConverter.class)
public class Site {
/** NT01 */
@XStreamAsAttribute
private String type;
/** 11000003@gd.zg */
@XStreamAsAttribute
private String uri;
... (省略后续字段 及相关get/set方法)
上述第一种方式就是在属性值上添加@XStreamAsAttribute
注解,可以实现提取<site type="NT03">
的type属性。
@XStreamAlias("site")
@XStreamConverter(value=com.thoughtworks.xstream.converters.extended.ToAttributedValueConverter.class)
public class Site {
/** NT01 */
private String type;
/** 11000003@gd.zg */
private String uri;
... (省略后续字段 及相关get/set方法)
而第二种方式,属于全局设置,更改类上面@XStreamConverter
注解中的转换器ToAttributedValueConverter.class
,可完成提取<site>
的所有标签属性,直接绑定到对应的参数名称上。
集合格式
<status exchid="E201308090300709@11000003@gd.zg">
<site type="NT01" uri="11000003@gd.zg" name="广东省委机要局交换箱" status="PS03" stadesc="已经处理" modify="2013-08-09 10:39:34" errcode="0" final="0"/>
<site type="NT03" uri="201308071@jm.gd.cp" name="江门市委办法规文件报备系统" status="PS03" stadesc="已经处理" modify="2013-08-09 10:39:45" errcode="0" final="0" wsaddr="http://172.17.4.241:8080/fgsys/services/datasynservice"/>
</status>
如上述所见的格式,<status>
中包含了2个<site>
,很明显<status>
里面声明的关于site内容应该是集合类型。
@XStreamAlias("status")
@XStreamConverter(value=com.thoughtworks.xstream.converters.extended.JavaClassConverter.class)
public class SendDocStatus {
/** E201308090300709@11000003@gd.zg */
@XStreamAsAttribute
private String exchid;
/** 状态记录 */
@XStreamImplicit //修饰site对应的标签是集合,会出现多个
private List<Site> site;
...(省略后续字段 及相关get/set方法)
如上述代码,@XStreamImplicit
完成了对集合类型的修饰,如果不声明该注解,可以试试解析会报什么错,应该会提醒你标签重复了,因为程序不知道它是集合类型,无法接收到。
具体操作
首先我们得有一个概念:在java里,一切都是对象,xml数据也不例外。
那么,我们要针对xml标签名称去创建对应的实体对象
如下图,先创建一个实体对象,将xml的对象对应到了该对象上,要求是数据格式一致。(这里只抽取了 <ROW> 对象展示)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yXuVPQH9-1637921539708)(file:///C:/Users/Dell/Desktop/工作文档/blog/xml/xml1.png)]
结合前面的格式讲解,现在再根据这个图的指向,应该能清楚xml和java实体是如何对应的了。
关于如何调用API在java实体与xml数据之间转换,看下面的例子,使用的均是com.leadal.csa.common.utils.XmlUtil
String xml= "<prints><operuri>21800168@jm.gd.cp</operuri><opername>测试用户2</opername><operdate>2013-10-30 9:19:55</operdate></prints>";
//xml数据 转 java实体
Prints prints = (Prints) XmlUtil.Xml2Object(xml, Prints.class);
//java实体 转 xml数据
XmlUtil.Object2Xml(prints, prints.getClass());
常见异常错误
No such field xxx
com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter$UnknownFieldException:
No such field com.leadal.csa.common.model.SendDocStatus.status
该错误是找不到对应的字段,如上述的错误案例,是找不到SendDocStatus.status下面的这个status属性。
通常这种错误,去检查字段属性名称是否拼错了,或者是否加了别名属性声明,导致最后无法与标签名称绑定上。
Duplicate field xxx
Exception in thread "main"
com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter$DuplicateFieldException:
Duplicate field print
这个错误,翻译过来是重复字段print
,意思就是print
这个属性重复出现了,但你的接收类型装不下多个,这种情况就要看自己是不是要根据xml结构,去把这个print
变成集合。
我们看一下原始数据关于print
的描述:
...
<prints>
...
<print num="0000016"/>
<print num="0000017"/>
</prints>
...
出现了2次这个标签,并且是以标签属性的方式设置值的,那么怎么设置类型才能将其成功封装到外部的<prints>
标签中呢?如下:
@XStreamAlias("prints")
@XStreamConverter(value=com.thoughtworks.xstream.converters.extended.JavaClassConverter.class)
public class Prints {
@XStreamImplicit//此注解就是集合注解,参数名正好与<print>标签名一致
private List<Print> print;
....(省略后续字段 及相关get/set方法)
}
@XStreamAlias("print")
@XStreamConverter(value=com.thoughtworks.xstream.converters.extended.ToAttributedValueConverter.class)
public class Print {
/**
* <print num="0000016"/>
* 这里的num正好对应标签属性值名称
*/
private String num;
....(省略后续字段 及相关get/set方法)
}
写在最后
以上就是关于如何解析xml的方式方法和将对象转成xml的方式,有问题可以再详细沟通,会使用到的注解我在下面统一列出并加上注释。
本文注解
//声明当前对象或属性名称的别名,必须与xml标签名一致,区分大小写
@XStreamAlias("prints")
//声明xml解析转换器,
//JavaClassConverter是普通java对象转换器
//ToAttributedValueConverter是适用于标签内属性值较多的转换器
@XStreamConverter(value=com.thoughtworks.xstream.converters.extended.JavaClassConverter.class)
//集合类型声明
@XStreamImplicit