Reputation: 4409
How can I use Retrofit2 to parse these two kinds of API responses?
Ok response (HTTP 200):
{
"data": {
"foo": "bar"
}
}
Error response (HTTP 200):
{
"error": {
"foo": "bar"
}
}
I've read tons of SO questions and tutorials, but I don't know how to do that, I've tried:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapterFactory(new ItemTypeAdapterFactory());
Gson gson = gsonBuilder.create();
final Retrofit retrofit = new Retrofit.Builder()
.client(getOkHttpClient())
.baseUrl(Constants.API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
And this is my ItemTypeAdapterFactory:
class ItemTypeAdapterFactory implements TypeAdapterFactory {
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> type) {
final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);
return new TypeAdapter<T>() {
public void write(JsonWriter out, T value) throws IOException {
delegate.write(out, value);
}
public T read(JsonReader in) throws IOException {
JsonElement jsonElement = elementAdapter.read(in);
if (jsonElement.isJsonObject()) {
JsonObject jsonObject = jsonElement.getAsJsonObject();
// Data key
if (jsonObject.has(Constants.JSON_KEY_DATA)) {
JsonElement jsonData = jsonObject.get(Constants.JSON_KEY_DATA);
// Primitive
if (jsonData.isJsonPrimitive()) {
jsonElement = jsonData.getAsJsonPrimitive();
}
// JSON object
else if (jsonData.isJsonObject()) {
jsonElement = jsonData;
}
// JSON object array
else if (jsonData.isJsonArray()) {
jsonElement = jsonData.getAsJsonArray();
}
}
}
return delegate.fromJsonTree(jsonElement);
}
}.nullSafe();
}
}
But now I don't know the type to be declared on retrofit2 interface, inside Call:
@GET("login")
Call<?> login(@Query(Constants.API_PARAM_TOKEN) String token);
Could you please point me in the right direction?
Upvotes: 8
Views: 6963
Reputation: 346
I used to do something like this
BaseResponse
public class BaseResponse<D,E>{
E error;
D data;
public boolean isSuccess(){
return error==null;
}
}
Retrofit interface
@GET("login")
Call<BaseResponse<LoginData,ErrorData>> login(@Query(Constants.API_PARAM_TOKEN) String token);
this approach will work OK when you have control over the REST API structure.
the only problem is that you need to check for success using isSuccess method for every request before using the data object.
Upvotes: 1
Reputation: 2176
In a similar case, I once used JsonObject
as type, so your function will look like this:
@GET("login")
Call<?> login(@Query(Constants.API_PARAM_TOKEN) String token);
Next, when you make a retrofit call, you keep the response as a string. So, in your java code, do something like this:
Call<JsonObject> call = RetrofitClient.getAPIService().login('YOUR_INPUT');
Data data = null;
Error error = null;
call.enqueue(new Callback<JsonObject>() {
@Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
if(response.isSuccessfull()){
String jsonString = response.body().toString();
if(jsonString.contains("data:")){
data = new Gson().fromJson(jsonString,Data.class);
}else{
error = new Gson().fromJson(jsonString,Error.class);
}
}
}
Here, I have used Data
and Error
these 2 classes. They are the POJOs. So Data can look something like this:
Data.java
:public class Data implements Serializable{
@SerializedName("foo")
@Expose
private Foo foo; // Foo is your desired data type
}
Same goes for Error
. So depending on your rest of the code, make necessary changes. Good luck.
Upvotes: 9