Hive對JSON格式的支持研究


一、背景

JSON是一種通用的存儲格式,在半結構化存儲中十分常見,部分場景已經開始存在以JSON格式貼源存儲的數據,作為下游數據使用方,我們亟需對JSON格式的數據進行加工和處理,以提取出我們需要的數據,以對外提供更完善的數據服務。

經過調研,目前hive已對JSON格式的數據提供了相應的支持,但在一些復雜場景可能無法達到我們的需求以及理想的性能,具體介紹如下。

二、解析JSON對象的方法

(一)get_json_object(string json_string, string path)

  1. 返回值:String
  2. 說明:解析json的字符串json_string,返回path指定的內容。如果輸入的json字符串無效,那么返回NUll,這個函數每次只能返回一個數據項。
  3. 測試a:
--1 測試get_json_object()
select get_json_object('{"name":"小明","age":"18"}','$.name');

輸出:小明
  1. 測試b:

數據json.txt

{"movie":"1193","rate":"5","timeStamp":"978300760","uid":"1"}
{"movie":"661","rate":"3","timeStamp":"978302109","uid":"1"}
{"movie":"914","rate":"3","timeStamp":"978301968","uid":"1"}
{"movie":"3408","rate":"4","timeStamp":"978300275","uid":"1"}
{"movie":"2355","rate":"5","timeStamp":"978824291","uid":"1"}
{"movie":"1197","rate":"3","timeStamp":"978302268","uid":"1"}
{"movie":"1287","rate":"5","timeStamp":"978302039","uid":"1"}
{"movie":"2804","rate":"5","timeStamp":"978300719","uid":"1"}
{"movie":"594","rate":"4","timeStamp":"978302268","uid":"1"}
create table json(data string);

load data local inpath '/home/hadoop/json.txt' into table json;

select * from json;
 
select get_json_object(data,'$.movie') as movie from json;

輸出:
get_json_object()測試

  1. 限制

該方法只能返回一個JSON屬性的數據,不能同時返回多個。若為了獲取多個JSON屬性的數據而多次調用get_json_object方法,相當於對JSON數據進行重復多次解析,性能會有所損耗。那么,是否有一個方法能夠解析一次JSON數據,返回多個JSON屬性的數據呢?有,那就是json_tuple()。

(二)json_tuple(jsonStr, k1, k2, ...)

  1. 返回值:tuple
  2. 說明:參數為json字符串和一組鍵k1,k2,…,返回值的元組。因為可以在一次調用中輸入多次鍵,一次可以解析多個Json字段,因此該方法比get_json_object高效。
  3. 測試:
select b.b_movie,b.b_rate,b.b_timeStamp,b.b_uid from json a lateral view
json_tuple(a.data,'movie','rate','timeStamp','uid') b as b_movie,b_rate,b_timeStamp,b_uid;
  1. 限制

該方法和上面介紹的get_json_object()方法都無法對JSON數組進行解析。我們將在第三節對JSON數組的解析進行專題介紹。

(三)自定義函數解析json對象

  1. 自定義函數
1.
2.	package com.data;
3.	
4.	import org.apache.commons.lang3.StringUtils;
5.	import org.apache.hadoop.hive.ql.exec.UDF;
6.	import org.json.JSONException;
7.	import org.json.JSONObject;
8.	import org.json.JSONTokener;
9.	
10.	/**
11.	 *
12.	 *  add jar jar/getJsonObjectUDF.jar;
13.	 * create temporary function getJsonObject as 'com.data.JsonObjectParsing';
14.	 * Json對象解析UDF
15.	 * @Author: 
16.	 * @Date: 2020/9/25
17.	 */
18.	public class JsonObjectParsing extends UDF {
19.	    public static String evaluate(String jsonStr, String keyName) throws JSONException {
20.	        if(StringUtils.isBlank(jsonStr) || StringUtils.isBlank(keyName)){
21.	            return null;
22.	        }
23.	        JSONObject jsonObject = new JSONObject(new JSONTokener(jsonStr));
24.	        Object objValue = jsonObject.get(keyName);
25.	        if(objValue==null){
26.	            return null;
27.	        }
28.	        return objValue.toString();
29.	    }
30.	}
  1. 測試:
