This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch camel-3.18.x in repository https://gitbox.apache.org/repos/asf/camel.git
commit 298f1e0a1fe46832d563cb125e7b961bccf64b0f Author: Radovan Netuka <rnet...@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 fba2d270aa6..8192c1d09f6 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 @@ -679,6 +679,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; }