JsonUtil(基於Jackson的實現)

血夜之末發表於2019-07-15

JsonUtil(基於Jackson的實現)

前言:

其實,我一直想寫一個有關Util的系列。

其中有四個原因:

  1. Util包作為專案的重要組成,是幾乎每個專案不可或缺的一部分。並且Util包的Util往往具有足夠的通用性,可用於不同的專案。
  2. Util包中的程式碼封裝往往非常有意思,對他們的學習,也有助於自身程式碼水平與認知的提高。
  3. 目前網上對Util包的總結很少,或者說很零散,沒有做成一個系列的。我希望能做成一個系列,以後缺什麼Util都可以直接通過這個系列找到需要的Util。
  4. 藉此機會,可以更好地與外界進行技術的交流,獲得更多的指導。

場景:

  1. 由於業務的需要(如session集中儲存),我們需要將某個物件(如使用者資訊)儲存到Redis中,而Redis無法儲存物件。所以我們需要將物件進行序列化操作,從而將物件儲存起來,並在日後提取出來時,進行反序列化。
  2. 由於業務的需求(如訊息佇列的訊息),我們需要將一組物件(如訂單資訊)傳送到訊息佇列,而訊息佇列是無法傳送物件的(RabbitMQ後面是支援的,另外,序列化的訊息,便於後臺檢視)。所以我們需要將一組物件進行序列化操作,從而將物件儲存起來,並在日後提取出來時,進行反序列化。

作用:

JsonUtil就是用來進行單個或複數個物件的序列化與反序列化操作。

程式碼:


    package top.jarry.learning.util;
    
    
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang3.StringUtils;
    import org.codehaus.jackson.map.DeserializationConfig;
    import org.codehaus.jackson.map.ObjectMapper;
    import org.codehaus.jackson.map.SerializationConfig;
    import org.codehaus.jackson.map.annotate.JsonSerialize;
    import org.codehaus.jackson.type.JavaType;
    import org.codehaus.jackson.type.TypeReference;
    
    import java.io.IOException;
    import java.text.SimpleDateFormat;
    
    /**
     * @Description:
     * @Author: jarry
     */
    @Slf4j
    public class JsonUtil {
    
        // 建立Jackson的ObjectMapper物件
        private static ObjectMapper objectMapper = new ObjectMapper();
    
        // 建立Json操作中的日期格式
        private static final String JSON_STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss";
        // DateTimeUtil.STANDARD_FORMAT = "yyyy-mm-dd HH:mm:ss";   
                // 日期格式如果設定為這個,會出現月份出錯的問題(先是5月變3月,然後就不斷增加,甚至超過12月),具體原因待查
    
        static {
    
            //物件的所有欄位全部列入
            objectMapper.setSerializationInclusion(JsonSerialize.Inclusion.ALWAYS);
    
            //取消預設轉換timestamps形式
            objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false);
    
            //忽略空Bean轉json的錯誤
            objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false);
    
            //所有的日期格式都統一為以下的樣式
            objectMapper.setDateFormat(new SimpleDateFormat(JSON_STANDARD_FORMAT));
    
            //反序列化
            //忽略 在json字串中存在,但是在java物件中不存在對應屬性的情況。防止錯誤
            objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        }
    
        /**
         * 完成物件序列化為字串
         * @param obj 源物件
         * @param <T>
         * @return
         */
        public static <T> String obj2String(T obj) {
            if (obj == null) {
                return null;
            }
            try {
                return obj instanceof String ? (String) obj : objectMapper.writeValueAsString(obj);
            } catch (Exception e) {
                log.warn("Parse Object to String error", e);
                return null;
            }
        }
    
        /**
         * 完成物件序列化為字串,但是字串會保證一定的結構性(提高可讀性,增加字串大小)
         * @param obj 源物件
         * @param <T>
         * @return
         */
        public static <T> String obj2StringPretty(T obj) {
            if (obj == null) {
                return null;
            }
            try {
                return obj instanceof String ? (String) obj : objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
            } catch (Exception e) {
                log.warn("Parse Object to String error", e);
                return null;
            }
        }
    
        /**
         * 完成字串反序列化為物件
         * @param str 源字串
         * @param clazz 目標物件的Class
         * @param <T>
         * @return
         */
        public static <T> T string2Obj(String str, Class<T> clazz) {
            if (StringUtils.isEmpty(str) || clazz == null) {
                return null;
            }
            try {
                return (clazz == String.class) ? (T) str : objectMapper.readValue(str, clazz);
            } catch (IOException e) {
                log.warn("Parse String to Object error", e);
                return null;
            }
        }
    
        //jackson在反序列化時,如果傳入List,會自動反序列化為LinkedHashMap的List
        //所以過載一下方法,解決之前String2Obj無法解決的問題
    
        /**
         * 進行復雜型別反序列化工作 (自定義型別的集合型別)
         *
         * @param str 源字串
         * @param typeReference 包含elementType與CollectionType的typeReference
         * @param <T>
         * @return
         */
        public static <T> T string2Obj(String str, TypeReference<T> typeReference) {
            if (StringUtils.isEmpty(str) || typeReference == null) {
                return null;
            }
            try {
                return (T) ((typeReference.getType().equals(String.class)) ? str : objectMapper.readValue(str, typeReference.getClass()));
            } catch (IOException e) {
                log.warn("Parse String to Object error", e);
                return null;
            }
        }
    
        /**
         * 進行復雜型別反序列化工作(可變型別數量的)
         *
         * @param str             需要進行反序列化的字串
         * @param collectionClass 需要反序列化的集合型別 由於這裡的型別未定,且為了防止與返回值型別T衝突,故採用<?>表示泛型
         * @param elementClasses  集合中的元素型別(可多個)   此處同上通過<?>...表示多個未知泛型
         * @param <T>             返回值的泛型型別是由javatype獲取的
         * @return
         */
        public static <T> T string2Obj(String str, Class<?> collectionClass, Class<?>... elementClasses) {
            JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass, elementClasses);
            try {
                return objectMapper.readValue(str, javaType);
            } catch (IOException e) {
                log.warn("Parse String to Object error", e);
                return null;
            }
        }
    }

