Kotlin中如何通过Retrofit将API响应映射为Room实体类对象?
解决Retrofit返回LinkedTreeMap而非Room实体类的问题
核心原因
Java泛型的类型擦除机制,导致Retrofit默认的Gson转换器无法识别Crud<T>中的具体实体类型(如LHPsalmodyJoin、LHPatristic),只能将响应解析为通用的LinkedTreeMap。
解决方案
1. 明确指定Retrofit接口的具体泛型返回类型
如果API按实体类拆分接口(不同表对应不同接口),直接在Retrofit接口中定义带具体泛型的返回类型,让Gson能精准解析:
public interface ApiService { @GET("changes/lhPsalmodyJoin") Call<Crud<LHPsalmodyJoin>> getLHPsalmodyJoinChanges(@Query("since") String date); @GET("changes/lhPatristic") Call<Crud<LHPatristic>> getLHPatristicChanges(@Query("since") String date); // 其余98个表的接口依此类推 }
这种方式最直接,不会出现类型解析错误,拿到响应后可直接调用BaseDao的CRUD方法。
2. 用TypeToken实现泛型动态解析(适合批量处理场景)
如果要统一处理所有实体类的变更请求,避免重复编写100个接口,可通过TypeToken给Gson传递具体泛型类型:
- 先封装通用请求方法,接收实体类的Class对象:
public <T> void fetchChanges(String endpoint, String sinceDate, Class<T> entityClass, Callback<Crud<T>> callback) { Type crudType = TypeToken.getParameterized(Crud.class, entityClass).getType(); // 手动解析响应 Call<ResponseBody> call = retrofit.create(ApiService.class).getGenericChanges(endpoint, sinceDate); call.enqueue(new Callback<ResponseBody>() { @Override public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) { if (response.isSuccessful() && response.body() != null) { try { String json = response.body().string(); Crud<T> crud = new Gson().fromJson(json, crudType); callback.onSuccess(crud); } catch (IOException e) { callback.onFailure(e); } } } // onFailure实现略 }); } // 对应的Retrofit通用接口 public interface ApiService { @GET Call<ResponseBody> getGenericChanges(@Url String endpoint, @Query("since") String date); }
调用时传入对应实体类的Class,就能得到正确映射的Crud<T>对象。
3. 给Crud类添加类型标识字段(动态多表场景推荐)
如果API响应中包含实体类的类型标识(比如entityType字段,值为"lhPsalmodyJoin"、"lhPatristic"),可通过映射关系动态解析:
- 修改Crud类,增加类型标识字段:
public class Crud<T> { private String type; // CREATE/UPDATE/DELETE private String entityType; private T data; // getter/setter }
- 创建实体类映射表,解析时根据标识获取对应Class:
private static final Map<String, Class<?>> ENTITY_MAPPER = new HashMap<>(); static { ENTITY_MAPPER.put("lhPsalmodyJoin", LHPsalmodyJoin.class); ENTITY_MAPPER.put("lhPatristic", LHPatristic.class); // 其余表的映射关系 } // 解析方法 public Crud<?> parseCrudResponse(String json) { JsonObject jsonObj = new Gson().fromJson(json, JsonObject.class); String entityType = jsonObj.get("entityType").getAsString(); Class<?> entityClass = ENTITY_MAPPER.get(entityType); Type crudType = TypeToken.getParameterized(Crud.class, entityClass).getType(); return new Gson().fromJson(json, crudType); }
解析完成后,可将Crud<?>强转为对应实体类的泛型类型,调用BaseDao方法。
4. 确保Room实体类符合Gson解析要求
- 实体类必须提供无参构造函数,可手动编写或用
@NoArgsConstructor(Lombok)生成; - 字段名需与API响应字段一致,或用
@SerializedName注解映射:
@Entity public class LHPsalmodyJoin { @PrimaryKey @SerializedName("psalmody_id") private long id; @SerializedName("psalmody_name") private String name; // 其他字段 @NoArgsConstructor public LHPsalmodyJoin() {} }
BaseDao调用注意事项
如果BaseDao是泛型接口,需确保方法能接收具体实体类:
@Dao public interface BaseDao<T> { @Insert void insert(T entity); @Update void update(T entity); @Delete void delete(T entity); default void handleCrud(Crud<T> crud) { switch(crud.getType()) { case "CREATE": insert(crud.getData()); break; case "UPDATE": update(crud.getData()); break; case "DELETE": delete(crud.getData()); break; } } }
拿到正确映射的Crud<LHPsalmodyJoin>等对象后,直接传入对应Dao的handleCrud方法即可执行操作。
内容的提问来源于stack exchange,提问作者A. Cedano
相关产品推荐
相关产品推荐

