Camel组件-JSONata组件

Camel官网资料: https://camel.apache.org/components/latest/jsonata-component.html
JSONata官网资料: http://docs.jsonata.org/overview.html

Jsonata组件允许你使用JSONATA规范语法进行JSON与JSON之间的消息转换,或者JSON与其他消息之间的转换。
Maven用户使用该组件需添加相关依赖:

<dependency>
  <groupId>org.apache.camel.springboot</groupId>
  <artifactId>camel-jsonata-starter</artifactId>
  <version>x.x.x</version>
  <!-- use the same version as your Camel core version -->
</dependency>

URL格式:

jsonata:specName[?options]
  • spaceName: 调用转换规则的文件路径

组件属性:

名称 描述 默认状态 类型
lazyStartProducer 生产者懒加载机制. 延迟启动可避免生产者启动过程中发生异常导致路由启动失败. false Boolean
autowiredEnabled 是否启动自动装配. 通过在注册表中进行查找是否存在匹配类型的实例, 将该实例作为自动装配实例并在组件上进行配置. 例: JDBC数据源. true Boolean

JSLT VS JSONATA:

  • 在JSLT转换规则中,支持if,else,else if等分支语句;JSONATA不支持上述分支语句,其支持三目运算法"?",具体示例下面会展示.
  • 使用JSNATA组件时在进行JSON转换后, 应重新将参数进行设置(<converBodyTo type="String"),否则将报错;JSLT没有太多约束.
  • JSONATA组件在发起时应在路径中规定入参格式, 即&inputType=JsonString; JSLT不需要.

JSONata语法:

数据示例:

{
  "FirstName": "Fred",
  "Surname": "Smith",
  "Age": 28,
  "Address": {
    "Street": "Hursley Park",
    "City": "Winchester",
    "Postcode": "SO21 2JN"
  },
  "Phone": [
    {
      "type": "home",
      "number": "0203 544 1234"
    },
    {
      "type": "office",
      "number": "01962 001234"
    },
    {
      "type": "office",
      "number": "01962 001235"
    },
    {
      "type": "mobile",
      "number": "077 7700 1234"
    }
  ],
  "Email": [
    {
      "type": "work",
      "address": ["fred.smith@my-work.com", "fsmith@my-work.com"]
    },
    {
      "type": "home",
      "address": ["freddy@my-social.com", "frederic.smith@very-serious.com"]
    }
  ],
  "Other": {
    "Over 18 ?": true,
    "Misc": null,
    "Alternative.Address": {
      "Street": "Brick Lane",
      "City": "London",
      "Postcode": "E1 6RF"
    }
  }
}

基础语法:

对于基本数据格式:

Surname ==> "Smith"
Age ==> 28
Address.City ==> "Winchester"
Other.Misc ==> "null"
Other.'Over 18 ?' ==> true

对于数组:

返回第一个数据:
Phone[0] ==>

{ "type": "home", "number": "0203 544 1234" }

返回最后一个数据:
Phone[-1] ==>

{ "type": "mobile", "number": "077 7700 1234" }

倒序返回数据:
Phone[-2] ==

{ "type": "office", "number": "01962 001235" }

指定字段:
Phone[0].number ==>

"0203 544 1234"

未指定索引:
Phone.number ==>

[ "0203 544 1234", "01962 001234", "01962 001235", "077 7700 1234" ]

通过索引数组返回数据:
Phone[[0..2]] ==>

[
  { "type": "home", "number": "0203 544 1234" },
  { "type": "office", "number": "01962 001234" },
  { "type": "office", "number": "01962 001235" },
]

返回指定属性数据:
Phone[type = 'mobile'] ==>

    { "type": "mobile",  "number": "077 7700 1234" }

Phone[type = 'mobile'].number ==>

"077 7700 1234"

Phone[type = 'office'].number ==>

[ "01962 001234",  "01962 001235" ]

特殊的, 利用"[]"来进行返回数组, 如上述例子中:
Phone[][type='mobile'].number ==>

[ "077 7700 1234" ]

通配符:

"*":

使用*来代替字段名

  1. Address.* ==>
