首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Jackson 反序列化 JSON 到泛型 List 完整指南:TypeReference 與 JavaType 兩種方案

     編輯:Java 相關知識

前言

Jackson 是 Java 生態中性能最優的 JSON 庫之一。將 JSON 字符串反序列化為 List<YourBean> 等泛型集合時,因 Java 泛型擦除,需顯式傳遞類型信息。本文詳解 TypeReference 與 JavaType 兩種核心方案、嵌套泛型處理、常見坑點及性能優化建議。

一、 核心問題:類型擦除

// 錯誤示例:運行時拋出 JsonMappingException
List<User> users = mapper.readValue(json, List.class); 
// 實際得到 List<LinkedHashMap>,非 List<User>

編譯期泛型信息擦除,運行時 List.class 無法感知元素類型,Jackson 預設反序列化為 LinkedHashMap。

二、 方案一:TypeReference(最常用、語義清晰)

import com.fasterxml.jackson.core.type.TypeReference;

List<User> users = mapper.readValue(jsonString, 
    new TypeReference<List<User>>() {});
  • 利用匿名內部類保留泛型類型信息(通過 getGenericSuperclass() 獲取)。
  • 適用於大多數集合、Map、嵌套泛型場景。
  • 注意:必須使用匿名內部類形式 new TypeReference<...>() {},直接 new TypeReference<List<User>>() 無效(編譯期類型參數仍被擦除)。

三、 方案二:JavaType(更靈活、程序化構建)

import com.fasterxml.jackson.databind.JavaType;

// 構建 List<User> 類型
JavaType listType = mapper.getTypeFactory().constructCollectionType(List.class, User.class);
List<User> users = mapper.readValue(jsonString, listType);

// 構建 Map<String, User> 類型
JavaType mapType = mapper.getTypeFactory().constructMapType(HashMap.class, String.class, User.class);
Map<String, User> map = mapper.readValue(jsonString, mapType);

// 構建複雜嵌套類型:Map<String, List<User>>
JavaType innerList = mapper.getTypeFactory().constructCollectionType(List.class, User.class);
JavaType complexMap = mapper.getTypeFactory().constructMapType(HashMap.class, String.class, innerList);
Map<String, List<User>> complex = mapper.readValue(jsonString, complexMap);
  • TypeFactory 提供豐富工廠方法:constructCollectionType、constructMapType、constructParametricType(通用參數化類型)。
  • 適合類型在運行期動態確定、或需構建高度嵌套泛型的場景。

四、 進階場景與最佳實踐

1. 通用反序列化工具方法

public class JacksonUtils {
    private static final ObjectMapper MAPPER = new ObjectMapper();
    
    static {
        MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        MAPPER.registerModule(new JavaTimeModule()); // 支持 LocalDateTime 等
    }

    public static <T> T readValue(String json, Class<T> clazz) {
        try { return MAPPER.readValue(json, clazz); }
        catch (IOException e) { throw new RuntimeException(e); }
    }

    public static <T> T readValue(String json, TypeReference<T> typeRef) {
        try { return MAPPER.readValue(json, typeRef); }
        catch (IOException e) { throw new RuntimeException(e); }
    }

    public static <T> T readValue(String json, JavaType javaType) {
        try { return MAPPER.readValue(json, javaType); }
        catch (IOException e) { throw new RuntimeException(e); }
    }

    // 預構建常用類型緩存,避免重複構建 JavaType
    private static final Map<String, JavaType> TYPE_CACHE = new ConcurrentHashMap<>();
    
    public static JavaType getCollectionType(Class<?> collectionClass, Class<?>... elementClasses) {
        String key = collectionClass.getName() + Arrays.toString(elementClasses);
        return TYPE_CACHE.computeIfAbsent(key, k -> 
            MAPPER.getTypeFactory().constructParametricType(collectionClass, elementClasses));
    }
}

2. 處理多態反序列化

若 JSON 包含類型標識(如 "@class":"com.example.AdminUser"),需啟用多態:

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
public abstract class User { ... }

@JsonSubTypes({@JsonSubTypes.Type(value = AdminUser.class, name = "admin")})
public class AdminUser extends User { ... }

3. 日期時間與可選值處理

// Java 8 時間
mapper.registerModule(new JavaTimeModule());
// 禁用 timestamp 格式,使用 ISO-8601 字符串
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

// Optional 支持
mapper.registerModule(new Jdk8Module());

五、 常見坑點與避雷指南

坑點現象解決
直接用 List.class得到 List<LinkedHashMap>使用 TypeReference 或 JavaType
TypeReference 未用匿名類類型參數擦除,同 List.class必須 new TypeReference<List<User>>() {}
嵌套泛型構建錯誤Map<String, List<User>> 解析失敗分層構建 JavaType:先內層 List,再外層 Map
字段名不匹配屬性為 null@JsonProperty("json_field") 或啟用駝峰/下劃線自動映射
未知屬性拋異常FAIL_ON_UNKNOWN_PROPERTIES關閉該特性或使用 @JsonIgnoreProperties(ignoreUnknown = true)
日期格式不兼容解析異常註冊 JavaTimeModule、自定義 DateFormat

六、 性能優化建議

  1. ObjectMapper 單例復用:線程安全,避免重複創建開銷。
  2. JavaType 緩存:如上文 TYPE_CACHE,避免重複構建類型對象。
  3. 啟用 Afterburner:mapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector()) 等優化模塊,或直接使用 mapper.enable(MapperFeature.USE_GETTERS_AS_SETTERS) 等特性。
  4. 流式 API 處理大 JSON:JsonParser / JsonGenerator 避免全量載入內存。
  5. 基本類型優化:使用 PrimitiveTypeDeserializer 避免裝箱。

七、 總結

  • TypeReference:簡潔、聲明式、適用 90% 靜態類型場景,**首選**。
  • JavaType:程序化、動態、支持極度嵌套泛型,**補充**。
  • 掌握兩者構建邏輯,配合單例 ObjectMapper、類型緩存、模塊註冊,構建高性能、健壯的 JSON 處理體系。

泛型擦除是 Java 的歷史包袱,Jackson 通過 TypeReference 匿名類技巧與 JavaType 程序化構建優雅解決。理解其原理,不再為「為什麼反序列化成 Map」而困惑。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved