jolt插件的主要作用是将一种json格式转化为另外一种json格式,它通过使用配置文件定义转换规则的方法,使得json转换可配置,只要输入的json格式是固定的(这一点很重要)就可以使用jolt进行转换。本篇主要介绍常用的几种转换方式,感兴趣的童鞋可以前往GitHub学习该插件的时服用方法。jolt插件GitHub地址:https://github.com/bazaarvoice/jolt/tree/jolt-0.1.0#Overview
一、jolt插件主要转换方法与基本使用
插件提供了5种转换方法:
(1)shift:主要作用是拷贝,将输入json的某个结构拷贝到目标json
(2)default:主要作用是设置默认值,如果输入json某个value不存在,则使用默认值补齐
(3)remove:主要作用是删除,可以删除输入json结构的某个结构
(4)sort:对key值进行排序
(5)cardinality:jsonobject和jsonarray之间的切换
注:5种转换方法分别对应了5个Java类:Shiftr
、Defaultr
、Removr
、Sortr
、CardinalityTransform
,如果定义的转换规则中只是使用到了以上转换中的一种,则可以使用这5个类的transform
方法转换,如果转换规则中使用了两种或者两种以上的转换,则需要使用另外一个类Chainr
中的转换方法。
以Shiftr
为例,java代码如下:
Object input = JsonUtils.classpathToObject("/jolt/Shiftr/sample01/input.json"); //读取input.json,如果input已经是一个java对象,则可以忽略这步
Object spec = JsonUtils.classpathToObject("/jolt/Shiftr/sample01/spec.json"); //使用Jolt自带的JsonUtils读取配置的转换规则spec.json作为一个object
Shiftr shiftr=new Shiftr(spec); //创建shiftr对象,传入转化规则
Object output = shiftr.transform(input); //转换input,得到output
System.out.println(JsonUtils.toJsonString(output));
input.json文件内容:
{
"foo": {
"barKey": "barValue",
"bazKey": "bazValue"
}
}
spec.json内容(具体怎样编写,后面会详细介绍):
{
"foo": {
"*": {
"$": "test.&(1,0).id"
}
}
}
其他类型进行转换的时候与此类似,使用Chainr
转换有点特殊,java代码如下:
Object input = JsonUtils.classpathToObject("/jolt/Chainr/sample01/input.json");
List<Object> spec = JsonUtils.classpathToList("/jolt/Chainr/sample01/spec.json"); //和其他类型不同,此处配置的转换规则是一个jsonarray,需要转换为List
Chainr chainr = Chainr.fromSpec(spec);
Object output = chainr.transform(input);
System.out.println(JsonUtils.toJsonString(output));
chainr转换的时候,转换规则配置的是一个jsonarray,通过operation来区分不同的转换方法,operation取值有shift,default,remove,cardinality,sort
,每一种转换的规则作为spec的value,如:
[{
"operation": "shift",
"spec": {
"rating": {
"quality": {
"value": "SecondaryRatings.quality.Value",
"max": "SecondaryRatings.quality.RatingRage"
}
}
}
}]
项目中引入jolt很简单,只需要在pom文件中增加以下依赖:
<dependency>
<groupId>com.bazaarvoice.jolt</groupId>
<artifactId>jolt-core</artifactId>
<version>${latest.jolt.version}</version>
</dependency>
<dependency>
<groupId>com.bazaarvoice.jolt</groupId>
<artifactId>json-utils</artifactId>
<version>${latest.jolt.version}</version>
</dependency>
目前最新版本是2018年5月2日更新的0.1.1。
二、shift转换
shift转换应该是我们平时使用最多的转换方式。与该种转换对应的类为Shiftr
。首先看一下最简单的转换:
如果有以下的input(输入json):
{
"rating": {
"quality": {
"value": 3,
"max": 5
}
}
}
我们想得到如下的output(目标json):
{
"SecondaryRatings": {
"quality": {
"Value": 3,
"RatingRange": 5
}
}
}
我们对比发现,原始json格式中,rating.quality.value
的3需要放置在SecondaryRatings.quality.Value
这样的结构中,rating.quality.max
的5需要放置在SecondaryRatings.quality.RatingRange
这个层级中,使用jolt,我们就可以编写如下的转换规则(spec):
{
"rating": {
"rating": {
"value": "SecondaryRatings.quality.Value",
"max": "SecondaryRatings.quality.RatingRange"
}
}
}
我们看到,转换规则的结构和input的结构是一致的,只不过转换规则中的value,其实是一个路径信息,即需要将该值拷贝到output结构的哪一个路径。如以上的转换规则,就是指需要将rating.rating.value
的值拷贝到SecondaryRatings.quality.Value
。
shift转换的基本用法就这么简单,不过,shift转换的最强大的地方,在于它的通配符。
(1)“*”
通配符(仅可以匹配input的key,不能匹配value)
使用 “*”
通配符的时候,需要保证输入json具有相同的层级结构,它可以匹配某一个层级结构中的所有key,也可以匹配一个key的部分字符。
匹配某层级所有key:
{
"rating": {
"quality": {
"value": 3,
"max": 5
},
"sharpness": {
"value": 7,
"max": 10
}
}
}
以上输入中,rating.quality
和rating.sharpness
具有相同的层级结构,可以使用rating.*
来代替rating下面的所有key(quality
和sharpness
)。
匹配key的部分字符:
{
"tag-Pro": "Awesome",
"tag-Con": "Bogus"
}
以上输入中,key值tag-Pro
和tag-Con
具有相同的开头"tag-"
,所以我们可以使用tag-*
来匹配具有相同开头的tag-Pro
和tag-Con
。
(2)“&”
通配符
“&”
通配符有两个参数&(0,1)
,第一个参数0表示从当前层级回溯多少级去寻找所需key,第二个参数1表示取第几个匹配到的参数(0表示取整体,1表示取第一个匹配结果,2表示取第二个匹配结果)。参数可以省略,&=&0=&(0)=&(0,0)
例如,有如下输入:
{
"foo": {
"test-bar-1": {
"bar-1": "test-1"
},
"test-baz-2": {
"baz-2": "test-2"
}
}
}
我们想经过转换得到如下结果:
{
"test": {
"bar": {
"bar-1": "test-1"
},
"baz": {
"baz-2": "test-2"
}
}
}
我们就可以编写如下的转换规则:
{
"foo":{
"test-*-*":{
"*":"test.&(1,1).&"
}
}
}
解释一下,转换规则中test-*-*
用来匹原json中foo
的子级结构test-bar-1
和test-baz-2
,当匹配到之后,我们再用*
来分别匹配下一层的bar-1
和baz-2
,bar-1
,baz-2
对应的值要放到哪里呢?我们想分别放到test.bar.bar-1
和test.baz.baz-2
下面,所以转换规则中的&(1,1)表示从当前层级回溯1级取值,即test-*-*
的值,而且我们想让key值为bar
或者baz
,所以我们就取第1个*
匹配到的值,所以写为&(1,1)
。然后对于第三层的bar-1
和baz-2
,我们想把自身的key作为输出json的key,所以我们就取当前层级的key值,即&
,或者&0
,&(0,0)
(3)“$”
通配符
$
通配符和&
通配符具有相同的语法结构,都有两个参数,“&”
通配符通常用于获取输入Json中某个key或者某部分key作为目标Json结构的key,$
通配符通常是将输入Json的key值作为目标json的value使用,如有以下输入:
{
"foo": {
"barKey":"barValue",
"bazKey":"bazValue"
}
}
我们想得到以下输出:
{
"test": {
"barKey": {
"id": "barKey"
},
"bazKey": {
"id": "bazKey"
}
}
}
那么,我们可以编写如下的转化规则:
{
"foo": {
"*": {
"$": "test.&(1,0).id"
}
}
}
我们使用"*"
通配符来匹配barKey
和bazKey
,然后使用"$"
通配符来获取barKey
和bazKey
,并将其作为value,写入到test.barKey.id
和test.bazKey.id
中。
(4)“@”
通配符
“@”
通配符也有两个参数@(0,1)
,"@"
和"$"
用法类似,不过"@"
取得是指定位置的value作为目标Json的value值,而“$”
取的是指定位置的key作为目标Json的value值,对于(3)中的输入,我们修改转换规则中的"$"
为"@"
,即
{
"foo": {
"*": {
"@": "test.&(1,0).id"
}
}
}
经过转换后,输出就会变为
{
"test": {
"barKey": {
"id": "barValue"
},
"bazKey": {
"id": "bazValue"
}
}
}
对比(3)中的转换结果,发现只是将转换规则中的“$”修改为了“@”,原先的barKey,bazKey就会变为barValue,bazValue,通过对比很明显可以看到两种通配符的区别
(5)“#”
通配符
“#”通配符最有用的一点是,可以按照输入Json某个字段的取值,对输出Json设置不同的值,例如:
{
"hidden": {
"true": {
"#disabled": "clients.clientId"
},
"false": {
"#enable": "clients.clientId"
}
}
}
当hidden=true的时候,就会向clients.clientId写入disabled,当hidden=false的时候,就会向clients.clientId写入enable
(6)“|”
通配符
“|”
表示“或”的逻辑,很容易理解,它只会对列出的字符进行匹配,如规则:
{
"sex":{
"male|female":{
"#normal":"status"
}
}
}
当sex取值male或者female的时候,才会给status设值normal。
(7)jsonarray的转换。
jsonarray的转换比较简单,举个例子说明:
input:
{
"Photos": ["AAA.jpg","BBB.jpg"]
}
spec:
{
"Photos": {
"*": "photo-&-url"
}
}
output
{
"photo-0-url": "AAA.jpg",
"photo-1-url": "BBB.jpg"
}
通过"*"
可以匹配所有的元素,&
获取当前元素的索引,数组中取值的时候,0,1,2等代表第1,2,3个元素,*
可以获取所有元素
三、default转换
default转换比较简单,对应的java类是Defaultr
,主要作用是预先设置一些默认值,如果input中不存在该值,则使用默认值进行补齐。
如有如下输入:
{
"Rating": 3,
"SecondaryRatings": {
"quality": {
"Range": 7,
"Value": 3,
"Id": "quality"
},
"sharpness": {
"Value": 4,
"Id": "sharpness"
}
}
}
想得到这样的输出:
{
"Rating": 3,
"RatingRange": 5,
"SecondaryRatings": {
"quality": {
"Range": 7,
"Value": 3,
"Id": "quality",
"MaxLabel": "Great",
"MinLabel": "Terrible",
"DisplayType": "NORMAL"
},
"sharpness": {
"Range": 5,
"Value": 4,
"Id": "sharpness",
"DisplayType": "NORMAL"
}
}
}
可以设置转换规则:
{
"RatingRange": 5,
"SecondaryRatings": {
"quality": {
"MaxLabel": "Great",
"MinLabel": "Terrible",
"DisplayType": "NORMAL"
},
"sharpness": {
"Range": 5
},
"*": {
"DisplayType": "NORMAL"
}
}
}
可以看到,input中没有RatingRange=5,我们在spec的相应位置设置了default值,在output中就展现了该值,其他值的设置也是类似的。
四、remove转换
remove也很简单,作用是删除input中的某些值。如果需要删除input中的个别字段,而其他字段保持不变,建议使用该转换。
如有以下input:
{
"ratings": {
"Set1": {
"a": "a",
"b": "b"
},
"Set2": {
"c": "c",
"b": "b"
}
}
}
我们想删除ratings.Set1.b以及ratings.Set2.b,那么spec规则可以这样写:
{
"ratings": {
"*": {
"b": ""
}
}
}
使用"*"
匹配ratings
下面的所有元素,然后删除key=b
的元素,转化后结果:
{
"ratings": {
"Set1": {
"a": "a"
},
"Set2": {
"c": "c"
}
}
}
以上主要介绍了shift,default,remove的使用,这三种也是经常会用到的三种转换方法。以下列举出一些例子,方便大家对规则的编写有个更深的了解。
五、转换实例
(1)拷贝JSONObject
输入一个JSONObject,想原封不动将该JSONObject拷贝到输出,我们可以使用如下规则:
{
"*":{
"@":"&0"
}
}
我们使用“*”
通配符匹配了JSONObject中的所有key,然后使用“@”
通配符获取所有value,放到“&0”
,即当前value所在的key中,最终经过转换,其实只是做了一次JSONObject的拷贝而已。
(2)拷贝JSONArray
输入一个JSONArray,想原封不动的将JSONArray拷贝到输出,转化规则:
{
"*": {
"@": "[&1]"
}
}
此处使用“*”
通配符匹配了数组中所有的元素,其实此处的“*”
是数组中元素的索引,即0,1,2…,然后对于数组中的每一个元素,使用“@”
通配符匹配value值,将value值放到[&1]
即数组的对应的索引处,完成拷贝。
(3)多条件判断
存在一种情况,当输入参数中某两个值为特定值的时候,需要将某些字段设置为特定值。如以下Json结构中,当action等于starting,并且type等于computer的时候,我们需要向原Json结构中增加一个字段status=running。
{
"id":"123",
"action":"starting",
"type":"computer"
}
我们可以编写如下转换规则:
{
"id":"id",
"type":"type",
"@(0,action)":"action",
"action":{
"starting":{
"@(2,type)":{
"computer":{
"#running":"status"
}
}
}
}
}
首先将id,type,action等参数进行拷贝,然后对action的取值进行判断,如果action等于starting,这时候需要继续判断type的取值,我们使用了@(2,type)表示从当前层级回溯2级去取type的值,然后对type进行判断,当type等于computer的时候,我们使用"#"通配符添加一个字段status,取值为running.
经过转化后,我们得到的结果如下:
{
"action": "starting",
"id": "123",
"type": "computer",
"status": "running"
}