AndyRoid
AndyRoid

Reputation: 5057

Gson not properly serializing JSON into POJO with Retrofit

I have the following JSON returning back from the NYTimes TopStories API;

{
"status": "OK",
"copyright": "Copyright (c) 2015 The New York Times Company. All Rights Reserved.",
"last_updated": "2015-07-03T01:25:02-05:00",
"num_results": 21,
"results": [
    {
        "section": "World",
        "subsection": "Africa",
        "title": "Tunisia Jihadist Died in Libya Strike, U.S. Official Says",
        "abstract": "Seifallah Ben Hassine, one of Osama bin Laden’s top lieutenants and Tunisia’s most wanted jihadist, was killed in an American airstrike in Libya last month, a senior United States official said.",
        "url": "http://www.nytimes.com/2015/07/03/world/africa/jihadist-from-tunisia-died-in-strike-in-libya-us-official-says.html",
        "byline": "By CARLOTTA GALL and ERIC SCHMITT",
        "item_type": "Article",
        "updated_date": "2015-07-02T21:18:07-5:00",
        "created_date": "2015-07-02T21:18:10-5:00",
        "published_date": "2015-07-03T04:00:00-5:00",
        "material_type_facet": "News",
        "kicker": "",
        "des_facet": [
            "Terrorism",
            "Defense and Military Forces"
        ],
        "org_facet": "",
        "per_facet": [
            "Hassine, Seifallah Ben",
            "bin Laden, Osama"
        ],
        "geo_facet": [
            "Tunisia",
            "Libya"
        ],
        "multimedia": ""
    }
]
}

NYTimes returns this JSON with a header of text/json, I am not sure if that has any effect on this.

I am using Retrofit, and my onSuccess method is never reached in the call back. I have the model classes declared like so:

Main Model Class

public class TopStories {

    @SerializedName("status")
    private String mStatus;
    public String getStatus() {
        return mStatus;
    }

    @SerializedName("copyright")
    private String mCopyright;
    public String getCopyright() {
        return mCopyright;
    }

    @SerializedName("last_updated")
    private String mLastUpdated;
    public String getLastUpdated() {
        return mLastUpdated;
    }

    @SerializedName("num_results")
    private int mNumResults;
    public int getNumResults() {
        return mNumResults;
    }

    @SerializedName("results")
    private List<Result> mResults;
    public List<Result> getResults() {
        return mResults;
    }

}

Results Model Class

public class Result {

    @SerializedName("section")
    private String mSection;
    public String getSection() {
        return mSection;
    }

    @SerializedName("subsection")
    private String mSubSection;
    public String getSubSection() {
        return mSubSection;
    }

    @SerializedName("title")
    private String mTitle;
    public String getTitle() {
        return mTitle;
    }

    @SerializedName("abstract")
    private String mAbstract;
    public String getAbstract() {
        return mAbstract;
    }

    @SerializedName("url")
    private String mUrl;
    public String getUrl() {
        return mUrl;
    }

    @SerializedName("byline")
    private String myByLine;
    public String getMyByLine() {
        return myByLine;
    }

    @SerializedName("item_type")
    private String mItemType;
    public String getItemType() {
        return mItemType;
    }

    @SerializedName("updated_date")
    private String mUpdatedDate;
    public String getUpdatedDate() {
        return mUpdatedDate;
    }

    @SerializedName("created_date")
    private String mCreatedDate;
    public String getCreatedDate() {
        return mCreatedDate;
    }

    @SerializedName("multimedia")
    private List<Multimedia> mMultimedia;
    public List<Multimedia> getMultimedia() {
        return mMultimedia;
    }

}

You get the idea, and I have another model class for "multimedia", ok moving on I created a RestAdapter like so:

private NYTimesService() {

    mAsyncRestAdapter = new RestAdapter.Builder()
            .setEndpoint(API_URL)
            .setRequestInterceptor(new RequestInterceptor() {
                @Override
                public void intercept(RequestFacade request) {
                    request.addEncodedQueryParam("api-key", API_KEY);
                }
            })
            .setLogLevel(RestAdapter.LogLevel.FULL)
            .build();
}

And I have an API Interface like so:

public interface ITopStories {

    @Headers("Content-Type: text/json")
    @GET("/topstories/v1/home.json")
    void getTopStories(Callback<TopStories> callback);

}

And my Callback<T> is defined like so:

