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

JSON路径工具类`JsonPathUtil`的实现与应用

JSON路径工具类JsonPathUtil的实现与应用

作者:zibo
日期:2024/11/25
口号:慢慢学,不要停。

文章目录

  • JSON路径工具类`JsonPathUtil`的实现与应用
    • 〇、完整代码
    • 一、引言
    • 二、功能概述
    • 三、代码实现详解
      • 1. 工具类基础结构
      • 2. 核心方法`getValue`
      • 3. 处理表达式片段`processPart`
      • 4. 处理数组类型的表达式片段`processArrayPart`
      • 5. 获取对象的字段值`getFieldValue`
      • 6. 测试主方法`main`
    • 四、应用示例
    • 五、总结
    • 六、后记

〇、完整代码

package com.kumy.requrchase.treasure.service.letusign.impl;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * JSON路径工具类
 * 用于根据表达式获取JSON字符串中的值
 * 支持以下功能:
 * 1. 获取普通属性值,如: user.name
 * 2. 获取数组元素,如: users[0].name
 * 3. 支持多层嵌套,如: company.department.employees[0].name
 * 
 * @author zibo
 * @date 2024/11/25
 * @slogan 慢慢学,不要停。
 */
public class JsonPathUtil {

    // 定义常量,提高代码可维护性
    private static final String DOT_SEPARATOR = "\\.";
    private static final String LEFT_BRACKET = "[";
    private static final String RIGHT_BRACKET = "]";
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    private JsonPathUtil() {
        throw new IllegalStateException("工具类不允许实例化");
    }

    /**
     * 根据表达式获取对象的值
     *
     * @param jsonString JSON字符串,不能为空
     * @param expression 表达式,不能为空
     * @return 表达式对应的值
     * @throws IllegalArgumentException 参数校验异常
     * @throws Exception 解析异常
     */
    public static Object getValue(String jsonString, String expression) throws Exception {
        // 参数校验
        if (Objects.isNull(jsonString) || Objects.isNull(expression)) {
            throw new IllegalArgumentException("参数不能为空");
        }

        // 将 JSON 字符串转换为 Map 对象
        Map<String, Object> rootObject = OBJECT_MAPPER.readValue(jsonString, new TypeReference<Map<String, Object>>() {});

        // 分割表达式并处理
        String[] parts = expression.split(DOT_SEPARATOR);
        Object currentObject = rootObject;

        for (String part : parts) {
            currentObject = processPart(currentObject, part);
            if (Objects.isNull(currentObject)) {
                return null;
            }
        }

        return currentObject;
    }

    /**
     * 处理表达式片段
     * 
     * @param currentObject 当前对象
     * @param part 表达式片段
     * @return 处理后的对象
     * @throws Exception 处理异常
     */
    private static Object processPart(Object currentObject, String part) throws Exception {
        if (part.contains(LEFT_BRACKET)) {
            return processArrayPart(currentObject, part);
        }
        return getFieldValue(currentObject, part);
    }

    /**
     * 处理数组类型的表达式片段
     * 
     * @param currentObject 当前对象
     * @param part 表达式片段
     * @return 处理后的对象
     * @throws Exception 处理异常
     */
    private static Object processArrayPart(Object currentObject, String part) throws Exception {
        String fieldName = part.substring(0, part.indexOf(LEFT_BRACKET));
        int index = Integer.parseInt(part.substring(part.indexOf(LEFT_BRACKET) + 1, part.indexOf(RIGHT_BRACKET)));
        Object arrayObject = getFieldValue(currentObject, fieldName);
        return arrayObject instanceof List ? ((List<?>) arrayObject).get(index) : null;
    }

    /**
     * 获取对象的字段值
     *
     * @param object 对象,不能为空
     * @param fieldName 字段名,不能为空
     * @return 字段值
     * @throws Exception 反射异常
     */
    @SuppressWarnings("all")
    private static Object getFieldValue(Object object, String fieldName) throws Exception {
        if (Objects.isNull(object) || Objects.isNull(fieldName)) {
            return null;
        }

        if (object instanceof Map) {
            return ((Map<?, ?>) object).get(fieldName);
        }
        
        Field field = object.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        return field.get(object);
    }

