问题描述
业务需要调用一个第三方图像识别的API,然后将识别出来的结果区分出不同类型,并分别装配到对应的Java实体类里
听起来是一个很正常的需求,现在JSON解析的工具有很多,而且都很成熟,但奈何识别出的JSON结果嵌套了很多复杂的结构,找不到现成的工具方法解决这个问题,JSON格式如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| { "words_result": [ { "result": { "AmountInWords": [ { "word": "贰佰圆整" } ], "InvoiceNumConfirm": [ { "word": "8xxxxx13" } ], "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,和实体类本身是父子关系)
问题解决
接口枚举类样例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| @Data public class VatInvoice implements Invoice {
private String invoiceNum; private String invoiceCode; private List<CommodityInfo> commodityList;
@InvoiceJsonField("AmountInFiguers") private String amount; private String amountInWords; }
@Data public class TicketInvoice implements Invoice {
@InvoiceJsonField("ticketNum") private String invoiceNum; private String invoiceName;
@InvoiceJsonField("AmountInFiguers") private String ticketAmount; private String amountInWords; }
@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() 方法构造目标对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
| 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{
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){ 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; }
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); }
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; } 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样例,进行解析测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class Main { public static void main(String[] args) { String testJson = "..........";
try { 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); } } }
|
测试打印结果,可以看到属性值都已经正确放到实体类中了
1
| 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=贰佰圆整)
|