[ "Hursley Park", "Winchester", "SO21 2JN" ]

返回Address下的所有属性对应值.

  1. *.Postcode ==>
"SO21 2JN"

返回子对象中Postcode对应值.
仅返回单节点下对应字段属性值.

"**":

无视子节点深度, 加强版"*.字段名"
**.Postcode ==>

[ "SO21 2JN", "E1 6RF" ]

不仅返回Address下的Postcode值, 同时返回Other.Alternative.Address.Postcode对应值.

常用函数:

在Jsonata中有很多函数, 它与Java中的用法类似. 大致如下:

字符函数:

$string(arg, prettify)

arg有以下规则:

  • 字符串不可被改变.
  • 函数可以转换空字符串.
  • 无穷大的数字与NaN将会抛出异常, 因为其不能表示为Json.
  • JSON.stringify函数将所有值转换为Json格式.

注意事项:

  • 如果arg未指定(即不带任何参数调用此函数), 则将上下文的值用作arg.
  • 如果prettify为true, 则转换为"prettified"JSON.(每行自动缩进).

示例:

$string(12345) => "12345"
[1,2,3].$string => ["1", "2", "3"]

$length(str)

返回字符串中的字符数.

  • 如果str未指定(即不带任何参数调用此函数), 则将上下文值用作str.
  • 如果str不是字符串, 则会抛出异常.

示例:
$length("Hello World") => 11

$substring(str, start[, length])

返回指定长度和指定开始下标的目标字符串.

  • 如果str未指定(即仅使用数字参数调用此函数), 则将上下文值用作str.
  • 如果str不是字符串, 则会抛出异常.
  • 如果length指定, 则返回字符串将包含指定length长度.
  • 如果start为负, 表示从尾部开始的字符数.

示例:

$substring("Hello World", 3) => "lo World"
$substring("Hello World", 3, 5) => "lo Wo"
$substring("Hello World", -4) => "orld"
$substring("Hello World", -4, 2) => "or"

$substringBefore(str, chars)

返回的字符串为"chars"在"str"中第一次出现前的子字符串.

  • 如果str未指定(即仅使用一个参数调用此函数), 则将上下文值用作str.
  • 如果str不包含chars, 则返回str.
  • 如果str和chars不是字符串, 则会抛出异常.

示例:

$substringBefore("Hello World", " ") => "Hello"

$substringAfter(str, chars)

返回的字符串为"chars"在"str"中第一次出现后的子字符串.

  • 如果str未指定(即仅使用一个参数调用此函数), 则将上下文值用作str.
  • 如果str不包含chars, 则返回str.
  • 如果str和chars不是字符串, 则会抛出异常.

示例:
$substringAfter("Hello World", " ") => "World"

$uppercase(str)

返回一个字符串, 其中str的所有字符都转换为大写.

  • 如果str未指定(即不带任何参数调用此函数), 则将上下文值用作str.
  • 如果str不是字符串, 则会抛出异常.

示例:
$uppercase("Hello World") => "HELLO WORLD"

$lowercase(str)

返回一个字符串, 其中str的所有字符都转换为小写.

  • 如果str未指定(即不带任何参数调用此函数), 则将上下文值用作str.
  • 如果str不是字符串, 则会抛出异常.

示例:
$lowercase("Hello World") => "hello world"

$trim(str)

str字符串将通过以下步骤来规范和修改所有空格字符:

  • 所有制表符, 回车符和换行符均替换为空格.

  • 连续的空格符修改为单个空格.

  • 首尾空格已删除.

  • 如果str未指定(即不带任何参数调用此函数), 则将上下文值用作str.

  • 如果str不是字符串, 则会抛出异常.

示例:
$trim(" Hello \n World ") => "Hello World"

$pad(str, width [, char])

返回字符串str的副本并对它额外填充. 并且它的字符的总数是width参数的绝对值.

  • 如果width是正数, 则该字符串被填充到右侧; 如果为负数, 则将其填充到左侧.可选的char参数指定填充字符(多个)使用.
  • 如果未指定, 则默认为空格字符.

