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
工具类的主要功能包括:
- 获取普通属性值:如
user.name
,获取user
对象的name
属性值。 - 获取数组元素:如
users[0].name
,获取users
数组中第一个元素的name
属性值。 - 支持多层嵌套:如
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;
}
解析:
- 参数校验:确保
jsonString
和expression
不为空,否则抛出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);
}
解析:
- 空值检查:如果
object
或fieldName
为null
,直接返回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反射的理解。
感谢阅读!