当前位置: 首页 > news >正文

Java反射实战-特殊嵌套格式JSON自定义解析装配

问题描述

业务需要调用一个第三方图像识别的API,然后将识别出来的结果区分出不同类型,并分别装配到对应的Java实体类里

听起来是一个很正常的需求,现在JSON解析的工具有很多,而且都很成熟,但奈何识别出的JSON结果嵌套了很多复杂的结构,找不到现成的工具方法解决这个问题,JSON格式如下所示:

{"words_result": [{"result": {"AmountInWords": [{"word": "贰佰圆整"}],"InvoiceNumConfirm": [{"word": "8xxxxx13"}],// ================================省略n行// ****************************************"CommodityPrice": [{"row": "1","word": "7.1238475"}],"CommodityNum": [{"row": "1","word": "24.8447205"}],"CommodityTaxRate": [{"row": "1","word": "13%"}],"InvoiceCode": [{"word": "0xxxxx611"}],"AmountInFiguers": [{"word": "200.00"}],"CommodityAmount": [{"row": "1","word": "176.99"}],"CommodityType": [{"row": "1","word": "92号"}],"CommodityTax": [{"row": "1","word": "23.01"}],"CommodityUnit": [{"row": "1","word": "升"}],"CommodityName": [{"row": "1","word": "*汽油*92号车用汽油(VIB)"}],"InvoiceNum": [{"word": "8xxxxx3"}]},"top": 0,"left": 0,"probability": 0.9595113397,"width": 845,"type": "vat_invoice","height": 544}],"words_result_num": 1,"pdf_file_size": 1,"log_id": "1xxxxxxxxxxxx8"
}

因此我需要手动设计JSON解析的方法

问题分析

因为需要根据不同的识别结果种类,装配到不同的实体类中,并且可能会灵活增加或删除种类,所以需要设计一套易于拓展,方便开发的的方案

根据这样的业务场景,我决定使用工厂模式的策略模式来实现,创建一个总的接口类,子类都实现该接口类,并且使用Java的反射机制,解析实体类的属性,和JSON识别结果的参数名进行一一查找对应,然后可以使用注解来标注类属性名和参数名的映射关系

这样可以实现通用的,易于拓展的代码,需要改变参数值或增加减少识别种类,只需要改变实体类属性本身,不需要改变装配代码

(原JSON中参数名下带"row"字段的,需要额外放到实体类下货品类数组中,row代表第几行的index,和实体类本身是父子关系)

问题解决

接口枚举类样例
// 接口类
public interface Invoice {String getInvoiceNum();String getAmount();
}// 字段映射注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface InvoiceJsonField {String value();
}// 识别种类枚举
public enum InvoiceTypeEnum {VAT_INVOICE("vat_invoice"),TICKET_INVOICE("ticket_invoice");private String typeCode;InvoiceTypeEnum(String typeCode) {this.typeCode = typeCode;}public String getTypeCode() {return typeCode;}public void setTypeCode(String typeCode) {this.typeCode = typeCode;}
}
实体类样例
// 实体类种类1-实现接口Invoice
@Data
public class VatInvoice implements Invoice {private String invoiceNum;private String invoiceCode;private List<CommodityInfo> commodityList;  // 货品list// 属性名与参数名不一致,同注解写明映射关系@InvoiceJsonField("AmountInFiguers")private String amount;private String amountInWords;
}// 实体类种类2-实现接口Invoice
@Data
public class TicketInvoice implements Invoice {@InvoiceJsonField("ticketNum")private String invoiceNum;private String invoiceName;@InvoiceJsonField("AmountInFiguers")private String ticketAmount;private String amountInWords;
}// 货品类,JSON中带"row"字段的属性,row代表第几行的index
@Data
public class CommodityInfo implements Serializable {@InvoiceJsonField("commodityName")private String name; //货物名称@InvoiceJsonField("commodityType")private String type;//规格型号@InvoiceJsonField("commodityUnit")private String unit;//单位@InvoiceJsonField("commodityNum")private String num;//数量@InvoiceJsonField("commodityPrice")private String price;//单价@InvoiceJsonField("commodityAmount")private String amount;//金额@InvoiceJsonField("commodityTaxRate")private String taxRate;//税率@InvoiceJsonField("commodityTax")private String tax;//税额
}
工厂类

parseInvoice() 方法构造目标对象

public class InvoiceFactory {public static Invoice parseInvoice(JSONObject resObject) throws JsonProcessingException, IllegalAccessException {ObjectMapper objectMapper = new ObjectMapper();JsonNode rootNode = objectMapper.readTree(resObject.toString());String type = rootNode.get("words_result").get(0).get("type").asText();JsonNode wordsResultNode = rootNode.get("words_result").get(0).get("result");Invoice invoice;Boolean isCommodity = false;if (type.equals(InvoiceTypeEnum.VAT_INVOICE.getTypeCode())){invoice = new VatInvoice();isCommodity = true;}else if (type.equals(InvoiceTypeEnum.TICKET_INVOICE.getTypeCode())){invoice = new TicketInvoice();}else{//===加新的类只需要在这加,类实现Invoice接口,添加枚举种类,类属性名和接口返回值属性名一致,驼峰命名首字母小写============throw new RuntimeException();  // 可改为自定义异常抛错}return parseInvoice(invoice, wordsResultNode,isCommodity);}private static Invoice parseInvoice(Invoice invoice, JsonNode wordsResultNode, Boolean isCommodity) throws IllegalAccessException {List<CommodityInfo> infoList = new ArrayList<>();// 反射机制-解析类Class<?> clazz = invoice.getClass();Field[] fields = clazz.getDeclaredFields();Iterator<Map.Entry<String, JsonNode>> fieldsIterator = wordsResultNode.fields();while (fieldsIterator.hasNext()) {// 遍历参数与实体类Map.Entry<String, JsonNode> jsonField = fieldsIterator.next();String jsonKey = jsonField.getKey();JsonNode jsonValue = jsonField.getValue();String javaFieldName = toLowerCaseCamelCase(jsonKey);if (isCommodity){// 装配List<CommodityInfo>setCommodity(infoList,javaFieldName,jsonValue);}// 装配一般字段setFieldValueFor(fields, invoice, jsonValue, javaFieldName);}if (isCommodity){for (Field field : fields) {if (field.getName().equals("commodityList")){field.setAccessible(true);field.set(invoice,infoList);}}}return invoice;}// 装配List<CommodityInfo>private static void setCommodity(List<CommodityInfo> commodity,String javaFieldName,JsonNode jsonValue){JsonNode jsonNode = jsonValue.get(0);if(jsonNode != null && jsonNode.get("row") != null){Integer index = jsonNode.get("row").asInt() - 1;if (commodity.size()<index+1){CommodityInfo commodityInfo = new CommodityInfo();setFieldValueFor(CommodityInfo.class.getDeclaredFields(),commodityInfo,jsonNode,javaFieldName);commodity.add(index,commodityInfo);}else{setFieldValueFor(CommodityInfo.class.getDeclaredFields(),commodity.get(index),jsonNode,javaFieldName);}}}// 属性名驼峰转换private static String toLowerCaseCamelCase(String str) {if (str == null || str.isEmpty()) {return str;}StringBuilder result = new StringBuilder();boolean capitalizeNext = false;for (char c : str.toCharArray()) {if (c == '_') {capitalizeNext = true;} else {if (capitalizeNext) {result.append(Character.toUpperCase(c));capitalizeNext = false;} else {result.append(c);}}}str = result.toString();return Character.toLowerCase(str.charAt(0)) + str.substring(1);}// 反射机制给实体类set值private static void setFieldValueFor(Field[] fields, Object object, JsonNode jsonValue, String jsonFieldName) {for (Field field : fields) {InvoiceJsonField annotation = field.getAnnotation(InvoiceJsonField.class);// 如果有注解映射关系,优先寻找,没有则默认按属性名走boolean shouldMap = (annotation != null && toLowerCaseCamelCase(annotation.value()).equals(jsonFieldName)) ||(annotation == null && field.getName().equals(jsonFieldName));if (shouldMap) {try {// 设置字段值Object value = parseFieldValue(field.getType(), jsonValue);field.setAccessible(true);if (value.getClass().isAssignableFrom(String.class)){field.set(object, value);}else{field.set(object, ((TextNode)value).textValue());}} catch (IllegalAccessException e) {e.printStackTrace();}}}}// 设置字段值private static Object parseFieldValue(Class<?> fieldType, JsonNode jsonValue) {if (jsonValue == null || jsonValue.isNull()) {return null;}// 去掉word这一层级,手动放值if (fieldType.isArray()) {ArrayNode arrayNode = (ArrayNode) jsonValue;List<String> list = new ArrayList<>();for (JsonNode item : arrayNode) {list.add(String.valueOf(item.get("word").asText()));}return list.toArray(new String[0]);}if (fieldType.isAssignableFrom(String.class)) {if (jsonValue.get("word")!=null){return jsonValue.get("word").asText();}else{ArrayNode arrayNode = (ArrayNode) jsonValue;String builder = "";if (arrayNode.isEmpty()){return "";}else{builder = builder + arrayNode.get(0).get("word").asText();for (int i = 1; i < arrayNode.size(); i++) {builder = builder + ",";builder = builder + arrayNode.get(i).get("word").asText();}}return builder.toString();}}return null;}}

结果测试

对于问题描述中给出的识别JSON样例,进行解析测试

public class Main {// 测试demopublic static void main(String[] args) {String testJson = "..........";		// 省略jsontry {Invoice invoice = InvoiceFactory.parseInvoice(JSONObject.parseObject(testJson));System.out.println(invoice);} catch (JsonProcessingException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);}}
}

测试打印结果,可以看到属性值都已经正确放到实体类中了

VatInvoice(invoiceNum=8xxxxx3, invoiceCode=0xxxxx611, commodityList=[CommodityInfo(name=*汽油*92号车用汽油(VIB), type=92, unit=, num=24.8447205, price=7.1238475, amount=176.99, taxRate=13%, tax=23.01)], amount=200.00, amountInWords=贰佰圆整)

http://www.mrgr.cn/news/97527.html

相关文章:

  • 初阶C++笔记第一篇:C++基础语法
  • I²S协议概述与信号线说明
  • 10-MySQL-性能优化思路
  • 网络相关题目
  • LeetCode 热题 100_完全平方数(84_279_中等_C++)(动态规划(完全背包))
  • DMA 概念与讲解
  • 深度学习驱动的车牌识别:技术演进与未来挑战
  • 【c++深入系列】:类和对象详解(下)
  • 【团体程序涉及天梯赛】L1~L2实战反思合集(C++)
  • CmLicense授权损耗规避措施
  • Mysql专题篇章
  • Vue3 路由权限管理:基于角色的路由生成与访问控制
  • ES6 新增特性 箭头函数
  • Redis - 字典(Hash)结构和 rehash 机制
  • 使用LangChain Agents构建Gradio及Gradio Tools(5)——gradio_tools的端到端示例详解
  • 类和对象(下篇)(详解)
  • 蓝桥杯真题——前缀总分、遗迹
  • 【区块链安全 | 第三十四篇】合约审计之重入漏洞
  • 深入解析嵌入式Linux系统架构:从Bootloader到用户空间
  • OpenCv(七)——模板匹配、打包、图像的旋转