Firebase Admin Java SDK反序列化异常:期望Map却得到ArrayList
嘿,我完全懂这种突然翻车的滋味——之前运行好好的每日聚合任务,突然抛出反序列化异常,尤其是涉及Firebase实时数据库这种动态结构的存储,确实容易因为数据结构的细微变化踩坑。结合你描述的场景(可选递增整数映射到对象列表的字段出问题),咱们一步步来排查解决:
1. 先揪出数据库里的“异类”数据
既然前几天都正常,今天突然报错,90%的概率是今日新增的某条数据里,目标字段的结构从Map变成了ArrayList。比如本该是这样的Map结构:
"targetField": { "1": [{"id": "xxx"}, {"id": "yyy"}], "2": [{"id": "zzz"}] }
结果被写成了数组结构:
"targetField": [ [{"id": "xxx"}, {"id": "yyy"}], [{"id": "zzz"}] ]
你可以直接去Firebase控制台的实时数据库面板,找到今日新增的数据集,用JSON视图检查目标字段的结构,确认是不是有这样的“异类”数据存在。
2. 核对你的Java模型类定义
检查一下你用来映射这个字段的Java类型是不是Map<Integer, List<YourObject>>(或者类似的泛型Map)?Firebase Admin SDK默认用Gson做反序列化,当它遇到数组(ArrayList)但预期是Map时,就会抛出你看到的异常。
- 如果最近修改过模型类(比如不小心把Map改成了List又改回来?),仔细核对字段的类型定义,确保和之前正常运行时一致。
3. 兼容混合类型的临时解决方案(如果存在两种结构的数据)
如果确实存在部分数据是Map、部分是ArrayList(比如写入端的bug导致),那你需要让反序列化逻辑兼容这两种类型。可以自定义Gson的TypeAdapter来处理:
步骤1:编写自定义TypeAdapter
import com.google.gson.*; import com.google.gson.reflect.TypeToken; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; public class MapOrListAdapter extends TypeAdapter<Map<Integer, List<YourObject>>> { private final Gson gson = new Gson(); @Override public void write(JsonWriter out, Map<Integer, List<YourObject>> value) throws IOException { // 如果需要写入数据,保持原Map结构输出 gson.getAdapter(TypeToken.get(Map.class)).write(out, value); } @Override public Map<Integer, List<YourObject>> read(JsonReader in) throws IOException { JsonToken token = in.peek(); Map<Integer, List<YourObject>> result = new HashMap<>(); if (token == JsonToken.BEGIN_ARRAY) { // 处理ArrayList情况:把数组索引作为递增整数键(从1开始对应你的需求) List<List<YourObject>> arrayList = gson.getAdapter( TypeToken.getParameterized(List.class, TypeToken.getParameterized(List.class, YourObject.class).getType()) ).read(in); for (int i = 0; i < arrayList.size(); i++) { result.put(i + 1, arrayList.get(i)); } } else if (token == JsonToken.BEGIN_OBJECT) { // 正常处理Map结构 result = gson.getAdapter( TypeToken.getParameterized(Map.class, Integer.class, TypeToken.getParameterized(List.class, YourObject.class).getType()) ).read(in); } return result; } }
步骤2:给模型字段加注解
在你的模型类中,给目标字段加上@JsonAdapter注解:
public class YourDataModel { // 其他字段... @JsonAdapter(MapOrListAdapter.class) private Map<Integer, List<YourObject>> targetField; // getter/setter... }
这样Gson就会自动识别并处理Map和ArrayList两种结构的数据了。
4. 修复写入端的根源问题
临时兼容只是权宜之计,一定要找到导致写入ArrayList的源头(比如前端代码、其他服务的写入逻辑):
- 比如如果是前端用了Firebase的
push()方法(会生成带时间戳的键),但你需要的是递增整数键,那应该改成用set()方法指定具体的整数键; - 检查写入逻辑是否有分支错误,在某些情况下误将List写入了本该是Map的字段。
只有修复了写入端的问题,才能彻底避免以后再出现同样的异常。
内容的提问来源于stack exchange,提问作者Matt Goodrich

