This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 5aa6b98f7078 CAMEL-24230: Fix jsonpath writeAsString for object 
expressions
5aa6b98f7078 is described below

commit 5aa6b98f707805c27c5561f1a2a757952a6b4a0e
Author: Omar Atie <[email protected]>
AuthorDate: Wed Jul 22 01:58:25 2026 -0700

    CAMEL-24230: Fix jsonpath writeAsString for object expressions
    
    When writeAsString=true and a JsonPath expression evaluates to a single
    JSON object (Map), JsonPathEngine now serializes the entire object to a
    valid JSON String instead of returning a Map with stringified values.
    The old Map branch (from CAMEL-11558) was made obsolete when CAMEL-17101
    taught the split EIP to handle Maps. Adds engine and route-level tests,
    updates JsonPathSplitWriteAsStringMapTest to use the wildcard pattern,
    and adds an upgrade guide entry.
    
    Closes #24995
    
    Co-authored-by: Cursor Agent <[email protected]>
---
 components/camel-jsonpath/pom.xml                  |   5 +
 .../org/apache/camel/jsonpath/JsonPathEngine.java  |  12 --
 .../jsonpath/JsonPathEngineWriteAsStringTest.java  | 102 ++++++++++++++++
 .../JsonPathSplitWriteAsStringMapTest.java         |  16 +--
 .../jsonpath/JsonPathWriteAsStringObjectTest.java  | 136 +++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_22.adoc    |   8 ++
 6 files changed, 255 insertions(+), 24 deletions(-)

diff --git a/components/camel-jsonpath/pom.xml 
b/components/camel-jsonpath/pom.xml
index 81e4620614c3..2fd1c3c7b757 100644
--- a/components/camel-jsonpath/pom.xml
+++ b/components/camel-jsonpath/pom.xml
@@ -100,6 +100,11 @@
             <version>${hamcrest-version}</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
 </project>
diff --git 
a/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathEngine.java
 
b/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathEngine.java
index 603ef1192320..bcd9ac02ea72 100644
--- 
a/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathEngine.java
+++ 
b/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathEngine.java
@@ -152,18 +152,6 @@ public class JsonPathEngine {
                     }
                 }
                 return list;
