我們都知道,Hive 內部提供了大量的內置函數用於處理各種類型的需求,參見官方文檔:Hive Operators and User-Defined Functions (UDFs)。我們從這些內置的 UDF 可以看到兩個用於解析 Json 的函數:get_json_object
和 json_tuple
。用過這兩個函數的同學肯定知道,其職能解析最普通的 Json 字符串,如下:
hive (
default
)>
SELECT
get_json_object(
'{"website":"www.iteblog.com","name":"過往記憶"}'
,
'$.website'
);
OK
www.iteblog.com
hive (
default
)>
SELECT
json_tuple(
'{"website":"www.iteblog.com","name":"過往記憶"}'
,
'website'
,
'name'
);
OK
www.iteblog.com 過往記憶
Time
taken: 0.074 seconds, Fetched: 1 row(s)
|
json_tuple
相對於 get_json_object
的優勢就是一次可以解析多個 Json 字段。但是如果我們有個 Json 數組,這兩個函數都無法處理,get_json_object
處理 Json 數組的功能很有限,如下:
hive (
default
)>
>
>
SELECT
get_json_object(
'[{"website":"www.iteblog.com","name":"過往記憶"}, {"website":"carbondata.iteblog.com","name":"carbondata 中文文檔"}]'
,
'$.[0].website'
);
OK
www.iteblog.com
Time
taken: 0.069 seconds, Fetched: 1 row(s)
|
如果我們想將整個 Json 數組里面的 website 字段都解析出來,如果這么寫將非常麻煩,因為我們無法確定數組的長度,而且即使確定了,這么寫可維護性也很差,所以我們需要想別的辦法。
使用 Hive 自帶的函數解析 Json 數組
在介紹如何處理之前,我們先來了解下 Hive 內置的 explode
函數,官方的解釋是:explode() takes in an array (or a map) as an input and outputs the elements of the array (map) as separate rows. UDTFs can be used in the SELECT expression list and as a part of LATERAL VIEW. 意思就是 explode()
接收一個 array 或 map 類型的數據作為輸入,然后將 array 或 map 里面的元素按照每行的形式輸出。其可以配合 LATERAL VIEW
一起使用。光看文字描述很不直觀,咱們來看看幾個例子吧。
hive (
default
)>
select
explode(array(
'A'
,
'B'
,
'C'
));
OK
A
B
C
Time
taken: 4.188 seconds, Fetched: 3 row(s)
hive (
default
)>
select
explode(map(
'A'
,10,
'B'
,20,
'C'
,30));
OK
A 10
B 20
C 30
|
相信不需要我描述大家就能看明白這個函數的意義。大家可能會問,這個函數和我們解析 Json 數組有毛關系啊。其實有關系,我們其實可以使用這個函數將 Json 數組里面的元素按照一行一行的形式輸出。根據這些已有的知識,我們可以寫出以下的 SQL 語句:
hive (
default
)>
SELECT
explode(split(regexp_replace(regexp_replace(
'[{"website":"www.iteblog.com","name":"過往記憶"},{"website":"carbondata.iteblog.com","name":"carbondata 中文文檔"}]'
,
'{'
,
'\\}\\;\\{'
),
'\\[|\\]'
,
''
),
'\\;'
));
OK
{
"website"
:
"www.iteblog.com"
,
"name"
:
"過往記憶"
}
{
"website"
:
"carbondata.iteblog.com"
,
"name"
:
"carbondata 中文文檔"
}
|
現在我們已經能正確的解析 Json 數據了。
explode
函數只能接收數組或 map 類型的數據,而split
函數生成的結果就是數組;- 第一個
regexp_replace
的作用是將 Json 數組元素之間的逗號換成分號,所以使用完這個函數之后,[{"website":"www.iteblog.com","name":"過往記憶"},{"website":"carbondata.iteblog.com","name":"carbondata 中文文檔"}] 會變成 [{"website":"www.iteblog.com","name":"過往記憶"};{"website":"carbondata.iteblog.com","name":"carbondata 中文文檔"}] - 第二個
regexp_replace
的作用是將 Json 數組兩邊的中括號去掉,所以使用完這個函數之后,[{"website":"www.iteblog.com","name":"過往記憶"},{"website":"carbondata.iteblog.com","name":"carbondata 中文文檔"}] 會變成 {"website":"www.iteblog.com","name":"過往記憶"},{"website":"carbondata.iteblog.com","name":"carbondata 中文文檔"}
然后我們可以結合 get_json_object
或 json_tuple
來解析里面的字段了:
hive (
default
)>
select
json_tuple(json,
'website'
,
'name'
)
from
(
SELECT
explode(split(regexp_replace(regexp_replace(
'[{"website":"www.iteblog.com","name":"過往記憶"},{"website":"carbondateblog.com","name":"carbondata 中文文檔"}]'
,
'\\}\\,\\{'
,
'\\}\\;\\{'
),
'\\[|\\]'
,
''
),
'\\;'
))
as
json) iteblog;
OK
www.iteblog.com 過往記憶
carbondata.iteblog.com carbondata 中文文檔
Time
taken: 0.189 seconds, Fetched: 2 row(s)
|
自定義函數解析 Json 數組
雖然可以使用 Hive 自帶的函數類解析 Json 數組,但是使用起來還是有些麻煩。值得高興的是, Hive 提供了強大的自定義函數(UDF)的接口,我們可以使用這個功能來編寫解析 Json 數組的 UDF。具體的代碼如下:
package
com.iteblog.udf.json;
import
org.apache.hadoop.hive.ql.exec.Description;
import
org.apache.hadoop.hive.ql.exec.UDF;
import
org.json.JSONArray;
import
org.json.JSONException;
import
java.util.ArrayList;
@Description
(name =
"json_array"
,
value =
"_FUNC_(array_string) - Convert a string of a JSON-encoded array to a Hive array of strings."
)
public
class
UDFJsonAsArray
extends
UDF {
public
ArrayList<String> evaluate(String jsonString) {
if
(jsonString ==
null
) {
return
null
;
}
try
{
JSONArray extractObject =
new
JSONArray(jsonString);
ArrayList<String> result =
new
ArrayList<String>();
for
(
int
ii =
0
; ii < extractObject.length(); ++ii) {
result.add(extractObject.get(ii).toString());
}
return
result;
}
catch
(JSONException e) {
return
null
;
}
catch
(NumberFormatException e) {
return
null
;
}
}
}
|
上面的代碼邏輯很簡單,我就不介紹了。將上面的代碼進行編譯打包,假設打包完的 jar 包名稱為 iteblog.jar,然后我們就可以如下使用這個函數了。
hive (
default
)>
add
jar /home/iteblog/iteblog.jar;
Added [/home/iteblog/iteblog.jar]
to
class path
Added resources: [/home/iteblog/iteblog.jar]
hive (
default
)>
create
temporary
function
json_array
as
'com.iteblog.udf.json.UDFJsonAsArray'
;
OK
Time
taken: 0.013 seconds
hive (
default
)>
>
select
explode(json_array(
'[{"website":"www.iteblog.com","name":"過往記憶"},{"website":"carbondata.iteblog.com","name":"carbondata 中文文檔"}]'
));
OK
{
"website"
:
"www.iteblog.com"
,
"name"
:
"過往記憶"
}
{
"website"
:
"carbondata.iteblog.com"
,
"name"
:
"carbondata 中文文檔"
}
Time
taken: 0.08 seconds, Fetched: 2 row(s)
hive (
default
)>
select
json_tuple(json,
'website'
,
'name'
)
from
(
SELECT
explode(json_array(
'[{"website":"www.iteblog.com","name":"過往記憶"},{"website":"carbondata.iteblog.com","name":"carbta 中文文檔"}]'
))
as
json) iteblog;
OK
www.iteblog.com 過往記憶
carbondata.iteblog.com carbondata 中文文檔
Time
taken: 0.082 seconds, Fetched: 2 row(s)
|
這個結果和上面使用 Hive 內置的函數結果一致。當然,你還可以實現其他的 UDF,邏輯和這個類似,就不再介紹了。