How to use gson TypeAdapter in Retrofit 2?

前端 未结 1 1688
离开以前
离开以前 2021-01-14 21:54

I have a working code wherein my retrofit client can retrieve a List of objects(country) from an api. The problem is, the parameter used by the api returns an ARRAY if I use

1条回答
  •  无人及你
    2021-01-14 22:26

    There are some issues in your code, and some of them can be either fixed or redesigned in order to simplify a lot of things. First, let's take a look at the service (the names differ). Retrofit interface must use @Query for requests with query parameters, not @Path. Also, your service methods must return a Call where its T declare the actual result type giving some information on what's returned by that method. The service you mentioned can return a single element or none (but never HTTP 404 for some reason), or a list depending on endpoint URLs. Let's assume, single values and none values are not meant to be 1- or 0-sized lists:

    interface IService {
    
        @GET("country/get/all")
        Call> getCountries();
    
        @GET("country/get/iso2code/{code}")
        Call getCountryByIso2Code(
                @Path("code") String code
        );
    
        @GET("country/get/iso3code/{code}")
        Call getCountryByIso3Code(
                @Path("code") String code
        );
    
        @GET("country/search")
        Call> searchCountries(
                @Query("text") String query
        );
    
    }
    

    Next, the Country class may look as follows:

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

    Note that @SerializedName can map an external name to a local field name, and the fields are final and nullified - Gson can deal with them itself. Next, I'm assuming that you don't really care the response structure, and only need $.Response.result that should be adapted respectively. TypeAdapterFactory is what you need and can work with any response, not necessarily for countries:

    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  TypeAdapter create(final Gson responseGson, final TypeToken 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
                extends TypeAdapter {
    
            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;
            }
    
        }
    
    }
    

    Putting it all together:

    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  Callback callback(final String name) {
        return new Callback() {
            @Override
            public void onResponse(final Call call, final Response 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 call, final Throwable ex) {
                ex.printStackTrace(System.err);
            }
        };
    }
    

    And the result (assuming the responses are received and processed sequentally + the long responses are cut):

    getCountries(): [Afghanistan(AF,AFG), Åland Islands(AX,ALA), Albania(AL,ALB), ..., Yemen(YE,YEM), Zambia(ZM,ZMB), Zimbabwe(ZW,ZWE)]

    getCountryByIso2Code(): Ukraine(UA,UKR)

    getCountryByIso3Code(): Ukraine(UA,UKR)

    searchCountries(): [Åland Islands(AX,ALA), Bouvet Island(BV,BVT), Cayman Islands(KY,CYM), ..., United States Minor Outlying Islands(UM,UMI), Virgin Islands (British)(VG,VGB), Virgin Islands (U.S.)(VI,VIR)]

    0 讨论(0)
提交回复
热议问题