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),"@“和””取的是指定位置的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