| = JSONPath Language |
| :doctitle: JSONPath |
| :shortname: jsonpath |
| :artifactid: camel-jsonpath |
| :description: Evaluates a JSONPath expression against a JSON message body. |
| :since: 2.13 |
| :supportlevel: Stable |
| :tabs-sync-option: |
| //Manually maintained attributes |
| :camel-spring-boot-name: jsonpath |
| |
| *Since Camel {since}* |
| |
| Camel supports https://github.com/json-path/JsonPath/[JSONPath] to allow |
| using xref:manual::expression.adoc[Expression] or xref:manual::predicate.adoc[Predicate] on JSON messages. |
| |
| |
| == JSONPath Options |
| |
| |
| // language options: START |
| include::partial$language-options.adoc[] |
| // language options: END |
| |
| == Usage |
| |
| |
| === JSONPath Syntax |
| |
| Using the JSONPath syntax takes some time to learn, even for basic predicates. |
| So for example, to find out all the cheap books you have to do: |
| |
| [source,text] |
| ---- |
| $.store.book[?(@.price < 20)] |
| ---- |
| |
| === Easy JSONPath Syntax |
| |
| However, what if you could just write it as: |
| |
| [source,text] |
| ---- |
| store.book.price < 20 |
| ---- |
| |
| And you can omit the path if you just want to look at nodes with a price key: |
| |
| [source,text] |
| ---- |
| price < 20 |
| ---- |
| |
| To support this there is a `EasyPredicateParser` which kicks-in if you have defined the predicate |
| using a basic style. That means the predicate must not start with the `$` sign, and only include one operator. |
| |
| The easy syntax is: |
| |
| [source,text] |
| ---- |
| left OP right |
| ---- |
| |
| You can use Camel simple language in the right operator, eg: |
| |
| [source,text] |
| ---- |
| store.book.price < ${header.limit} |
| ---- |
| |
| See the https://github.com/json-path/JsonPath[JSONPath] project page for more syntax examples. |
| |
| == Examples |
| |
| For example, you can use JSONPath in a xref:manual::predicate.adoc[Predicate] |
| with the xref:eips:choice-eip.adoc[Content-Based Router] EIP. |
| |
| [tabs] |
| ==== |
| |
| Java:: |
| + |
| [source,java] |
| ---- |
| from("queue:books.new") |
| .choice() |
| .when().jsonpath("$.store.book[?(@.price < 10)]") |
| .to("jms:queue:book.cheap") |
| .when().jsonpath("$.store.book[?(@.price < 30)]") |
| .to("jms:queue:book.average") |
| .otherwise() |
| .to("jms:queue:book.expensive"); |
| ---- |
| |
| XML DSL:: |
| + |
| [source,xml] |
| ---- |
| <route> |
| <from uri="direct:start"/> |
| <choice> |
| <when> |
| <jsonpath>$.store.book[?(@.price < 10)]</jsonpath> |
| <to uri="mock:cheap"/> |
| </when> |
| <when> |
| <jsonpath>$.store.book[?(@.price < 30)]</jsonpath> |
| <to uri="mock:average"/> |
| </when> |
| <otherwise> |
| <to uri="mock:expensive"/> |
| </otherwise> |
| </choice> |
| </route> |
| ---- |
| |
| ==== |
| |
| === Supported message body types |
| |
| Camel JSONPath supports message body using the following types: |
| |
| [width="100%",cols="3m,7",options="header"] |
| |=== |
| | Type | Comment |
| | File | Reading from files |
| | String | Plain strings |
| | Map | Message bodies as `java.util.Map` types |
| | List | Message bodies as `java.util.List` types |
| | POJO | *Optional* If Jackson is on the classpath, then camel-jsonpath |
| is able to use Jackson to read the message body as POJO and convert to `java.util.Map` |
| which is supported by JSONPath. For example, you can add `camel-jackson` as dependency to include Jackson. |
| | InputStream | If none of the above types matches, then Camel will attempt to read the message body as a `java.io.InputStream`. |
| |=== |
| |
| If a message body is of unsupported type, then an exception is thrown by default. |
| However, you can configure JSONPath to suppress exceptions (see below) |
| |
| |
| === Suppressing exceptions |
| |
| By default, jsonpath will throw an exception if the json payload does not |
| have a valid path accordingly to the configured jsonpath expression. In |
| some use-cases, you may want to ignore this in case the json payload |
| contains optional data. Therefore, you can set the option |
| `suppressExceptions` to `true` to ignore this as shown: |
| |
| [tabs] |
| ==== |
| |
| Java:: |
| + |
| [source,java] |
| ---- |
| from("direct:start") |
| .choice() |
| // use true to suppress exceptions |
| .when().jsonpath("person.middlename", true) |
| .to("mock:middle") |
| .otherwise() |
| .to("mock:other"); |
| ---- |
| |
| XML DSL:: |
| + |
| [source,xml] |
| ---- |
| <route> |
| <from uri="direct:start"/> |
| <choice> |
| <when> |
| <jsonpath suppressExceptions="true">person.middlename</jsonpath> |
| <to uri="mock:middle"/> |
| </when> |
| <otherwise> |
| <to uri="mock:other"/> |
| </otherwise> |
| </choice> |
| </route> |
| ---- |
| |
| ==== |
| |
| This option is also available on the `@JsonPath` annotation. |
| |
| === Inline Simple expressions |
| |
| It's possible to inlined xref:languages:simple-language.adoc[Simple] language |
| in the JSONPath expression using the simple syntax `$\{xxx}`. |
| |
| An example is shown below: |
| |
| [tabs] |
| ==== |
| |
| Java:: |
| + |
| [source,java] |
| ---- |
| from("direct:start") |
| .choice() |
| .when().jsonpath("$.store.book[?(@.price < ${header.cheap})]") |
| .to("mock:cheap") |
| .when().jsonpath("$.store.book[?(@.price < ${header.average})]") |
| .to("mock:average") |
| .otherwise() |
| .to("mock:expensive"); |
| ---- |
| |
| XML DSL:: |
| + |
| [source,xml] |
| ---- |
| <route> |
| <from uri="direct:start"/> |
| <choice> |
| <when> |
| <jsonpath>$.store.book[?(@.price < ${header.cheap})]</jsonpath> |
| <to uri="mock:cheap"/> |
| </when> |
| <when> |
| <jsonpath>$.store.book[?(@.price < ${header.average})]</jsonpath> |
| <to uri="mock:average"/> |
| </when> |
| <otherwise> |
| <to uri="mock:expensive"/> |
| </otherwise> |
| </choice> |
| </route> |
| ---- |
| |
| ==== |
| |
| You can turn off support for inlined Simple expression by setting the option `allowSimple` to `false` as shown: |
| |
| [tabs] |
| ==== |
| |
| Java:: |
| + |
| [source,java] |
| ---- |
| .when().jsonpath("$.store.book[?(@.price < 10)]", false, false) |
| ---- |
| |
| XML DSL:: |
| + |
| [source,xml] |
| ---- |
| <jsonpath allowSimple="false">$.store.book[?(@.price < 10)]</jsonpath> |
| ---- |
| |
| ==== |
| |
| === Using variables as source |
| |
| By default, the message body is the source for the jsonpath evaluation. |
| However, if you need to refer to a variable or message header instead as the body, |
| then this is easy as shown below: |
| |
| [source,java] |
| ---- |
| from("direct:start") |
| .setVariable("cars", constant("[\"Ford\", \"BMW\", \"Fiat\"]")) |
| .setBody(simple("${jsonpath(variable:cars , $.length())}")) |
| .to("mock:cars"); |
| ---- |
| |
| Here we set a variable named _cars_ with a json array of 3 cars. Then we want to count the length |
| of the array using jsonpath length function. Notice how we use the inlined simple language, and can |
| easily refer to the variable as the source using `variable:cars`. |
| |
| === JSONPath injection |
| |
| You can use xref:manual::bean-integration.adoc[Bean Integration] to invoke a |
| method on a bean and use various languages such as JSONPath (via the `@JsonPath` annotation) |
| to extract a value from the message and bind it to a method parameter, as shown below: |
| |
| [source,java] |
| ---- |
| public class Foo { |
| |
| @Consume("activemq:queue:books.new") |
| public void doSomething(@JsonPath("$.store.book[*].author") String author, @Body String json) { |
| // process the inbound message here |
| } |
| } |
| ---- |
| |
| === Encoding Detection |
| |
| The encoding of the JSON document is |
| detected automatically, if the document is encoded in unicode (UTF-8, |
| UTF-16LE, UTF-16BE, UTF-32LE, UTF-32BE) as specified in RFC-4627. |
| If the encoding is a non-unicode encoding, you can either make sure that |
| you enter the document in String format to JSONPath, or you |
| can specify the encoding in the header `CamelJsonPathJsonEncoding` which |
| is defined as a constant in: `JsonpathConstants.HEADER_JSON_ENCODING`. |
| |
| === Split JSON data into sub rows as JSON |
| |
| You can use JSONPath to split a JSON document, such as: |
| |
| [source,java] |
| ---- |
| from("direct:start") |
| .split().jsonpath("$.store.book[*]", List.class) |
| .to("log:book"); |
| ---- |
| |
| IMPORTANT: Notice how we specify `List.class` as the result-type. This is because if there is only |
| a single element (only 1 book), then jsonpath will return the single entity as a `Map` instead of `List<Map>`. |
| Therefore, we tell Camel that the result should always be a `List`, and Camel will then automatic wrap the |
| single element into a new `List` object. |
| |
| Then each book is logged, however the message body is a `Map` instance. Sometimes |
| you may want to output this as plain String JSON value instead, which can be done |
| with the `writeAsString` option as shown: |
| |
| [source,java] |
| ---- |
| from("direct:start") |
| .split().jsonpathWriteAsString("$.store.book[*]", List.class) |
| .to("log:book"); |
| ---- |
| |
| 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 a 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 |
| by specifying the `source` option. |
| |
| For example, to count the number of books from a JSON document that |
| was stored in a header named `books` you can do: |
| |
| [source,java] |
| ---- |
| var jp = expression().jsonpath("$..store.book.length()").resultType(int.class) |
| .source("header:books").end(); |
| |
| from("direct:start") |
| .setHeader("numberOfBooks", jp) |
| .to("mock:result"); |
| ---- |
| |
| And you can also inline the expression: |
| |
| [source,java] |
| ---- |
| from("direct:start") |
| .setHeader("numberOfBooks", expression().jsonpath("$..store.book.length()").resultType(int.class) |
| .source("header:books").end()) |
| .to("mock:result"); |
| ---- |
| |
| In the `jsonpath` expression above we specify the name of the header as `books`, |
| and we also told that we wanted the result to be converted as an integer by `int.class`. |
| |
| TIP: You can also use `variable:` as source prefix to refer to an Exchange variable instead of a header. |
| |
| The same example in XML DSL would be easier to do: |
| |
| [source,xml] |
| ---- |
| <route> |
| <from uri="direct:start"/> |
| <setHeader name="numberOfBooks"> |
| <jsonpath source="header:books" resultType="int">$..store.book.length()</jsonpath> |
| </setHeader> |
| <to uri="mock:result"/> |
| </route> |
| ---- |
| |
| === 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 JSonPath language as: |
| |
| [source,json] |
| ---- |
| { |
| "company": "${jsonpath($.customer.name)}", |
| "location": "${jsonpath($.customer.address.country)}", |
| "gold": ${jsonpath($.customer.orders.length() > 5)} |
| } |
| ---- |
| |
| Here we use the simple language to define the structure and use JSonPath as inlined functions via the `${jsonpath(exp)}` syntax. |
| |
| This makes it possible to use simple as a template language to define a basic structure and then JSonPath 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="${jsonpath($.customer.orders.length() > 5)}"> |
| <company>${jsonpath($.customer.name)}</company> |
| <location>${jsonpath($.customer.address.country)}</location> |
| </customer> |
| ---- |
| |
| include::spring-boot:partial$starter.adoc[] |