依賴:

  1. commons-lang3
  2. jackson(該jar包可能有點老,可以考慮更新,不過可以正常使用)

應用:


        public void onInitializationInclinationMessage(String initializationInclinationStr,
                                                       @Headers Map<String, Object> headers, Channel channel) throws IOException {
    
            log.info("InitializationInclinationConsumer/onInitializationInclinationMessage has received: {}", initializationInclinationStr);
    
            // 1.接收資料,並反序列化出物件
            InitializationInclination initializationInclination = JsonUtil.string2Obj(initializationInclinationStr, InitializationInclination.class);
    
            // 2.資料校驗,判斷是否屬於該終端資料
            if (initializationInclination == null) {
                Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
                channel.basicAck(deliveryTag, false);
            }
    
            if (!GuavaCache.getKey(TERMINAL_ID).equals(initializationInclination.getTerminalId())) {
                log.info("refuse target initializationInclination with terminalId({}).current_terminalId({})", initializationInclination.getTerminalId(), GuavaCache.getKey(TERMINAL_ID));
                return;
            }
    
            // 3.將訊息傳入業務服務,進行消費
            ServerResponse response = iInitializationInclinationService.receiveInitializationInclinationFromMQ(initializationInclination);
    
            // 4.對成功消費的資料進行簽收
            if (response.isSuccess()) {
                //由於配置中寫的是手動簽收,所以這裡需要通過Headers來進行簽收
                Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
                channel.basicAck(deliveryTag, false);
            }

        public void onInclinationTotalMessage(String inclinationTotalListStr, @Headers Map<String, Object> headers, Channel channel) throws Exception {
    
            log.info("InclinationConsumer/onInclinationTotalMessage has received: {}", inclinationTotalListStr);
    
            // 1.對訊息進行反序列化操作
            List<InclinationTotal> inclinationTotalList = JsonUtil.string2Obj(inclinationTotalListStr, List.class, InclinationTotal.class);
    
            // 2.對資料進行校驗
            if (CollectionUtils.isEmpty(inclinationTotalList)){
                //由於配置中寫的是手動簽收,所以這裡需要通過Headers來進行簽收
                Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
                channel.basicAck(deliveryTag, false);
            }
    
            // 3.將訊息傳入業務服務,進行消費
            ServerResponse response = iInclinationService.insertTotalDataByList(inclinationTotalList);
    
            // 4.對成功消費的資料進行簽收
            if (response.isSuccess()) {
                //由於配置中寫的是手動簽收,所以這裡需要通過Headers來進行簽收
                Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
                channel.basicAck(deliveryTag, false);
            }
        }

問題:

在使用這個JsonUtil的過程中,遇到過一個問題,就是日期序列化,反序列化,出現問題。

不過,經過一次次除錯與追蹤後,發現只要修改了日期格式就可以避免這個問題(其實當時真的沒有想到Util會出現這種問題,所以花了不少時間)。

具體原因,問了一圈,也沒有得到答案。看來只能留待日後了。

總結:

Json序列化的Util當然不止這一種。還有很多方式,乃至不是基於Jackson的,如基於Gson的。
有機會日後會進行補充的。

如果大家對這個系列有什麼意見或者期待,可以給我留言,謝謝。

相關文章