--1 將getJsonObjectUDF.jar上傳到hdfs
source ${HADOOP_CLIENT}/bigdata_env
kinit -k etluser/hadoop -t ${WORK_ROOT}/etl_tools/config/etluser.keytab

hadoop fs -rm -f -r /user/etluser/pgm/tempUDF
hadoop fs -mkdir -f -r /user/etluser/pgm/tempUDF
hdsoop fs -put ${WORK_ROOT}/pgm/tempUDF/getJsonObjectUDF.jar /user/etluser/pgm/tempUDF/

--2 創建臨時函數getJsonObject()
use udf;
set hive.security.temporary.function.need.admin=false;
create temporary function getJsonObject as 'com.data.JsonObjectParsing' using jar '/user/etluser/pgm/tempUDF/getJsonObjectUDF.jar';

--3 建表db.json
DROP TABLE IF EXISTS DB.JSON;
CREATE TABLE IF NOT EXISTS DB.JSON (
    JSON_DATA STRING COMMENT 'json'
)
COMMENT 'hive json'
PARTITIONED BY (pt_dt STRING)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\27'
STORED AS TEXTFILE;

--4 插入測試數據
INSERT INTO TABLE DB.JSON VALUES('{"id":"3","age":"12"}');

--5 使用臨時函數getJsonObject()
SELECT getJsonObject(JSON_DATA,'id') as id, getJsonObject(JSON_DATA,'age') as age FROM DB.JSON WHERE pt_dt='2020-09-25';

輸出:3  12

三、解析JSON數組的方法

(一)使用Hive自帶的函數解析Json數組

  1. 說明:Hive的內置的explode函數,explode()函數接收一個 array或者map 類型的數據作為輸入,然后將 array 或 map 里面的元素按照每行的形式輸出。其可以配合 LATERAL VIEW 一起使用。

  2. 測試:

select explode(array('A','B','C'));
輸出: A
       B
       C

select explode(map('A',10,'B',20,'C',30));
輸出:A       10
     B       20
     C       30

這個explode函數和我們解析json數據是有關系的,我們可以使用explode函數將json數組里面的元素按照一行一行的形式輸出:

SELECT explode(split(
    regexp_replace(
        regexp_replace(
            '[
                {"website":"www.baidu.com","name":"百度"},
                {"website":"google.com","name":"谷歌"}
            ]', 
            '\\[|\\]',''),  --將 Json 數組兩邊的中括號去掉
            
                 '\\}\\,\\{'    --將 Json 數組元素之間的逗號換成分號
                ,'\\}\\;\\{'),
                
                 '\\;'));    --以分號作為分隔符

輸出:{"website":"www.baidu.com","name":"百度"}
     {"website":"google.com","name":"谷歌"}

結合 get_json_object 或 json_tuple 來解析里面的字段:

select json_tuple(json, 'website', 'name') from (SELECT explode(split(regexp_replace(regexp_replace('[{"website":"www.baidu.com","name":"百},{"website":"google.com","name":"谷歌"}]', '\\[|\\]',''),'\\}\\,\\{','\\}\\;\\{'),'\\;')) as json) test;

輸出:
www.baidu.com   百度
google.com      谷歌

(二)自定義函數解析JSON數組

雖然可以使用Hive自帶的函數類解析Json數組,但是使用起來有些麻煩。Hive提供了強大的自定義函數(UDF)的接口,我們可以使用這個功能來編寫解析JSON數組的UDF。

  1. 自定義函數
