Moshi Expected BEGIN_OBJECT but was BEGIN_ARRAY - custom converter ignored

Luke picture Luke · Mar 10, 2018 · Viewed 7.9k times · Source

I'm using Moshi as converter for Retrofit, but for one particular request it doesn't work and exception is thrown: com.squareup.moshi.JsonDataException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at path $.results

The JSON I want to parse:

{
  "id": 423,
  "results": [
    {
      "id": "53484dfec3a3684b930000bd",
      "iso_639_1": "en",
      "iso_3166_1": "US",
      "key": "u_jE7-6Uv7E",
      "name": "Trailer",
      "site": "YouTube",
      "size": 360,
      "type": "Trailer"
    },
    {
      "id": "57e16bb0c3a36808bc000641",
      "iso_639_1": "en",
      "iso_3166_1": "US",
      "key": "BFwGqLa_oAo",
      "name": "Trailer",
      "site": "YouTube",
      "size": 1080,
      "type": "Trailer"
    }
  ]
}

The model classes:

public class VideosResponse {
    private int id;
    private List<Video> results;
    //+ getters & setters
}
public class Video {
    private String id;
    @Json(name = "iso_639_1")
    private String iso6391;
    @Json(name = "iso_3166_1")
    private String iso31661;
    private String key;
    private String name;
    private String site;
    private Integer size;
    private String type;
    //+getters & setters
}

This is Retrofit call:

@GET("3/movie/{id}/videos")
Call<List<Video>> movieVideos(
      @Path("id") int id,
      @Query("api_key") String apiKey);

So as you can see I'm expecting list of objects, but the JSON is actually an objecy itself, therefore I prepared my custom converter:

public class VideosJsonConverter {
    @FromJson
    public List<Video> fromJson(VideosResponse json) {
        return json.getResults();
    }
}

... and I'm adding it to my Retrofit like that:

public Retrofit provideRetrofit(@Named("baseUrl") String basUrl) {
        Moshi moshi = new Moshi.Builder().add(new VideosJsonConverter()).build();
        return new Retrofit.Builder()
                .baseUrl(basUrl)
                .addConverterFactory(MoshiConverterFactory.create(moshi))
                .build();
    }

My custom converter isn't actually called so it looks like Moshi can't convert JSON to my VideosResponse wrapper class. If I change my converter to accept Map<String, Object> it goes there, but not for VideosResponse. It also works when I change my retrofit enpoint to return directly VideosResponse. Is it possible that there is a conflict with other POJO classes (I have similar classes but with a list of different objects)?

Answer

Eric Cochran picture Eric Cochran · Mar 10, 2018

The problem is that the adapter is going to be used by both your desired result and the inner list in VideosResponse. So, the adapter is expecting a VideoResponse-formatted JSON blob within the VideoResponse and fails when it finds the real array on reentry.

You can qualify one of the lists to differentiate them. Here's an example of qualifying the resulting list.

@Retention(RUNTIME)
@JsonQualifier
public @interface Wrapped {
}

public class VideosJsonConverter {
  @Wrapped @FromJson
  public List<Video> fromJson(VideosResponse json) {
    return json.results;
  }

  @ToJson
  public VideosResponse toJson(@Wrapped List<Video> value) {
    throw new UnsupportedOperationException();
  }
}

@GET("3/movie/{id}/videos")
@Wrapped
Call<List<Video>> movieVideos(
  @Path("id") int id,
  @Query("api_key") String apiKey);