alibaba / alibaba/fastjson2

TypeReference with custom List implementation loses generic type, elements become JSONObject

Open
#7,684 2 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.