add jsonpath enhance

This commit is contained in:
Looly
2025-12-12 16:59:02 +08:00
parent a7da8ec502
commit 263811c1bc
9 changed files with 338 additions and 61 deletions

View File

@@ -838,30 +838,30 @@ public class CollUtil {
/**
* 截取列表的部分
*
* @param <T> 集合元素类型
* @param list 被截取的数组
* @param start 开始位置(包含)
* @param end 结束位置(不包含)
* @param step 步进
* @param <T> 集合元素类型
* @param list 被截取的数组
* @param startInclude 开始位置(包含)
* @param endExclude 结束位置(不包含)
* @param step 步进
* @return 截取后的数组当开始位置超过最大时返回空的List
* @see ListUtil#sub(List, int, int, int)
* @since 4.0.6
*/
public static <T> List<T> sub(final List<T> list, final int start, final int end, final int step) {
return ListUtil.sub(list, start, end, step);
public static <T> List<T> sub(final List<T> list, final int startInclude, final int endExclude, final int step) {
return ListUtil.sub(list, startInclude, endExclude, step);
}
/**
* 截取集合的部分
*
* @param <T> 集合元素类型
* @param collection 被截取的数组
* @param start 开始位置(包含)
* @param end 结束位置(不包含)
* @param <T> 集合元素类型
* @param collection 被截取的数组
* @param startInclude 开始位置(包含)
* @param endExclude 结束位置(不包含)
* @return 截取后的数组当开始位置超过最大时返回null
*/
public static <T> List<T> sub(final Collection<T> collection, final int start, final int end) {
return sub(collection, start, end, 1);
public static <T> List<T> sub(final Collection<T> collection, final int startInclude, final int endExclude) {
return sub(collection, startInclude, endExclude, 1);
}
/**
@@ -869,19 +869,19 @@ public class CollUtil {
*
* @param <T> 集合元素类型
* @param collection 被截取的数组
* @param start 开始位置(包含)
* @param end 结束位置(不包含)
* @param startInclude 开始位置(包含)
* @param endExclude 结束位置(不包含)
* @param step 步进
* @return 截取后的数组,当开始位置超过最大时,返回空集合
* @since 4.0.6
*/
public static <T> List<T> sub(final Collection<T> collection, final int start, final int end, final int step) {
public static <T> List<T> sub(final Collection<T> collection, final int startInclude, final int endExclude, final int step) {
if (isEmpty(collection)) {
return ListUtil.empty();
}
final List<T> list = collection instanceof List ? (List<T>) collection : ListUtil.of(collection);
return sub(list, start, end, step);
return sub(list, startInclude, endExclude, step);
}
// endregion
@@ -1399,12 +1399,12 @@ public class CollUtil {
* 将两个列表的元素按照索引一一配对,通过指定的函数进行合并,返回一个新的结果列表。
* 新列表的长度将以两个输入列表中较短的那个为准。
*
* @param <A> 第一个列表的元素类型
* @param <B> 第二个列表的元素类型
* @param <R> 结果列表的元素类型
* @param collectionA 第一个列表
* @param collectionB 第二个列表
* @param zipper 合并函数接收来自listA和listB的两个元素返回一个结果元素
* @param <A> 第一个列表的元素类型
* @param <B> 第二个列表的元素类型
* @param <R> 结果列表的元素类型
* @param collectionA 第一个列表
* @param collectionB 第二个列表
* @param zipper 合并函数接收来自listA和listB的两个元素返回一个结果元素
* @return 合并后的新列表
* @since 5.8.42
*/
@@ -1419,7 +1419,7 @@ public class CollUtil {
final Iterator<A> aIterator = collectionA.iterator();
final Iterator<B> bIterator = collectionB.iterator();
while(size-- > 0) {
while (size-- > 0) {
result.add(zipper.apply(aIterator.next(), bIterator.next()));
}
return result;

View File

@@ -27,6 +27,7 @@ import cn.hutool.v7.core.comparator.PropertyComparator;
import cn.hutool.v7.core.lang.Assert;
import cn.hutool.v7.core.lang.Validator;
import cn.hutool.v7.core.lang.page.PageInfo;
import cn.hutool.v7.core.reflect.ConstructorUtil;
import cn.hutool.v7.core.util.ObjUtil;
import java.util.*;
@@ -114,8 +115,8 @@ public class ListUtil {
* 新建一个List<br>
* 提供的参数为null时返回空{@link ArrayList}
*
* @param <T> 集合元素类型
* @param isLinked 是否新建LinkedList
* @param <T> 集合元素类型
* @param isLinked 是否新建LinkedList
* @param enumeration {@link Enumeration}
* @return ArrayList对象
* @since 3.0.8
@@ -238,12 +239,12 @@ public class ListUtil {
/**
* 获取一个只包含一个元素的List不可变
*
* @param <T> 元素类型
* @param <T> 元素类型
* @param element 元素
* @return 只包含一个元素的List
* @since 6.0.0
*/
public static <T> List<T> singleton(final T element){
public static <T> List<T> singleton(final T element) {
return Collections.singletonList(element);
}
@@ -497,12 +498,12 @@ public class ListUtil {
/**
* 在指定位置设置元素。当index小于List的长度时替换指定位置的值否则追加{@code paddingElement}直到到达index后设置值
*
* @param <T> 元素类型
* @param list List列表
* @param index 位置
* @param element 新元素
* @param <T> 元素类型
* @param list List列表
* @param index 位置
* @param element 新元素
* @param paddingElement 填充的值
* @param indexLimit 最大索引限制
* @param indexLimit 最大索引限制
* @return 原List
* @since 5.8.28
*/
@@ -512,7 +513,7 @@ public class ListUtil {
if (index < size) {
list.set(index, element);
} else {
if(indexLimit > 0){
if (indexLimit > 0) {
// issue#3286, 增加安全检查
Validator.checkIndexLimit(index, indexLimit);
}
@@ -541,41 +542,47 @@ public class ListUtil {
* 截取集合的部分<br>
* 此方法与{@link List#subList(int, int)} 不同在于子列表是新的副本,操作子列表不会影响原列表。
*
* @param <T> 集合元素类型
* @param list 被截取的数组
* @param begionInclude 开始位置(包含)
* @param endExclude 结束位置(不包含)
* @param step 步进
* @param <T> 集合元素类型
* @param list 被截取的数组
* @param startInclude 开始位置(包含)
* @param endExclude 结束位置(不包含)
* @param step 步进
* @return 截取后的数组当开始位置超过最大时返回空的List
* @since 4.0.6
*/
public static <T> List<T> sub(final List<T> list, int begionInclude, int endExclude, int step) {
@SuppressWarnings("unchecked")
public static <T> List<T> sub(final List<T> list, int startInclude, int endExclude, int step) {
if (list == null) {
return null;
}
List<T> result = ConstructorUtil.newInstanceIfPossible(list.getClass());
if (null == result) {
result = new ArrayList<>(0);
}
if (list.isEmpty()) {
return new ArrayList<>(0);
return result;
}
final int size = list.size();
if (begionInclude < 0) {
begionInclude += size;
if (startInclude < 0) {
startInclude += size;
}
if (endExclude < 0) {
endExclude += size;
}
if (begionInclude == size) {
return new ArrayList<>(0);
if (startInclude == size) {
return result;
}
if (begionInclude > endExclude) {
final int tmp = begionInclude;
begionInclude = endExclude;
if (startInclude > endExclude) {
final int tmp = startInclude;
startInclude = endExclude;
endExclude = tmp;
}
if (endExclude > size) {
if (begionInclude >= size) {
return new ArrayList<>(0);
if (startInclude >= size) {
return result;
}
endExclude = size;
}
@@ -584,8 +591,7 @@ public class ListUtil {
step = 1;
}
final List<T> result = new ArrayList<>();
for (int i = begionInclude; i < endExclude; i += step) {
for (int i = startInclude; i < endExclude; i += step) {
result.add(list.get(i));
}
return result;
@@ -630,7 +636,7 @@ public class ListUtil {
* @return 分段列表
* @since 5.4.5
*/
public static <T> List<List<T>> partition(final List<T> list, final int size) {
public static <T> List<List<T>> partition(final List<T> list, final int size) {
if (CollUtil.isEmpty(list)) {
return empty();
}

View File

@@ -18,7 +18,7 @@ package cn.hutool.v7.core.pool;
/**
* 对象工厂接口,用于自定义对象创建、验证和销毁<br>
* 来自https://github.com/DanielYWoo/fast-object-pool/
* 来自:<a href="https://github.com/DanielYWoo/fast-object-pool/">fast-object-pool</a>
*
* @param <T> 对象类型
* @author Daniel

View File

@@ -22,6 +22,7 @@ import cn.hutool.v7.core.lang.Validator;
import cn.hutool.v7.core.lang.mutable.MutableEntry;
import cn.hutool.v7.json.serializer.impl.ArrayTypeAdapter;
import cn.hutool.v7.json.serializer.impl.IterTypeAdapter;
import cn.hutool.v7.json.support.InternalJSONUtil;
import cn.hutool.v7.json.writer.JSONWriter;
import java.io.Serial;
@@ -117,6 +118,7 @@ public class JSONArray extends ListWrapper<JSON> implements JSON, JSONGetter<Int
}
// region ----- addValue
/**
* 加入{@code null}元素如果设置中忽略null值则忽略
*
@@ -255,7 +257,7 @@ public class JSONArray extends ListWrapper<JSON> implements JSON, JSONGetter<Int
// 越界则追加到指定位置
final int size = size();
if(index == size){
if (index == size) {
add(element);
return null;
}
@@ -331,6 +333,18 @@ public class JSONArray extends ListWrapper<JSON> implements JSON, JSONGetter<Int
return (List<T>) IterTypeAdapter.INSTANCE.deserialize(this, ArrayList.class, elementType);
}
/**
* 截取子JSONArray不修改原JSONArray生成新的JSONArray
*
* @param startInclude 起始位置,包含此位置
* @param endExclude 结束位置,不包含此位置
* @param step 步长默认为1
* @return 截取后的JSONArray对象如果原始数组为空则返回一个空的JSONArray
*/
public JSONArray sub(final int startInclude, final int endExclude, final int step) {
return InternalJSONUtil.sub(this, startInclude, endExclude, step);
}
/**
* 转为JSON字符串无缩进
*

View File

@@ -25,6 +25,7 @@ import cn.hutool.v7.core.text.CharUtil;
import cn.hutool.v7.core.text.StrUtil;
import cn.hutool.v7.core.util.ObjUtil;
import cn.hutool.v7.json.JSON;
import cn.hutool.v7.json.JSONArray;
import cn.hutool.v7.json.JSONConfig;
import cn.hutool.v7.json.JSONFactory;
import cn.hutool.v7.json.serializer.JSONMapper;
@@ -206,6 +207,58 @@ public final class InternalJSONUtil {
return rawHashMap;
}
/**
* 根据给定的JSONArray截取子数组包含startInclude不包含endExclude
*
* @param jsonArray JSONArray 对象
* @param startInclude 起始位置,包含此位置
* @param endExclude 结束位置,不包含此位置
* @param step 步长默认为1
* @return 截取后的JSONArray对象如果原始数组为空则返回一个空的JSONArray
*/
public static JSONArray sub(final JSONArray jsonArray, int startInclude, int endExclude, int step) {
if (jsonArray == null) {
return null;
}
if (jsonArray.isEmpty()) {
return new JSONArray(0, jsonArray.getFactory());
}
final int size = jsonArray.size();
if (startInclude < 0) {
startInclude += size;
}
if (endExclude < 0) {
endExclude += size;
}
if (startInclude == size) {
return new JSONArray(0, jsonArray.getFactory());
}
if (startInclude > endExclude) {
final int tmp = startInclude;
startInclude = endExclude;
endExclude = tmp;
}
if (endExclude > size) {
if (startInclude >= size) {
return new JSONArray(0, jsonArray.getFactory());
}
endExclude = size;
}
if (step < 1) {
step = 1;
}
final int resultLength = (int) Math.ceil((endExclude - startInclude) / (double) step);
final JSONArray result = new JSONArray(resultLength, jsonArray.getFactory());
for (int i = startInclude; i < endExclude; i += step) {
result.add(jsonArray.get(i));
}
return result;
}
// --------------------------------------------------------------------------------------------- Private method start
/**

View File

@@ -0,0 +1,75 @@
package cn.hutool.v7.json.support;
import cn.hutool.v7.json.JSON;
import cn.hutool.v7.json.JSONArray;
import cn.hutool.v7.json.JSONFactory;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
/**
* JSONArray Collector
*
* @author Looly
* @since 7.0.0
*/
public class JSONArrayCollector implements Collector<JSON, JSONArray, JSONArray> {
/**
* 返回一个Collector它将输入元素收集到一个新的JSONArray中
*
* @param factory JSON 工厂
* @return {@link JSONArrayCollector}
*/
public static JSONArrayCollector toJSONArray(final JSONFactory factory) {
return new JSONArrayCollector(factory);
}
private final JSONFactory factory;
/**
* 构造
*
* @param factory JSON 工厂
*/
public JSONArrayCollector(final JSONFactory factory) {
this.factory = factory;
}
@Override
public Supplier<JSONArray> supplier() {
return this.factory::ofArray;
}
@Override
public BiConsumer<JSONArray, JSON> accumulator() {
return JSONArray::addValue;
}
@Override
public BinaryOperator<JSONArray> combiner() {
return (left, right) -> {
left.addAll(right);
return left;
};
}
@Override
public Function<JSONArray, JSONArray> finisher() {
return Function.identity();
}
@Override
public Set<Characteristics> characteristics() {
return Collections.unmodifiableSet(EnumSet.of(
Characteristics.IDENTITY_FINISH,
Characteristics.CONCURRENT
));
}
}

View File

@@ -127,7 +127,23 @@ public class JSONNodeBeanFactory implements NodeBeanFactory<JSON> {
if (bean instanceof JSONObject) {
return ((JSONObject) bean).get(name);
} else if (bean instanceof JSONArray) {
return ((JSONArray) bean).get(Integer.parseInt(name));
if("*".equals(name)){
// issue#IDC78B@Gitee 支持数组的*取值
return bean;
}
try{
final int index = Integer.parseInt(name);
// 数字返回对应的下标元素
return ((JSONArray) bean).get(index);
} catch(final NumberFormatException e){
// 非数字则认为是key返回JSONArray中每个元素key对应的value
return ((JSONArray) bean).stream().map(jsonEle -> {
if(jsonEle instanceof JSONObject){
return ((JSONObject) jsonEle).get(name);
}
throw new UnsupportedOperationException("Can not get by name for: " + jsonEle.getClass());
}).collect(JSONArrayCollector.toJSONArray(bean.getFactory()));
}
}
throw new UnsupportedOperationException("Can not get by name for: " + bean.getClass());
@@ -143,7 +159,7 @@ public class JSONNodeBeanFactory implements NodeBeanFactory<JSON> {
*/
private Object getValueByRangeNode(final JSON bean, final RangeNode node) {
if (bean instanceof JSONArray) {
return CollUtil.sub((JSONArray) bean, node.getStart(), node.getEnd(), node.getStep());
return ((JSONArray) bean).sub(node.getStart(), node.getEnd(), node.getStep());
}
throw new UnsupportedOperationException("Can not get range value for: " + bean.getClass());

View File

@@ -16,9 +16,11 @@
package cn.hutool.v7.json;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
/**
* JSON路径单元测试
*
@@ -31,9 +33,9 @@ public class JSONPathTest {
public void getByPathTest() {
final String json = "[{\"id\":\"1\",\"name\":\"xingming\"},{\"id\":\"2\",\"name\":\"mingzi\"}]";
Object value = JSONUtil.parseArray(json).getByPath("[0].name", Object.class);
Assertions.assertEquals("xingming", value);
assertEquals("xingming", value);
value = JSONUtil.parseArray(json).getByPath("[1].name", Object.class);
Assertions.assertEquals("mingzi", value);
assertEquals("mingzi", value);
}
@Test
@@ -41,6 +43,21 @@ public class JSONPathTest {
final String str = "{'accountId':111}";
final JSON json = JSONUtil.parse(str);
final Long accountId = JSONUtil.getByPath(json, "$.accountId", 0L);
Assertions.assertEquals(111L, accountId.longValue());
assertEquals(111L, accountId.longValue());
}
@Test
void issueIDC78BTest() {
final String json = "{\"actionMessage\":{\"alertResults\":[],\"decodeFeas\":[{\"body\":{\"lats\":[{\"begin\":4260,\"text\":\"呵呵\"},{\"begin\":4260,\"text\":\"你好 \"}]}}]}}";
final JSON json1 = JSONUtil.parse(json);
final JSON byPath = json1.getByPath("$.actionMessage.decodeFeas[0].body.lats[*]");
assertInstanceOf(JSONArray.class, byPath);
assertEquals(2, byPath.size());
final JSON byPath2 = json1.getByPath("$.actionMessage.decodeFeas[0].body.lats[*].text");
assertInstanceOf(JSONArray.class, byPath2);
assertEquals(2, byPath2.size());
assertEquals("呵呵", byPath2.asJSONArray().getStr(0));
assertEquals("你好 ", byPath2.asJSONArray().getStr(1));
}
}

View File

@@ -0,0 +1,96 @@
package cn.hutool.v7.json.support;
import cn.hutool.v7.json.*;
import org.junit.jupiter.api.Test;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collector;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.*;
class JSONArrayCollectorTest {
private final JSONFactory factory = JSONFactory.of(JSONConfig.of());
@Test
void testSupplier() {
final JSONArrayCollector collector = new JSONArrayCollector(factory);
final JSONArray array = collector.supplier().get();
assertTrue(array.isEmpty());
}
@Test
void testAccumulator() {
final JSONArrayCollector collector = new JSONArrayCollector(factory);
final JSONArray array = collector.supplier().get();
final JSONObject obj = factory.ofObj();
obj.putValue("key", "value");
collector.accumulator().accept(array, obj);
assertEquals(1, array.size());
assertEquals(obj, array.get(0));
}
@Test
void testCombiner() {
final JSONArrayCollector collector = new JSONArrayCollector(factory);
final JSONArray left = factory.ofArray();
final JSONArray right = factory.ofArray();
final JSONObject obj1 = factory.ofObj();
obj1.putValue("key1", "value1");
left.add(obj1);
final JSONObject obj2 = factory.ofObj();
obj2.putValue("key2", "value2");
right.add(obj2);
final JSONArray combined = collector.combiner().apply(left, right);
assertEquals(2, combined.size());
assertEquals(obj1, combined.get(0));
assertEquals(obj2, combined.get(1));
}
@Test
void testFinisher() {
final JSONArrayCollector collector = new JSONArrayCollector(factory);
final JSONArray array = factory.ofArray();
final JSONArray result = collector.finisher().apply(array);
assertSame(array, result);
}
@Test
void testCharacteristics() {
final JSONArrayCollector collector = new JSONArrayCollector(factory);
final Set<Collector.Characteristics> characteristics = collector.characteristics();
assertEquals(2, characteristics.size());
assertTrue(characteristics.contains(Collector.Characteristics.IDENTITY_FINISH));
assertTrue(characteristics.contains(Collector.Characteristics.CONCURRENT));
}
@Test
void testFullCollectionProcess() {
final JSONObject obj1 = factory.ofObj();
obj1.putValue("key1", "value1");
final JSONObject obj2 = factory.ofObj();
obj2.putValue("key2", "value2");
final JSONArray result = Stream.of(obj1, obj2)
.collect(JSONArrayCollector.toJSONArray(factory));
assertEquals(2, result.size());
assertEquals(obj1, result.get(0));
assertEquals(obj2, result.get(1));
}
@SuppressWarnings("RedundantOperationOnEmptyContainer")
@Test
void testEmptyCollection() {
final JSONArray result = Collections.<JSON>emptyList().stream()
.collect(JSONArrayCollector.toJSONArray(factory));
assertTrue(result.isEmpty());
}
}