From de5fdd6382691c9622e3614a96ae8ac489cf168b Mon Sep 17 00:00:00 2001 From: lunascim Date: Tue, 2 Jul 2024 07:11:26 +0100 Subject: [PATCH 1/3] Implemented the possibility to add custom path functions --- .../function/PathFunctionFactory.java | 13 ++++++-- .../internal/function/CustomFunctionTest.java | 33 +++++++++++++++++++ .../internal/function/ToUpperCase.java | 14 ++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 json-path/src/test/java/com/jayway/jsonpath/internal/function/CustomFunctionTest.java create mode 100644 json-path/src/test/java/com/jayway/jsonpath/internal/function/ToUpperCase.java diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/PathFunctionFactory.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/PathFunctionFactory.java index 3a31151f2..c84a6c22b 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/function/PathFunctionFactory.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/PathFunctionFactory.java @@ -1,6 +1,7 @@ package com.jayway.jsonpath.internal.function; import com.jayway.jsonpath.InvalidPathException; +import com.jayway.jsonpath.internal.Path; import com.jayway.jsonpath.internal.function.json.Append; import com.jayway.jsonpath.internal.function.json.KeySetFunction; import com.jayway.jsonpath.internal.function.numeric.Average; @@ -14,6 +15,7 @@ import com.jayway.jsonpath.internal.function.text.Concatenate; import com.jayway.jsonpath.internal.function.text.Length; +import java.io.InvalidClassException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -28,7 +30,7 @@ */ public class PathFunctionFactory { - public static final Map FUNCTIONS; + private static final Map FUNCTIONS; static { // New functions should be added here and ensure the name is not overridden @@ -56,7 +58,7 @@ public class PathFunctionFactory { map.put("index", Index.class); - FUNCTIONS = Collections.unmodifiableMap(map); + FUNCTIONS = map; } /** @@ -85,4 +87,11 @@ public static PathFunction newFunction(String name) throws InvalidPathException } } } + + public static void addCustomFunction(String name,Class function) throws InvalidClassException { + if (!PathFunction.class.isAssignableFrom(function)){ + throw new InvalidClassException("Function with name: " + name + "must be a instance of PathFunction class"); + } + FUNCTIONS.put(name,function); + } } diff --git a/json-path/src/test/java/com/jayway/jsonpath/internal/function/CustomFunctionTest.java b/json-path/src/test/java/com/jayway/jsonpath/internal/function/CustomFunctionTest.java new file mode 100644 index 000000000..1d1dd1cc2 --- /dev/null +++ b/json-path/src/test/java/com/jayway/jsonpath/internal/function/CustomFunctionTest.java @@ -0,0 +1,33 @@ +package com.jayway.jsonpath.internal.function; + +import com.jayway.jsonpath.Configuration; +import com.jayway.jsonpath.Configurations; +import com.jayway.jsonpath.internal.EvaluationContext; +import com.jayway.jsonpath.internal.PathRef; +import org.junit.jupiter.api.Test; + +import java.io.InvalidClassException; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class CustomFunctionTest extends BaseFunctionTest { + + private Configuration conf = Configurations.JSON_SMART_CONFIGURATION; + + public class InvalidFunction { + + } + @Test + void testAddInvalidFunction(){ + assertThrows(InvalidClassException.class, () -> PathFunctionFactory.addCustomFunction("invalid", + InvalidFunction.class)); + } + + @Test + void testAddValidFunction(){ + assertDoesNotThrow(()->PathFunctionFactory.addCustomFunction("toUpperCase",ToUpperCase.class)); + verifyTextFunction(conf,"$['text'][0].toUpperCase()","A"); + } +} diff --git a/json-path/src/test/java/com/jayway/jsonpath/internal/function/ToUpperCase.java b/json-path/src/test/java/com/jayway/jsonpath/internal/function/ToUpperCase.java new file mode 100644 index 000000000..6a7c4c1ce --- /dev/null +++ b/json-path/src/test/java/com/jayway/jsonpath/internal/function/ToUpperCase.java @@ -0,0 +1,14 @@ +package com.jayway.jsonpath.internal.function; + +import com.jayway.jsonpath.internal.EvaluationContext; +import com.jayway.jsonpath.internal.PathRef; + +import java.util.List; + +public class ToUpperCase implements PathFunction{ + @Override + public Object invoke(String currentPath, PathRef parent, Object model, EvaluationContext ctx, + List parameters) { + return ((String) model).toUpperCase(); + } +} From 1782b8e0ba396e99c99d2fb2724693f661c87dc1 Mon Sep 17 00:00:00 2001 From: lunascim Date: Mon, 3 Feb 2025 16:15:33 +0000 Subject: [PATCH 2/3] Added the validation to avoid override existent function. --- .../internal/function/PathFunctionFactory.java | 5 +++-- .../internal/function/CustomFunctionTest.java | 12 ++++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/PathFunctionFactory.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/PathFunctionFactory.java index c84a6c22b..a9f7df5f1 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/function/PathFunctionFactory.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/PathFunctionFactory.java @@ -1,7 +1,6 @@ package com.jayway.jsonpath.internal.function; import com.jayway.jsonpath.InvalidPathException; -import com.jayway.jsonpath.internal.Path; import com.jayway.jsonpath.internal.function.json.Append; import com.jayway.jsonpath.internal.function.json.KeySetFunction; import com.jayway.jsonpath.internal.function.numeric.Average; @@ -16,7 +15,6 @@ import com.jayway.jsonpath.internal.function.text.Length; import java.io.InvalidClassException; -import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -89,6 +87,9 @@ public static PathFunction newFunction(String name) throws InvalidPathException } public static void addCustomFunction(String name,Class function) throws InvalidClassException { + if(FUNCTIONS.containsKey(name)){ + throw new InvalidPathException("Function with name: " + name + " already exists"); + } if (!PathFunction.class.isAssignableFrom(function)){ throw new InvalidClassException("Function with name: " + name + "must be a instance of PathFunction class"); } diff --git a/json-path/src/test/java/com/jayway/jsonpath/internal/function/CustomFunctionTest.java b/json-path/src/test/java/com/jayway/jsonpath/internal/function/CustomFunctionTest.java index 1d1dd1cc2..3c9593ca9 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/internal/function/CustomFunctionTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/internal/function/CustomFunctionTest.java @@ -2,17 +2,15 @@ import com.jayway.jsonpath.Configuration; import com.jayway.jsonpath.Configurations; -import com.jayway.jsonpath.internal.EvaluationContext; -import com.jayway.jsonpath.internal.PathRef; +import com.jayway.jsonpath.InvalidPathException; import org.junit.jupiter.api.Test; import java.io.InvalidClassException; -import java.util.List; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrows; -public class CustomFunctionTest extends BaseFunctionTest { +class CustomFunctionTest extends BaseFunctionTest { private Configuration conf = Configurations.JSON_SMART_CONFIGURATION; @@ -30,4 +28,10 @@ void testAddValidFunction(){ assertDoesNotThrow(()->PathFunctionFactory.addCustomFunction("toUpperCase",ToUpperCase.class)); verifyTextFunction(conf,"$['text'][0].toUpperCase()","A"); } + + @Test + void testAddExistentFunction(){ + assertThrows(InvalidPathException.class, () -> PathFunctionFactory.addCustomFunction("avg", + ToUpperCase.class)); + } } From bbace61ffdb7bfa11ae0a7db84778cb9d74643a9 Mon Sep 17 00:00:00 2001 From: movabo Date: Fri, 19 Dec 2025 10:05:16 +0100 Subject: [PATCH 3/3] Make custom path functions configurable --- README.md | 23 +++++ .../com/jayway/jsonpath/Configuration.java | 78 +++++++++++++-- .../java/com/jayway/jsonpath/JsonPath.java | 2 +- .../function/PassthruPathFunction.java | 1 + .../function/PathFunctionFactory.java | 98 ------------------- .../internal/function/json/Append.java | 2 +- .../function/json/KeySetFunction.java | 2 +- .../function/numeric/AbstractAggregation.java | 2 +- .../sequence/AbstractSequenceAggregation.java | 2 +- .../internal/function/text/Concatenate.java | 2 +- .../internal/function/text/Length.java | 2 +- .../internal/path/EvaluationContextImpl.java | 2 + .../internal/path/FunctionPathToken.java | 12 ++- .../jsonpath/internal/path/PathCompiler.java | 6 +- .../jsonpath/internal/path/PathToken.java | 2 +- .../DefaultPathFunctionProvider.java | 62 ++++++++++++ .../pathFunction}/PathFunction.java | 3 +- .../pathFunction/PathFunctionProvider.java | 22 +++++ .../jsonpath/CustomPathFunctionTest.java | 44 +++++++++ .../internal/function/CustomFunctionTest.java | 37 ------- .../internal/function/ToUpperCase.java | 14 --- 21 files changed, 243 insertions(+), 175 deletions(-) delete mode 100644 json-path/src/main/java/com/jayway/jsonpath/internal/function/PathFunctionFactory.java create mode 100644 json-path/src/main/java/com/jayway/jsonpath/spi/pathFunction/DefaultPathFunctionProvider.java rename json-path/src/main/java/com/jayway/jsonpath/{internal/function => spi/pathFunction}/PathFunction.java (92%) create mode 100644 json-path/src/main/java/com/jayway/jsonpath/spi/pathFunction/PathFunctionProvider.java create mode 100644 json-path/src/test/java/com/jayway/jsonpath/CustomPathFunctionTest.java delete mode 100644 json-path/src/test/java/com/jayway/jsonpath/internal/function/CustomFunctionTest.java delete mode 100644 json-path/src/test/java/com/jayway/jsonpath/internal/function/ToUpperCase.java diff --git a/README.md b/README.md index 0c1b32a6e..28ff3e53e 100644 --- a/README.md +++ b/README.md @@ -514,4 +514,27 @@ CacheProvider.setCache(new Cache() { }); ``` +### Path Function Provider SPI + +This SPI allows adding, overriding, and removing the path functions available in the JsonPath path. +Therefore, change the `PathFunctionProvider` of the `Configuration`. + +```java +String json = "..."; + +Configuration conf = Configuration.defaultConfiguration().pathFunctionProvider(new DefaultPathFunctionProvider() { + @Override + public PathFunction newFunction(String name) throws InvalidPathException { + if (name.equals("toUpperCase")) { + return (currentPath, parent, model, ctx, parameters) -> ((String) model).toUpperCase(); + } + + // Fall back to default functions + return super.newFunction(name); + } +}); + +JsonPath.using(conf).parse(json).read("$.store.book[0].author.toUpperCase()").toString(); // NIGEL REES +``` + [![Analytics](https://ga-beacon.appspot.com/UA-54945131-1/jsonpath/index)](https://github.com/igrigorik/ga-beacon) diff --git a/json-path/src/main/java/com/jayway/jsonpath/Configuration.java b/json-path/src/main/java/com/jayway/jsonpath/Configuration.java index 69427e1f0..949c9c03f 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/Configuration.java +++ b/json-path/src/main/java/com/jayway/jsonpath/Configuration.java @@ -15,8 +15,11 @@ package com.jayway.jsonpath; import com.jayway.jsonpath.internal.DefaultsImpl; +import com.jayway.jsonpath.spi.pathFunction.DefaultPathFunctionProvider; +import com.jayway.jsonpath.spi.pathFunction.PathFunction; import com.jayway.jsonpath.spi.json.JsonProvider; import com.jayway.jsonpath.spi.mapper.MappingProvider; +import com.jayway.jsonpath.spi.pathFunction.PathFunctionProvider; import java.util.*; @@ -50,16 +53,19 @@ private static Defaults getEffectiveDefaults(){ private final MappingProvider mappingProvider; private final Set