【问题标题】:how can I work with wrapped data using moshi, retrofit and java?如何使用 moshi、retrofit 和 java 处理包装数据?
【发布时间】:2020-11-19 18:52:47
【问题描述】:

我正在使用一个 API,其中所有数据都包装在一个自定义对象中(见下文),因此我无法使用 moshi 将改造主体直接转换为我的模型。在这种情况下,与 moshi 合作的最佳方式是什么?

#COLLECTIONS ENDPOINT

{
    "status": 200,
    "data": [
        {
            "id": 28122,
            "name": "Abandonei",
            "counts": {
                "books": 3
            }
        },
        {
            "id": 21091,
            "name": "Lendo",
            "counts": {
                "books": 6
            }
        },
    ],
    "errors": [],
    "pagination": {
        "after": 2,
        "hasNextPage": true
    }
}

所有api端点使用相同的json结构,默认字段为:

{
    "status": 200,
    "data": [],
    "errors": [],
    "pagination": {
        "after": 1,
        "hasNextPage": true
    }
}

我的收藏模型:

public class BookCollection {
    public long id;
    public String name;
    public ArrayList<Book> books;

    public BookCollection(long id, String name) {
        this.id = id;
        this.name = name;
    }
}

【问题讨论】:

  • 您的模型现在有什么代码?您的 moshi 适配器应该能够毫无问题地处理这些数据。
  • @RayHunter 我添加了我在此请求中使用的模型以及所有端点上 api 返回的 json 示例
  • 你的 json 需要匹配你的模型。因此,您可以创建一个新的,而不是 BookCollection,让 json 数据中的所有字段都返回。
  • 但是这样我需要为我的所有模型创建一个父类。我认为这不是工作的最佳做​​法。在 Gson 中,我可以创建一个自定义适配器,该适配器将用于解析器进程以仅获取 json 数据属性中的数据,然后使用可能的模型返回解析的数据。我知道在 Moshi 中也可以,但我不知道该怎么做。
  • 没错,api 强制你用自定义的父类包装它。我不是这种 json 响应的忠实粉丝。状态和错误应该在 http 级别而不是在响应负载中处理。对于 moshi,您可以创建一个适配器来处理父级和处理数据并将 java/kotlin 对象返回给您。 Gson 也可以使用自定义反序列化器来完成。

标签: java android retrofit moshi


【解决方案1】:

您需要设置 gson/moshi 以使用您为 json 到对象映射创建的类。这是这些 java 类的外观示例。您也可以在 kotlin 中使用数据类。对于 moshi,您必须创建适配器来帮助进行 json 到对象的映射。

publci class CollectionResponse {

    public int status;
    public List<BookCollection> data;
    public List<Error> errors;
    public Pagination pagination;
}

public class Pagination {
    public int after;
    public boolean hasNextPage;
}

public class BookCollection {
    public long id;
    public String name;
    public Count counts;
    
}

public Count {
    public int books;
}

public class Error {
    
}

【讨论】:

  • 但是这样我需要为我的所有模型创建一个父类。我认为这不是工作的最佳做​​法。我想创建一个自定义适配器,用于解析器进程,以仅获取 json 数据属性中的数据,然后使用可能的模型返回解析的数据。
  • 有一个父类是oop,所以有这个很好。您只需要确保可以为您可能拥有的不同数据类型处理数据对象。反序列化器 (gson) 或您的 moshi 适配器也可以提供帮助。
  • 谢谢,但我认为创建大量重复同一段代码的类并不是最好的 OOP 方法。我的想法是创建一个独特的展开类,我可以在整个项目中重用它。请参阅下面的答案。
【解决方案2】:

为避免为每个模型创建父类,我实现了一种使用接收泛型类型的类的方法。

为了让它发挥作用,我将 Moshi 类更改为 Gson。

我的模特:

public class BookCollection {
    public long id;
    public String name;
    public ArrayList<Book> books;

    public BookCollection(long id, String name) {
        this.id = id;
        this.name = name;
    }
}

用于解包 json 数据的包装类:

public class ApiWrapper<T> {
    public final int status;
    public final T data;
    public final List<ApiError> errors = new ArrayList<>();

    public ApiWrapper(int status, T data, List<ApiError> errors) {
        this.status = status;
        this.data = data;
        this.errors.addAll(errors);
    }

}

Errors 类,在上面的类中引用:

public class ApiError {
    public int code;
    public String message;
    public String error;
}

用法:

public interface NetAPI {
    @GET("me/collections")
    Call<ResponseBody> getCollections(@Header("Authorization") String auth);
}

public class CollectionViewModel extends ViewModel {
    private final MutableLiveData<List<Collection>> collections = new MutableLiveData<>();
    private final MutableLiveData<Boolean> loading = new MutableLiveData<>();
    private final MutableLiveData<Boolean> collectionError = new MutableLiveData<>();

    private Call<ResponseBody> call;
    
    private void fetchCollections() {
        loading.setValue(true);
        call = Api.getInstance().getCollections(TOKEN);
        call.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                try {
                    collectionError.setValue(false);
                    
                    //THE SECRET
                    Gson gson = new Gson();
                    ApiWrapper<List<Collection>> apiResponse = null;
                    apiResponse = gson.fromJson(response.body().string(), new TypeToken<ApiWrapper<List<Collection>>>(){}.getType());

                    collections.setValue(apiResponse.data);

                    loading.setValue(false);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                Log.e(getClass().getSimpleName(), "Error loading data", t);
                collectionError.setValue(true);
                loading.setValue(false);
            }
        });
    }
}

通过这种方式,我可以将我的 ApiWrapper 类重用于任何模型(书籍、用户、登录等)。

谢谢。

【讨论】:

    猜你喜欢
    • 2020-05-14
    • 2020-04-19
    • 2021-05-24
    • 2020-09-22
    • 1970-01-01
    • 2021-04-24
    • 2020-06-06
    • 2016-08-29
    • 2020-05-11
    相关资源
    最近更新 更多