This is an automated email from the ASF dual-hosted git repository.
Jackie-Jiang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git
The following commit(s) were added to refs/heads/master by this push:
new ed2240ee74b Render non-String scalars in jsonPathString without JSON
quotes (#19029)
ed2240ee74b is described below
commit ed2240ee74bf073c0200366712805fac1c5e3da1
Author: Xiaotian (Jackie) Jiang <[email protected]>
AuthorDate: Tue Jul 21 10:58:19 2026 -0700
Render non-String scalars in jsonPathString without JSON quotes (#19029)
---
.../common/function/scalar/JsonFunctions.java | 35 ++++++++++++-------
.../pinot/common/function/JsonFunctionsTest.java | 40 ++++++++++++++++++++++
2 files changed, 63 insertions(+), 12 deletions(-)
diff --git
a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/JsonFunctions.java
b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/JsonFunctions.java
index d21129dd68a..ef9a2d455aa 100644
---
a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/JsonFunctions.java
+++
b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/JsonFunctions.java
@@ -31,10 +31,13 @@ import com.jayway.jsonpath.spi.cache.CacheProvider;
import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
import java.io.IOException;
+import java.time.LocalDate;
+import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
+import java.util.UUID;
import javax.annotation.Nullable;
import org.apache.commons.lang3.StringUtils;
import org.apache.pinot.common.function.FastJsonPathExtractor;
@@ -223,10 +226,7 @@ public class JsonFunctions {
public static String jsonPathString(Object object, String jsonPath)
throws JsonProcessingException {
Object jsonValue = jsonPath(object, jsonPath);
- if (jsonValue instanceof String) {
- return (String) jsonValue;
- }
- return jsonValue == null ? null : JsonUtils.objectToString(jsonValue);
+ return jsonValue != null ? jsonValueToString(jsonValue) : null;
}
/**
@@ -239,15 +239,29 @@ public class JsonFunctions {
}
try {
Object jsonValue = jsonPath(object, jsonPath);
- if (jsonValue instanceof String) {
- return (String) jsonValue;
- }
- return jsonValue == null ? defaultValue :
JsonUtils.objectToString(jsonValue);
+ return jsonValue != null ? jsonValueToString(jsonValue) : defaultValue;
} catch (Exception ignore) {
return defaultValue;
}
}
+ /// Renders a value resolved by a JSON path to its string form for the
`jsonPathString*` family. A `String`
+ /// leaf is returned verbatim (unquoted). [UUID] / [LocalDate] / [LocalTime]
are non-JSON-native scalars a
+ /// record extractor can materialize; [JsonUtils#objectToString] would wrap
them in JSON string quotes, so
+ /// they are rendered via their natural (canonical UUID / ISO-8601)
`toString` instead. Every other value -
+ /// `Number`, `Boolean`, `Timestamp` (epoch millis), `byte[]`, and `Map` /
`Collection` / array containers -
+ /// goes through [JsonUtils#objectToString], matching the
json-path-to-string behavior of `jsonExtractScalar`.
+ private static String jsonValueToString(Object jsonValue)
+ throws JsonProcessingException {
+ if (jsonValue instanceof String) {
+ return (String) jsonValue;
+ }
+ if (jsonValue instanceof UUID || jsonValue instanceof LocalDate ||
jsonValue instanceof LocalTime) {
+ return jsonValue.toString();
+ }
+ return JsonUtils.objectToString(jsonValue);
+ }
+
/// Opt-in fast variant of [#jsonPathString(Object, String, String)] with
identical results: a simple
/// linear path over a JSON string is resolved in a single forward pass
instead of building a full Jayway DOM
/// (see [FastJsonPathExtractor]), and complex paths / non-JSON input fall
back to Jayway. Faster; choose
@@ -274,10 +288,7 @@ public class JsonFunctions {
}
try {
Object jsonValue = fastJsonPath(object, jsonPath, false, earlyExit);
- if (jsonValue instanceof String) {
- return (String) jsonValue;
- }
- return jsonValue == null ? defaultValue :
JsonUtils.objectToString(jsonValue);
+ return jsonValue != null ? jsonValueToString(jsonValue) : defaultValue;
} catch (Exception ignore) {
return defaultValue;
}
diff --git
a/pinot-common/src/test/java/org/apache/pinot/common/function/JsonFunctionsTest.java
b/pinot-common/src/test/java/org/apache/pinot/common/function/JsonFunctionsTest.java
index 96d83b95119..8d47ba55f78 100644
---
a/pinot-common/src/test/java/org/apache/pinot/common/function/JsonFunctionsTest.java
+++
b/pinot-common/src/test/java/org/apache/pinot/common/function/JsonFunctionsTest.java
@@ -21,10 +21,16 @@ package org.apache.pinot.common.function;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.jayway.jsonpath.InvalidJsonException;
import java.io.IOException;
+import java.math.BigDecimal;
+import java.sql.Timestamp;
+import java.time.LocalDate;
+import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
import org.apache.pinot.common.function.scalar.JsonFunctions;
import org.apache.pinot.spi.utils.JsonUtils;
import org.testng.Assert;
@@ -455,6 +461,40 @@ public class JsonFunctionsTest {
assertEquals(value, expected);
}
+ @Test
+ public void testJsonPathStringOnExtractedValues()
+ throws JsonProcessingException {
+ // A value resolved from an already-parsed record tree (not a JSON string)
keeps its runtime Java type.
+ // UUID / LocalDate / LocalTime are non-JSON-native scalars a record
extractor can materialize; they render
+ // as their natural unquoted string, never wrapped in JSON string quotes.
+ UUID uuid = UUID.fromString("657ae8f8-b702-3cf4-9a05-300348c1623e");
+ assertEquals(JsonFunctions.jsonPathString(Map.of("v", uuid), "$.v"),
"657ae8f8-b702-3cf4-9a05-300348c1623e");
+ assertEquals(JsonFunctions.jsonPathString(Map.of("v", uuid), "$.v",
"default"),
+ "657ae8f8-b702-3cf4-9a05-300348c1623e");
+ assertEquals(JsonFunctions.jsonPathString(Map.of("v", LocalDate.of(2026,
7, 20)), "$.v"), "2026-07-20");
+ assertEquals(JsonFunctions.jsonPathString(Map.of("v", LocalTime.of(19, 54,
37)), "$.v"), "19:54:37");
+
+ // The fast-path variants share the same rendering helper and must produce
identical results.
+ assertEquals(JsonFunctions.jsonPathStringFast(Map.of("v", uuid), "$.v",
"default"),
+ "657ae8f8-b702-3cf4-9a05-300348c1623e");
+ assertEquals(JsonFunctions.jsonPathStringFirstMatch(Map.of("v",
LocalDate.of(2026, 7, 20)), "$.v", "default"),
+ "2026-07-20");
+
+ // Numbers, Boolean, and Map / List / Set containers follow the
json-path-to-string behavior of
+ // jsonExtractScalar (i.e. JsonUtils.objectToString), so a scalar number
is unquoted and a container is JSON.
+ assertEquals(JsonFunctions.jsonPathString(Map.of("v", 42), "$.v"), "42");
+ assertEquals(JsonFunctions.jsonPathString(Map.of("v", 1.5d), "$.v"),
"1.5");
+ assertEquals(JsonFunctions.jsonPathString(Map.of("v", new
BigDecimal("123.45")), "$.v"), "123.45");
+ assertEquals(JsonFunctions.jsonPathString(Map.of("v", true), "$.v"),
"true");
+ assertEquals(JsonFunctions.jsonPathString(Map.of("v", Map.of("k", "w")),
"$.v"), "{\"k\":\"w\"}");
+ assertEquals(JsonFunctions.jsonPathString(Map.of("v", List.of(1, 2, 3)),
"$.v"), "[1,2,3]");
+ assertEquals(JsonFunctions.jsonPathString(Map.of("v", Set.of("x")),
"$.v"), "[\"x\"]");
+
+ // Timestamp follows objectToString (portable epoch millis), not its
timezone-dependent JDBC toString form.
+ Timestamp timestamp = new Timestamp(1000L);
+ assertEquals(JsonFunctions.jsonPathString(Map.of("v", timestamp), "$.v"),
JsonUtils.objectToString(timestamp));
+ }
+
@DataProvider
public static Object[][] jsonPathArrayTestCases() {
return new Object[][]{
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]