深入解析Jolt

Jolt概览

目前json数据已经逐步取代xml成为主流的数据交换和存储格式。在数据交互过程中经常遇到数据格式不一致的问题,在xml时代有XSTL(Extensible Stylesheet Language Transformation)可以实现,在json时代,我们可以用jolt。在当今大数据场景主流的ETL pipeline 工具如NIFI和 StreamSets都支持jolt作为json转换插件,在读取ElasticSearch, MongoDb, Cassandra等数据时可以用jolt做数据格式转换。

Jolt的核心功能:

  • 提供了一组转换,可以“链接”在一起形成整体JSON到JSON的转换。
  • 专注于转换JSON数据的结构,而不是操纵特定值
  • 消费并生成新结构的JSON:实际是内存中的Map,List,String等。

jolt插件主要转(transfrom)换方法
(1)shift:主要作用是拷贝,将输入json的某个结构拷贝到目标json
(2)default:主要作用是设置默认值,如果输入json某个value不存在,则使用默认值补齐
(3)remove:主要作用是删除,可以删除输入json结构的某个结构
(4)sort:对key值进行排序
(5)cardinality:jsonobject和jsonarray之间的切换
(6)modify:修改json数值

每个转换都有自己的DSL(Domain Special Language 领域特定语言),以便定义于它特定的工作。目前,所有默认插件变换只会影响数据的“结构”。要进行数据操作,需要自行编写Java代码。

Jolt转换语法规则DSL的说明

Jolt使用json数据定义(specification)转换规则。由于json语法结构的限制,jolt需要组合不同的DSL以便解析程序能够正确的执行、避免歧义。所以jolt约定转换规则的输入必须是一个json array,通过定义多个转换spec定义形成一个转换链从而实现在不同语法规则场景下的转换。
Jolt spec示例:

[
  {
    "operation": "shift",
    "spec": {}
  },
  {
    "operation": "modify-default-beta",
    "spec": {}
  }
  ,
  {
    "operation": "sort"
  }
]

shift:

在shift语境中,json的key值表示输入json的路径,value值表示目标路径。简单理解就是key是“匹配”,value是“去哪”,告诉程序匹配哪部分数据输出到什么位置 。多少数据转换场景是同样业务含义的字段名称不一样,位置不一样等,这种可以通过shift语义实现。

shift通配符

  • “*” 通配符:仅可以匹配input的key,不能匹配value
    使用 “*”通配符的时候,需要保证输入json具有相同的层级结构,它可以匹配某一个层级结构中的所有key,也可以匹配一个key的部分字符。
  • “&”通配符
    “&”通配符有两个参数&(0,1),第一个参数0表示从当前层级回溯多少级去寻找所需key,第二个参数1表示取第几个匹配到的参数(0表示取整体,1表示取第一个匹配结果,2表示取第二个匹配结果)。参数可以省略,&=&0=&(0)=&(0,0)
  • “$”通配符
    KaTeX parse error: Expected 'EOF', got '&' at position 5: 通配符和&̲通配符具有相同的语法结构,都有…通配符通常是将输入Json的key值作为目标json的value使用,
  • “@”通配符
    “@”通配符也有两个参数@(0,1),"@“和” " 用 法 类 似 , 不 过 " @ " 取 得 是 指 定 位 置 的 v a l u e 作 为 目 标 J s o n 的 v a l u e 值 , 而 “ "用法类似,不过"@"取得是指定位置的value作为目标Json的value值,而“ ""@"valueJsonvalue,”取的是指定位置的key作为目标Json的value值
  • “#”通配符
    “#”通配符最有用的一点是,可以按照输入Json某个字段的取值,对输出Json设置不同的值
  • “|”通配符
    “|”表示“或”的逻辑,很容易理解,它只会对列出的字符进行匹配

shift数据转换场景

(1)拷贝JSONObject

{
  "*": {
    "@": "[&1]"
  }
}

(2)字典映射
定义

 "TYPE": {
        "A": {
          "#AA": "TYPE"
        },
        "B": {
          "#BB": "TYPE"
        }
}

输入

{
  "TYPE": "A"
}

输出

{
  "TYPE" : "AA"
}

如前文所说“jolt所有默认插件变换只会影响数据的结构,要进行数据操作,需要自行编写Java代码”。数据字典码表映射是典型的数据值的操作,枚举值较少的情况下,可以通过以上变通的方法实现。但如果有大量的码表,则需要另外自行实现代码。

(3)json key输出成value

{
	"foo": {
		"*": {
			"$": "test.&(1,0).id"
		}
	}
}

输入

{
	"foo": {
		"barKey":"barValue",
		"bazKey":"bazValue"
	}
}

输出

{
	"test": {
		"barKey": {
			"id": "barKey"
		},
		"bazKey": {
			"id": "bazKey"
		}
	}
}

modify:

