当前位置: 首页 > 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 {
    // 测试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);
        }
    }
}

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

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

相关文章:

  • Exce格式化批处理工具详解:高效处理,让数据更干净!
  • C语言-查表法详解与实践
  • 网络游戏服务器如何构建全方位防御体系?DDoS与CC攻击实战防护指南
  • 鸿蒙开发_ARKTS快速入门_语法说明_自定义组件开发---纯血鸿蒙HarmonyOS5.0工作笔记011
  • 如何将/dev/ubuntu-vg/lv-data的空间扩展到/dev/ubuntu-vg/ubuntu-lv的空间上
  • 杂谈:模型训练参数是否存在临界点?
  • DNS服务(Linux)
  • 软考笔记9——数据库技术基础
  • python应用之使用pdfplumber 解析pdf文件内容
  • 第二篇:系统分析师——7-11章
  • 网站缓存怎么检查是否生效?
  • 常州 d??
  • 标题:PoRI:递归智能的工作量证明范式
  • NO.72十六届蓝桥杯备战|搜索算法-DFS|选数|飞机降落|八皇后|数独(C++)
  • 程序化广告行业(67/89):DMP系统标签制作与人群拓展深度解析
  • Wayland介绍
  • css画右上角 角标三角形
  • C++ 提高编程:模板与 STL 深度剖析
  • 交换机转发原理 和 DNS服务
  • C++(类模板的运用)
  • 门户网站建设调查问卷/网站优化方案
  • 做企业网站织梦和wordpress哪个好/现在如何进行网上推广
  • zencart 团购网站/seo关键词排名优化联系方式
  • 顺德网站建设基本流程/郑州网站推广公司咨询
  • 网上书城网站开发外文参考文献/免费刷粉网站推广免费
  • 网站选项按钮/哈尔滨百度搜索排名优化