DataSonnet
Since Camel 3.7
Camel supports DataSonnet transformations to allow an Expression or Predicate to be used in the DSL.
For example, you could use DataSonnet to create a Predicate in a Message Filter or as an Expression for a Recipient List.
To use a DataSonnet expression, use the following Java code:
datasonnet("someDSExpression"); DataSonnet Options
The DataSonnet language supports the following options which are listed below.
| Name | Default | Java Type | Description |
|---|---|---|---|
| The media type of the message body, such as application/json. | ||
| The media type to use for the output result. | ||
| Source to use, instead of message body. You can prefix with variable:, header:, or property: to specify kind of source. Otherwise, the source is assumed to be a variable. Use empty or null to use default source, which is the message body. | ||
| The class of the result type (type from output). | ||
|
| Whether to trim the source code to remove leading and trailing whitespaces and line breaks. | |
|
| Whether a result of the expression that is a String starting with resource: is loaded as a resource and its content becomes the result, e.g. a script that returns resource:file:order.json or resource:classpath:templates/order.json (a name without a scheme is a classpath resource). Off by default; the resource: prefix on the expression text itself is always resolved. Applies to the expression used as a value, not as a predicate. |
Usage
Setting a result type
The DataSonnet expression will return a com.datasonnet.document.Document by default. The document preserves the content type metadata along with the contents of the transformation result. In predicates, however, the Document will be automatically unwrapped and the boolean content will be returned. Similarly, any time you want the content in a specific result type like a String. To do this, you have to instruct the DataSonnet which result type to return.
-
Java
-
XML
-
YAML
from("direct:start")
.setBody(datasonnet("body.foo", String.class))
.to("mock:result"); <route>
<from uri="direct:start"/>
<setBody>
<datasonnet resultType="java.lang.String">body.foo</datasonnet>
</setBody>
<to uri="mock:result"/>
</route> - route:
from:
uri: direct:start
steps:
- setBody:
expression:
datasonnet:
expression: body.foo
resultType: java.lang.String
- to:
uri: mock:result In XML and YAML DSL, resultType is the fully qualified class name.
If the expression results in an array, or an object, you can instruct the expression to return you List.class or Map.class, respectively. However, you must also set the output media type to application/x-java-object.
The default Document object is useful in situations where there are intermediate transformation steps, and so retaining the content metadata through a route execution is valuable. |
Specifying Media Types
Traditionally, the input and output media types are specified through the DataSonnet Header. The DataSonnet expression provides convenience options for specifying the body and output media types without the need for a Header, this is useful if the transformation is a one-liner, for example.
The DataSonnet expression will look for a body media type in the following order:
-
If the body is a
Documentit will use the metadata in the object -
If the bodyMediaType parameter was provided in the DSL, it will use its value
-
A
CamelDatasonnetBodyMediaTypeexchange property -
A
Content-Typemessage header -
The DataSonnet Header payload media type directive
-
application/x-java-object
And for output media type:
-
If the outputMediaType parameter was provided in the DSL, it will use its value
-
A "CamelDatasonnetOutputMediaType" exchange property
-
A "CamelDatasonnetOutputMediaType" message header
-
The DataSonnet Header output media type directive
-
application/x-java-object
Functions
Camel adds the following DataSonnet functions that can be used to access the exchange:
| Function | Argument | Type | Description |
|---|---|---|---|
cml.properties | key for property | String | To look up a property using the Properties component (property placeholders). |
cml.header | the header name | String | Will return the message header. |
cml.exchangeProperty | key for property | String | Will return the exchange property. |
cml.variable | the variable name | String | Will return the exchange variable. |
Null Handling Functions
| Function | Example | Description |
|---|---|---|
cml.defaultVal(value, fallback) |
| Returns |
cml.isEmpty(value) |
| Returns |
Type Coercion Functions
| Function | Example | Description |
|---|---|---|
cml.toInteger(value) |
| Converts a string or number to an integer. Returns null for null input. |
cml.toDecimal(value) |
| Converts a string to a decimal number. Returns null for null input. |
cml.toBoolean(value) |
| Converts a string ( |
Date/Time Functions
| Function | Example | Description |
|---|---|---|
cml.now() |
| Returns the current timestamp as an ISO-8601 string. |
cml.nowFmt(format) |
| Returns the current UTC time formatted with the given pattern. |
cml.formatDate(value, format) |
| Formats an ISO-8601 date, date-time or time string (or epoch milliseconds) using the given pattern, in the default locale. Returns null for null input. |
cml.formatDateLocale(value, format, locale) |
| Like |
cml.parseDate(value, format) |
| Parses a date string into epoch milliseconds. Returns null for null input. |
cml.formatNumber(value, format) |
| Formats a number with a |
cml.formatNumberLocale(value, format, locale) |
| Like |
cml.parseDateTime(value, format, type) |
| Parses a string in the given format (ISO-8601 when the format is null), or a number of seconds since the epoch, into an ISO-8601 string of the given type: |
cml.dateAdd(value, amount) |
| Adds an ISO-8601 period or duration (such as |
cml.datePart(value, part) |
| A part of an ISO-8601 date or time: |
Math Functions
| Function | Example | Description |
|---|---|---|
cml.sqrt(value) |
| Computes the square root of a number. Returns null for null input. |
Utility Functions
| Function | Example | Description |
|---|---|---|
cml.uuid() |
| Generates a random UUID string. |
cml.typeOf(value) |
| Returns the type name: |
Camel Standard Library (camel.libsonnet)
Camel ships a standard library of helper functions that can be imported in any DataSonnet script:
local c = import 'camel.libsonnet';
{
total: c.sumBy(body.items, function(i) i.price * i.qty),
names: c.join(std.map(function(i) i.name, body.items), ', '),
topItem: c.first(c.sortBy(body.items, function(i) -i.price))
} String Helpers
| Function | Description |
|---|---|
| Capitalizes the first letter. |
| Strips whitespace from both ends. |
| Splits a string by separator. |
| Joins an array of strings with separator. |
| Checks if string contains substring. |
| Checks if string starts with prefix. |
| Checks if string ends with suffix. |
| Replaces all occurrences. |
| Case conversion. |
Collection Helpers
| Function | Description |
|---|---|
| Sums all elements. |
| Sums by applying function |
| First/last element, or null if empty. |
| Array length. |
| Average of all elements, or null if empty. |
| Removes duplicates. |
| Removes duplicates by key function (keeps first occurrence). |
| Maps and flattens. |
| Sorts by key function. |
| Groups into object by key function. |
| Minimum/maximum element. |
| First/last n elements. |
| Zips two arrays into pairs. |
Object Helpers
| Function | Description |
|---|---|
| Selects only the listed keys. |
| Removes the listed keys. |
| Merges two objects (b overrides a). |
| Object keys/values as arrays. |
| Converts to |
| Converts |
Here’s an example showing some of these functions in use:
-
Java
-
XML
-
YAML
from("direct:in")
.setBody(datasonnet("'hello, ' + cml.properties('toGreet')", String.class))
.to("mock:camel"); <route>
<from uri="direct:in"/>
<setBody>
<datasonnet resultType="java.lang.String">'hello, ' + cml.properties('toGreet')</datasonnet>
</setBody>
<to uri="mock:camel"/>
</route> - route:
from:
uri: direct:in
steps:
- setBody:
expression:
datasonnet:
expression: "'hello, ' + cml.properties('toGreet')"
resultType: java.lang.String
- to:
uri: mock:camel Loading script from external resource
You can externalize the script and have Apache Camel load it from a resource such as "classpath:", "file:", or "http:".
This is done using the following syntax: "resource:scheme:location", e.g., to refer to a file on the classpath you can do:
.setHeader("myHeader").datasonnet("resource:classpath:mydatasonnet.ds"); Examples
Here is a simple example using a DataSonnet expression as a predicate in a Message Filter:
-
Java
-
XML
-
YAML
// let's route if a line item is over $100
from("queue:foo")
.filter(datasonnet("ds.arrays.firstWith(body.lineItems, function(item) item > 100) != null"))
.to("queue:bar"); <route>
<from uri="queue:foo"/>
<filter>
<datasonnet>ds.arrays.firstWith(body.lineItems, function(item) item > 100) != null</datasonnet>
<to uri="queue:bar"/>
</filter>
</route> - route:
from:
uri: queue:foo
steps:
- filter:
expression:
datasonnet:
expression: "ds.arrays.firstWith(body.lineItems, function(item) item > 100) != null"
steps:
- to:
uri: queue:bar Here is an example of a simple DataSonnet expression as a transformation EIP. This example will transform an XML body with lineItems into JSON while filtering out lines that are under 100.
-
Java
-
XML
-
YAML
from("queue:foo")
.transform(datasonnet("ds.filter(body.lineItems, function(item) item > 100)", String.class, "application/xml", "application/json"))
.to("queue:bar"); <route>
<from uri="queue:foo"/>
<filter>
<datasonnet bodyMediaType="application/xml" outputMediaType="application/json" resultType="java.lang.String" >
ds.filter(body.lineItems, function(item) item > 100)
</datasonnet>
<to uri="queue:bar"/>
</filter>
</route> - route:
from:
uri: queue:foo
steps:
- transform:
expression:
datasonnet:
expression: "ds.filter(body.lineItems, function(item) item > 100)"
resultType: java.lang.String
bodyMediaType: application/xml
outputMediaType: application/json
- to:
uri: queue:bar DataWeave Support
Camel can execute DataWeave (.dwl) scripts directly by auto-detecting and transpiling them to DataSonnet at load time. No extra configuration is needed — just reference a .dwl resource and Camel handles the conversion automatically.
Auto-detection
DataWeave scripts are detected in two ways:
-
By file extension: any resource ending in
.dwl(e.g.,resource:classpath:transform.dwl) -
By content: inline expressions starting with the
%dwheader
Once detected, the script is transpiled to DataSonnet and executed via the datasonnet-mapper runtime. The conversion happens once at route initialization, so there is no runtime overhead.
Example
Given a DataWeave file transform.dwl on the classpath:
%dw 2.0
output application/json
---
{
orderId: payload.order_id,
customer: payload.customer.first_name ++ " " ++ payload.customer.last_name,
total: payload.items map ((item) -> item.price * item.qty)
reduce ((item, acc = 0) -> acc + item)
} You can use it directly in a route:
-
Java
-
XML
-
YAML
from("direct:in")
.transform(datasonnet("resource:classpath:transform.dwl", String.class))
.to("mock:result"); <route>
<from uri="direct:in"/>
<transform>
<datasonnet resultType="java.lang.String">resource:classpath:transform.dwl</datasonnet>
</transform>
<to uri="mock:result"/>
</route> - route:
from:
uri: direct:in
steps:
- transform:
expression:
datasonnet:
expression: "resource:classpath:transform.dwl"
resultType: java.lang.String
- to:
uri: mock:result CLI Transpiler
You can also convert DataWeave scripts ahead of time using the Camel CLI:
camel transform dataweave -e "payload.name ++ ' ' ++ payload.surname"
camel transform dataweave --input my-transform.dwl --output my-transform.ds
camel transform dataweave --input src/main/resources/dwl/ --output src/main/resources/ds/ How DataWeave is converted
The converted script keeps the DataWeave semantics, which differ from plain Jsonnet in important ways. For example, in DataWeave a missing field, an index out of range or a selector on null gives null, a field selector on an array selects the field of every element (payload.items.price), and most functions accept null. The converted script uses the camel-dataweave.libsonnet library of camel-datasonnet for these semantics (imported as dw), DataSonnet’s ds library, and the cml functions above.
The conversion either produces a script that gives the same result as the DataWeave script, or fails:
-
Invalid DataWeave fails with a
DataWeaveConversionExceptionthat reports the line and column. -
A construct that cannot be converted, and any function or variable the converter does not know, makes the route fail to start with a
DataWeaveConversionException, instead of running a script that gives a different result. The CLI transpiler emits such constructs as aTODOcomment andnull, and reports how many need manual review.
Supported Constructs
-
Header:
%dw,output(JSON, XML, CSV, Java and text; for JSON the writer propertiesskipNullOn,indentandencoding),input,importof thedw::coremodules,varandfundeclarations (also with default parameters, recursive and calling each other), andtypedeclarations (ignored). -
Selectors:
.field,."quoted field",[index](negative from the end),[from to to],["key"],.*field,..field,.@attribute,field?and[?(condition)]. -
Mule variables and attributes:
payloadis the message body,vars.name(orvars["name"]) an exchange variable, andattributes.headers,attributes.queryParamsandattributes.uriParamsmessage headers.attributes.method,requestPath,requestUri,queryString,statusCodeandreasonPhraseare the corresponding Camel HTTP headers. -
Operators, with the DataWeave precedence: arithmetic, comparison,
~=,and,or,not,++,--, removing a key with-,default,if/else,unless/else,match(literal,is Type,matches /regex/,ifguards andelsecases),doblocks andusing. -
Functions in prefix and infix form (
upper(x),xs map f), with lambdas(item, index) → …, the$,` and `$shorthand, and references to functions:map,filter,reduce,flatMap,flatten,distinctBy,groupBy,orderBy,mapObject,filterObject,pluck,keysOf,namesOf,valuesOf,entriesOf,zip,unzip,sizeOf,isEmpty,isBlank,sum,avg,min,max,maxBy,minBy,then,to,upper,lower,trim,contains,startsWith,endsWith,splitBy,joinBy,replace … with,matches,scan,find,abs,ceil,floor,round,sqrt,pow,mod,isEven,isOdd,isInteger,isDecimal,typeOf,now,uuid,p,log,fail,readandwrite, and of thedw::coremodulescamelize,capitalize,dasherize,underscore,pluralize,singularize,leftPad,rightPad,substringBefore,substringAfter(and the otherStringsfunctions),take,drop,splitAt,divideBy,indexOf,countBy,sumBy,every,some,partition,firstWithandmergeWith. -
Objects: dynamic keys
(key): value, conditional fields(key: value) if condition, and object spread{ (expression) }of an object or an array of objects. -
Strings: interpolation (
"Hello $(expression)","Hello $name"and$of a lambda), and regular expressions/…/inreplace(also with a function of the matched groups:replace /(\d+)/ with ($[1] ++ "!")),contains,splitBy,matches,scanandfind. -
Type coercion:
as String,as Number,as Boolean,as String {format: …}of numbers and dates,as Date,as DateTime,as LocalDateTime,as Timeandas LocalTime(with or withoutformat), date literals such as|2020-01-31|, date arithmetic with periods such as|P1D|, and the parts of a date such as.year. Dates and times are ISO-8601 strings in the converted script; as in DataWeave, a date coerced with a format is written in that format. Numbers and dates are formatted in the default locale (as DataWeave does), unless the coercion has alocaleproperty.
The expected output of the DataWeave scripts in the tests of the conversion is verified with the DataWeave CLI.
These constructs are not converted (the conversion fails): the update operator, try (dw::Runtime), overloaded functions, custom modules, as with a custom type, XML namespaces, XML attributes in the output, and writer properties other than the ones listed above. The XML support is limited: an element with text is read as its text (as in DataWeave), but the attributes of such an element are only kept when they are selected directly (payload.order.@id) or in a lambda (payload.orders.*order map $.@id).
Dependencies
To use scripting languages in your camel routes, you need to add a dependency on camel-datasonnet.
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).
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-datasonnet</artifactId>
<version>x.x.x</version>
</dependency>