1.1 背景
1.1.1 所谓的背景,也就是写这篇博客目的与原因是什么呢?
最近在工作中遇到了一个非常奇葩的需求,通过WebService的方式去调第三方接口,但第三方接口的返回值是一个Xml结构,本来到这里并没什么奇怪。但接下来意外发生了,这个Xml结构的字段名是中文,而且我所需的节点中的数据还不是个JSON结构。所以我需要先将Xml节点中的数据解析出来,然后使用split进行分割,转化成JSON结构,最终对JSON做自定义对象的映射。但有个问题,我需要将该字段中文与Java实体字段相匹配,存到数据库中。但由于字段太多,如果用枚举或者if等方式一个一个匹配太麻烦了,还不够简洁,就想着有没有一个注解可以解决这个问题?
首先我第一个想到的是MyBatis-Plus中,@TableField注解,但该注解主要用于实体类字段与数据库表字段的映射,与我的需求不符合(哈哈,因为毕业不久,我之前没遇到过这个问题,所以@JsonProperty("") 与@JSONField(name = "")这俩个注解我也是刚刚了解)。
接着通过查阅了解到@JsonProperty("") 与@JSONField(name = "")的使用,经过测试我最终选用的是@JsonProperty("")。
1.2 关于@JsonProperty("")
1.2.1 简介
@JsonProperty
是 Jackson 库中的一个注解,用于在反序列化 JSON 数据时,将 JSON 对象中的某个属性映射到 Java 类中的某个字段上。
@JsonProperty
的语法格式为 @JsonProperty("propertyName")
,其中 propertyName
是 JSON 对象中的属性名,也就是要映射到 Java 类中的字段名。
使用 @JsonProperty
注解可以确保 JSON 数据和 Java 类之间的字段名匹配,从而正确地进行反序列化。例如,如果 JSON 数据中有一个属性名为 "name",那么在 Java 类中应该有一个名为 "name" 的字段,并且使用 @JsonProperty("name")
注解进行标记,这样 Jackson 库就可以正确地将 JSON 数据中的 "name" 属性映射到 Java 类中的 "name" 字段上。
除了指定属性名之外,@JsonProperty
还支持一些其他的选项,例如可以设置字段的顺序、是否可序列化、是否可反序列化等。
总之,@JsonProperty
是 Jackson 库中用于反序列化 JSON 数据时进行字段映射的重要注解之一。
1.2.2 举个例子(@JsonProperty
不仅可以映射英文,还可以映射中文)
注:不要引错了依赖:import com.fasterxml.jackson.annotation.JsonProperty;
这里我就以映射中文举例了:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserBo implements Serializable {
@JsonProperty("姓名")
private String name;
@JsonProperty("电话")
private String phone;
@JsonProperty("城市")
private String city;
}
/**
* @Author ZhaoShuHao
* @Date 2023/11/11 15:09
*/
public class UserController {
public static void main(String[] args) throws IOException {
String user = "{\"姓名\":\"张三\",\"城市\":\"北京\",\"电话\":\"1212323\"}";
// UserBo userBo1 = JSONObject.parseObject(user, UserBo.class);
ObjectMapper objectMapper = new ObjectMapper();
UserBo userBo1 = objectMapper.readValue(user, UserBo.class);
System.out.println(userBo1);
}
}
1.3 关于@JSONField(name = "")
1.3.1 简介
@JSONField(name = "")
是 Jackson 库中的一个注解,用于在序列化或反序列化 JSON 数据时,将 Java 类中的某个字段映射到 JSON 对象中的某个属性上。
@JSONField
注解的语法格式为 @JSONField(name = "property_name")
,其中 name
是 JSON 对象中的属性名,也就是要映射到 Java 类中的字段名。
使用 @JSONField
注解可以确保 Java 类和 JSON 数据之间的字段名匹配,从而正确地进行序列化或反序列化。例如,如果 Java 类中有一个名为 "name" 的字段,那么在 JSON 数据中应该有一个属性名为 "property_name",并且使用 @JSONField(name = "property_name")
注解进行标记,这样 Jackson 库就可以正确地将 Java 类中的 "name" 字段映射到 JSON 数据中的 "property_name" 属性上。
除了指定属性名之外,@JSONField
还支持一些其他的选项,例如可以设置字段的顺序、是否可序列化、是否可反序列化等。
总之,@JSONField
是 Jackson 库中用于序列化或反序列化 JSON 数据时进行字段映射的重要注解之一。
1.3.2 举个例子
注:查阅资料,@JSONField也可以映射中英文,但我映射中文没有成功,暂时没有找到问题所在,这里就以映射英文举例了
import com.alibaba.fastjson.annotation.JSONField;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserBo implements Serializable {
@JSONField(name = "names")
// @JsonProperty("姓名")
private String name;
@JSONField(name = "phones")
// @JsonProperty("电话")
private String phone;
@JSONField(name = "citys")
// @JsonProperty("城市")
private String city;
}
/**
* @Author ZhaoShuHao
* @Date 2023/11/11 15:09
*/
public class UserController {
public static void main(String[] args) throws IOException {
// String user = "{\"姓名\":\"张三\",\"城市\":\"北京\",\"电话\":\"1212323\"}";
String user = "{\"names\":\"张三\",\"citys\":\"北京\",\"phones\":\"1212323\"}";
UserBo userBo1 = JSONObject.parseObject(user, UserBo.class);
/* ObjectMapper objectMapper = new ObjectMapper();
UserBo userBo1 = objectMapper.readValue(user, UserBo.class);*/
System.out.println(userBo1);
}
}
1.4 对于我所遇到的需求的解决方案
具体就不说了,直接步入正题。
1.4.1 结构
看看我的结构(不光是中文,结构还有些差异☺):
(1)
<Response>
<resultCode>0</resultCode>
<resultMessage>
病例数:10,
费用极高病例:0,
费用极低病例:0,
医疗总费用(元):507753.35,
DRG支付标准(元):457775.76,
盈亏额(元):-15109.75,
次均盈亏额(元):-1510.98,
DRG支付费用(元):182516.69,
CMI:1,
总权重:42.8062,
入组率:80.00%,
DRG组数:4,
盈余病组:6,
亏损病组:4,
平均住院日:12.3,
药品费(元):100457.29,
药占比:19.66%,
耗材费(元):164819.55,
耗占比:32.46%,
检查费(元):37136,
检查费占比:7.31%,
检验费(元):75670.5,
检验费占比:14.90%,
时间消耗指数:1.06,
费用消耗指数:1.11
</resultMessage>
</Response>
(2)这个更坑,key不仅是中文,还多了几个字,转化完Json,还要截取(刚开始没发现)
<Response>
<resultCode>0</resultCode>
<resultMessage>
近一月:入径人数:37,
完成人数:37,
出径人数:0,
出院人数:47,
变异人数:0
</resultMessage>
</Response>
1.4.2 解决方案
(1)先对xml进行解析,获取到resultMessage节点中的数据
(2)将节点中的数据拼接成一个字符串,去掉换行符
(3)因为解析出的数据不符合JSON格式,所以我们先用split进行分割,重新组装成Map结构
(4)将Map结构转化为Json字符串
(5)将Json字符串与自定义对象做映射
(6)因为好几个接口都需要解析xml,但映射的对象不同,返回类型不同,所以我这里用了泛型和反射
(7)又因为接口返回的xml中,数据的结构还有差异,所以我们还需一个类型,来判断是否需要做特殊处理
大功告成,代码如下:
package com.lc.ibps.medical.utils;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lc.ibps.medical.enums.BizDataMethodEnums;
import lombok.extern.slf4j.Slf4j;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;
/**
* Xml的解析工具类,根据泛型,动态返回不同的对象
* @Author ZhaoShuHao
* @Date 2023/11/10 15:52
*/
@Slf4j
public class ParseXmlUtils {
/**
* 解析xml为对应的对象
* @param clazz 自定义对象的类
* @param xmlString 需要解析的xml对象
* @param code 接口标识(因为个别接口返回的结构不一样,所以这里对特别的结构进行处理)
* @param <T> 自定义对象
* @return
* @throws Exception
*/
public static <T> T getData(Class<T> clazz ,String xmlString,String code) throws Exception {
//根据泛型,动态返回不同的对象
T obj = clazz.getDeclaredConstructor().newInstance();
// 创建一个 DocumentBuilderFactory 实例
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
// 创建 DocumentBuilder 实例
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
InputSource is = new InputSource(new StringReader(xmlString));
// 使用 parse 方法解析输入源
Document doc = dBuilder.parse(is);
// 检查解析是否成功
if (doc != null) {
log.info("解析输入源成功:"+doc);
// 获取所有 name 节点
NodeList nodes = doc.getElementsByTagName("resultMessage");
StringBuilder stringBuilder = new StringBuilder();
// 遍历所有的 name 节点
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
// 获取每个节点的文本内容
stringBuilder.append(node.getTextContent());
}
//因为获取到的信息不符合Json格式,所以不能直接转化为Josn,或者通过JSON转其他格式,对字符串进行处理
// 解析字符串为 Map
String oldStr = String.valueOf(stringBuilder);
String str = oldStr.replaceAll("\\n", "");
//TODO (等接口调通还需确认)因为,个人临床路径数据和个人电子病历数据返回的结构有点变化,所以这里进行处理,去掉多余字符
if(BizDataMethodEnums.MES0735.getCode().equals(code)||BizDataMethodEnums.MES0736.getCode().equals(code)){
str = str.substring(4);
}
log.info("xml转字符串:"+str);
Map<String, Object> map = new HashMap<>();
String[] pairs = str.split(",");
for (String pair : pairs) {
String[] keyValue = pair.split(":");
//解决value不存在的情况,默认给他设置为null
if(keyValue.length==1){
map.put(keyValue[0], null);
}else {
map.put(keyValue[0], keyValue[1]);
}
}
log.info("字符串转map:"+map);
// 将 Map 转换为 JSON 字符串
String jsonString = JSON.toJSONString(map);
log.info("map转Json:"+jsonString);
ObjectMapper objectMapper = new ObjectMapper();
//根据不同的类型返回不同的对象
obj = (T) objectMapper.readValue(jsonString, obj.getClass());
log.info("Json映射对象:"+obj);
}
return obj;
}
}
@ApiModel(value = "DRG质量数据对象")
public class DrgYearTbl extends AbstractPo<String>{
@ApiModelProperty(value = "主键")
@JsonProperty("主键")
protected String id; /*主键*/
@ApiModelProperty(value = "外键")
@JsonProperty("外键")
protected String parentId; /*外键*/
@ApiModelProperty(value = "租户ID")
@JsonProperty("租户ID")
protected String tenantId; /*租户ID*/
@ApiModelProperty(value = "IP地址")
@JsonProperty("IP地址")
protected String ip; /*IP地址*/
@ApiModelProperty(value = "数据删除状态")
@JsonProperty("数据删除状态")
protected String deleted; /*数据删除状态*/
@ApiModelProperty(value = "版本")
@JsonProperty("版本")
protected Long version; /*版本*/
@ApiModelProperty(value = "序号")
@JsonProperty("序号")
protected Long orderNo; /*序号*/
@ApiModelProperty(value = "流程状态")
@JsonProperty("流程状态")
protected String flowStatus; /*流程状态*/
@ApiModelProperty(value = "部门")
@JsonProperty("部门")
protected String sysDeptId; /*部门*/
@ApiModelProperty(value = "公司")
@JsonProperty("公司")
protected String sysOrgId; /*公司*/
@ApiModelProperty(value = "姓名")
@JsonProperty("姓名")
protected String name; /*姓名*/
@ApiModelProperty(value = "工号")
@JsonProperty("工号")
protected String number; /*工号*/
@ApiModelProperty(value = "年度")
@JsonProperty("年度")
protected String year; /*年度*/
@ApiModelProperty(value = "CMI")
@JsonProperty("CMI")
protected String cmi; /*CMI*/
@ApiModelProperty(value = "平均住院日")
@JsonProperty("平均住院日")
protected String pjzyr; /*平均住院日*/
@ApiModelProperty(value = "入组率")
@JsonProperty("入组率")
protected String rzl; /*入组率*/
@ApiModelProperty(value = "药占比")
@JsonProperty("药占比")
protected String yzb; /*药占比*/
@ApiModelProperty(value = "耗占比")
@JsonProperty("耗占比")
protected String hzb; /*耗占比*/
@ApiModelProperty(value = "次均费")
@JsonProperty("次均费")
protected String cjf; /*次均费*/
@ApiModelProperty(value = "检查占比")
@JsonProperty("检查占比")
protected String jczb; /*检查占比*/
@ApiModelProperty(value = "耗材费(元)")
@JsonProperty("耗材费(元)")
protected String hcf; /*耗材费(元)*/
@ApiModelProperty(value = "检查费(元)")
@JsonProperty("检查费(元)")
protected String jcf; /*检查费(元)*/
@ApiModelProperty(value = "检查费占比")
@JsonProperty("检查费占比")
protected String jcfzb; /*检查费占比*/
@ApiModelProperty(value = "检验费(元)")
@JsonProperty("检验费(元)")
protected String jyf; /*检验费(元)*/
@ApiModelProperty(value = "检验费占比")
@JsonProperty("检验费占比")
protected String jyfzb; /*检验费占比*/
@ApiModelProperty(value = "时间消耗指数")
@JsonProperty("时间消耗指数")
protected String sjxhzs; /*时间消耗指数*/
@ApiModelProperty(value = "费用消耗指数")
@JsonProperty("费用消耗指数")
protected String fyxhzs; /*费用消耗指数*/
@ApiModelProperty(value = "病例数")
@JsonProperty("病例数")
protected String bls; /*病例数*/
@ApiModelProperty(value = "费用极高病例")
@JsonProperty("费用极高病例")
protected String fyjgbls; /*费用极高病例*/
@ApiModelProperty(value = "费用极低病例")
@JsonProperty("费用极低病例")
protected String fyjdbls; /*费用极低病例*/
@ApiModelProperty(value = "医疗总费用(元)")
@JsonProperty("医疗总费用(元)")
protected String ylzfy; /*医疗总费用(元)*/
@ApiModelProperty(value = "DRG支付标准(元)")
@JsonProperty("DRG支付标准(元)")
protected String drgzfbz; /*DRG支付标准(元)*/
@ApiModelProperty(value = "盈亏额(元)")
@JsonProperty("盈亏额(元)")
protected String yke; /*盈亏额(元)*/
@ApiModelProperty(value = "次均盈亏额(元)")
@JsonProperty("次均盈亏额(元)")
protected String cjyke; /*次均盈亏额(元)*/
@ApiModelProperty(value = "DRG支付费用(元)")
@JsonProperty("DRG支付费用(元)")
protected String drgzffy; /*DRG支付费用(元)*/
@ApiModelProperty(value = "总权重")
@JsonProperty("总权重")
protected String zqz; /*总权重*/
@ApiModelProperty(value = "DRG组数")
@JsonProperty("DRG组数")
protected String drgzs; /*DRG组数*/
@ApiModelProperty(value = "盈余病组")
@JsonProperty("盈余病组")
protected String ykbz; /*盈余病组*/
@ApiModelProperty(value = "亏损病组")
@JsonProperty("亏损病组")
protected String ksbz; /*亏损病组*/
@ApiModelProperty(value = "药品费(元)")
@JsonProperty("药品费(元)")
protected String ypf; /*药品费(元)*/
@ApiModelProperty(value = "月度")
@JsonProperty("月度")
protected String month; /*月度*/
//省略get、set方法
}
/**调用该方法的代码,只粘贴有关的内容:**/
String result = "<Response>\n" +
" <resultCode>0</resultCode>\n" +
" <resultMessage>病例数:10,\n" +
"费用极高病例:0,\n" +
"费用极低病例:0,\n" +
"医疗总费用(元):507753.35,\n" +
"DRG支付标准(元):457775.76,\n" +
"盈亏额(元):-15109.75,\n" +
"次均盈亏额(元):-1510.98,\n" +
"DRG支付费用(元):182516.69,\n" +
"CMI:,\n" +
"总权重:42.8062,\n" +
"入组率:80.00%,\n" +
"DRG组数:4,\n" +
"盈余病组:6,\n" +
"亏损病组:4,\n" +
"平均住院日:12.3,\n" +
"药品费(元):100457.29,\n" +
"药占比:19.66%,\n" +
"耗材费(元):164819.55,\n" +
"耗占比:32.46%,\n" +
"检查费(元):37136,\n" +
"检查费占比:7.31%,\n" +
"检验费(元):75670.5,\n" +
"检验费占比:14.90%,\n" +
"时间消耗指数:1.06,\n" +
"费用消耗指数:1.11</resultMessage>\n" +
"</Response>";
DrgYearTbl drgYearTbl = new DrgYearTbl();
try {
drgYearTbl=ParseXmlUtils.getData(DrgYearTbl.class,result,BizDataMethodEnums.MES0734.getCode());
} catch (Exception e) {
log.error("xml转对象出现异常:"+e);
}
1.5 扩展(自定义工具类)
public class ObjectConvertUtil {
/**
* @param sourceClass 源对象
* @param targetClass 目的对象class
* */
public static <T,U> U objectConvert(T sourceClass, Class<U> targetClass){
String sourceStr= JSON.toJSONString(sourceClass);
return JSONObject.parseObject(sourceStr,targetClass);
}
}
想转什么对象,直接使用工具类去转就好了,省去很多麻烦,举个例子:
UserPo userpo = new UserPo(); User user = ObjectConvertUtil.objectConvert(userpo, User.class);DogVo dogvo= new DogVo();
Dog dog= ObjectConvertUtil.objectConvert(dogvo, Dog.class);