Elim的博客

用来记录一些原创性的总结


Elim

JAXB之XmlAnyElement之XML转对象

假设现在需要解析一段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);
}
所以我们在上面定义DataResponse时也可以把obj属性定义为`org.w3c.dom.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日)