@Subscribe
public void onLoadTopStories(LoadTopStories loadTopStories) {
    Log.d(TAG, "onLoadTopStories");
    Callback<TopStories> callback = new Callback<TopStories>() {
        @Override
        public void success(TopStories topStories, Response response) {
            Log.d(TAG, "onSuccess");
            mBus.post(new LoadedTopStories(topStories));
        }

        @Override
        public void failure(RetrofitError error) {

        }
    };
    sClient.getTopStories(callback);
}

The Log.d(TAG, "onLoadTopStories"); gets called fine, the problem is that Log.d(TAG, "onSuccess"); is never reached. What is the issue here?

Some Notes:

Also Retrofit has no problem with the GET request and it initiates it properly:

enter image description here

Update

Added log statements to onFailure and I get the following:

com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was STRING at line 1 column 10819 path $.results[4].multimedia

Multimedia can be an Array or an Empty String

"multimedia": [
            {
                "url": "http://static01.nyt.com/images/2015/07/04/world/04Greece1-web/04Greece1-web-thumbStandard.jpg",
                "format": "Standard Thumbnail",
                "height": 75,
                "width": 75,
                "type": "image",
                "subtype": "photo",
                "caption": "A Greek Orthodox priest giving money to a man on a street in Thessaloniki, Greece, on Friday.",
                "copyright": "Giannis Papanikos/Associated Press"
            },
            {
                "url": "http://static01.nyt.com/images/2015/07/04/world/04Greece1-web/04Greece1-web-thumbLarge.jpg",
                "format": "thumbLarge",
                "height": 150,
                "width": 150,
                "type": "image",
                "subtype": "photo",
                "caption": "A Greek Orthodox priest giving money to a man on a street in Thessaloniki, Greece, on Friday.",
                "copyright": "Giannis Papanikos/Associated Press"
            },
            {
                "url": "http://static01.nyt.com/images/2015/07/04/world/04Greece1-web/04Greece1-web-articleInline.jpg",
                "format": "Normal",
                "height": 127,
                "width": 190,
                "type": "image",
                "subtype": "photo",
                "caption": "A Greek Orthodox priest giving money to a man on a street in Thessaloniki, Greece, on Friday.",
                "copyright": "Giannis Papanikos/Associated Press"
            },
            {
                "url": "http://static01.nyt.com/images/2015/07/04/world/04Greece1-web/04Greece1-web-mediumThreeByTwo210.jpg",
                "format": "mediumThreeByTwo210",
                "height": 140,
                "width": 210,
                "type": "image",
                "subtype": "photo",
                "caption": "A Greek Orthodox priest giving money to a man on a street in Thessaloniki, Greece, on Friday.",
                "copyright": "Giannis Papanikos/Associated Press"
            }
        ]

Upvotes: 2

Views: 1048

Answers (2)

AndyRoid
AndyRoid

Reputation: 5057

Ok I was able to get this working by creating a custom Deserialization class that extends Gson's JsonDeserializer<T> class. The following code worked for me:

public class ResultsDeserializerJson implements JsonDeserializer<Result> {

@Override
public Result deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {

    JsonElement titleElement = json.getAsJsonObject().get("title");
    JsonElement multimediaElement = json.getAsJsonObject().get("multimedia");
    if (multimediaElement.isJsonArray()) {
        return new Result(
                titleElement.toString(),
                (Multimedia[]) context.deserialize(multimediaElement.getAsJsonArray(), Multimedia[].class));
    } else if (multimediaElement.getAsString().equals("")) {
        Multimedia multimedia = new Multimedia();
        multimedia.setFormat("");
        multimedia.setUrl("");
        return new Result(titleElement.toString(), multimedia);
    } else {
        Log.d("ResultsDeserializerJson", multimediaElement.toString());
        throw new JsonParseException("Unsupported type of multimedia element");
    }
}
}

I added the following constructor to Result.java:

public Result(String title, Multimedia ... multimedia) {
    this.mTitle = title.replace("\"", "");;
    mMultimedia = Arrays.asList(multimedia);
}

I still feel like this is somewhat hacky, and there is a better solution. I'll investigate this some more but for now this is working fine. You can add more parameters to the Multimedia constructor but this still does not satisfy me fully as an answer in some ways.

This is the result I see now:

enter image description here

Sourcecode for the application is here: NYTimes Android App Repo

Upvotes: 2

Rick Sanchez
Rick Sanchez

Reputation: 4746

Your POJO is incorrect, Multimedia is not a list, it's just a String, according to your JSON

Upvotes: 0

Related Questions