Chapter 64. JSONPath
Chapter 64. JSONPath Camel supports JSONPath to allow using Expression or Predicate on JSON messages. 64.1. Dependencies When using jsonpath with Red Hat build of Camel Spring Boot make sure to use the following Maven dependency to have support for auto configuration: <dependency> <groupId>org.apache.camel.springboot</groupId> <artifactId>camel-jsonpath-starter</artifactId> </dependency> 64.2. JSONPath Options The JSONPath language supports 8 options, which are listed below. Name Default Java Type Description resultType String Sets the class name of the result type (type from output). suppressExceptions Boolean Whether to suppress exceptions such as PathNotFoundException. allowSimple Boolean Whether to allow in inlined Simple exceptions in the JSONPath expression. allowEasyPredicate Boolean Whether to allow using the easy predicate parser to pre-parse predicates. writeAsString Boolean Whether to write the output of each row/element as a JSON String value instead of a Map/POJO value. headerName String Name of header to use as input, instead of the message body. option Enum To configure additional options on JSONPath. Multiple values can be separated by comma. Enum values: DEFAULT_PATH_LEAF_TO_NULL ALWAYS_RETURN_LIST AS_PATH_LIST SUPPRESS_EXCEPTIONS REQUIRE_PROPERTIES trim Boolean Whether to trim the value to remove leading and trailing whitespaces and line breaks. 64.3. Examples For example, you can use JSONPath in a Predicate with the Content Based Router EIP . from("queue:books.new") .choice() .when().jsonpath("USD.store.book[?(@.price < 10)]") .to("jms:queue:book.cheap") .when().jsonpath("USD.store.book[?(@.price < 30)]") .to("jms:queue:book.average") .otherwise() .to("jms:queue:book.expensive"); And in XML DSL: <route> <from uri="direct:start"/> <choice> <when> <jsonpath>USD.store.book[?(@.price &lt; 10)]</jsonpath> <to uri="mock:cheap"/> </when> <when> <jsonpath>USD.store.book[?(@.price &lt; 30)]</jsonpath> <to uri="mock:average"/> </when> <otherwise> <to uri="mock:expensive"/> </otherwise> </choice> </route> 64.4. 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: USD.store.book[?(@.price < 20)] 64.4.1. Easy JSONPath Syntax However, what if you could just write it as: store.book.price < 20 And you can omit the path if you just want to look at nodes with a price key: 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 USD sign, and only include one operator. The easy syntax is: left OP right You can use Camel simple language in the right operator, eg: store.book.price < USD{header.limit} See the JSONPath project page for more syntax examples. 64.5. Supported message body types Camel JSonPath supports message body using the following types: 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) 64.6. 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: from("direct:start") .choice() // use true to suppress exceptions .when().jsonpath("person.middlename", true) .to("mock:middle") .otherwise() .to("mock:other"); And in XML DSL: <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. 64.7. Inline Simple expressions It's possible to inlined Simple language in the JSONPath expression using the simple syntax USD{xxx} . An example is shown below: from("direct:start") .choice() .when().jsonpath("USD.store.book[?(@.price < USD{header.cheap})]") .to("mock:cheap") .when().jsonpath("USD.store.book[?(@.price < USD{header.average})]") .to("mock:average") .otherwise() .to("mock:expensive"); And in XML DSL: <route> <from uri="direct:start"/> <choice> <when> <jsonpath>USD.store.book[?(@.price &lt; USD{header.cheap})]</jsonpath> <to uri="mock:cheap"/> </when> <when> <jsonpath>USD.store.book[?(@.price &lt; USD{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: .when().jsonpath("USD.store.book[?(@.price < 10)]", false, false) And in XML DSL: <jsonpath allowSimple="false">USD.store.book[?(@.price &lt; 10)]</jsonpath> 64.8. JSonPath injection You can use 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: public class Foo { @Consume("activemq:queue:books.new") public void doSomething(@JsonPath("USD.store.book[*].author") String author, @Body String json) { // process the inbound message here } } 64.9. 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 . 64.10. Split JSON data into sub rows as JSON You can use JSONPath to split a JSON document, such as: from("direct:start") .split().jsonpath("USD.store.book[*]") .to("log:book"); 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: from("direct:start") .split().jsonpathWriteAsString("USD.store.book[*]") .to("log:book"); Then each book is logged as a String JSON value. 64.11. 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 headerName option. For example to count the number of books from a JSON document that was stored in a header named books you can do: from("direct:start") .setHeader("numberOfBooks") .jsonpath("USD..store.book.length()", false, int.class, "books") .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 . The same example in XML DSL would be: <route> <from uri="direct:start"/> <setHeader name="numberOfBooks"> <jsonpath headerName="books" resultType="int">USD..store.book.length()</jsonpath> </setHeader> <to uri="mock:result"/> </route> 64.12. Spring Boot Auto-Configuration The component supports 8 options, which are listed below. Name Description Default Type camel.language.jsonpath.allow-easy-predicate Whether to allow using the easy predicate parser to pre-parse predicates. true Boolean camel.language.jsonpath.allow-simple Whether to allow in inlined Simple exceptions in the JSONPath expression. true Boolean camel.language.jsonpath.enabled Whether to enable auto configuration of the jsonpath language. This is enabled by default. Boolean camel.language.jsonpath.header-name Name of header to use as input, instead of the message body. String camel.language.jsonpath.option To configure additional options on JSONPath. Multiple values can be separated by comma. String camel.language.jsonpath.suppress-exceptions Whether to suppress exceptions such as PathNotFoundException. false Boolean camel.language.jsonpath.trim Whether to trim the value to remove leading and trailing whitespaces and line breaks. true Boolean camel.language.jsonpath.write-as-string Whether to write the output of each row/element as a JSON String value instead of a Map/POJO value. false Boolean
<dependency> <groupId>org.apache.camel.springboot</groupId> <artifactId>camel-jsonpath-starter</artifactId> </dependency> from("queue:books.new") .choice() .when().jsonpath("USD.store.book[?(@.price < 10)]") .to("jms:queue:book.cheap") .when().jsonpath("USD.store.book[?(@.price < 30)]") .to("jms:queue:book.average") .otherwise() .to("jms:queue:book.expensive"); <route> <from uri="direct:start"/> <choice> <when> <jsonpath>USD.store.book[?(@.price &lt; 10)]</jsonpath> <to uri="mock:cheap"/> </when> <when> <jsonpath>USD.store.book[?(@.price &lt; 30)]</jsonpath> <to uri="mock:average"/> </when> <otherwise> <to uri="mock:expensive"/> </otherwise> </choice> </route> USD.store.book[?(@.price < 20)] store.book.price < 20 price < 20 left OP right store.book.price < USD{header.limit} from("direct:start") .choice() // use true to suppress exceptions .when().jsonpath("person.middlename", true) .to("mock:middle") .otherwise() .to("mock:other"); <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> from("direct:start") .choice() .when().jsonpath("USD.store.book[?(@.price < USD{header.cheap})]") .to("mock:cheap") .when().jsonpath("USD.store.book[?(@.price < USD{header.average})]") .to("mock:average") .otherwise() .to("mock:expensive"); <route> <from uri="direct:start"/> <choice> <when> <jsonpath>USD.store.book[?(@.price &lt; USD{header.cheap})]</jsonpath> <to uri="mock:cheap"/> </when> <when> <jsonpath>USD.store.book[?(@.price &lt; USD{header.average})]</jsonpath> <to uri="mock:average"/> </when> <otherwise> <to uri="mock:expensive"/> </otherwise> </choice> </route> .when().jsonpath("USD.store.book[?(@.price < 10)]", false, false) <jsonpath allowSimple="false">USD.store.book[?(@.price &lt; 10)]</jsonpath> public class Foo { @Consume("activemq:queue:books.new") public void doSomething(@JsonPath("USD.store.book[*].author") String author, @Body String json) { // process the inbound message here } } from("direct:start") .split().jsonpath("USD.store.book[*]") .to("log:book"); from("direct:start") .split().jsonpathWriteAsString("USD.store.book[*]") .to("log:book"); from("direct:start") .setHeader("numberOfBooks") .jsonpath("USD..store.book.length()", false, int.class, "books") .to("mock:result"); <route> <from uri="direct:start"/> <setHeader name="numberOfBooks"> <jsonpath headerName="books" resultType="int">USD..store.book.length()</jsonpath> </setHeader> <to uri="mock:result"/> </route>