TypeReference with custom List implementation loses generic type, elements become JSONObject
- Dominant language
- Java
- Stars
- 4.4k
- Forks
- 613
- Avg merge
- 1d 22h
- Merged PRs (30d)
- 6
Description
## 问题描述
`TypeReference>`(其中 `CustomList` 是自定义实现 `List` 接口的类)在 fastjson 2.x 下泛型信息丢失,元素被反序列化为 `JSONObject`。遍历时抛出 `ClassCastException: JSONObject cannot be cast to Xxx`。
fastjson 1.x (1.2.83) 无此问题。
## 环境信息
- fastjson 版本: 2.0.62 (bundled fastjson2: 2.0.61)
- JDK: 8+
## 重现步骤
```java
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import java.util.*;
public class Repro {
public static class CustomList implements List {
private List list = new ArrayList<>();
public int size() { return list.size(); }
public boolean isEmpty() { return list.isEmpty(); }
public boolean contains(Object o) { return list.contains(o); }
public Iterator iterator() { return list.iterator(); }
public Object[] toArray() { return list.toArray(); }
public T[] toArray(T[] a) { return list.toArray(a); }
public boolean add(E e) { return list.add(e); }
public boolean remove(Object o) { return list.remove(o); }
public boolean containsAll(Collection c) { return list.containsAll(c); }
public boolean addAll(Collection c) { return list.addAll(c); }
public boolean addAll(int index, Collection c) { return list.addAll(index, c); }
public boolean removeAll(Collection c) { return list.removeAll(c); }
public boolean retainAll(Collection c) { return list.retainAll(c); }
public void clear() { list.clear(); }
public E get(int index) { return list.get(index); }
public E set(int index, E element) { return list.set(index, element); }
public void add(int index, E element) { list.add(index, element); }
public E remove(int index) { return list.remove(index); }
public int indexOf(Object o) { return list.indexOf(o); }
public int lastIndexOf(Object o) { return list.lastIndexOf(o); }
public ListIterator listIterator() { return list.listIterator(); }
public ListIterator listIterator(int index) { return list.listIterator(index); }
public List subList(int fromIndex, int toIndex) { return list.subList(fromIndex, toIndex); }
}
public static class Setting {
private String name;
private String value;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getValue() { return value; }
public void setValue(String value) { this.value = value; }
}
@SuppressWarnings({"unchecked", "rawtypes"})
public static void main(String[] args) {
// 场景 A:单元素 → 元素类型丢失为 JSONObject,抛 ClassCastException
System.out.println("=== A: 单元素 ===");
Map item = new HashMap<>();
item.put("name", "key1");
item.put("value", "val1");
String singleJson = JSON.toJSONString(Collections.singletonList(item));
CustomList resultA = JSON.parseObject(
singleJson,
new TypeReference>() {}.getType());
System.out.println("element is JSONObject? " + (resultA.get(0) instanceof JSONObject));
// true → 类型丢失
try {
CustomList typed = (CustomList) (List) resultA;
for (Setting s : typed) { System.out.println(s.getName()); }
} catch (ClassCastException e) {
System.out.println("ClassCastException: " + e.getMessage());
}
// 场景 B:多元素 → 直接抛 JSONException
System.out.println("\n=== B: 多元素 ===");
Map item2 = new HashMap<>();
item2.put("name", "key2");
item2.put("value", "val2");
String multiJson = JSON.toJSONString(Arrays.asList(item, item2));
try {
JSON.parseObject(multiJson, new TypeReference>() {}.getType());
} catch (JSONException e) {
System.out.println("JSONException: " + e.getMessage());
}
// 场景 C:原生 List 接口正常
System.out.println("\n=== C: List 正常 ===");
List resultC = JSON.parseObject(
multiJson, new TypeReference>() {}.getType());
System.out.println("element class: " + resultC.get(0).getClass().getName());
}
}
```
## 输出
```
=== A: 单元素 ===
element is JSONObject? true
ClassCastException: com.alibaba.fastjson.JSONObject incompatible with Repro$Setting
=== B: 多元素 ===
JSONException: expect {, but [, class Repro$CustomList, offset 1, ...
=== C: List 正常 ===
element class: Repro$Setting
```
## 预期行为
应同 `TypeReference>` 一样,正确将元素反序列化为 `Xxx` 而非 `JSONObject`。
## 对比
- fastjson 1.x (1.2.83): ✅ 正常
- fastjson 2.x (2.0.62): ❌ 异常
- jackson: ✅ 正常
## 备注
已知 issue #3926 已修复类似的 List 子类问题,但该修复针对的是「作为另一个对象的字段」的场景(如 `MyDTO.myList`),不涵盖 `TypeReference>` 直接解析的场景。当前问题仍存在。
Contributor guide
Research direction
Start with the JSON.parseObject calls using TypeReference> and compare them with the working List path; review the related List-subclass fix in issue #3926. Reproduce the single- and multi-element cases, then verify that CustomList elements deserialize as Setting and both cases complete without exceptions.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100