3

Some of my server response for success are:

{ "error": null, "data": null }

My model is:

public class BaseResponse<E > {

    @Json(name = "error")
    public ErrorModel error;

    @Json(name = "data")
    public E data;
}

If my endpoint is

@POST("user")
Call<BaseResponse<String>> createUser(@Body Credentials credentials);  

This works, but the type String is useless as data will always be null. But if i make it:

@POST("user")
Call<BaseResponse> createUser(@Body Credentials credentials);  

I got crash at

Call<BaseResponse> call = apiService.createUser(creds);

full log:

java.lang.IllegalArgumentException: Unable to create converter for 
    class common.model.responses.BaseResponse for method MyEndpoints.createUser
  ...
Caused by: java.lang.IllegalArgumentException: Expected a Class, ParameterizedType, 
   or GenericArrayType, but <null> is of type null
  at com.squareup.moshi.Types.getRawType(Types.java:167)
  at com.squareup.moshi.ClassJsonAdapter$1.createFieldBindings(ClassJsonAdapter.java:83)
  at com.squareup.moshi.ClassJsonAdapter$1.create(ClassJsonAdapter.java:75)
  at com.squareup.moshi.Moshi.adapter(Moshi.java:100)
  at com.squareup.moshi.ClassJsonAdapter$1.createFieldBindings(ClassJsonAdapter.java:91)
  at com.squareup.moshi.ClassJsonAdapter$1.create(ClassJsonAdapter.java:75)
  at com.squareup.moshi.Moshi.adapter(Moshi.java:100)
  at retrofit2.converter.moshi.MoshiConverterFactory.responseBodyConverter(MoshiConverterFactory.java:91)
  at retrofit2.Retrofit.nextResponseBodyConverter(Retrofit.java:330)
  at retrofit2.Retrofit.responseBodyConverter(Retrofit.java:313)
  at retrofit2.ServiceMethod$Builder.createResponseConverter(ServiceMethod.java:736)
 ...
JJD
  • 50,076
  • 60
  • 203
  • 339
X3Btel
  • 1,408
  • 1
  • 13
  • 21

1 Answers1

3

This specific error surfaces because there is no way to know the type of the E field, since it was not specified in the raw type usage.

Since you know that this field will always be null, the best solution is to use a different type (that lacks the field) to clarify this.

If you really cannot use a different type, you could use Void as the generic type argument. You would need to register a JsonAdapter on your Moshi instance for the Void field to be deserialized, though: new Moshi.Builder().add(VOID_JSON_ADAPTER).build()

static final Object VOID_JSON_ADAPTER = new Object() {
  @FromJson Void fromJson(JsonReader reader) throws IOException {
    return reader.nextNull();
  }

  @ToJson void toJson(JsonWriter writer, Void v) throws IOException {
    writer.nullValue();
  }
};

But, using a different type makes the most sense.

Eric Cochran
  • 8,414
  • 5
  • 50
  • 91
  • I have custom callback that have BaseResponse as generic, that handles the errors and preffer to have only one root response class. I thought about the void but it seems implemented the adapter wrong – X3Btel Jul 18 '17 at 23:25
  • I have one quck question for the Adapter- The ones ive seen so far have String parameter, your way there is JsonWriter. Are they the same thing? – X3Btel Jul 18 '17 at 23:29
  • The writer is a little bit clearer and faster. There are other ways to make these composed adapters with delegated types, yes. valid `@FromJson` formats: https://github.com/square/moshi/blob/76f50df2ccb59c72c647bfa5feea953ccf457be8/moshi/src/main/java/com/squareup/moshi/AdapterMethodsFactory.java#L259 valid `@ToJson` formats: https://github.com/square/moshi/blob/76f50df2ccb59c72c647bfa5feea953ccf457be8/moshi/src/main/java/com/squareup/moshi/AdapterMethodsFactory.java#L194 – Eric Cochran Jul 19 '17 at 00:27