From 263811c1bc3829ceb6749cd68d65d9b98bc43749 Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 12 Dec 2025 16:59:02 +0800 Subject: [PATCH] add jsonpath enhance --- .../hutool/v7/core/collection/CollUtil.java | 48 +++++----- .../hutool/v7/core/collection/ListUtil.java | 64 +++++++------ .../cn/hutool/v7/core/pool/ObjectFactory.java | 2 +- .../java/cn/hutool/v7/json/JSONArray.java | 16 +++- .../v7/json/support/InternalJSONUtil.java | 53 ++++++++++ .../v7/json/support/JSONArrayCollector.java | 75 +++++++++++++++ .../v7/json/support/JSONNodeBeanFactory.java | 20 +++- .../java/cn/hutool/v7/json/JSONPathTest.java | 25 ++++- .../json/support/JSONArrayCollectorTest.java | 96 +++++++++++++++++++ 9 files changed, 338 insertions(+), 61 deletions(-) create mode 100644 hutool-json/src/main/java/cn/hutool/v7/json/support/JSONArrayCollector.java create mode 100644 hutool-json/src/test/java/cn/hutool/v7/json/support/JSONArrayCollectorTest.java diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/collection/CollUtil.java b/hutool-core/src/main/java/cn/hutool/v7/core/collection/CollUtil.java index 87b8758feb..255f449ca2 100644 --- a/hutool-core/src/main/java/cn/hutool/v7/core/collection/CollUtil.java +++ b/hutool-core/src/main/java/cn/hutool/v7/core/collection/CollUtil.java @@ -838,30 +838,30 @@ public class CollUtil { /** * 截取列表的部分 * - * @param 集合元素类型 - * @param list 被截取的数组 - * @param start 开始位置(包含) - * @param end 结束位置(不包含) - * @param step 步进 + * @param 集合元素类型 + * @param list 被截取的数组 + * @param startInclude 开始位置(包含) + * @param endExclude 结束位置(不包含) + * @param step 步进 * @return 截取后的数组,当开始位置超过最大时,返回空的List * @see ListUtil#sub(List, int, int, int) * @since 4.0.6 */ - public static List sub(final List list, final int start, final int end, final int step) { - return ListUtil.sub(list, start, end, step); + public static List sub(final List list, final int startInclude, final int endExclude, final int step) { + return ListUtil.sub(list, startInclude, endExclude, step); } /** * 截取集合的部分 * - * @param 集合元素类型 - * @param collection 被截取的数组 - * @param start 开始位置(包含) - * @param end 结束位置(不包含) + * @param 集合元素类型 + * @param collection 被截取的数组 + * @param startInclude 开始位置(包含) + * @param endExclude 结束位置(不包含) * @return 截取后的数组,当开始位置超过最大时,返回null */ - public static List sub(final Collection collection, final int start, final int end) { - return sub(collection, start, end, 1); + public static List sub(final Collection collection, final int startInclude, final int endExclude) { + return sub(collection, startInclude, endExclude, 1); } /** @@ -869,19 +869,19 @@ public class CollUtil { * * @param 集合元素类型 * @param collection 被截取的数组 - * @param start 开始位置(包含) - * @param end 结束位置(不包含) + * @param startInclude 开始位置(包含) + * @param endExclude 结束位置(不包含) * @param step 步进 * @return 截取后的数组,当开始位置超过最大时,返回空集合 * @since 4.0.6 */ - public static List sub(final Collection collection, final int start, final int end, final int step) { + public static List sub(final Collection collection, final int startInclude, final int endExclude, final int step) { if (isEmpty(collection)) { return ListUtil.empty(); } final List list = collection instanceof List ? (List) 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 第一个列表的元素类型 - * @param 第二个列表的元素类型 - * @param 结果列表的元素类型 - * @param collectionA 第一个列表 - * @param collectionB 第二个列表 - * @param zipper 合并函数,接收来自listA和listB的两个元素,返回一个结果元素 + * @param 第一个列表的元素类型 + * @param 第二个列表的元素类型 + * @param 结果列表的元素类型 + * @param collectionA 第一个列表 + * @param collectionB 第二个列表 + * @param zipper 合并函数,接收来自listA和listB的两个元素,返回一个结果元素 * @return 合并后的新列表 * @since 5.8.42 */ @@ -1419,7 +1419,7 @@ public class CollUtil { final Iterator aIterator = collectionA.iterator(); final Iterator bIterator = collectionB.iterator(); - while(size-- > 0) { + while (size-- > 0) { result.add(zipper.apply(aIterator.next(), bIterator.next())); } return result; diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/collection/ListUtil.java b/hutool-core/src/main/java/cn/hutool/v7/core/collection/ListUtil.java index 06f04a7df4..4b85850d68 100644 --- a/hutool-core/src/main/java/cn/hutool/v7/core/collection/ListUtil.java +++ b/hutool-core/src/main/java/cn/hutool/v7/core/collection/ListUtil.java @@ -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
* 提供的参数为null时返回空{@link ArrayList} * - * @param 集合元素类型 - * @param isLinked 是否新建LinkedList + * @param 集合元素类型 + * @param isLinked 是否新建LinkedList * @param enumeration {@link Enumeration} * @return ArrayList对象 * @since 3.0.8 @@ -238,12 +239,12 @@ public class ListUtil { /** * 获取一个只包含一个元素的List,不可变 * - * @param 元素类型 + * @param 元素类型 * @param element 元素 * @return 只包含一个元素的List * @since 6.0.0 */ - public static List singleton(final T element){ + public static List singleton(final T element) { return Collections.singletonList(element); } @@ -497,12 +498,12 @@ public class ListUtil { /** * 在指定位置设置元素。当index小于List的长度时,替换指定位置的值,否则追加{@code paddingElement}直到到达index后,设置值 * - * @param 元素类型 - * @param list List列表 - * @param index 位置 - * @param element 新元素 + * @param 元素类型 + * @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 { * 截取集合的部分
* 此方法与{@link List#subList(int, int)} 不同在于子列表是新的副本,操作子列表不会影响原列表。 * - * @param 集合元素类型 - * @param list 被截取的数组 - * @param begionInclude 开始位置(包含) - * @param endExclude 结束位置(不包含) - * @param step 步进 + * @param 集合元素类型 + * @param list 被截取的数组 + * @param startInclude 开始位置(包含) + * @param endExclude 结束位置(不包含) + * @param step 步进 * @return 截取后的数组,当开始位置超过最大时,返回空的List * @since 4.0.6 */ - public static List sub(final List list, int begionInclude, int endExclude, int step) { + @SuppressWarnings("unchecked") + public static List sub(final List list, int startInclude, int endExclude, int step) { if (list == null) { return null; } + List 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 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 List> partition(final List list, final int size) { + public static List> partition(final List list, final int size) { if (CollUtil.isEmpty(list)) { return empty(); } diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/pool/ObjectFactory.java b/hutool-core/src/main/java/cn/hutool/v7/core/pool/ObjectFactory.java index acf8468a5e..87a8a25ec4 100644 --- a/hutool-core/src/main/java/cn/hutool/v7/core/pool/ObjectFactory.java +++ b/hutool-core/src/main/java/cn/hutool/v7/core/pool/ObjectFactory.java @@ -18,7 +18,7 @@ package cn.hutool.v7.core.pool; /** * 对象工厂接口,用于自定义对象创建、验证和销毁
- * 来自:https://github.com/DanielYWoo/fast-object-pool/ + * 来自:
fast-object-pool * * @param 对象类型 * @author Daniel diff --git a/hutool-json/src/main/java/cn/hutool/v7/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/v7/json/JSONArray.java index 3f43214e9e..e835b8faf7 100644 --- a/hutool-json/src/main/java/cn/hutool/v7/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/v7/json/JSONArray.java @@ -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 implements JSON, JSONGetter implements JSON, JSONGetter implements JSON, JSONGetter) 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字符串,无缩进 * diff --git a/hutool-json/src/main/java/cn/hutool/v7/json/support/InternalJSONUtil.java b/hutool-json/src/main/java/cn/hutool/v7/json/support/InternalJSONUtil.java index 38f92e4f4c..1aaecee865 100644 --- a/hutool-json/src/main/java/cn/hutool/v7/json/support/InternalJSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/v7/json/support/InternalJSONUtil.java @@ -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 /** diff --git a/hutool-json/src/main/java/cn/hutool/v7/json/support/JSONArrayCollector.java b/hutool-json/src/main/java/cn/hutool/v7/json/support/JSONArrayCollector.java new file mode 100644 index 0000000000..cfd2af5701 --- /dev/null +++ b/hutool-json/src/main/java/cn/hutool/v7/json/support/JSONArrayCollector.java @@ -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 { + + /** + * 返回一个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 supplier() { + return this.factory::ofArray; + } + + @Override + public BiConsumer accumulator() { + return JSONArray::addValue; + } + + @Override + public BinaryOperator combiner() { + return (left, right) -> { + left.addAll(right); + return left; + }; + } + + @Override + public Function finisher() { + return Function.identity(); + } + + @Override + public Set characteristics() { + return Collections.unmodifiableSet(EnumSet.of( + Characteristics.IDENTITY_FINISH, + Characteristics.CONCURRENT + )); + } +} diff --git a/hutool-json/src/main/java/cn/hutool/v7/json/support/JSONNodeBeanFactory.java b/hutool-json/src/main/java/cn/hutool/v7/json/support/JSONNodeBeanFactory.java index b194d42ea2..bd2091876d 100644 --- a/hutool-json/src/main/java/cn/hutool/v7/json/support/JSONNodeBeanFactory.java +++ b/hutool-json/src/main/java/cn/hutool/v7/json/support/JSONNodeBeanFactory.java @@ -127,7 +127,23 @@ public class JSONNodeBeanFactory implements NodeBeanFactory { 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 { */ 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()); diff --git a/hutool-json/src/test/java/cn/hutool/v7/json/JSONPathTest.java b/hutool-json/src/test/java/cn/hutool/v7/json/JSONPathTest.java index 1f71d96263..7d3f61f008 100644 --- a/hutool-json/src/test/java/cn/hutool/v7/json/JSONPathTest.java +++ b/hutool-json/src/test/java/cn/hutool/v7/json/JSONPathTest.java @@ -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)); } } diff --git a/hutool-json/src/test/java/cn/hutool/v7/json/support/JSONArrayCollectorTest.java b/hutool-json/src/test/java/cn/hutool/v7/json/support/JSONArrayCollectorTest.java new file mode 100644 index 0000000000..8fdebe2a07 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/v7/json/support/JSONArrayCollectorTest.java @@ -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 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.emptyList().stream() + .collect(JSONArrayCollector.toJSONArray(factory)); + + assertTrue(result.isEmpty()); + } +}