用来记录一些原创性的总结
假设现在需要解析一段XML,这段XML的结构是不固定的,它可能是如下这样的:
<response>
<data>123</data>
</response>
也可能是如下这样的:
<response>
<data1>
<abc>123</abc>
</data1>
</response>
如果需要把它转换为对象应该怎么办呢?正常情况下第一段XML对应的Java类的结构大概是这样的:
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name="response")
class DataResponse {
private String data;
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}
第二段XML对应的Java类的结构大概是这样的:
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name="response")
class DataResponse {
@XmlElement(name="data1")
private DataObj data;
public DataObj getData() {
return data;
}
public void setData(DataObj data) {
this.data = data;
}
}
@XmlAccessorType(XmlAccessType.FIELD)
class DataObj {
private String abc;
public String getAbc() {
return abc;
}
public void setAbc(String abc) {
this.abc = abc;
}
}
这个时候我们就可以使用@XmlAnyElement来映射response下面的子节点了,@XmlAnyElement可以用来匹配任何不能够被精确匹配的元素,所以我们可以定义我们的DataResponse的结构为如下结构:
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name="response")
class DataResponse {
@XmlAnyElement
private Object obj;
public Object getObj() {
return obj;
}
public void setObj(Object obj) {
this.obj = obj;
}
}
因为不知道是什么类型,所以这里定义为Object类型。实际上这里会默认被解析为org.w3c.dom.Element
类型的对象。
@Test
public void test() {
String xml = ...;
DataResponse unmarshalledResponse = JAXB.unmarshal(new StringReader(xml), DataResponse.class);
Assert.assertTrue(unmarshalledResponse.getObj() instanceof Element);
}
。如果我们的XML中可能存在多个不能精确匹配的元素,我们也可以把@XmlAnyElement对应的属性定义为List类型。如:
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name="response")
class DataResponse {
@XmlAnyElement
private List<Element> objs;
public List<Element> getObjs() {
return objs;
}
public void setObjs(List<Element> objs) {
this.objs = objs;
}
}
此外,对于某些能精确的匹配为一个对应的情况,我们希望它能进行精确匹配。比如对于第一段XML,我们希望元素<data>123</data>
能精确的匹配到Data类型的对象,而第二段XML里面的<data1>...</data1>
可以精确的匹配为DataObj类型的对象,对应response元素中包含的如果是其它类型的对象则把它映射为Object。这可以通过指定@XMLAnyElement的lax=”true”来实现,指定了lax=”true”后@XmlAnyElement在映射XML时如果能精确的映射为其可识别的某个对象,则会把它映射为对应的对象,而不是默认的Element对象。这样我们就可以把DataResponse定义为如下这样:
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name="response")
class DataResponse {
@XmlAnyElement(lax=true)
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name="data")
class Data {
@XmlValue
private String value;
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name="data1")
class DataObj {
private String abc;
public String getAbc() {
return abc;
}
public void setAbc(String abc) {
this.abc = abc;
}
}
这种情况,需要自动匹配的对象需要匹配的标签名需要由对应类上的@XmlRootElement来指定,比如上面的Data类上的@XmlRootElement(name=”data”)。进行转换的时候需要在创建对应的JAXBContext时加入我们需要自动精确匹配的Class,这样JAXBContext才能识别这些Class。
@Test
public void test() throws JAXBException {
String xml = ...;//获取上述的第一段XML
JAXBContext jaxbContext = JAXBContext.newInstance(DataResponse.class, Data.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
DataResponse unmarshalledResponse = (DataResponse) unmarshaller.unmarshal(new StringReader(xml));
Assert.assertTrue(unmarshalledResponse.getData() instanceof Data);
}
@Test
public void test2() throws JAXBException {
String xml = ...;//获取上述的第二段XML
JAXBContext jaxbContext = JAXBContext.newInstance(DataResponse.class, DataObj.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
DataResponse result = (DataResponse) unmarshaller.unmarshal(new StringReader(xml));
Assert.assertTrue(result.getData() instanceof DataObj);
}
如果需要匹配的任意元素有多个,也需要对于能够精确匹配的Class能够自动精确匹配,也可以加上lax=”true”。
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name="response")
class DataResponse {
@XmlAnyElement(lax=true)
private List<Object> data;
public List<Object> getData() {
return data;
}
public void setData(List<Object> data) {
this.data = data;
}
}
(注:本文由Elim写于2017年9月11日)