【问题标题】:How to use gson TypeAdapter in Retrofit 2?如何在 Retrofit 2 中使用 gson TypeAdapter?
【发布时间】:2017-03-07 07:50:16
【问题描述】:

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

java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 4 column 17 path $.RestResponse.result

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

我在这个线程中尝试了接受的答案 How to handle parameters that can be an ARRAY or OBJECT in Retrofit on Android?

但它仍然不起作用。这是我的实现。

API

http://services.groupkt.com/country/get/all
http://services.groupkt.com/country/get/iso2code/{alpha2_code}

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

国家类型适配器

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

ApiClient

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

}

编辑: 休息响应

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


}

【问题讨论】:

  • 尝试调用> getCountry();并从 APICLIENT 中删除 Gson Tyadapter
  • 仍然显示异常..
  • 如果 API 响应不同,您不能对所有请求使用“示例”。一个是类似“>”的东西,另一个只是一个“Example”对象。
  • 我已经添加了 RestResponse 类。从另一个线程我创建了一个接受可变参数的构造函数,这样我就可以将它转换为一个列表,即使它只包含一个对象..
  • 您不能重复使用 - private List result = null - 两个响应。当您获得所有结果时,API 返回一个数组,当您获得一个结果时返回一个“结果”。尝试复制您的 RestResponse 并将其替换为私有 Result 结果

标签: android gson retrofit2


【解决方案1】:

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

interface IService {

    @GET("country/get/all")
    Call<List<Country>> getCountries();

    @GET("country/get/iso2code/{code}")
    Call<Country> getCountryByIso2Code(
            @Path("code") String code
    );

    @GET("country/get/iso3code/{code}")
    Call<Country> getCountryByIso3Code(
            @Path("code") String code
    );

    @GET("country/search")
    Call<List<Country>> searchCountries(
            @Query("text") String query
    );

}

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

final class Country {

    @SerializedName("name")
    final String name = null;

    @SerializedName("alpha2_code")
    final String code2 = null;

    @SerializedName("alpha3_code")
    final String code3 = null;

    @Override
    public String toString() {
        return name + '(' + code2 + ',' + code3 + ')';
    }

}

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

final class ResponseExtractorTypeAdapterFactory
        implements TypeAdapterFactory {

    private final Gson gson;

    private ResponseExtractorTypeAdapterFactory(final Gson gson) {
        this.gson = gson;
    }

    static TypeAdapterFactory getResponseExtractorTypeAdapterFactory(final Gson gson) {
        return new ResponseExtractorTypeAdapterFactory(gson);
    }

    @Override
    public <T> TypeAdapter<T> create(final Gson responseGson, final TypeToken<T> typeToken) {
        // Using responseGson would result in infinite recursion since this type adapter factory overrides any type
        return new ResponseExtractorTypeAdapter<>(gson, typeToken.getType());
    }

    private static final class ResponseExtractorTypeAdapter<T>
            extends TypeAdapter<T> {

        private final Gson gson;
        private final Type type;

        private ResponseExtractorTypeAdapter(final Gson gson, final Type type) {
            this.gson = gson;
            this.type = type;
        }

        @Override
        public void write(final JsonWriter out, final T value) {
            throw new UnsupportedOperationException();
        }

        @Override
        public T read(final JsonReader in)
                throws IOException {
            T result = null;
            // Strip the top most enclosing { for $
            in.beginObject();
            final String name1 = in.nextName();
            switch ( name1 ) {
            case "RestResponse":
                // RestResponse { for $.Response
                in.beginObject();
                while ( in.hasNext() ) {
                    final String name2 = in.nextName();
                    switch ( name2 ) {
                    case "messages":
                        // If it's just $.Response.message, then skip it
                        in.skipValue();
                        break;
                    case "result":
                        // If it's $.Response.result, then delegate it to "real" Gson
                        result = gson.fromJson(in, type);
                        break;
                    default:
                        throw new MalformedJsonException("Unexpected at $.RestResponse: " + name2);
                    }
                }
                // RestResponse } for $.Response
                in.endObject();
                break;
            default:
                throw new MalformedJsonException("Unexpected at $: " + name1);
            }
            // Strip the top most enclosing } for $
            in.endObject();
            return result;
        }

    }

}

把它们放在一起:

public static void main(final String... args) {
    final Gson gson = new GsonBuilder()
            // configure whatever you like
            .create();
    final Gson responseGson = new GsonBuilder()
            .registerTypeAdapterFactory(getResponseExtractorTypeAdapterFactory(gson))
            .create();
    final Retrofit retrofit = new Builder()
            .baseUrl("http://services.groupkt.com/")
            .addConverterFactory(GsonConverterFactory.create(responseGson))
            .build();
    final IService apiInterface = retrofit.create(IService.class);
    apiInterface.getCountries().enqueue(callback("getCountries()")); // http://services.groupkt.com/country/get/all
    apiInterface.getCountryByIso2Code("UA").enqueue(callback("getCountryByIso2Code()")); // http://services.groupkt.com/country/get/iso2code/UA
    apiInterface.getCountryByIso3Code("UKR").enqueue(callback("getCountryByIso3Code()")); // http://services.groupkt.com/country/get/iso3code/UKR
    apiInterface.searchCountries("land").enqueue(callback("searchCountries()")); // http://services.groupkt.com/country/search?text=land
}

private static <T> Callback<T> callback(final String name) {
    return new Callback<T>() {
        @Override
        public void onResponse(final Call<T> call, final Response<T> response) {
            // Just make sure the output is not written in middle
            synchronized ( System.out ) {
                System.out.print(name);
                System.out.print(": ");
                System.out.println(response.body());
                System.out.println();
            }
        }

        @Override
        public void onFailure(final Call<T> call, final Throwable ex) {
            ex.printStackTrace(System.err);
        }
    };
}

结果(假设响应被依次接收和处理+长响应被剪切):

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

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-04-02
    • 1970-01-01
    • 1970-01-01
    • 2015-12-02
    • 2016-03-17
    • 1970-01-01
    • 2014-12-28
    • 1970-01-01
    相关资源
    最近更新 更多