    public static void main(String[] args) {
        try {
            // 测试JSON字符串
            String jsonString = "{"
                    + "\"userInfo\": {"
                    + "\"id\": 1,"
                    + "\"photoPath\": \"yx.mm.com\","
                    + "\"realName\": \"张三\","
                    + "\"examInfoDict\": ["
                    + "{\"id\": 1, \"examType\": 0, \"answerIs\": 1},"
                    + "{\"id\": 2, \"examType\": 0, \"answerIs\": 0}"
                    + "]"
                    + "},"
                    + "\"flag\": 1"
                    + "}";

            // 测试不同场景
            System.out.println("测试普通属性: " + getValue(jsonString, "userInfo.realName")); // 输出张三
            System.out.println("测试数组访问: " + getValue(jsonString, "userInfo.examInfoDict[0].id")); // 输出1
            System.out.println("测试空值处理: " + getValue(jsonString, "userInfo.notExist")); // 输出null
            
        } catch (Exception e) {
            System.err.println("处理异常: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

一、引言

在日常的Java开发中,经常需要根据特定的路径或表达式,从JSON字符串中提取所需的数据。虽然市场上有诸如JsonPath等强大的工具可以实现这一需求,但有时候我们需要一个轻量级、可自定义的解决方案。本文将介绍一个自定义实现的JSON路径工具类JsonPathUtil,它可以根据表达式从JSON字符串中获取对应的值,支持获取普通属性、数组元素以及多层嵌套的属性值。

二、功能概述

JsonPathUtil工具类的主要功能包括:

  1. 获取普通属性值:如user.name,获取user对象的name属性值。
  2. 获取数组元素:如users[0].name,获取users数组中第一个元素的name属性值。
  3. 支持多层嵌套:如company.department.employees[0].name,获取嵌套结构中指定员工的姓名。

三、代码实现详解

1. 工具类基础结构

首先,定义了JsonPathUtil工具类,并声明了一些常量:

package com.kumy.requrchase.treasure.service.letusign.impl;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Objects;

public class JsonPathUtil {

    private static final String DOT_SEPARATOR = "\\.";
    private static final String LEFT_BRACKET = "[";
    private static final String RIGHT_BRACKET = "]";
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    private JsonPathUtil() {
        throw new IllegalStateException("工具类不允许实例化");
    }

    // 其他方法...
}

解析:

  • 使用了ObjectMapper来处理JSON字符串的解析。
  • 工具类的构造方法被私有化,防止实例化。

2. 核心方法getValue

getValue方法是工具类的核心,用于根据表达式从JSON字符串中获取对应的值。

public static Object getValue(String jsonString, String expression) throws Exception {
    // 参数校验
    if (Objects.isNull(jsonString) || Objects.isNull(expression)) {
        throw new IllegalArgumentException("参数不能为空");
    }

    // 将 JSON 字符串转换为 Map 对象
    Map<String, Object> rootObject = OBJECT_MAPPER.readValue(jsonString, new TypeReference<Map<String, Object>>() {});

    // 分割表达式并处理
    String[] parts = expression.split(DOT_SEPARATOR);
    Object currentObject = rootObject;

    for (String part : parts) {
        currentObject = processPart(currentObject, part);
        if (Objects.isNull(currentObject)) {
            return null;
        }
    }

    return currentObject;
}

解析:

  • 参数校验:确保jsonStringexpression不为空,否则抛出IllegalArgumentException
  • JSON解析:使用ObjectMapper将JSON字符串解析为Map<String, Object>类型的rootObject
  • 表达式解析:根据.分隔符,将表达式拆分为多个部分parts,然后逐一处理每个部分。
  • 逐层解析:通过循环,每次处理表达式的一部分,并不断更新currentObject,直到获取最终的值。

3. 处理表达式片段processPart

该方法用于处理表达式中的每一部分,判断是普通属性还是数组访问。

private static Object processPart(Object currentObject, String part) throws Exception {
    if (part.contains(LEFT_BRACKET)) {
        return processArrayPart(currentObject, part);
    }
    return getFieldValue(currentObject, part);
}

解析:

  • 如果表达式部分包含[,说明需要处理数组,调用processArrayPart方法。
  • 否则,直接调用getFieldValue获取属性值。

4. 处理数组类型的表达式片段processArrayPart

该方法用于解析数组元素的访问。

private static Object processArrayPart(Object currentObject, String part) throws Exception {
    String fieldName = part.substring(0, part.indexOf(LEFT_BRACKET));
    int index = Integer.parseInt(part.substring(part.indexOf(LEFT_BRACKET) + 1, part.indexOf(RIGHT_BRACKET)));
    Object arrayObject = getFieldValue(currentObject, fieldName);
    return arrayObject instanceof List ? ((List<?>) arrayObject).get(index) : null;
}

解析:

  • 获取字段名和索引:通过字符串操作,提取数组字段名fieldName和索引index
  • 获取数组对象:使用getFieldValue方法获取对应的数组对象arrayObject
  • 获取数组元素:检查arrayObject是否为List的实例,如果是,则返回对应索引的元素。

5. 获取对象的字段值getFieldValue

该方法用于获取当前对象中指定字段的值。

@SuppressWarnings("all")
private static Object getFieldValue(Object object, String fieldName) throws Exception {
    if (Objects.isNull(object) || Objects.isNull(fieldName)) {
        return null;
    }

    if (object instanceof Map) {
        return ((Map<?, ?>) object).get(fieldName);
    }
    
    Field field = object.getClass().getDeclaredField(fieldName);
    field.setAccessible(true);
    return field.get(object);
}

解析:

  • 空值检查:如果objectfieldNamenull,直接返回null
  • 处理Map类型:如果当前对象是Map,直接获取对应键的值。
  • 处理普通对象:使用反射获取对象的字段值,即使字段是私有的(通过setAccessible(true))。

6. 测试主方法main

编写了一个main方法用于测试工具类的功能。

public static void main(String[] args) {
    try {
        // 测试JSON字符串
        String jsonString = "{"
                + "\"userInfo\": {"
                + "\"id\": 1,"
                + "\"photoPath\": \"yx.mm.com\","
                + "\"realName\": \"张三\","
                + "\"examInfoDict\": ["
                + "{\"id\": 1, \"examType\": 0, \"answerIs\": 1},"
                + "{\"id\": 2, \"examType\": 0, \"answerIs\": 0}"
                + "]"
                + "},"
                + "\"flag\": 1"
                + "}";

        // 测试不同场景
        System.out.println("测试普通属性: " + getValue(jsonString, "userInfo.realName")); // 输出张三
        System.out.println("测试数组访问: " + getValue(jsonString, "userInfo.examInfoDict[0].id")); // 输出1
        System.out.println("测试空值处理: " + getValue(jsonString, "userInfo.notExist")); // 输出null
        
    } catch (Exception e) {
        System.err.println("处理异常: " + e.getMessage());
        e.printStackTrace();
    }
}

解析:

  • 构造了一个包含嵌套对象和数组的JSON字符串。
  • 测试了获取普通属性、数组元素以及处理不存在的属性的情况。
  • 输出结果验证了工具类的功能。

四、应用示例

为了更清晰地展示JsonPathUtil的应用,下面提供一个实际例子。

示例JSON字符串:

{
    "employee": {
        "name": "李华",
        "age": 30,
        "department": {
            "name": "研发部",
            "location": "北京"
        },
        "skills": [
            {"name": "Java", "level": "高级"},
            {"name": "Python", "level": "中级"}
        ]
    }
}

示例代码:

String jsonString = "{...}"; // 如上所示的JSON字符串

// 获取员工姓名
String name = (String) JsonPathUtil.getValue(jsonString, "employee.name");
System.out.println("员工姓名:" + name); // 输出:员工姓名:李华

// 获取部门名称
String departmentName = (String) JsonPathUtil.getValue(jsonString, "employee.department.name");
System.out.println("部门名称:" + departmentName); // 输出:部门名称:研发部

// 获取第一项技能的名称
String firstSkill = (String) JsonPathUtil.getValue(jsonString, "employee.skills[0].name");
System.out.println("第一项技能:" + firstSkill); // 输出:第一项技能:Java

// 尝试获取不存在的属性
Object nonExistent = JsonPathUtil.getValue(jsonString, "employee.address");
System.out.println("不存在的属性:" + nonExistent); // 输出:不存在的属性:null

解析:

  • 使用JsonPathUtil.getValue方法,根据不同的表达式,成功获取了嵌套对象和数组中的值。
  • 当尝试获取不存在的属性时,方法返回null,程序没有抛出异常,这体现了对异常情况的良好处理。

五、总结

本文详细介绍了JsonPathUtil工具类的实现原理和应用。通过逐步解析代码,我们了解到:

  • 如何解析复杂的JSON路径表达式,包括嵌套属性和数组元素。
  • 使用ObjectMapper将JSON字符串转换为可操作的Java对象。
  • 通过反射和类型检查,实现了对Map和普通Java对象的字段访问。

优点:

  • 轻量级:不依赖于第三方库,适合对JSON路径解析需求不复杂的场景。
  • 易于理解和扩展:代码简洁明了,方便根据需求进行定制。

不足:

  • 功能有限:不支持复杂的表达式,如过滤条件、通配符等。
  • 性能考虑:对于大规模的JSON数据和高并发场景,可能需要优化或选择性能更优的方案。

建议:

  • 对于简单的JSON解析需求,可以直接使用JsonPathUtil工具类。
  • 如果需要更高级的JSON路径功能,建议使用专业的JSON路径解析库,如Jayway的JsonPath。
    • JsonPath 开源地址:https://github.com/json-path/JsonPath
    • 在线语法检查:https://jsonpath.com/

六、后记

“慢慢学,不要停。”在编程的道路上,理解每一段代码背后的原理,都能让我们走得更远。希望通过本文的讲解,能帮助到有需要的读者,加深对JSON解析和Java反射的理解。


感谢阅读!


http://www.kler.cn/a/410876.html

相关文章:

  • VOLO实战:使用VOLO实现图像分类任务(二)
  • 20241125复盘日记
  • 研0找实习【学nlp】14--BERT理解
  • 【C++】static修饰的“静态成员函数“--静态成员在哪定义?静态成员函数的作用?
  • 桥梁、隧道、道路、铁路、结构、岩土,哪个发展更好?
  • 深入理解下oracle 11g block组成
  • 算法打卡 Day44(动态规划)-最后一块石头的重量 II+ 目标和 + 一和零
  • 【git】commit之后,想撤销commit
  • LVGL学习之按钮,开关部件(基于正点原子)
  • 嵌入式AI之rknn yolov5初探
  • 【Fargo】27:ffmpeg ffprobe 和python分析h264文件并绘制
  • D79【 python 接口自动化学习】- python基础之HTTP
  • 鸿蒙系统的架构与运行机制
  • 关于“内网可以访问21端口,通过防火墙映射后无法访问”的问题解决
  • lvgl学习复选框部件和进度条部件(基于正点原子)
  • Vue3 nextTick 使用教程
  • SQL 复杂查询
  • C++ Lambda 表达式
  • 【小白学机器学习34】用python进行基础的数据统计 mean,var,std,median,mode ,四分位数等
  • GitCode 平台设置访问令牌 从而git仓库(附pycharm创建版本控制项目)
  • 《UnityShader 入门精要》更复杂的光照
  • 力扣——寻找峰值
  • 智能合约运行原理
  • 实现可视化大屏的适配,并且解决缩放导致的事件偏移问题
  • 【源码】Sharding-JDBC源码分析之SQL中分片键路由ShardingSQLRouter的原理
  • pytorch torch.Tensor.item() 方法介绍