今日在公司负责公司支付业务模块,因为银行系统主要采用XML的报文格式,因此在不想做XML拼接的情况下,使用了Jackson作为xml报文与Bean之间的互相转换关系,但是在使用到集合形式的数据转化时,却和我期望的结果有比较大的差异,因此这边文章作为记录,希望可以帮到其他有需要的小伙伴。
依赖以及基础环境
我们还是从一个简单的demo来引入使用的一些基本操作吧, 一下为基础环境:
- JAVA 1.8
- Jackson 2.11.1
- maven
- idea
XmlMapper的创建
在Jackson操作xml最主要就是XmlMapper对象的使用,通过查看源码可以知道, 其实XmlMapper也是ObjectMapper的一个子类,ObjectMapper这个类对于大家来说其实并不陌生,因为我们在操作JSON的时候,使用的就是这个类,
public class XmlMapper extends ObjectMapper { .... }
XmlUtil
当我们在操作前,为Xml操作提供一个工具类,工具类中最主要就是对xml的操作,具体代码如下:
package com.jackson;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import java.util.Objects;
/**
* Xml工具类
*/
public class XmlUtil {
private XmlUtil() {
throw new UnsupportedOperationException("不支持该操作");
}
private static final XmlMapper XML_MAPPER = new XmlMapper();
static {
// 美化输出
XML_MAPPER.enable(SerializationFeature.INDENT_OUTPUT);
}
public static String toXmlString(Object obj) {
if (Objects.isNull(obj)) {
return null;
}
try {
return XML_MAPPER.writeValueAsString(obj);
} catch (JsonProcessingException e) {
throw new RuntimeException("转换对象为xml失败", e);
}
}
public static <T> T toJavaBean(String xml, Class<T> clazz) {
if (Objects.isNull(xml) || Objects.isNull(clazz)) {
return null;
}
try {
return XML_MAPPER.readValue(xml, clazz);
} catch (JsonProcessingException e) {
throw new RuntimeException("xml转换失败", e);
}
}
}
在这个工具类中有两个方法:
- toXmlString : 将java对象转换为xml字符创
- toJavaBean: 将xml转换为对应对象
常用注解
@JacksonXmlProperty
该注解主要用来指定对象中属性在xml中对应节点的名称,主要有以下几个属性:
- localName: 用来指定节点名称,当为空是,和当前属性名称进行匹配
- isAttribute: 指定当前属性值是否为节点属性. 例如:
<key id = "2">, 如果需要读取节点属性,则可以指定为true
@JacksonXmlRootElement
该注解主要标记xml的根节点, 该注解可以与**@JacksonXmlProperty一起使用,用于标记根节点下的不同路径。 属于基本与@JacksonXmlProperty**一致
@JacksonXmlElementWrapper
该节点可以标记结合,生成集合xml信息,在下面的实例中,会涉及到。
- localName: 上同
- useWrapping: 用于标记是否将集合元素进行包裹,在下面实例中有详细说明
XML处理集合数据
问题描述
当我在与银行数据进行处理的时候,需要批量向不同账户进行转账,则需要在报文中生成相同元素的节点,具体格式如下:
<DETAIL>
<ACCINFO>
<PAYACC>111111</PAYACC>
<RECACC>111112</PAYACC>
</ACCINFO>
<ACCINFO>
<PAYACC>2222</PAYACC>
<RECACC>22223</PAYACC>
</ACCINFO>
</DETAIL>
这里这是举个例子,比如我需要生成以上的结构的数据。接下来我们就配置对应的代码
业务实体
根据以上的例子,这里我们只需要配置一个Bean实例即可,具体代码如下:
@Data
@JacksonXmlRootElement(localName = "NODE")
public class PayDetail {
@JacksonXmlProperty(localName = "ACCINFO")
private List<AccInfo> accInfo;
@Data
public static class AccInfo {
@JacksonXmlProperty(localName = "PAYACC")
private String payAcc;
@JacksonXmlProperty(localName = "RECACC")
private String recAcc;
}
}
这里是我创建的业务实体,这里面我希望accInfo属性能够解析成为列表,我们写一个客户端测试一下,看看输出结果怎样:
测试程序
package com.jackson;
import com.jackson.entity.PayDetail;
import java.util.ArrayList;
import java.util.List;
public class XmlCollectionTest {
public static void main(String[] args) {
PayDetail payDetail = new PayDetail();
List<PayDetail.AccInfo> accInfos = new ArrayList<>();
accInfos.add(new PayDetail.AccInfo("111111", "111112"));
accInfos.add(new PayDetail.AccInfo("2222", "22223"));
payDetail.setAccInfo(accInfos);
System.out.println(XmlUtil.toXmlString(payDetail));
}
}
查看输出结果:
<NODE>
<ACCINFO>
<ACCINFO>
<PAYACC>111111</PAYACC>
<RECACC>111112</RECACC>
</ACCINFO>
<ACCINFO>
<PAYACC>2222</PAYACC>
<RECACC>22223</RECACC>
</ACCINFO>
</ACCINFO>
</NODE>
通过输出结果,我们发现一个问题,就是ACCINFO节点被输出了两次,这个其实不是我们所想要的,这时候就会用到上面针对集合的注解*@JacksonXmlElementWrapper*, 因此我们改造实体Bean
@JacksonXmlElementWrapper
- useWrapping:该属性主要用来控制结合的包裹,我们看下在不同值的情况下的不同效果。
useWrapping = true
实体Bean的代码如下:
package com.jackson.entity;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.List;
@Data
@JacksonXmlRootElement(localName = "NODE")
public class PayDetail {
@JacksonXmlElementWrapper(useWrapping = true, localName = "ACCINFO")
private List<AccInfo> accInfo;
@Data
@AllArgsConstructor
public static class AccInfo {
@JacksonXmlProperty(localName = "PAYACC")
private String payAcc;
@JacksonXmlProperty(localName = "RECACC")
private String recAcc;
}
}
输出结果如下:
<NODE>
<ACCINFO>
<accInfo>
<PAYACC>111111</PAYACC>
<RECACC>111112</RECACC>
</accInfo>
<accInfo>
<PAYACC>2222</PAYACC>
<RECACC>22223</RECACC>
</accInfo>
</ACCINFO>
</NODE>
通过以上输出结果可以得出一下结果:
- useWrapping = true : 表示了输出了结果的列表,并且需要将列表再用一个节点进行包裹
- localName: 只是指定了包裹的节点的名称
从上面的输出来看,和我们期望的结果相差还是很远,这种也不是我们所需要的这种格式
useWrapping = false
我们修改实体Bean代码如下:
package com.jackson.entity;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.List;
@Data
@JacksonXmlRootElement(localName = "NODE")
public class PayDetail {
@JacksonXmlElementWrapper(useWrapping = false, localName = "ACCINFO")
private List<AccInfo> accInfo;
@Data
@AllArgsConstructor
public static class AccInfo {
@JacksonXmlProperty(localName = "PAYACC")
private String payAcc;
@JacksonXmlProperty(localName = "RECACC")
private String recAcc;
}
}
输出结果如下:
<NODE>
<accInfo>
<PAYACC>111111</PAYACC>
<RECACC>111112</RECACC>
</accInfo>
<accInfo>
<PAYACC>2222</PAYACC>
<RECACC>22223</RECACC>
</accInfo>
</NODE>
通过输出结果可以得知,当useWrapping = false的时候,这个时候localName的配置其实是没有效果的,这个时候和我们期望的结果很相似,但是节点的名称不相符合。
其实到了这里和我们期望的结果集就已经很接近了,但是节点的名称无法更改,此时我们应该想到是否可以通过**@JacksonXmlProperty**注解的方式来改变节点的名称。
@JacksonXmlProperty
我们继续修改Bean实体的代码,这个时候通过该注解指定节点名称,看是否能够生效。具体代码如下:
package com.jackson.entity;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.List;
@Data
@JacksonXmlRootElement(localName = "NODE")
public class PayDetail {
@JacksonXmlElementWrapper(useWrapping = false)
@JacksonXmlProperty(localName = "ACCINFO")
private List<AccInfo> accInfo;
@Data
@AllArgsConstructor
public static class AccInfo {
@JacksonXmlProperty(localName = "PAYACC")
private String payAcc;
@JacksonXmlProperty(localName = "RECACC")
private String recAcc;
}
}
输出结果如下:
<NODE>
<ACCINFO>
<PAYACC>111111</PAYACC>
<RECACC>111112</RECACC>
</ACCINFO>
<ACCINFO>
<PAYACC>2222</PAYACC>
<RECACC>22223</RECACC>
</ACCINFO>
</NODE>
这个时候我们可以看到,这个时候我们预期结果一直
结论
当我们使用Jackson处理集合时,尤其是需要自定义xml格式的时候,这个时候每个注解不能单独的独立的来对待,对于这次遇到的这种情况,是需要—
- @JacksonXmlElementWrapper(useWrapping = false)
- @JacksonXmlProperty(localName = “ACCINFO”)
这两个注解的配合使用来解决。
常见问题
1. com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException该怎么解决?
该异常主要是因为xml中定义的属性比实体Bean的属性多时,就会出现这样的异常信息,因为有时候我们不需要关系所有的xml属性信息,这个时候我们可以将该异常过掉,主要在XmlMapper中配置,实例代码如下:
XML_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
以上就是Jackson在处理集合时候的相关基础知识,希望可以帮助到你。。