jolt内置了一些修改json数据的方法,通过modify语法实现,包含modify-overwrite-beta、modify-default-beta、modify-define-beta。
在modify语境中,json的key值表示输入json的路径,value值表示对该节点value的变换。在modify语境中语法定义的叶子节点所指向的input结构中必须也是叶子节点,这样才能够对这个叶子节点的值进行修改。

modify的“=”操作符

Modifier定义了大量的内置函数,可以实现常见的数据转换,比如大小写转换、字符串处理、求和、计数等、排序等。在value定义字符串中以“=”号开头标识后续是方法引用。modify语境中vaule值只能是常量或者输入json中的值(以@符号引用)。
支持以下语法:

  • =abs 这是个语法糖,等价于=abs(@(1,&0))
  • =abs(@(1,&0)) 调用“abs”函数,参数是当前节点的value,@(1,&0)的含义同shift中一样,基于当前key的位置获得相对位置的value
  • =abs(@(1,&0),-1,-3) 调用“abs”函数,除输入json中的value外,还有其他的两个参数。

自定义数据处理

jolt默认不处理数据值的转换,在复杂的数据字典映射时上文的定义方式非常繁琐,我们需要自定义数据字典的定义语义。
通常情况下,数据字典映射是两个字符串集合的一一对应,json的key-value结构刚好符合这个结构,在jolt整体语义体系下,我们可以做这样的约定:

{
	"operation": "com.github.weixingluo.json.transfrom.DataDictionaryTransform",
	"spec": {
		"rating": {
			"primary": {
				"value": "=dict(@(1,name),dictName)"
			}
		},
		"dictionary": {
			"dictName": {
				"x": "yy",
				"y": "zz"
			}
		}
	}
}

因为字典映射类似于修改,所以我们直接接用modity的语义定义,只是针对字典映射表做特殊处理。也就是说json的key值表示输入json的路径,value值表示对该节点value的变换。
在operation定义了一个新的语义,我们在spec中定义了一个特殊的属性“dictionary”,我们在这个对象中定义字典映射表。除"dictionary"外,我们认为其他的key值都是普通的json转换定义。我们新定义个函数“dict”,约定“dict(@(1,name),dictName)”含义为对当前key的值做映射,依赖的数据映射字典为“dictName”。这样我们就定义了一个基于字典映射当前节点value到一个新的集合的方案。
为了让我们的定义执行起来,我们需要在jolt的体系中实现自己的代码。jolt在编译spec定义时,如果operation的值不在默认的值范围时,jolt会默认这是一个类路径,并尝试通过默认构造函数反射出来。在spec中我们定义了一个新的映射,jolt在初始化DataDictionaryTransform时将spec整个map作为构造函数传给了DataDictionaryTransform。
核心代码

public class DataDictionaryFunction  implements Function {
	private Map<String,Object> dictionary;
	public DataDictionaryFunction(Map<String,Object> dictionary) {
		this.dictionary = Collections.unmodifiableMap(dictionary);
	}

	@Override
	public Optional<Object> apply(Object... args) {
	//...
		Map dict = (Map)dictionary.get(args[1]);
		Object value = dict.get(args[0]);
		return value==null?Optional.empty():Optional.of(value);
	}
}
public class DataDictionaryTransform implements ContextualTransform, SpecDriven {
	//...
	public DataDictionaryTransform(Object spec) {
		//...
        Object dict = spec.get(dictString);
		Map<String, Object> dictionary = (Map<String, Object>) dict;
		DataDictionaryFunction dictMapping = new DataDictionaryFunction(dictionary);
		Map<String,Function> functionsMap = new HashMap<>();
		functionsMap.put("dict", dictMapping);

		functionsMap = Collections.unmodifiableMap(functionsMap);
		TemplatrSpecBuilder templatrSpecBuilder = new TemplatrSpecBuilder(OpMode.OVERWRITR, functionsMap);
		rootSpec = new ModifierCompositeSpec(ROOT_KEY, (Map<String, Object>) spec, OpMode.OVERWRITR,
				templatrSpecBuilder);
	}

	@Override
	public Object transform( final Object input, final Map<String, Object> context ) {
		Map<String, Object> contextWrapper = new HashMap<>(  );
        contextWrapper.put( ROOT_KEY, context );

        MatchedElement rootLpe = new MatchedElement( ROOT_KEY );
        WalkedPath walkedPath = new WalkedPath();
        walkedPath.add( input, rootLpe );

        rootSpec.apply( ROOT_KEY, Optional.of( input), walkedPath, null, contextWrapper );
        return input;
	}

}

参考

Jolt官方文档: https://github.com/bazaarvoice/jolt
jolt插件使用简介: https://blog.csdn.net/m0_37674755/article/details/85530142
JSON JOLT介绍 及语法详解-shift篇: https://blog.csdn.net/weixin_36048246/article/details/89287200
jolt操作演示: https://jolt-demo.appspot.com

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值