示例:

$pad("foo", 5) => "foo  "
$pad("foo", -5) => "  foo"
$pad("foo", -5, "#") => "##foo"
$formatBase(35, 2) ~> $pad(-8, '0') => "00100011"

$contains(str, pattern)

str符合pattern的匹配规则, 返回true, 否则返回false. 该pattern参数可以是字符串或正则表达式(regex).

  • 如果str未指定(即使用一个参数调用此函数), 则将上下文值用作str.
  • 如果是字符串, pattern的字符在str中存在且连续则返回true, 否则该函数将返回str.
  • 如果它是正则表达式, 则当该正则表达式与的内容匹配时返回true, 否则该函数将返回str.

示例:

$contains("abracadabra", "bra") => true
$contains("abracadabra", /a.*a/) => true
$contains("abracadabra", /ar.*a/) => false
$contains("Hello World", /wo/) => false
$contains("Hello World", /wo/i) => true
Phone[$contains(number, /^077/)] => { "type": "mobile", "number": "077 7700 1234" }

$split(str, separator [, limit])

将str参数拆分为子字符串数组. 该separator参数可以是字符串或正则表达式(regex).

  • 如果str未指定, 则将上下文值用作str.
  • 如果str不是字符串, 则会抛出异常.
  • 如果是字符串, 则指定str应该在其中拆分的字符.
  • 如果为空字符串, str将被拆分为单个字符的数组.
  • 如果它是正则表达式, 将字符串与匹配该正则表达式的任何字符分开.
  • limit参数是一个数字, 它指定包含在结果数组中的最大子字符串数, 任何其他子字符串都将被丢弃.
  • 如果limit未指定, 则将str完全拆分, 对结果的数组大小没有限制.
  • 如果limit不是非负数, 则会抛出异常.

示例:

$split("so many words", " ") => [ "so", "many", "words" ]
$split("so many words", " ", 2) => [ "so", "many" ]
$split("too much, punctuation. hard; to read", /[ ,.;]+/) => ["too", "much", "punctuation", "hard", "to", "read"]

$join(array[, separator])

将单个字符串拼接成一个字符串中, 每个组件字符串可由separator参数分隔.

  • 如果输入数组包含不是字符串的项目, 则会抛出异常.
  • 如果separator未指定, 则默认为空字符串, 即各个字符串之间没有分隔符.
  • 如果separator不是字符串, 则会抛出异常.

示例:

$join(['a','b','c']) => "abc"
$split("too much, punctuation. hard; to read", /[ ,.;]+/, 3) ~> $join(', ') => "too, much, punctuation"

$match(str, pattern [, limit])

将str字符串与pattern正则表达式进行匹配, 返回一个对象数组, 每个对象都包含每次匹配的字符串.
每个对象包含以下字段:

  • match 通过正则表达式匹配的子字符串.

  • index 匹配所需要偏移的字符数.

  • groups 如果正则表达式包含括号, 则它表示所匹配的字符串的数组.

  • 如果str未指定, 则将上下文值用作str.

  • 如果str不是字符串, 则会抛出异常.

示例:

$match("ababbabbcc",/a(b+)/) =>
[
  {
    "match": "ab",
    "index": 0,
    "groups": ["b"]
  },
  {
    "match": "abb",
    "index": 2,
    "groups": ["bb"]
  },
  {
    "match": "abb",
    "index": 5,
    "groups": ["bb" ]
  }
]

$base64encode()

Base64加密.

示例:
$base64encode("myuser:mypass") ===> "bXl1c2VyOm15cGFzcw=="

$base64decode()

Base64解密.

示例:
$base64decode("bXl1c2VyOm15cGFzcw==") ===> "myuser:mypass"

$encodeUrlComponent(str)

对路径参数和资源符进行加密.

示例:
$encodeUrlComponent("?x=test") ===> "%3Fx%3Dtest"

$encodeUrl(str)

对路径参数进行加密.

示例:

$encodeUrl("[https://mozilla.org/?x=](https://mozilla.org/?x=)шеллы") ===> "[https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B](https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B)"

$decodeUrlComponent(str)

