问题描述

业务需要调用一个第三方图像识别的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"
}
],
// ================================省略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,和实体类本身是父子关系)

问题解决

接口枚举类样例

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
// 实体类种类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() 方法构造目标对象

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{
//===加新的类只需要在这加,类实现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样例,进行解析测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main {
// 测试demo
public static void main(String[] args) {
String testJson = ".........."; // 省略json

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=贰佰圆整)