天天看點

JsonUtil(基于Jackson的實作)

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的。

有機會日後會進行補充的。

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