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
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 null
ified - 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)]