天天看點

c# 把一個list轉為json_含有泛型的 JSON 反序列化

c# 把一個list轉為json_含有泛型的 JSON 反序列化

一、背景

今天無聊之園提了一個問題,涉及的示例大緻如下:

public static void main(String[] args) {

 String jsonString = "["a","b"]";

 List<String> list = JSONObject.parseObject(jsonString, List.class);

 System.out.println(list);

}
           

為什麼 IDEA 會給出下面的警告,該如何解決?

c# 把一個list轉為json_含有泛型的 JSON 反序列化

有些同學說直接使用抑制注解,抑制掉這個警告就好了。

抑制掉警告就可以了????

二、分析

2.1 事出詭異必有妖

IDEA 不會無緣無故給出警告提示,警告的原因上圖已經給出。

把不帶泛型的 List 指派給帶泛型的 List, Java 編譯器并不知道右側傳回不帶泛型的實際 List 是否符合帶泛型的 List 限制。

和下面的例子非常類似:

public static void main(String[] args) {

 List first = new ArrayList();

 first.add(1);

 first.add("2");

 first.add('3');

​

 // 提示上述警告

 List<String> third = first;

 System.out.println(third);

 }
           

将 first 指派給 third 時,不能保證 first 元素符合 List的限制,即清單中全是 String。

如果你執行上述代碼,會發現沒有報錯,哈哈。

但是如果你使用 foreach 循環或者疊代器取 String 循環時會發生類型轉換異常。

public static void main(String[] args) {

 List first = new ArrayList();

 first.add(1);

 first.add("2");

 first.add('3');

​

 List<String> third = first;

 for (String each : third) { // 類型轉換異常

 System.out.println(each);

 }

 }
           

類型轉換異常?

我們使用 IDEA 的 jclasslib 反編譯插件,得到 main 函數的 Code 如下:

0 new #2 <java/util/ArrayList>

3 dup

4 invokespecial #3 <java/util/ArrayList.>

7 astore_1

8 aload_1

9 iconst_1

10 invokestatic #4 <java/lang/Integer.valueOf>

13 invokeinterface #5 <java/util/List.add> count 2

18 pop

19 aload_1

20 ldc #6 <2>

22 invokeinterface #5 <java/util/List.add> count 2

27 pop

28 aload_1

29 bipush 51

31 invokestatic #7 <java/lang/Character.valueOf>

34 invokeinterface #5 <java/util/List.add> count 2

39 pop

40 aload_1

41 astore_2

42 aload_2

43 invokeinterface #8 <java/util/List.iterator> count 1

48 astore_3

49 aload_3

50 invokeinterface #9 <java/util/Iterator.hasNext> count 1

55 ifeq 79 (+24)

58 aload_3

59 invokeinterface #10 <java/util/Iterator.next> count 1

64 checkcast #11 <java/lang/String>

67 astore_4

69 getstatic #12 <java/lang/System.out>

72 aload_4

73 invokevirtual #13 <java/io/PrintStream.println>

76 goto 49 (-27)

79 return

從 42 到76 行 對應 foreach 循環的邏輯,可以看出底層使用 List 的疊代器進行周遊,取出每個元素後強轉為 String 類型,存儲到局部變量表索引為 4 的位置,然後進行列印。

如果對反編譯不熟悉可以去 target 目錄,輕按兩下編譯後的class 檔案,使用 IDEA 自帶的插件進行反編譯:

//

// Source code recreated from a .class file by IntelliJ IDEA

// (powered by Fernflower decompiler)

//

​

package com.chujianyun.common.json;

​

import java.util.ArrayList;

import java.util.Iterator;

import java.util.List;

​

public class JsonGenericDemo {

 public JsonGenericDemo() {

 }

​

 public static void main(String[] args) {

 List first = new ArrayList();

 first.add(1);

 first.add("2");

 first.add('3');

 List<String> third = first;

 Iterator var3 = first.iterator();

​

 while(var3.hasNext()) {

 String each = (String)var3.next();

 System.out.println(each);

 }

​

 }

}
           

印證了上述說法,顯然在

String each = (String)var3.next();

這裡出現了類型轉換異常。

三、解決之道

3.1 猜想驗證

我們猜測是不是可以通過某種途徑将泛型作為參數傳給 fastjson, 讓 fastjson 某個傳回值是帶泛型的,進而解決這個告警呢?

顯然我們要去源碼中尋找, 在 JSONObject 類中找到了下面的方法:

/** 

 * <pre>

 * String jsonStr = "[{"id":1001,"name":"Jobs"}]";

 * List<Model> models = JSON.parseObject(jsonStr, new TypeReference<List<Model>>() {});

 * </pre>

 * @param text json string

 * @param type type refernce

 * @param features

 * @return

 */

 @SuppressWarnings("unchecked")

 public static <T> T parseObject(String text, TypeReference<T> type, Feature... features) {

 return (T) parseObject(text, type.type, ParserConfig.global, DEFAULT_PARSER_FEATURE, features);

 }
           

該函數的注釋上還貼心地給出了相關用法,是以我們改造下:

