[ 
https://issues.apache.org/jira/browse/CAMEL-24230?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18097944#comment-18097944
 ] 

Omar Atie edited comment on CAMEL-24230 at 7/21/26 7:00 PM:
------------------------------------------------------------

CAMEL-24230(https://issues.apache.org/jira/browse/CAMEL-24230) is a real bug in 
`camel-jsonpath`. The reporter's root-cause analysis is correct.
 # 
 ## The bug

With `writeAsString="true"`, a JSONPath that returns a *{*}single JSON 
object{*}* (e.g. `$.args` → `\{age: 30, name: "Alice"}`) should produce a 
*{*}JSON string{*}*:

```json

{"age":30,"name":"Alice"}

```

Instead you get a `java.util.Map` whose `toString()` looks like:

```

{age=30, name="Alice"}

```
 # 
 ## Root cause

In `JsonPathEngine.read()`, the `writeAsString` handling treats `Map` 
differently from everything else:

```155:172:components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathEngine.java
            } 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)

{                     return json;                 }

            }
```

Problems with this branch:

1. It *{*}returns the Map{*}*, not a `String`.
2. It only stringifies *{*}values{*}*, not the whole object.
3. Keys stay as Java map keys, so you get invalid/non-JSON output.

The `Iterable` branch (arrays) and the final `else` branch behave correctly — 
they call `adapter.writeAsString(o, exchange)` and return JSON strings. Maps 
are the outlier.
 # 
 ## The fix

Replace the `Map` branch with the same logic as the `else` branch — serialize 
the *{*}entire{*}* object:

```java
} else if (answer instanceof Map) {
    String json = adapter.writeAsString(answer, exchange);
    if (json != null)

{         return json;     }
}
```

Or simply *{*}remove the `Map` branch{*}* and let it fall through to the 
existing `else`:

```java
if (answer instanceof Iterable) \{     // ... existing list handling ... } else 
{
    String json = adapter.writeAsString(answer, exchange);
    if (json != null) \{         return json;     }

}
```

`JacksonJsonAdapter.writeAsString()` already handles `Map` correctly via 
`ObjectMapper.writeValueAsString(value)`.
 # 
 ## Regression to watch

There is one existing test that relies on the current (buggy) Map behavior:

`JsonPathSplitWriteAsStringMapTest` — splits on `$.content` where `content` is 
a *{*}Map{*}* (`content-map.json`), and expects each split message to be a 
`Map.Entry` with a stringified value.

After the fix, `writeAsString("$.content")` would return one JSON string for 
the whole `content` object, not a Map of stringified values.

*{*}Update that test{*}* to use a path that returns an *{*}array{*}*, which is 
the documented split pattern:

```java
.split().jsonpathWriteAsString("$.content.*")  // returns array of inner objects
```

That aligns with `JsonPathSplitWriteAsStringTest`, which already works 
correctly for array content.
 # 
 ## Tests to add

Add a test that reproduces the JIRA scenario:

```java
@Test
public void testWriteAsStringObjectExpression() throws Exception {
    String json = """
       

{           "args": \\{ "age": 30, "name": "Alice" }

        }
        """;

    MockEndpoint mock = getMockEndpoint("mock:result");
    mock.expectedMessageCount(1);
    mock.message(0).body().isInstanceOf(String.class);
    mock.message(0).body().isEqualTo("\{\"age\":30,\"name\":\"Alice\"}");

    template.sendBody("direct:start", json);
    MockEndpoint.assertIsSatisfied(context);
}
```

Route:

```java
from("direct:start")
    .setHeader("NewBody").jsonpath("$.args", true)  // writeAsString=true
    .to("mock:result");
```

Also test with XML DSL (`<jsonpath writeAsString="true">$.args</jsonpath>`) if 
you want parity with the reporter's case.
 # 
 ## Workaround (until fixed)

If you need this today without patching Camel:
 - Use *{*}`resultType` won't help{*}* — `TypeConverter` would still turn the 
Map into `Map.toString()`.
 - Use a *{*}JSONPath that returns an array{*}*, then take the first element:
  ```xml
  <jsonpath writeAsString="true">$.args.[*]</jsonpath>
  ```
  (depends on Jayway returning a list, which triggers the working `Iterable` 
branch.)
 - Or skip `writeAsString` and marshal with Jackson in a `.process()` / 
`marshal().json()`.

—

This is a small, focused fix — 

This answer is generated by cursor AI 


was (Author: JIRAUSER313980):
[CAMEL-24230](https://issues.apache.org/jira/browse/CAMEL-24230) is a real bug 
in `camel-jsonpath`. The reporter's root-cause analysis is correct.

## The bug

With `writeAsString="true"`, a JSONPath that returns a **single JSON object** 
(e.g. `$.args` → `\{age: 30, name: "Alice"}`) should produce a **JSON string**:

```json
{"age":30,"name":"Alice"}
```

Instead you get a `java.util.Map` whose `toString()` looks like:

```
{age=30, name="Alice"}
```

## Root cause

In `JsonPathEngine.read()`, the `writeAsString` handling treats `Map` 
differently from everything else:

```155:172:components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathEngine.java
            } 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) {
                    return json;
                }
            }
```

Problems with this branch:

1. It **returns the Map**, not a `String`.
2. It only stringifies **values**, not the whole object.
3. Keys stay as Java map keys, so you get invalid/non-JSON output.

The `Iterable` branch (arrays) and the final `else` branch behave correctly — 
they call `adapter.writeAsString(o, exchange)` and return JSON strings. Maps 
are the outlier.

## The fix

Replace the `Map` branch with the same logic as the `else` branch — serialize 
the **entire** object:

```java
} else if (answer instanceof Map) {
    String json = adapter.writeAsString(answer, exchange);
    if (json != null) {
        return json;
    }
}
```

Or simply **remove the `Map` branch** and let it fall through to the existing 
`else`:

```java
if (answer instanceof Iterable) {
    // ... existing list handling ...
} else {
    String json = adapter.writeAsString(answer, exchange);
    if (json != null) {
        return json;
    }
}
```

`JacksonJsonAdapter.writeAsString()` already handles `Map` correctly via 
`ObjectMapper.writeValueAsString(value)`.

## Regression to watch

There is one existing test that relies on the current (buggy) Map behavior:

`JsonPathSplitWriteAsStringMapTest` — splits on `$.content` where `content` is 
a **Map** (`content-map.json`), and expects each split message to be a 
`Map.Entry` with a stringified value.

After the fix, `writeAsString("$.content")` would return one JSON string for 
the whole `content` object, not a Map of stringified values.

**Update that test** to use a path that returns an **array**, which is the 
documented split pattern:

```java
.split().jsonpathWriteAsString("$.content.*")  // returns array of inner objects
```

That aligns with `JsonPathSplitWriteAsStringTest`, which already works 
correctly for array content.

## Tests to add

Add a test that reproduces the JIRA scenario:

```java
@Test
public void testWriteAsStringObjectExpression() throws Exception {
    String json = """
        {
          "args": \{ "age": 30, "name": "Alice" }
        }
        """;

    MockEndpoint mock = getMockEndpoint("mock:result");
    mock.expectedMessageCount(1);
    mock.message(0).body().isInstanceOf(String.class);
    mock.message(0).body().isEqualTo("\{\"age\":30,\"name\":\"Alice\"}");

    template.sendBody("direct:start", json);
    MockEndpoint.assertIsSatisfied(context);
}
```

Route:

```java
from("direct:start")
    .setHeader("NewBody").jsonpath("$.args", true)  // writeAsString=true
    .to("mock:result");
```

Also test with XML DSL (`<jsonpath writeAsString="true">$.args</jsonpath>`) if 
you want parity with the reporter's case.

## Workaround (until fixed)

If you need this today without patching Camel:

- Use **`resultType` won't help** — `TypeConverter` would still turn the Map 
into `Map.toString()`.
- Use a **JSONPath that returns an array**, then take the first element:
  ```xml
  <jsonpath writeAsString="true">$.args.[*]</jsonpath>
  ```
  (depends on Jayway returning a list, which triggers the working `Iterable` 
branch.)
- Or skip `writeAsString` and marshal with Jackson in a `.process()` / 
`marshal().json()`.

---

This is a small, focused fix — good candidate for a quick PR. 

> jsonpath language with writeAsString=true returns Map instead of JSON String 
> for object expressions
> ---------------------------------------------------------------------------------------------------
>
>                 Key: CAMEL-24230
>                 URL: https://issues.apache.org/jira/browse/CAMEL-24230
>             Project: Camel
>          Issue Type: Bug
>          Components: camel-jsonpath
>    Affects Versions: 4.21.0
>            Reporter: Raymond
>            Priority: Minor
>
> I have the following json:
> {code:java}
> {
>   "args": {
>     "age": 30, 
>     "name": "Alice"
>   }, 
>   "headers": {
>     "Accept": 
> "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
>  
>     "Accept-Encoding": "gzip, deflate, br, zstd", 
>     "Accept-Language": "nl-NL,nl;q=0.9,en-US;q=0.8,en;q=0.7", 
>     "Host": "httpbin.org", 
>     "Priority": "u=0, i", 
>     "Sec-Ch-Ua": "\"Google Chrome\";v=\"149\", \"Chromium\";v=\"149\", 
> \"Not)A;Brand\";v=\"24\"", 
>     "Sec-Ch-Ua-Mobile": "?0", 
>     "Sec-Ch-Ua-Platform": "\"Windows\"", 
>     "Sec-Fetch-Dest": "document", 
>     "Sec-Fetch-Mode": "navigate", 
>     "Sec-Fetch-Site": "none", 
>     "Sec-Fetch-User": "?1", 
>     "Upgrade-Insecure-Requests": "1", 
>     "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) 
> AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36", 
>     "X-Amzn-Trace-Id": "Root=1-6a2c1b7f-2c55fb0260804ec036ffb630"
>   }, 
>   "origin": "178.227.111.11", 
>   "url": "https://httpbin.org/get?name=Alice&age=30";
> }{code}
> And the following jsonpath:
>  
> {code:java}
> $.args 
> {code}
> The online jsonpath (https://jsonpath.com/) tester gives:
>  
> {code:java}
> [  {    "age": 30,    "name": "Alice"  }]{code}
> However when evaluating the same jsonpath in Apache Camel (with jsonpath on 
> the classpath):
> {code:java}
> <setHeaders>
>                 <setHeader name="NewBody">
>                     <jsonpath writeAsString="true">$.args</jsonpath>
>                 </setHeader>
>             </setHeaders> {code}
> I get:
> {{}}
> {code:java}
> {age=30, name="Alice"} {code}
> {{Thus the values are correct, but the keys not. }}When using the 
> {{jsonpath}} expression language with {{writeAsString="true"}} on a query 
> that evaluates to a single JSON Object (e.g., {{{}$.args{}}}), 
> {{JsonPathEngine}} returns a {{java.util.Map<Object, Object>}} where the 
> values are serialized, rather than serializing the entire Map into a valid 
> JSON string.
> *Summary*
> {{}}
> *Expected Behavior:* {{<jsonpath writeAsString="true">$.args</jsonpath>}} 
> should return a {{java.lang.String}} containing valid JSON (e.g., 
> {{{}{"age":30,"name":"Alice"}{}}}).
> {{}}
> *Actual Behavior:* It returns a {{java.util.Map}} whose {{.toString()}} 
> evaluates to {{{}{age=30, name="Alice"}{}}}.
> {{}}
> *Possible Root Cause:* In {{{}org.apache.camel.jsonpath.JsonPathEngine{}}}, 
> the {{answer instanceof Map}} branch iterates over entries and stringifies 
> {{{}entry.getValue(){}}}, but returns the {{Map}} instance itself instead of 
> passing the entire {{answer}} object through 
> {{{}adapter.writeAsString(answer, exchange){}}}.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to