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。
import com.fasterxml.jackson.core.type.TypeReference;
List<User> users = mapper.readValue(jsonString,
new TypeReference<List<User>>() {});
getGenericSuperclass() 獲取)。new TypeReference<...>() {},直接 new TypeReference<List<User>>() 無效(編譯期類型參數仍被擦除)。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(通用參數化類型)。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));
}
}
若 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 { ... }
// 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 |
TYPE_CACHE,避免重複構建類型對象。mapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector()) 等優化模塊,或直接使用 mapper.enable(MapperFeature.USE_GETTERS_AS_SETTERS) 等特性。JsonParser / JsonGenerator 避免全量載入內存。PrimitiveTypeDeserializer 避免裝箱。泛型擦除是 Java 的歷史包袱,Jackson 通過 TypeReference 匿名類技巧與 JavaType 程序化構建優雅解決。理解其原理,不再為「為什麼反序列化成 Map」而困惑。