You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 09:40:21