-            } else if (answer instanceof Map) {
-                Map<Object, Object> map = (Map<Object, Object>) answer;
-                for (Map.Entry<Object, Object> entry : map.entrySet()) {
-                    Object value = entry.getValue();
-                    if (adapter != null) {
-                        String json = adapter.writeAsString(value, exchange);
-                        if (json != null) {
-                            map.put(entry.getKey(), json);
-                        }
-                    }
-                }
-                return map;
             } else {
                 String json = adapter.writeAsString(answer, exchange);
                 if (json != null) {
diff --git 
a/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathEngineWriteAsStringTest.java
 
b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathEngineWriteAsStringTest.java
new file mode 100644
index 000000000000..b78a0f5e46e4
--- /dev/null
+++ 
b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathEngineWriteAsStringTest.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.jsonpath;
+
+import java.util.List;
+import java.util.Map;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.camel.Exchange;
+import org.apache.camel.support.DefaultExchange;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Unit tests for {@link JsonPathEngine} {@code writeAsString} handling.
+ */
+public class JsonPathEngineWriteAsStringTest extends CamelTestSupport {
+
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
+    private static final String HTTPBIN_JSON = """
+            {
+              "args": {
+                "age": 30,
+                "name": "Alice"
+              },
+              "content": [
+                {"id": 1, "value": "first"},
+                {"id": 2, "value": "second"}
+              ]
+            }
+            """;
+
+    @Test
+    void writeAsStringSerializesObjectExpressionAsJsonString() throws 
Exception {
+        JsonPathEngine engine = new JsonPathEngine("$.args", null, true, 
false, true, null, context);
+
+        Exchange exchange = new DefaultExchange(context);
+        exchange.getIn().setBody(HTTPBIN_JSON);
+
+        Object result = engine.read(exchange);
+
+        assertThat(result).isInstanceOf(String.class);
+        assertThat(MAPPER.readTree((String) 
result)).isEqualTo(MAPPER.readTree("""
+                {"age":30,"name":"Alice"}
+                """));
+    }
+
+    @Test
+    void writeAsStringWithoutFlagReturnsMap() throws Exception {
+        JsonPathEngine engine = new JsonPathEngine("$.args", null, false, 
false, true, null, context);
+
+        Exchange exchange = new DefaultExchange(context);
+        exchange.getIn().setBody(HTTPBIN_JSON);
+
+        Object result = engine.read(exchange);
+
+        assertThat(result).isInstanceOf(Map.class);
+        @SuppressWarnings("unchecked")
+        Map<String, Object> map = (Map<String, Object>) result;
+        assertThat(map)
+                .containsEntry("age", 30)
+                .containsEntry("name", "Alice");
+    }
+
+    @Test
+    void writeAsStringSerializesEachArrayElement() throws Exception {
+        JsonPathEngine engine = new JsonPathEngine("$.content[*]", null, true, 
false, true, null, context);
+
+        Exchange exchange = new DefaultExchange(context);
+        exchange.getIn().setBody(HTTPBIN_JSON);
+
+        Object result = engine.read(exchange);
+
+        assertThat(result).isInstanceOf(List.class);
+        @SuppressWarnings("unchecked")
+        List<String> jsonRows = (List<String>) result;
+        assertThat(jsonRows).hasSize(2);
+        
assertThat(MAPPER.readTree(jsonRows.get(0))).isEqualTo(MAPPER.readTree("""
+                {"id":1,"value":"first"}
+                """));
+        
assertThat(MAPPER.readTree(jsonRows.get(1))).isEqualTo(MAPPER.readTree("""
+                {"id":2,"value":"second"}
+                """));
+    }
+}
diff --git 
a/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathSplitWriteAsStringMapTest.java
 
b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathSplitWriteAsStringMapTest.java
index de6c09d0665d..c8e0bcf9ff95 100644
--- 
a/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathSplitWriteAsStringMapTest.java
+++ 
b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathSplitWriteAsStringMapTest.java
@@ -17,15 +17,12 @@
 package org.apache.camel.jsonpath;
 
 import java.io.File;
-import java.util.Map;
 
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.component.mock.MockEndpoint;
 import org.apache.camel.test.junit6.CamelTestSupport;
 import org.junit.jupiter.api.Test;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
 public class JsonPathSplitWriteAsStringMapTest extends CamelTestSupport {
 
     @Override
@@ -34,7 +31,7 @@ public class JsonPathSplitWriteAsStringMapTest extends 
CamelTestSupport {
             @Override
             public void configure() {
                 from("direct:start")
-                        .split().jsonpathWriteAsString("$.content")
+                        .split().jsonpathWriteAsString("$.content.*")
                         .to("mock:line")
                         .to("log:line")
                         .end();
@@ -46,18 +43,13 @@ public class JsonPathSplitWriteAsStringMapTest extends 
CamelTestSupport {
     public void testSplitToJSon() throws Exception {
         MockEndpoint mock = getMockEndpoint("mock:line");
         mock.expectedMessageCount(2);
+        mock.allMessages().body().isInstanceOf(String.class);
+        
mock.message(0).body().isEqualTo("{\"action\":\"CU\",\"id\":123,\"modifiedTime\":\"2015-07-28T11:40:09.520+02:00\"}");
+        
mock.message(1).body().isEqualTo("{\"action\":\"CU\",\"id\":456,\"modifiedTime\":\"2015-07-28T11:42:29.510+02:00\"}");
 
         template.sendBody("direct:start", new 
File("src/test/resources/content-map.json"));
 
         MockEndpoint.assertIsSatisfied(context);
-
-        Map.Entry<?, ?> row = 
mock.getReceivedExchanges().get(0).getIn().getBody(Map.Entry.class);
-        assertEquals("foo", row.getKey());
-        
assertEquals("{\"action\":\"CU\",\"id\":123,\"modifiedTime\":\"2015-07-28T11:40:09.520+02:00\"}",
 row.getValue());
-
-        row = 
mock.getReceivedExchanges().get(1).getIn().getBody(Map.Entry.class);
-        assertEquals("bar", row.getKey());
-        
assertEquals("{\"action\":\"CU\",\"id\":456,\"modifiedTime\":\"2015-07-28T11:42:29.510+02:00\"}",
 row.getValue());
     }
 
 }
diff --git 
a/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathWriteAsStringObjectTest.java
 
b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathWriteAsStringObjectTest.java
new file mode 100644
index 000000000000..8fd1e22d502f
--- /dev/null
+++ 
b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathWriteAsStringObjectTest.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.jsonpath;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@code writeAsString=true} when the JsonPath expression evaluates 
to a single JSON object (Map).
+ */
+public class JsonPathWriteAsStringObjectTest extends CamelTestSupport {
+
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
+    private static final String HTTPBIN_JSON = """
+            {
+              "args": {
+                "age": 30,
+                "name": "Alice"
+              },
+              "headers": {
+                "Host": "httpbin.org",
+                "Accept": "application/json"
+              },
+              "origin": "178.227.111.11",
+              "url": "https://httpbin.org/get?name=Alice&age=30";
+            }
+            """;
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:header")
+                        .setHeader("NewBody").jsonpathWriteAsString("$.args")
+                        .to("mock:header");
+
+                from("direct:body")
+                        .setBody().jsonpathWriteAsString("$.args")
+                        .to("mock:body");
+
+                from("direct:nested")
+                        .setBody().jsonpathWriteAsString("$.headers")
+                        .to("mock:nested");
+            }
+        };
+    }
+
+    @Test
+    void writeAsStringOnObjectExpressionInHeader() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:header");
+        mock.expectedMessageCount(1);
+
+        template.sendBody("direct:header", HTTPBIN_JSON);
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        String header = 
mock.getReceivedExchanges().get(0).getIn().getHeader("NewBody", String.class);
+        assertThat(header).isNotNull();
+        assertJsonEquals(header, """
+                {"age":30,"name":"Alice"}
+                """);
+    }
+
+    @Test
+    void writeAsStringOnObjectExpressionInBody() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:body");
+        mock.expectedMessageCount(1);
+
+        template.sendBody("direct:body", HTTPBIN_JSON);
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        String body = 
mock.getReceivedExchanges().get(0).getIn().getBody(String.class);
+        assertThat(body).isNotNull();
+        assertJsonEquals(body, """
+                {"age":30,"name":"Alice"}
+                """);
+    }
+
+    @Test
+    void writeAsStringOnNestedObjectExpression() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:nested");
+        mock.expectedMessageCount(1);
+
+        template.sendBody("direct:nested", HTTPBIN_JSON);
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        String body = 
mock.getReceivedExchanges().get(0).getIn().getBody(String.class);
+        assertThat(body).isNotNull();
+        assertJsonEquals(body, """
+                {"Host":"httpbin.org","Accept":"application/json"}
+                """);
+    }
+
+    @Test
+    void writeAsStringDoesNotReturnMapToString() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:body");
+        mock.expectedMessageCount(1);
+
+        template.sendBody("direct:body", HTTPBIN_JSON);
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        String body = 
mock.getReceivedExchanges().get(0).getIn().getBody(String.class);
+        assertThat(body)
+                .startsWith("{")
+                .endsWith("}")
+                .doesNotContain("=");
+    }
+
+    private static void assertJsonEquals(String actual, String expected) 
throws Exception {
+        
assertThat(MAPPER.readTree(actual)).isEqualTo(MAPPER.readTree(expected));
+    }
+}
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
index 4461eb1f6f84..1d83c20e43a7 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
@@ -561,6 +561,14 @@ The `camel infra list` table now sizes its `DESCRIPTION` 
column to the terminal
 the `IMPLEMENTATION` and `SERVICE_DATA` columns with an ellipsis instead of 
letting the raw service
 data overflow the terminal. The complete, structured service data remains 
available via `--json`.
 
+=== camel-jsonpath
+
+The `writeAsString` option now correctly serializes single JSON object results 
(Maps) to a JSON String.
+Previously, a JsonPath expression evaluating to a JSON object (e.g. `$.args`) 
with `writeAsString=true`
+would return a `java.util.Map` with individually stringified values instead of 
a valid JSON String.
+If you were relying on the old behavior to split a Map result, change the 
expression to use a wildcard
+(e.g. `$.content.*` instead of `$.content`) to get a splittable list of JSON 
strings.
+
 === camel-aws2-kinesis - Fixed-shardId consumer no longer calls DescribeStream 
on every poll
 
 The Kinesis consumer with a configured `shardId` previously called the 
`DescribeStream` API on every

Reply via email to