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 9ea0c6242ca CAMEL-18768: Add jq function to simple language to make it
easy to grab some data from JSon message body and use simple for basic JSon
transformation (#12188)
9ea0c6242ca is described below
commit 9ea0c6242ca6d8e9000dfe78ab155d429df8e567
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Nov 24 10:02:06 2023 +0100
CAMEL-18768: Add jq function to simple language to make it easy to grab
some data from JSon message body and use simple for basic JSon transformation
(#12188)
---
components/camel-jq/src/main/docs/jq-language.adoc | 43 +++++++++++++++-
.../org/apache/camel/language/jq/JqExpression.java | 3 ++
.../org/apache/camel/language/jq/JqFunctions.java | 27 ++++++++++
.../camel/language/jq/JqSimpleTransformTest.java | 57 ++++++++++++++++++++++
.../modules/languages/pages/simple-language.adoc | 3 ++
.../simple/ast/SimpleFunctionExpression.java | 20 ++++++++
6 files changed, 152 insertions(+), 1 deletion(-)
diff --git a/components/camel-jq/src/main/docs/jq-language.adoc
b/components/camel-jq/src/main/docs/jq-language.adoc
index ea0f33a0633..5f876cfaad8 100644
--- a/components/camel-jq/src/main/docs/jq-language.adoc
+++ b/components/camel-jq/src/main/docs/jq-language.adoc
@@ -11,7 +11,7 @@
*Since Camel {since}*
-Camel supports https://stedolan.github.io/jq[JQ] to allow using
xref:manual::expression.adoc[Expression] or
xref:manual::predicate.adoc[Predicate] on JSON messages.
+Camel supports https://jqlang.github.io/jq/[JQ] to allow using
xref:manual::expression.adoc[Expression] or
xref:manual::predicate.adoc[Predicate] on JSON messages.
== JQ Options
@@ -57,10 +57,13 @@ from("direct:start")
== Camel supplied JQ Functions
+NOTE: JQ comes with about a hundred built-in functions, and you can see many
examples from https://jqlang.github.io/jq/[JQ] documentation.
+
The camel-jq adds the following functions:
* `header` - Allow to access the Message header in a JQ expression.
* `property` - Allow to access the Exchange property in a JQ expression.
+* `constant` - Allow to use a constant value as-is in a JQ expression.
For example, to set the property foo with the value from the Message header
`MyHeader':
@@ -82,6 +85,44 @@ from("direct:start")
.to("mock:result");
----
+And using a constant value
+
+[source, java]
+----
+from("direct:start")
+ .transform()
+ .jq(".foo = constant(\"Hello World\")")
+ .to("mock:result");
+----
+
+== Transforming a JSon message
+
+For basic JSon transformation where you have a fixed structure you can
represent with a combination of using
+Camel simple and JQ language as:
+
+[source]
+----
+{
+ "company": "${jq(.customer.name)}",
+ "location": "${jq(.customer.address.country)}",
+ "gold": ${jq(.customer.orders[] | length > 5)}
+}
+----
+
+Here we use the simple language to define the structure and use JQ as inlined
functions via the `${jq(exp)}` syntax.
+
+This makes it possible to use simple as a template language to define a basic
structure and then JQ to grab the data
+from an incoming JSon message. The output of the transformation is also JSon,
but with simple you could
+also make it XML or plain text based:
+
+[source,xml]
+----
+<customer gold="${jq(.customer.orders[] | length > 5)}">
+ <company>${jq(.customer.name)}</company>
+ <location>${jq(.customer.address.country)}</location>
+</customer>
+----
+
== Dependencies
If you use Maven you could just add the following to your `pom.xml`,
substituting the version number for the latest and greatest release (see the
download page for the latest versions).
diff --git
a/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqExpression.java
b/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqExpression.java
index b11611330c6..06689ca9bfd 100644
---
a/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqExpression.java
+++
b/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqExpression.java
@@ -36,6 +36,7 @@ import org.apache.camel.TypeConverter;
import org.apache.camel.spi.ExpressionResultTypeAware;
import org.apache.camel.support.CamelContextHelper;
import org.apache.camel.support.ExpressionAdapter;
+import org.apache.camel.support.MessageHelper;
public class JqExpression extends ExpressionAdapter implements
ExpressionResultTypeAware {
@@ -217,6 +218,8 @@ public class JqExpression extends ExpressionAdapter
implements ExpressionResultT
if (payload == null) {
throw new InvalidPayloadException(exchange, JsonNode.class);
}
+ // if body is stream cached then reset, so we can re-read it again
+ MessageHelper.resetStreamCache(exchange.getMessage());
} else {
if (headerName != null) {
payload = exchange.getMessage().getHeader(headerName,
JsonNode.class);
diff --git
a/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqFunctions.java
b/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqFunctions.java
index 114d0312a84..82d5ce182fa 100644
---
a/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqFunctions.java
+++
b/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqFunctions.java
@@ -30,6 +30,7 @@ import net.thisptr.jackson.jq.Scope;
import net.thisptr.jackson.jq.Version;
import net.thisptr.jackson.jq.Versions;
import net.thisptr.jackson.jq.exception.JsonQueryException;
+import net.thisptr.jackson.jq.internal.tree.FunctionCall;
import net.thisptr.jackson.jq.path.Path;
import org.apache.camel.CamelContext;
import org.apache.camel.Exchange;
@@ -84,6 +85,8 @@ public final class JqFunctions {
scope.addFunction(Header.NAME, 2, new Header());
scope.addFunction(Property.NAME, 1, new Property());
scope.addFunction(Property.NAME, 2, new Property());
+ scope.addFunction(Constant.NAME, 1, new Constant());
+ scope.addFunction(Constant.NAME, 2, new Constant());
}
public abstract static class ExchangeAwareFunction implements Function {
@@ -227,4 +230,28 @@ public final class JqFunctions {
}
}
}
+
+ /**
+ * A function that returns a constant value as part of JQ expression
evaluation.
+ *
+ * As example, the following JQ expression sets the {@code .name} property
to the constant value Donald.
+ *
+ * <pre>
+ * {@code
+ * .name = constant(\"Donald\")"
+ * }
+ * </pre>
+ *
+ */
+ public static class Constant implements Function {
+ public static final String NAME = "constant";
+
+ @Override
+ public void apply(Scope scope, List<Expression> args, JsonNode in,
Path path, PathOutput output, Version version)
+ throws JsonQueryException {
+ FunctionCall fc = (FunctionCall) args.get(0);
+ String t = fc.toString();
+ output.emit(new TextNode(t), null);
+ }
+ }
}
diff --git
a/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqSimpleTransformTest.java
b/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqSimpleTransformTest.java
new file mode 100644
index 00000000000..46c94c7c7d2
--- /dev/null
+++
b/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqSimpleTransformTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.language.jq;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.junit.jupiter.api.Test;
+
+public class JqSimpleTransformTest extends JqTestSupport {
+
+ private static String EXPECTED = """
+ {
+ "roll": 123,
+ "country": "sweden",
+ "fullname": "scott"
+ }""";
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:start")
+ .transform().simple("""
+ {
+ "roll": ${jq(.id)},
+ "country": "${jq(.country //
constant('sweden'))}",
+ "fullname": "${jq(.name)}"
+ }""")
+ .to("mock:result");
+ }
+ };
+ }
+
+ @Test
+ public void testTransform() throws Exception {
+ getMockEndpoint("mock:result").expectedBodiesReceived(EXPECTED);
+
+ template.sendBody("direct:start", "{\"id\": 123, \"age\": 42,
\"name\": \"scott\"}");
+
+ MockEndpoint.assertIsSatisfied(context);
+ }
+}
diff --git
a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc
b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc
index b4825370a24..4325100dd0f 100644
---
a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc
+++
b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc
@@ -252,6 +252,9 @@ If no type is given the default is used. It is also
possible to use a custom `Uu
and bind the bean to the xref:manual::registry.adoc[Registry] with an id. For
example `${uuid(myGenerator}`
where the ID is _myGenerator_.
+|jq(exp) | Object | When working with JSon data, then this allows to use the
xref:languages::jq-language.adoc[JQ] language
+for example to extract data from the message body (in JSon format). This
requires having camel-jq JAR on the classpath.
+
|=======================================================================
== OGNL expression support
diff --git
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java
index f6b659fb725..edf10b84a0c 100644
---
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java
+++
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java
@@ -88,6 +88,11 @@ public class SimpleFunctionExpression extends
LiteralExpression {
if (answer != null) {
return answer;
}
+ // custom languages
+ answer = createSimpleCustomLanguage(function, strict);
+ if (answer != null) {
+ return answer;
+ }
// camelContext OGNL
String remainder = ifStartsWithReturnRemainder("camelContext",
function);
@@ -446,6 +451,21 @@ public class SimpleFunctionExpression extends
LiteralExpression {
return null;
}
+ private Expression createSimpleCustomLanguage(String function, boolean
strict) {
+ // jq
+ String remainder = ifStartsWithReturnRemainder("jq(", function);
+ if (remainder != null) {
+ String exp = StringHelper.beforeLast(remainder, ")");
+ if (exp == null) {
+ throw new SimpleParserException("Valid syntax: ${jq(exp)} was:
" + function, token.getIndex());
+ }
+ exp = StringHelper.removeQuotes(exp);
+ return ExpressionBuilder.languageExpression("jq", exp);
+ }
+
+ return null;
+ }
+
private Expression createSimpleExpressionDirectly(CamelContext
camelContext, String expression) {
if (ObjectHelper.isEqualToAny(expression, "body", "in.body")) {
return ExpressionBuilder.bodyExpression();