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 2022/10/19 16:19:17 UTC

[camel] branch main updated: [CAMEL-18612] Inconsistency in JsonPath component causes problems with databinding (#8571)

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 2f757c4b6a3 [CAMEL-18612] Inconsistency in JsonPath component causes problems with databinding (#8571)
2f757c4b6a3 is described below

commit 2f757c4b6a33978277ec9c426f2d6f8a844a9d45
Author: Radovan Netuka <rn...@redhat.com>
AuthorDate: Wed Oct 19 18:19:10 2022 +0200

    [CAMEL-18612] Inconsistency in JsonPath component causes problems with databinding (#8571)
---
 .../src/main/docs/jsonpath-language.adoc           | 13 +++++++++
 .../apache/camel/jsonpath/JsonPathExpression.java  | 28 ++++++++++++++-----
 .../apache/camel/jsonpath/JsonPathLanguage.java    | 19 +++++++++++--
 .../camel/jsonpath/JsonPathLanguageTest.java       | 32 ++++++++++++++++++++++
 .../org/apache/camel/builder/ExpressionClause.java | 12 ++++++++
 .../camel/builder/ExpressionClauseSupport.java     | 15 ++++++++++
 .../camel/model/language/JsonPathExpression.java   | 14 ++++++++++
 .../language/JsonPathExpressionReifier.java        |  7 +++--
 8 files changed, 128 insertions(+), 12 deletions(-)

diff --git a/components/camel-jsonpath/src/main/docs/jsonpath-language.adoc b/components/camel-jsonpath/src/main/docs/jsonpath-language.adoc
index 9243f364659..1e52cc68582 100644
--- a/components/camel-jsonpath/src/main/docs/jsonpath-language.adoc
+++ b/components/camel-jsonpath/src/main/docs/jsonpath-language.adoc
@@ -272,6 +272,19 @@ from("direct:start")
 
 Then each book is logged as a String JSON value.
 
+== Unpack a single-element array into an object
+
+It is possible to unpack a single-element array into an object:
+
+[source,java]
+----
+from("direct:start")
+    .setBody().jsonpathUnpack("$.store.book", Book.class)
+    .to("log:book");
+----
+
+If book array contains only one book, it will be converted into a Book object.
+
 == Using header as input
 
 By default, JSONPath uses the message body as the input source. However, you can also use a header as input
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 c2cb7df322a..cb595abccc4 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
@@ -42,6 +42,7 @@ public class JsonPathExpression extends ExpressionAdapter {
     private boolean allowSimple = true;
     private boolean allowEasyPredicate = true;
     private boolean writeAsString;
+    private boolean unpackArray;
     private String headerName;
     private Option[] options;
 
@@ -116,6 +117,17 @@ public class JsonPathExpression extends ExpressionAdapter {
         this.writeAsString = writeAsString;
     }
 
+    public boolean isUnpackArray() {
+        return unpackArray;
+    }
+
+    /**
+     * Whether to unpack a single element json-array into an object.
+     */
+    public void setUnpackArray(boolean unpackArray) {
+        this.unpackArray = unpackArray;
+    }
+
     public String getHeaderName() {
         return headerName;
     }
@@ -143,13 +155,15 @@ public class JsonPathExpression extends ExpressionAdapter {
         try {
             Object result = evaluateJsonPath(exchange, engine);
             if (resultType != null) {
-                // 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 resultIsCollection = Collection.class.isAssignableFrom(resultType);
-                boolean singleElement = result instanceof List && ((List) result).size() == 1;
-                if (singleElement && !resultIsCollection) {
-                    result = ((List) result).get(0);
-                    LOG.trace("Unwrapping result: {} from single element List before converting to: {}", result, 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 a int/boolean/String etc
+                    boolean resultIsCollection = Collection.class.isAssignableFrom(resultType);
+                    boolean singleElement = result instanceof List && ((List) result).size() == 1;
+                    if (singleElement && !resultIsCollection) {
+                        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 {
diff --git a/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathLanguage.java b/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathLanguage.java
index 5b79903c03e..650fd912ef6 100644
--- a/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathLanguage.java
+++ b/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathLanguage.java
@@ -38,6 +38,7 @@ public class JsonPathLanguage extends LanguageSupport implements PropertyConfigu
     private boolean allowSimple = true;
     private boolean allowEasyPredicate = true;
     private boolean writeAsString;
+    private boolean unpackArray;
     private String headerName;
     private Option[] options;
 
@@ -81,6 +82,14 @@ public class JsonPathLanguage extends LanguageSupport implements PropertyConfigu
         this.writeAsString = writeAsString;
     }
 
+    public boolean isUnpackArray() {
+        return unpackArray;
+    }
+
+    public void setUnpackArray(boolean unpackArray) {
+        this.unpackArray = unpackArray;
+    }
+
     public String getHeaderName() {
         return headerName;
     }
@@ -113,6 +122,7 @@ public class JsonPathLanguage extends LanguageSupport implements PropertyConfigu
         answer.setAllowEasyPredicate(allowEasyPredicate);
         answer.setHeaderName(headerName);
         answer.setWriteAsString(writeAsString);
+        answer.setUnpackArray(unpackArray);
         answer.setHeaderName(headerName);
         answer.setOptions(options);
         answer.init(getCamelContext());
@@ -134,8 +144,9 @@ public class JsonPathLanguage extends LanguageSupport implements PropertyConfigu
         answer.setAllowSimple(property(boolean.class, properties, 2, allowSimple));
         answer.setAllowEasyPredicate(property(boolean.class, properties, 3, allowEasyPredicate));
         answer.setWriteAsString(property(boolean.class, properties, 4, writeAsString));
-        answer.setHeaderName(property(String.class, properties, 5, headerName));
-        String option = (String) properties[6];
+        answer.setUnpackArray(property(boolean.class, properties, 5, unpackArray));
+        answer.setHeaderName(property(String.class, properties, 6, headerName));
+        String option = (String) properties[7];
         if (option != null) {
             List<Option> list = new ArrayList<>();
             for (String s : option.split(",")) {
@@ -192,6 +203,10 @@ public class JsonPathLanguage extends LanguageSupport implements PropertyConfigu
             case "writeAsString":
                 setWriteAsString(PropertyConfigurerSupport.property(camelContext, boolean.class, value));
                 return true;
+            case "unpackarray":
+            case "unpackArray":
+                setUnpackArray(PropertyConfigurerSupport.property(camelContext, boolean.class, value));
+                return true;
             case "options":
                 setOptions(PropertyConfigurerSupport.property(camelContext, Option[].class, value));
                 return true;
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 8b9f068f64f..37cae41c8fa 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
@@ -137,4 +137,36 @@ public class JsonPathLanguageTest extends CamelTestSupport {
         assertNull(nofoo);
     }
 
+    @Test
+    public void testUnpackJsonArray() {
+        Exchange exchange = new DefaultExchange(context);
+        exchange.getIn().setBody(new File("src/test/resources/expensive.json"));
+
+        JsonPathLanguage language = (JsonPathLanguage) context.resolveLanguage("jsonpath");
+        language.setUnpackArray(true);
+        language.setResultType(String.class);
+
+        JsonPathExpression expression = (JsonPathExpression) language.createExpression("$.store.book");
+        String json = (String) expression.evaluate(exchange);
+
+        // check that a single json object is returned, not an array
+        assertTrue(json.startsWith("{") && json.endsWith("}"));
+    }
+
+    @Test
+    public void testDontUnpackJsonArray() {
+        Exchange exchange = new DefaultExchange(context);
+        exchange.getIn().setBody(new File("src/test/resources/expensive.json"));
+
+        JsonPathLanguage language = (JsonPathLanguage) context.resolveLanguage("jsonpath");
+        language.setUnpackArray(false);
+        language.setResultType(String.class);
+
+        JsonPathExpression expression = (JsonPathExpression) language.createExpression("$.store.book");
+        String json = (String) expression.evaluate(exchange);
+
+        // check that an array is returned, not a single object
+        assertTrue(json.startsWith("[") && json.endsWith("]"));
+    }
+
 }
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClause.java b/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClause.java
index d785fe9a27c..309c6b75233 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClause.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClause.java
@@ -570,6 +570,18 @@ public class ExpressionClause<T> implements Expression, Predicate {
         return delegate.jsonpathWriteAsString(text, suppressExceptions, true, headerName);
     }
 
+    /**
+     * Evaluates a <a href="http://camel.apache.org/jsonpath.html">Json Path expression</a> with unpacking a
+     * single-element array into an object enabled.
+     *
+     * @param  text               the expression to be evaluated
+     * @param  resultType         the return type expected by the expression
+     * @return                    the builder to continue processing the DSL
+     */
+    public T jsonpathUnpack(String text, Class<?> resultType) {
+        return delegate.jsonpathUnpack(text, resultType);
+    }
+
     /**
      * Evaluates an <a href="http://camel.apache.org/ognl.html">OGNL expression</a>
      *
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java b/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java
index df03eed5add..a9262005c75 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java
@@ -680,6 +680,21 @@ public class ExpressionClauseSupport<T> implements ExpressionFactoryAware, Predi
         return expression(expression);
     }
 
+    /**
+     * Evaluates a <a href="http://camel.apache.org/jsonpath.html">Json Path expression</a> with unpacking a
+     * single-element array into an object enabled.
+     *
+     * @param  text               the expression to be evaluated
+     * @param  resultType         the return type expected by the expression
+     * @return                    the builder to continue processing the DSL
+     */
+    public T jsonpathUnpack(String text, Class<?> resultType) {
+        JsonPathExpression expression = new JsonPathExpression(text);
+        expression.setUnpackArray(Boolean.toString(true));
+        expression.setResultType(resultType);
+        return expression(expression);
+    }
+
     /**
      * Evaluates an <a href="http://camel.apache.org/ognl.html">OGNL expression</a>
      *
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/language/JsonPathExpression.java b/core/camel-core-model/src/main/java/org/apache/camel/model/language/JsonPathExpression.java
index bae9b501d86..5a675b3b455 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/language/JsonPathExpression.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/language/JsonPathExpression.java
@@ -49,6 +49,9 @@ public class JsonPathExpression extends ExpressionDefinition {
     @Metadata(defaultValue = "false", javaType = "java.lang.Boolean")
     private String writeAsString;
     @XmlAttribute
+    @Metadata(defaultValue = "false", javaType = "java.lang.Boolean")
+    private String unpackArray;
+    @XmlAttribute
     @Metadata(label = "advanced")
     private String headerName;
     @XmlAttribute
@@ -129,6 +132,17 @@ public class JsonPathExpression extends ExpressionDefinition {
         this.writeAsString = writeAsString;
     }
 
+    public String getUnpackArray() {
+        return unpackArray;
+    }
+
+    /**
+     * Whether to unpack a single element json-array into an object.
+     */
+    public void setUnpackArray(String unpackArray) {
+        this.unpackArray = unpackArray;
+    }
+
     public String getHeaderName() {
         return headerName;
     }
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/JsonPathExpressionReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/JsonPathExpressionReifier.java
index 5ef7d1eaec5..b3de1829766 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/JsonPathExpressionReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/JsonPathExpressionReifier.java
@@ -43,14 +43,15 @@ public class JsonPathExpressionReifier extends ExpressionReifier<JsonPathExpress
     }
 
     private Object[] createProperties() {
-        Object[] properties = new Object[7];
+        Object[] properties = new Object[8];
         properties[0] = definition.getResultType();
         properties[1] = parseBoolean(definition.getSuppressExceptions());
         properties[2] = parseBoolean(definition.getAllowSimple());
         properties[3] = parseBoolean(definition.getAllowEasyPredicate());
         properties[4] = parseBoolean(definition.getWriteAsString());
-        properties[5] = parseString(definition.getHeaderName());
-        properties[6] = parseString(definition.getOption());
+        properties[5] = parseBoolean(definition.getUnpackArray());
+        properties[6] = parseString(definition.getHeaderName());
+        properties[7] = parseString(definition.getOption());
         return properties;
     }