You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2024/03/05 10:56:14 UTC

(camel) 01/01: CAMEL-20508: camel-jsonpath - Align how it converts to resultType like camel-jq

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

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

commit bff3c8238b941541ca65cf76ad5da1f22e197e7d
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Tue Mar 5 11:55:54 2024 +0100

    CAMEL-20508: camel-jsonpath - Align how it converts to resultType like camel-jq
---
 components/camel-jsonpath/pom.xml                  |   5 +
 .../apache/camel/jsonpath/JsonPathExpression.java  |  55 ++++++-----
 .../camel/jsonpath/JsonPathExpressionPojoTest.java | 105 +++++++++++++++++++++
 .../camel/jsonpath/JsonPathLanguageTest.java       |   6 +-
 .../ROOT/pages/camel-4x-upgrade-guide-4_5.adoc     |  17 ++++
 5 files changed, 163 insertions(+), 25 deletions(-)

diff --git a/components/camel-jsonpath/pom.xml b/components/camel-jsonpath/pom.xml
index fc7feb78e59..d080b26bc14 100644
--- a/components/camel-jsonpath/pom.xml
+++ b/components/camel-jsonpath/pom.xml
@@ -72,6 +72,11 @@
             <artifactId>camel-test-spring-junit5</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-jackson</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>org.apache.camel</groupId>
             <artifactId>camel-platform-http-vertx</artifactId>
diff --git a/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathExpression.java b/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathExpression.java
index cbd451d7c2c..0a94ccacf0a 100644
--- a/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathExpression.java
+++ b/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathExpression.java
@@ -19,6 +19,8 @@ package org.apache.camel.jsonpath;
 import java.util.Collection;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
 
 import com.jayway.jsonpath.Option;
 import org.apache.camel.CamelContext;
@@ -153,31 +155,40 @@ public class JsonPathExpression extends ExpressionAdapter {
     public Object evaluate(Exchange exchange) {
         try {
             Object result = evaluateJsonPath(exchange, engine);
-            if (resultType != null) {
-                if (unpackArray) {
-                    // in some cases we get a single element that is wrapped in a List, so unwrap that
-                    // if we for example want to grab the single entity and convert that to a int/boolean/String etc
-                    boolean resultTypeIsCollection = Collection.class.isAssignableFrom(resultType);
-                    boolean singleElement = result instanceof List && ((List<?>) result).size() == 1;
-                    if (singleElement && !resultTypeIsCollection) {
-                        result = ((List<?>) result).get(0);
-                        LOG.trace("Unwrapping result: {} from single element List before converting to: {}", result,
-                                resultType);
-                    }
-                } else {
-                    // special for List
-                    boolean resultTypeIsCollection = Collection.class.isAssignableFrom(resultType);
-                    boolean resultIsCollection = result instanceof List;
-                    if (resultTypeIsCollection && !resultIsCollection) {
-                        var list = new LinkedList<>();
-                        list.add(result);
-                        result = list;
-                    }
+            boolean resultTypeIsCollection = resultType != null && Collection.class.isAssignableFrom(resultType);
+            if (unpackArray) {
+                // in some cases we get a single element that is wrapped in a List, so unwrap that
+                // if we for example want to grab the single entity and convert that to an int/boolean/String etc
+                boolean singleElement = result instanceof List && ((List<?>) result).size() == 1;
+                if (singleElement && !resultTypeIsCollection) {
+                    result = ((List<?>) result).get(0);
+                    LOG.trace("Unwrapping result: {} from single element List before converting to: {}", result,
+                            resultType);
                 }
-                return exchange.getContext().getTypeConverter().convertTo(resultType, exchange, result);
-            } else {
+            }
+            if (resultType == null) {
                 return result;
             }
+            if (resultTypeIsCollection) {
+                // we want a list as output
+                boolean resultIsCollection = result instanceof List;
+                if (!resultIsCollection) {
+                    var list = new LinkedList<>();
+                    list.add(result);
+                    result = list;
+                }
+                return exchange.getContext().getTypeConverter().convertTo(resultType, exchange, result);
+            } else if (result instanceof Collection<?> col) {
+                // convert each element in the list
+                result = col.stream()
+                        .filter(Objects::nonNull) // skip null
+                        .map(item -> exchange.getContext().getTypeConverter().convertTo(resultType, exchange, item))
+                        .collect(Collectors.toList());
+            }
+            if (result instanceof Collection<?> col && col.size() == 1) {
+                result = col.stream().findFirst().get();
+            }
+            return exchange.getContext().getTypeConverter().convertTo(resultType, exchange, result);
         } catch (Exception e) {
             throw new ExpressionEvaluationException(this, exchange, e);
         }
diff --git a/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathExpressionPojoTest.java b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathExpressionPojoTest.java
new file mode 100644
index 00000000000..8848b65d6a6
--- /dev/null
+++ b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathExpressionPojoTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.Objects;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.jackson.JacksonConstants;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+public class JsonPathExpressionPojoTest extends CamelTestSupport {
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        CamelContext answer = super.createCamelContext();
+        answer.getGlobalOptions().put(JacksonConstants.ENABLE_TYPE_CONVERTER, "true");
+        answer.getGlobalOptions().put(JacksonConstants.TYPE_CONVERTER_TO_POJO, "true");
+        return answer;
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:start")
+                        .transform().jsonpath(".book", Book.class)
+                        .to("mock:result");
+            }
+        };
+    }
+
+    @Test
+    public void testExpression() throws Exception {
+        getMockEndpoint("mock:result").expectedBodiesReceived(new Book("foo", "bar"));
+
+        template.sendBody("direct:start", "{\"book\":{\"author\":\"foo\",\"title\":\"bar\"}}");
+
+        MockEndpoint.assertIsSatisfied(context);
+    }
+
+    public static class Book {
+        String author;
+        String title;
+
+        public Book() {
+        }
+
+        public Book(String author, String title) {
+            this.author = author;
+            this.title = title;
+        }
+
+        public String getAuthor() {
+            return author;
+        }
+
+        public void setAuthor(String author) {
+            this.author = author;
+        }
+
+        public String getTitle() {
+            return title;
+        }
+
+        public void setTitle(String title) {
+            this.title = title;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (!(o instanceof Book)) {
+                return false;
+            }
+            Book book = (Book) o;
+            return Objects.equals(getAuthor(), book.getAuthor()) && Objects.equals(getTitle(), book.getTitle());
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(getAuthor(), getTitle());
+        }
+    }
+
+}
diff --git a/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathLanguageTest.java b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathLanguageTest.java
index badc320399c..e0c582c2435 100644
--- a/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathLanguageTest.java
+++ b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathLanguageTest.java
@@ -145,7 +145,7 @@ public class JsonPathLanguageTest extends CamelTestSupport {
         JsonPathLanguage language = (JsonPathLanguage) context.resolveLanguage("jsonpath");
 
         Expression expression = language.createExpression("$.store.book",
-                new Object[] { String.class, null, null, null, null, null, true });
+                new Object[] { null, null, null, null, null, null, true, true });
         String json = expression.evaluate(exchange, String.class);
 
         // check that a single json object is returned, not an array
@@ -155,12 +155,12 @@ public class JsonPathLanguageTest extends CamelTestSupport {
     @Test
     public void testDontUnpackJsonArray() {
         Exchange exchange = new DefaultExchange(context);
-        exchange.getIn().setBody(new File("src/test/resources/expensive.json"));
+        exchange.getIn().setBody(new File("src/test/resources/books.json"));
 
         JsonPathLanguage language = (JsonPathLanguage) context.resolveLanguage("jsonpath");
 
         Expression expression = language.createExpression("$.store.book",
-                new Object[] { String.class, null, null, null, false });
+                new Object[] { null, null, null, null, false, true });
         String json = expression.evaluate(exchange, String.class);
 
         // check that an array is returned, not a single object
diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_5.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_5.adoc
index bcd6ff122b8..9229bcda797 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_5.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_5.adoc
@@ -117,6 +117,23 @@ moved into a new group `camel.debug` with more options to configure the backlog
 
 To enable backlog tracing you should now set `camel.trace.enabled=true` instead of `camel.main.backlogTracing=true`.
 
+=== camel-jsonpath
+
+The `camel-jsonpath` will now work more similar as `camel-jq` when you specify a `resultType` and have a list of entities.
+Before `camel-jsonapath` would attempt to convert the `List` to the given `restultType` which often is not useable. What
+users want is to be able to convert each entry in the list to a given type such as a POJO.
+
+For example the snippet below select all books from a JSon document, which will be in a `List<Map>` object where each
+book is an entry as a `Map`. Before Camel would attempt to convert `List` to `Book` which would not be possible.
+From this release onwards, Camel will convert each entry to a `Book` so the result is `List<Book>`.
+
+This is also how `camel-jq` works.
+
+[source,java]
+----
+.transform().jsonpath(".book", Book.class)
+----
+
 === camel-kamelet
 
 Routes created by Kamelets are no longer registered as JMX MBeans to avoid cluttering up with unwanted MBeans, as a Kamelet