我們都知道,Hive內(nèi)部提供了大量的內(nèi)置函數(shù)用于處理各種類型的需求,參見官方文檔:Hive Operators and User-Defined Functions (UDFs)。我們從這些內(nèi)置的 UDF 可以看到兩個用于解析 Json 的函數(shù):get_json_object和json_tuple。用過這兩個函數(shù)的同學肯定知道,其職能解析最普通的 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的優(yōu)勢就是一次可以解析多個 Json 字段。但是如果我們有個 Json 數(shù)組,這兩個函數(shù)都無法處理,get_json_object處理 Json 數(shù)組的功能很有限,如下:
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)
注意spark不支持,和hive的區(qū)別在于$后面有沒有.
select?
get_json_object(get_json_object(repair_content,'$.repairUseMac'),'$[0].count')
from
info.ods_xmsfactory_t_srv_factory_repair
where?id='XZ2007023300233'
如果我們想將整個 Json 數(shù)組里面的 website 字段都解析出來,如果這么寫將非常麻煩,因為我們無法確定數(shù)組的長度,而且即使確定了,這么寫可維護性也很差,所以我們需要想別的辦法。
如果想及時了解Spark、Hadoop或者Hbase相關(guān)的文章,歡迎關(guān)注微信公共帳號:iteblog_hadoop
使用Hive自帶的函數(shù)解析 Json 數(shù)組
在介紹如何處理之前,我們先來了解下Hive內(nèi)置的explode函數(shù),官方的解釋是: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 類型的數(shù)據(jù)作為輸入,然后將 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
相信不需要我描述大家就能看明白這個函數(shù)的意義。大家可能會問,這個函數(shù)和我們解析 Json 數(shù)組有毛關(guān)系啊。其實有關(guān)系,我們其實可以使用這個函數(shù)將 Json 數(shù)組里面的元素按照一行一行的形式輸出。根據(jù)這些已有的知識,我們可以寫出以下的 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 中文文檔"}
現(xiàn)在我們已經(jīng)能正確的解析 Json 數(shù)據(jù)了。
你現(xiàn)在肯定不知道上面一堆的 SQL 是啥含義,這里我來一步一步的解釋。
explode函數(shù)只能接收數(shù)組或 map 類型的數(shù)據(jù),而split函數(shù)生成的結(jié)果就是數(shù)組;
第一個regexp_replace的作用是將 Json 數(shù)組元素之間的逗號換成分號,所以使用完這個函數(shù)之后,[{"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 數(shù)組兩邊的中括號去掉,所以使用完這個函數(shù)之后,[{"website":"www.iteblog.com","name":"過往記憶"},{"website":"carbondata.iteblog.com","name":"carbondata 中文文檔"}]會變成{"website":"www.iteblog.com","name":"過往記憶"},{"website":"carbondata.iteblog.com","name":"carbondata 中文文檔"}
然后我們可以結(jié)合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)
自定義函數(shù)解析 Json 數(shù)組
雖然可以使用 Hive 自帶的函數(shù)類解析 Json 數(shù)組,但是使用起來還是有些麻煩。值得高興的是, Hive 提供了強大的自定義函數(shù)(UDF)的接口,我們可以使用這個功能來編寫解析 Json 數(shù)組的 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;
????????}
????}
}
上面的代碼邏輯很簡單,我就不介紹了。將上面的代碼進行編譯打包,假設(shè)打包完的 jar 包名稱為 iteblog.jar,然后我們就可以如下使用這個函數(shù)了。
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)
這個結(jié)果和上面使用 Hive 內(nèi)置的函數(shù)結(jié)果一致。當然,你還可以實現(xiàn)其他的 UDF,邏輯和這個類似,就不再介紹了。