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