1.
2.	package com.data;
3.	
4.	import org.apache.hadoop.hive.ql.exec.Description;
5.	import org.apache.hadoop.hive.ql.exec.UDF;
6.	import org.json.JSONArray;
7.	import org.json.JSONException;
8.	import java.util.ArrayList;
9.	
10.	
11.	public class JsonArray extends UDF{
12.	        public ArrayList<String> evaluate(String jsonString) {
13.	            if (jsonString == null) {
14.	                return null;
15.	            }
16.	            try {
17.	                JSONArray extractObject = new JSONArray(jsonString);
18.	                ArrayList<String> result = new ArrayList<String>();
19.	                for (int ii = 0; ii < extractObject.length(); ++ii) {
20.	                    result.add(extractObject.get(ii).toString());
21.	                }
22.	                return result;
23.	                } catch (JSONException e) {
24.	                return null;
25.	                } catch (NumberFormatException e) {
26.	                return null;
27.	            }
28.	        }
29.	
30.	}

將上面的代碼進行編譯打包,jar包名為:getJsonArrayUDF.jar

--1 將getJsonArrayUDF.jar上傳到hdfs
source ${HADOOP_CLIENT}/bigdata_env
kinit -k etluser/hadoop -t ${WORK_ROOT}/etl_tools/config/etluser.keytab

hadoop fs -rm -f -r /user/etluser/pgm/tempUDF
hadoop fs -mkdir -f -r /user/etluser/pgm/tempUDF
hdsoop fs -put ${WORK_ROOT}/pgm/tempUDF/getJsonArrayUDF.jar /user/etluser/pgm/tempUDF/

--2 創建臨時函數getJsonObject()
use udf;
set hive.security.temporary.function.need.admin=false;
create temporary function getJsonArray as 'com.data.JsonArray' using jar '/user/etluser/pgm/tempUDF/getJsonArrayUDF.jar';

--3 使用臨時函數getJsonObject()
select explode(getJsonArray('[{"website":"www.baidu.com","name":"百度"},{"website":"google.com"name":"谷歌"}]'));

輸出:www.baidu.com   百度
     google.com      谷歌

四、對where條件的支持

(一)get_json_object(string json_string, string path)對where條件的支持

  1. 是否支持:是
  2. 實驗
SELECT  b.b_movie,b.b_rate,b.b_timeStamp,b.b_uid from db.json a
lateral view json_tuple(a.json_data, 'movie', 'rate', 'timeStamp', 'uid') b as b_movie,b_rate,b_timestamp,b_uid
where a.pt_dt='2020-09-25'
and get_json_object(a.json_data,'$.movie')='661';

(1) 輸出:
661 3 9978302109 1

(2) 源表數據量:9條JSON格式數據,每條JSON數據又4個屬性。

(3) 耗時:35.39s

(二)json_tuple(jsonStr, k1, k2, ...)對where條件的支持

  1. 是否支持:是
  2. 實驗
SELECT  b.b_movie,b.b_rate,b.b_timeStamp,b.b_uid from db.json a
lateral view json_tuple(a.json_data, 'movie', 'rate', 'timeStamp', 'uid') b as b_movie,b_rate,b_timestamp,b_uid
where a.pt_dt='2020-09-25'
and b.b_movie='661' and b.b_rate='3';

(1) 輸出
661 3 978302109 1

(2) 源表數據量:9條JSON格式數據,每條JSON數據又4個屬性。

(3) 耗時:42.422s

五、總結

在一些數據加工場景下,例如,當我們需要獲取源表JSON字段中的相關信息時,就需要對該字段的JSON數據進行解析。這時候就十分需要Hive對JSON格式的數據解析提供支持。

目前Hive官方提供了get_json_object();json_tuple();explode();splite();regexp_replace()等函數,運用得當還是能解決不少問題。當然,這些函數的組合使用存在一定的限制性,編碼風格也較為復雜,可讀性較差;可以考慮使用自建UDF函數的方法,將JSON數據的解析放到Java等高級語言中去實現,簡化解析JSON數據時的復雜編碼,且能做到組件化復用。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM