如何在Retrofit 2中使用gson TypeAdapter?

zbr*_*yan 5 android gson retrofit2

我有一个工作代码,其中我的改造客户端可以从 api 检索对象(国家/地区)列表。问题是,如果我用来检索所有国家/地区,则 api 使用的参数会返回一个 ARRAY,那么当我想要查询单个国家/地区时,它会返回单个 OBJECT。结果显示以下异常。

java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 4 column 17 path $.RestResponse.result
Run Code Online (Sandbox Code Playgroud)

顺便说一句,我正在使用Retrofit 2。

我尝试了此线程中已接受的答案 How to handleparameters that can be an ARRAY or OBJECT in Retrofit on Android?

但仍然行不通。这是我的实现。

应用程序编程接口

http://services.groupkt.com/country/get/all
http://services.groupkt.com/country/get/iso2code/{alpha2_code}
Run Code Online (Sandbox Code Playgroud)

API接口

public interface ApiInterface {

    @GET("country/get/all")
    Call<Example> getCountry();

    @GET("country/get/iso2code/{alpha2_code}")
    Call<Example> searchCountryByIso2Code(@Path("alpha2_code") String alpha2Code);

    @GET("country/get/iso3code/{alpha3_code}")
    Call<Example> searchCountryByIso3Code(@Path("alpha3_code") String alpha3Code);

    @GET("country/search?text={text to search}")
    Call<Example> searchCountry(@Path("text to search") String searchText);
}
Run Code Online (Sandbox Code Playgroud)

国家/地区类型适配器

public class CountryTypeAdapter extends TypeAdapter<RestResponse> {

    private Gson mGson = new Gson();

    @Override
    public void write(JsonWriter jsonWriter, RestResponse restResponse) throws IOException {
        mGson.toJson(restResponse, RestResponse.class, jsonWriter);
    }

    @Override
    public RestResponse read(JsonReader jsonReader) throws IOException {
        RestResponse result;

        jsonReader.beginObject();
        jsonReader.nextName();

        if (jsonReader.peek() == JsonToken.BEGIN_ARRAY) {
            result = new RestResponse((Result[]) mGson.fromJson(jsonReader, Result.class));
        } else if (jsonReader.peek() == JsonToken.BEGIN_OBJECT) {
            result = new RestResponse((Result) mGson.fromJson(jsonReader, Result.class));
        } else {
            throw new JsonParseException("Unexpected token " + jsonReader.peek());
        }

        jsonReader.endObject();
        return result;
    }
}
Run Code Online (Sandbox Code Playgroud)

API客户端

public class ApiClient {

    public static final String BASE_URL = "http://services.groupkt.com/";
    private static Retrofit mRetrofit;

    public static Retrofit getmRetrofitClient(){

        if (mRetrofit == null) {
            mRetrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create(new GsonBuilder().registerTypeAdapter(CountryTypeAdapter.class, new CountryTypeAdapter()).create()))
                    .build();
        }
        return mRetrofit;
    }

}
Run Code Online (Sandbox Code Playgroud)

编辑:休息响应

public class RestResponse {

    @SerializedName("messages")
    @Expose
    private List<String> messages = null;
    @SerializedName("result")
    @Expose
    private List<Result> result = null;

    public RestResponse() {
    }

    public RestResponse(List<String> messages, List<Result> result) {
        this.messages = messages;
        this.result = result;
    }

    public RestResponse(Result... result) {
        this.result = Arrays.asList(result);
    }

    public List<String> getMessages() {
        return messages;
    }

    public void setMessages(List<String> messages) {
        this.messages = messages;
    }

    public List<Result> getResult() {
        return result;
    }

    public void setResult(List<Result> result) {
        this.result = result;
    }


}
Run Code Online (Sandbox Code Playgroud)

Lyu*_*riv 6

您的代码中存在一些问题,其中一些问题可以修复或重新设计,以简化很多事情。首先,我们来看看该服务(名称不同)。Retrofit 接口必须用于@Query带有查询参数的请求,而不是@Path. 此外,您的服务方法必须返回一个声明实际结果类型的Call位置,提供有关该方法返回的内容的一些信息。T您提到的服务可以返回单个元素或不返回任何元素(但由于某种原因永远不会返回 HTTP 404),也可以返回一个列表,具体取决于端点 URL。假设,单个值和无值并不意味着是 1 或 0 大小的列表:

\n\n
interface IService {\n\n    @GET("country/get/all")\n    Call<List<Country>> getCountries();\n\n    @GET("country/get/iso2code/{code}")\n    Call<Country> getCountryByIso2Code(\n            @Path("code") String code\n    );\n\n    @GET("country/get/iso3code/{code}")\n    Call<Country> getCountryByIso3Code(\n            @Path("code") String code\n    );\n\n    @GET("country/search")\n    Call<List<Country>> searchCountries(\n            @Query("text") String query\n    );\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

接下来,Country可能如下所示:

\n\n
final class Country {\n\n    @SerializedName("name")\n    final String name = null;\n\n    @SerializedName("alpha2_code")\n    final String code2 = null;\n\n    @SerializedName("alpha3_code")\n    final String code3 = null;\n\n    @Override\n    public String toString() {\n        return name + \'(\' + code2 + \',\' + code3 + \')\';\n    }\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

请注意,@SerializedName可以将外部名称映射到本地字段名称,并且字段是finalnull化的 - Gson 可以自己处理它们。接下来,我假设您并不真正关心响应结构,只需要$.Response.result分别进行调整。TypeAdapterFactory是您所需要的,并且可以与任何响应一起使用,不一定适用于国家/地区:

\n\n
final class ResponseExtractorTypeAdapterFactory\n        implements TypeAdapterFactory {\n\n    private final Gson gson;\n\n    private ResponseExtractorTypeAdapterFactory(final Gson gson) {\n        this.gson = gson;\n    }\n\n    static TypeAdapterFactory getResponseExtractorTypeAdapterFactory(final Gson gson) {\n        return new ResponseExtractorTypeAdapterFactory(gson);\n    }\n\n    @Override\n    public <T> TypeAdapter<T> create(final Gson responseGson, final TypeToken<T> typeToken) {\n        // Using responseGson would result in infinite recursion since this type adapter factory overrides any type\n        return new ResponseExtractorTypeAdapter<>(gson, typeToken.getType());\n    }\n\n    private static final class ResponseExtractorTypeAdapter<T>\n            extends TypeAdapter<T> {\n\n        private final Gson gson;\n        private final Type type;\n\n        private ResponseExtractorTypeAdapter(final Gson gson, final Type type) {\n            this.gson = gson;\n            this.type = type;\n        }\n\n        @Override\n        public void write(final JsonWriter out, final T value) {\n            throw new UnsupportedOperationException();\n        }\n\n        @Override\n        public T read(final JsonReader in)\n                throws IOException {\n            T result = null;\n            // Strip the top most enclosing { for $\n            in.beginObject();\n            final String name1 = in.nextName();\n            switch ( name1 ) {\n            case "RestResponse":\n                // RestResponse { for $.Response\n                in.beginObject();\n                while ( in.hasNext() ) {\n                    final String name2 = in.nextName();\n                    switch ( name2 ) {\n                    case "messages":\n                        // If it\'s just $.Response.message, then skip it\n                        in.skipValue();\n                        break;\n                    case "result":\n                        // If it\'s $.Response.result, then delegate it to "real" Gson\n                        result = gson.fromJson(in, type);\n                        break;\n                    default:\n                        throw new MalformedJsonException("Unexpected at $.RestResponse: " + name2);\n                    }\n                }\n                // RestResponse } for $.Response\n                in.endObject();\n                break;\n            default:\n                throw new MalformedJsonException("Unexpected at $: " + name1);\n            }\n            // Strip the top most enclosing } for $\n            in.endObject();\n            return result;\n        }\n\n    }\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

把它们放在一起:

\n\n
public static void main(final String... args) {\n    final Gson gson = new GsonBuilder()\n            // configure whatever you like\n            .create();\n    final Gson responseGson = new GsonBuilder()\n            .registerTypeAdapterFactory(getResponseExtractorTypeAdapterFactory(gson))\n            .create();\n    final Retrofit retrofit = new Builder()\n            .baseUrl("http://services.groupkt.com/")\n            .addConverterFactory(GsonConverterFactory.create(responseGson))\n            .build();\n    final IService apiInterface = retrofit.create(IService.class);\n    apiInterface.getCountries().enqueue(callback("getCountries()")); // http://services.groupkt.com/country/get/all\n    apiInterface.getCountryByIso2Code("UA").enqueue(callback("getCountryByIso2Code()")); // http://services.groupkt.com/country/get/iso2code/UA\n    apiInterface.getCountryByIso3Code("UKR").enqueue(callback("getCountryByIso3Code()")); // http://services.groupkt.com/country/get/iso3code/UKR\n    apiInterface.searchCountries("land").enqueue(callback("searchCountries()")); // http://services.groupkt.com/country/search?text=land\n}\n\nprivate static <T> Callback<T> callback(final String name) {\n    return new Callback<T>() {\n        @Override\n        public void onResponse(final Call<T> call, final Response<T> response) {\n            // Just make sure the output is not written in middle\n            synchronized ( System.out ) {\n                System.out.print(name);\n                System.out.print(": ");\n                System.out.println(response.body());\n                System.out.println();\n            }\n        }\n\n        @Override\n        public void onFailure(final Call<T> call, final Throwable ex) {\n            ex.printStackTrace(System.err);\n        }\n    };\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

结果(假设按顺序接收和处理响应+长响应被剪切):

\n\n
\n

getCountries(): [阿富汗(AF,AFG), \xc3\x85land 群岛(AX,ALA), 阿尔巴尼亚(AL,ALB), ..., 也门(YE,YEM), 赞比亚(ZM,ZMB), 津巴布韦( ZW,ZWE)]

\n\n

getCountryByIso2Code():乌克兰(UA、UKR)

\n\n

getCountryByIso3Code():乌克兰(UA、UKR)

\n\n

searchCountries(): [\xc3\x85land 群岛(AX,ALA), 布韦岛(BV,BVT), 开曼群岛(KY,CYM), ..., 美国本土外小岛屿(UM,UMI), 维尔京群岛 (英国)(VG,VGB),维尔京群岛(美国)(VI,VIR)]

\n
\n