解密

示例:
$decodeUrlComponent("%3Fx%3Dtest") ===> "?x=test"

$decodeUrl(str)

解密

示例:

$decodeUrl("[https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B"](https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B")) ===> "[https://mozilla.org/?x=](https://mozilla.org/?x=)шеллы"

布尔型函数:

$exists(arg)

判断arg是否存在, 存在则返回true, 不存在返回false, 可在该方法基础上进行参数判断.

  • 如果arg不穿则会抛出异常.

示例:

对于
{
  "req": "logout",
  "agentId": **.workNumber,
  "tenantid": "default"
}
$exists(retCode) => false 
对于
{
  "resp" : {
    "respCode" : retCode = "1" ? "0" : retCode
  }
}
$exists(retCode) => true

时间函数:

$now([picture [, timezone]])

返回符合ISO 8601格式的UTC时间戳的字符串.

  • 如果定义了时间格式和时区(即picture与timezone), 则会返回对应的格式, 与$fromMillis()方法类似.

示例:
$now() => "2021-03-26T10:16:34.152Z"

$millis()
将返回的时间进行毫秒处理.

示例:
$millis() => 1616724968087

$fromMillis(number [ , picture [ , timezone]])

将传入的number按照对应的规则与时区进行转换.

  • 如果picture未进行传值, 默认采用ISO 8601格式转换.
  • 如果提供了picture参数, 那么将按照该参数定义的规则进行格式化. 特殊的, 此函数的定义规则与Xpath/XQuery函数中的fn: format-dateTime的参数版本应保持一致.
  • 如果提供了timezone参数, 那么格式化的时间应在该定义时区. timezone的格式为"±HHMM", 加减为时区偏移量. UTC以东的为正偏移, UTC以西的为负偏移.

示例:

$fromMillis(1510067557121) => "2017-11-07T15:12:37.121Z"
$fromMillis(1510067557121, '[M01]/[D01]/[Y0001] [h#1]:[m01][P]') => "11/07/2017 3:12pm"
$fromMillis(1510067557121, '[H01]:[m01]:[s01] [z]', '-0500') => "10:12:37 GMT-05:00"

其他运算符:

&

合并运算符, 将字符串拼接.
如果该运算中参数不为字符串, 将先执行$string方法进行转换.

示例:

"Hello" & "World" => "HelloWorld"

? :

三目运算符, 可根据实际情况返回相对应结果.

示例:

Price < 50 ? "Cheap" : "Expensive"
  • Price为属性, 其对应值如果小于50则返回"Cheap", 反之"Expensive".
  • 其中判断条件和响应结果可自定义.
  • 可与其他函数结合使用.(如下述实例).

:=

自定义函数, 函数规则可以是函数式也可以是常量.

示例:

$four := 4
$square := function($n) { $n * $n }

~>

函数连接链, 可将多层嵌套函数进行简化书写.

示例:

$uppercase($substringBefore($substringAfter(Customer.Email, "@"), "."))  =====> 
Customer.Email ~> $substringAfter("@") ~> $substringBefore(".") ~> $uppercase()

$sum(Account.Order.Product.(Price * Quantity)) =====>
Account.Order.Product.(Price * Quantity) ~> $sum()

该运算符也可以与上述:=结合使用.
如:

$uppertrim := $trim ~> $uppercase;
$uppertrim("       Hello      World    ")  ==> 
"HELLO WORLD"
  • 该示例为创建一个新函数"uppertrim", 该函数为"trim"与"$uppercase"两个方法结合.
  • 可根据实际情况设置相关函数.

... ~> | ... | ... |

对象转换操作符, 可进行参数值的改变或其部分属性的删除.
语法结构:
head ~> | location | update [, delete] |

  • head为要进行相关操作的属性变量.
  • location为操作的属性变量中需要改变的部分.
  • update为需要更新的location中的属性, 并在更新完后重新整合在location中.
  • delete为在执行相关操作后, 对指定属性进行删除, 可省略.

示例:
| Account.Order.Product | { 'Price' : Price * 1.2 } |
该例为将Product属性中的Price值增加20%. 当需要多次调用时, 可与上述":="或"~>"相结合:
$increasePrice := |Account.Order.Product|{'Price': Price * 1.2}| 或 payload ~> |Account.Order.Product|{'Price': Price * 1.2}|

同时可以对变量添加属性:
| Account.Order.Product | { 'Price' : Price * 1.2 , 'Total' : Price * Quantity} |
该表达式为变量添加一个新属性, 但需要注意的是, "Total"中的"Price"指的是原属性中"Price"对应的值, 而不是改变后的Price值.

可以删除变量中相关属性:
$ ~> |Account.Order.Product|{'Total': Price * Quantity}, ['Price', 'Quantity']|
对于"Product", 在每次运行时都会添加一条"Total"属性, 同时会删除其"Price"与"Quantity"属性.

实例:

电话渠道的坐席签出操作:

传入参数:
xml参数:
Accept : application/xml

<?xml version="1.0" encoding="UTF-8"?>
<signOff>
 <appId>b23abb6d451346efa13370172d1921ef</appId>
 <workNumber>00000001</workNumber>
</signOff>

Json传参:
Accept : application/json

{
 "signOff" : {
 "appId" : "b23abb6d451346efa13370172d1921ef",
 "workNumber" : "00000001"
 } }

路由代码:

<route xmlns="http://camel.apache.org/schema/spring" id="signOff">
  <from uri="rest:post:signOff"/>
  <convertBodyTo type="String"/>
  <when>
    <!--XML传参-->
    <simple>${header.Accept} == 'application/xml'</simple>
    <to uri="xj:file:config/camel/transform/xml/X2J.xsl?transformDirection=XML2JSON"/>
  </when>
  <!--传入参数转换-->
  <to uri="jsonata:file:config/camel/transform/json/fl_signOff.json?contentCache=true&amp;inputType=JsonString"/>
  <convertBodyTo type="String"/>
  <!--打印参数转换-->
  <log message="request params --------------> ${body}"/>
  <to uri="http:{{freelink.server.url}}?bridgeEndpoint=true"/>
  <!--响应参数转换-->
  <to uri="jsonata:file:config/camel/transform/json/fl_signOff_response.json?contentCache=true&amp;inputType=JsonString"/>
</route>
  • xj: Camel组件, 详情看xj组件文档.

fl_signOff.json:

{
  "req": "logout",
  "agentId" : **.workNumber,
  "tenantid":"default"
}

  • 实现参数名称转换.
  • ** : Jsonata特定语法, 直接找到目标节点, 忽略其是否有父节点. 该特性解决XML文件转换时忽略掉根节点的问题.

fl_signOff_response.json:

{
    "resp" : {
    "respCode" : retCode = "1" ? "0" : retCode
    }
}
  • 实现参数名称转换.
  • 三目运算符解决参数判断(可嵌套判断, 可自定义响应结果).

整合参数:

$exists(retCode) = true
?
({
  "resp" : {
    "respCode" : retCode = "1" ? "0" : retCode
  }
})
:
{
  "req": "logout",
  "agentId": **.workNumber,
  "tenantid": "default"
}
  • $exists()判断该次响应是否发送给freeLink.其中括号里的字段名为freeLink处理该请求后响应的字段.
  • 运用"?"该运算符目的为将两次参数格式转换文件整合在一个文件中, 方便维护. 同样可以将两次参数格式转换文件分别写出.

日志打印:

[图片上传失败...(image-94022d-1667401091691)]
[图片上传失败...(image-aab07e-1667401091691)]

相关异常以及解决方法(踩坑)

  • com.api.jsonata4java.expressions.ParseException: 此异常为编写转换规则中语法不规范或标点符号导致, 检查转换规则中的语句编写书否有错误或标点符号是否正确.
  • No body available of type: java.io.InputStream but has value: 此异常为在接收参数并转换后发送请求时, 未重新设置参数导致. 解决方法为在通过JSONATA组件转换后, 添加"<convertBodyTo type="String"/>"或添加<setBody>标签.
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355

推荐阅读更多精彩内容