public static void main(String[] args) {

 String jsonString = "["a","b"]";

 List<String> list = JSONObject.parseObject(jsonString, new TypeReference<List<String>>() {

 });

 System.out.println(list);

}
           

警告解除了。

是以大功告成?

難道上述做法僅僅是為了消除一個警告,滿足強迫症們的心願而已嗎??

且慢,我們看下面的例子:

import lombok.Data;

@Data
public class User {
 private Long id;

 private String name;
}
           
import com.alibaba.fastjson.JSON;

import com.alibaba.fastjson.JSONObject;

import java.util.ArrayList;

import java.util.List;

public class JsonGenericDemo {

 public static void main(String[] args) {

 // 構造資料

 User user = new User();

 user.setId(0L);

 user.setName("tom");

​

 List<User> users = new ArrayList<>();

 users.add(user);

 // 轉為JSON字元串
 String jsonString = JSON.toJSONString(users);


 // 反序列化
 List<User> usersGet = JSONObject.parseObject(jsonString, List.class);

 for (User each : usersGet) {
 System.out.println(each);
 }
 }
}
           

大家執行上述例子會出現類型轉換異常!

Exception in thread “main” java.lang.ClassCastException: com.alibaba.fastjson.JSONObject cannot be cast to com.chujianyun.common.json.User at com.chujianyun.common.json.JsonGenericDemo.main(JsonGenericDemo.java:26)

有了第二部分的分析,大家可能就可以比較容易地想到

JSONObject.parseObject(jsonString, List.class) 構造出來的 List 存放的是 JSONObject 元素, foreach 循環底層使用疊代器周遊每個元素并強轉為 User 類型是報類型轉換異常。

那麼為啥 fastjson 不能幫我們轉換為

List<User>

類型呢?

有人說“由于泛型擦除,沒有泛型資訊,是以無法逆向構造回原有類型”。

其實看下

JSONObject.parseObject(jsonString, List.class);

第一個參數是字元串,第二個參數是 List.class。

作為這個工具函數本身,怎麼猜得到要 List 裡面究竟該存放啥類型呢?

是以如果能夠通過某種途徑,告訴它泛型的類型,就可以幫助你反序列化成真正的類型。

使用

JSONObject.parseObject(jsonString, new TypeReference<List<User>>() { });

即可。

是以

我們使用 TypeReference 并不僅僅是為了消除警告,而是為了告知 fastjson 泛型的具體類型,正确反序列化泛型的類型

那麼底層原理是啥呢?我們看下

com.alibaba.fastjson.TypeReference#TypeReference()

/**

 * Constructs a new type literal. Derives represented class from type

 * parameter.

 *

 * <p>Clients create an empty anonymous subclass. Doing so embeds the type

 * parameter in the anonymous class's type hierarchy so we can reconstitute it

 * at runtime despite erasure.

 */

 protected TypeReference(){

 // 擷取父類的 Type

 Type superClass = getClass().getGenericSuperclass();

​

 // 如果父類是參數化類型,會傳回 java.lang.reflect.ParameterizedType

 // 調用 getActualTypeArguments 擷取實際類型的數組 并拿到第一個

 Type type = ((ParameterizedType) superClass).getActualTypeArguments()[0];

​

 // 緩存中有優先取緩存,沒有則存入并設定

 Type cachedType = classTypeCache.get(type);

 if (cachedType == null) {

 classTypeCache.putIfAbsent(type, type);

 cachedType = classTypeCache.get(type);

 }

 this.type = cachedType;
 }
           

通過代碼和注釋我們了解到:

建立一個空的匿名子類。将類型參數嵌入到匿名繼承結構中,即使運作時類型擦除也可以重建。

再回到 parseObject 函數,可以看到底層用的就是這個 type。

/**

 * <pre>

 * String jsonStr = "[{"id":1001,"name":"Jobs"}]";

 * List<Model> models = JSON.parseObject(jsonStr, new TypeReference<List<Model>>() {});

 * </pre>

 * @param text json string

 * @param type type refernce

 * @param features

 * @return

 */

 @SuppressWarnings("unchecked")

 public static <T> T parseObject(String text, TypeReference<T> type, Feature... features) {

 return (T) parseObject(text, type.type, ParserConfig.global, DEFAULT_PARSER_FEATURE, features);

 }
           
3.2 舉一反三

很多其他架構也會采用類似的方法來擷取泛型類型。

大家可以看看其他 gson 類庫

<dependency>
  <groupId>com.google.code.gson</groupId>
  <artifactId>gson</artifactId>
  <version>2.8.6</version>
</dependency>
           

中的

com.google.gson.reflect.TypeToken

類,是不是似曾相識呢?

此外,如果我們自己除了 JSON反序列化場景之外也有類似擷取泛型參數的需求,是不是也可以采用類似的方法呢?

四、總結

希望大家能夠重視 IDEA 的警告。

遇到問題能夠從更合理的角度思考,了解問題的本質。

學習一個問題可以嘗試舉一反三,活學活用。

作者:明明如月

連結:http://www.imooc.com/article/details/id/308298

來源:慕課網

本文首次釋出于慕課網 ,轉載請注明出處,謝謝合作