如何使用 moshi、retrofit 和 java 处理包装数据?

how can I work with wrapped data using moshi, retrofit and java?

我正在使用 API,其中所有数据都包含在自定义 object 中(见下文),因此我无法使用 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
    }
}

我的Collection模特:

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;
    }
}

您将需要设置 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 {
    
}

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

为了让它工作,我将 Moshi class 更改为 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 数据的包装器 class:

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);
    }

}

上面 class 中引用的错误 class:

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 class 重新用于任何模型(图书、用户、登录等)。

谢谢。