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]

Reply via email to