fadelakin
fadelakin

Reputation: 3843

Expected BEGIN_OBJECT but was STRING with custom TypeAdapter

So I'm trying to solve a problem that I have with my custom TypeAdapter with Gson and Retrofit. I keep getting a Expected BEGIN_OBJECT but was STRING error but I'm not really sure how to solve it. Previously I was getting a Expected BEGIN_ARRAY but was STRING error and I solved that. I'm not sure if it needs to be the same way with this new error. I've listed my classes below and any help is appreciated. This is what my json looks like here: http://pastie.org/private/bfo86iznldacbz10rtsdsg The main problem is the multimedia field in the json. It's an empty string if there is no value but if there is a value, it returns a jsonarray that contains jsonobjects.

ArrayAdapter.java

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;


public class ArrayAdapter<T> extends TypeAdapter<List<T>> {
    private Class<T> adapterclass;

    public ArrayAdapter(Class<T> adapterclass) {
        this.adapterclass = adapterclass;
    }

    public List<T> read(JsonReader reader) throws IOException {

        List<T> list = new ArrayList<T>();

        Gson gson = new GsonBuilder()
                .registerTypeAdapterFactory(new ArrayAdapterFactory())
                .create();

        if (reader.peek() == JsonToken.STRING) {
            T inning = gson.fromJson(reader, adapterclass);
            list.add(inning);
        } else if (reader.peek() == JsonToken.BEGIN_ARRAY) {

            reader.beginArray();
            while (reader.hasNext()) {
                T inning = gson.fromJson(reader, adapterclass);
                list.add(inning);
            }
            reader.endArray();

        } else if (reader.peek() == JsonToken.BEGIN_OBJECT) {
            reader.beginObject();
            while(reader.hasNext()) {
            }
        }

        return list;
    }

    public void write(JsonWriter writer, List<T> value) throws IOException {

    }

}

ArraryAdapterFactory

import java.lang.reflect.ParameterizedType;
import java.util.List;

import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;

public class ArrayAdapterFactory implements TypeAdapterFactory {

    @SuppressWarnings({ "unchecked", "rawtypes" })
    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> type) {

        TypeAdapter<T> typeAdapter = null;

        try {
            if (type.getRawType() == List.class)
                typeAdapter = new ArrayAdapter(
                        (Class) ((ParameterizedType) type.getType())
                                .getActualTypeArguments()[0]);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return typeAdapter;


    }

}

Times.java

public class NYTimes {

    // uses the new york times api
    // gets the top stores on the nytimes homepage
    private static final String API_URL = "http://api.nytimes.com/svc/news/v3/content/all/all";

    static Gson gson = new GsonBuilder().registerTypeAdapterFactory(new ArrayAdapterFactory()).create();

    private static final RestAdapter REST_ADAPTER = new RestAdapter.Builder()
            .setConverter(new GsonConverter(gson))
            .setEndpoint(API_URL)
            .build();

    private static final NYTimesService SERVICE = REST_ADAPTER.create(NYTimesService.class);

    public static NYTimesService getService() {
        return SERVICE;
    }
}

POJO Classes

News.java

public class News {

    @Expose
    private String status;
    @Expose
    private String copyright;
    @SerializedName("num_results")
    @Expose
    private Integer numResults;
    @Expose
    private List<Result> results = new ArrayList<Result>();

    /**
     *
     * @return
     * The status
     */
    public String getStatus() {
        return status;
    }

    /**
     *
     * @param status
     * The status
     */
    public void setStatus(String status) {
        this.status = status;
    }

    /**
     *
     * @return
     * The copyright
     */
    public String getCopyright() {
        return copyright;
    }

    /**
     *
     * @param copyright
     * The copyright
     */
    public void setCopyright(String copyright) {
        this.copyright = copyright;
    }

    /**
     *
     * @return
     * The numResults
     */
    public Integer getNumResults() {
        return numResults;
    }

    /**
     *
     * @param numResults
     * The num_results
     */
    public void setNumResults(Integer numResults) {
        this.numResults = numResults;
    }

    /**
     *
     * @return
     * The results
     */
    public List<Result> getResults() {
        return results;
    }

    /**
     *
     * @param results
     * The results
     */
    public void setResults(List<Result> results) {
        this.results = results;
    }

}

Result.java

public class Result {
    @Expose
    private String section;
    @Expose
    private String subsection;
    @Expose
    private String title;
    @SerializedName("abstract")
    @Expose
    private String _abstract;
    @Expose
    private String url;
    @Expose
    private String byline;
    @SerializedName("thumbnail_standard")
    @Expose
    private String thumbnailStandard;
    @SerializedName("item_type")
    @Expose
    private String itemType;
    @Expose
    private String source;
    @SerializedName("updated_date")
    @Expose
    private String updatedDate;
    @SerializedName("created_date")
    @Expose
    private String createdDate;
    @SerializedName("published_date")
    @Expose
    private String publishedDate;
    @SerializedName("material_type_facet")
    @Expose
    private String materialTypeFacet;
    @Expose
    private String kicker;
    @Expose
    private String subheadline;
    @SerializedName("des_facet")
    @Expose
    private List<String> desFacet = new ArrayList<>();
    @SerializedName("org_facet")
    @Expose
    private List<String> orgFacet = new ArrayList<>();
    @SerializedName("per_facet")
    @Expose
    private List<String> perFacet = new ArrayList<>();
    @SerializedName("geo_facet")
    @Expose
    private List<String> geoFacet = new ArrayList<>();
    @SerializedName("related_urls")
    @Expose
    private Object relatedUrls;
    @Expose
    private List<Multimedium> multimedia;

    /**
     *
     * @return
     * The section
     */
    public String getSection() {
        return section;
    }

    /**
     *
     * @param section
     * The section
     */
    public void setSection(String section) {
        this.section = section;
    }

    /**
     *
     * @return
     * The subsection
     */
    public String getSubsection() {
        return subsection;
    }

    /**
     *
     * @param subsection
     * The subsection
     */
    public void setSubsection(String subsection) {
        this.subsection = subsection;
    }

    /**
     *
     * @return
     * The title
     */
    public String getTitle() {
        return title;
    }

    /**
     *
     * @param title
     * The title
     */
    public void setTitle(String title) {
        this.title = title;
    }

    /**
     *
     * @return
     * The _abstract
     */
    public String getAbstract() {
        return _abstract;
    }

    /**
     *
     * @param _abstract
     * The abstract
     */
    public void setAbstract(String _abstract) {
        this._abstract = _abstract;
    }

    /**
     *
     * @return
     * The url
     */
    public String getUrl() {
        return url;
    }

    /**
     *
     * @param url
     * The url
     */
    public void setUrl(String url) {
        this.url = url;
    }

    /**
     *
     * @return
     * The byline
     */
    public String getByline() {
        return byline;
    }

    /**
     *
     * @param byline
     * The byline
     */
    public void setByline(String byline) {
        this.byline = byline;
    }

    /**
     *
     * @return
     * The thumbnailStandard
     */
    public String getThumbnailStandard() {
        return thumbnailStandard;
    }

    /**
     *
     * @param thumbnailStandard
     * The thumbnail_standard
     */
    public void setThumbnailStandard(String thumbnailStandard) {
        this.thumbnailStandard = thumbnailStandard;
    }

    /**
     *
     * @return
     * The itemType
     */
    public String getItemType() {
        return itemType;
    }

    /**
     *
     * @param itemType
     * The item_type
     */
    public void setItemType(String itemType) {
        this.itemType = itemType;
    }

    /**
     *
     * @return
     * The source
     */
    public String getSource() {
        return source;
    }

    /**
     *
     * @param source
     * The source
     */
    public void setSource(String source) {
        this.source = source;
    }

    /**
     *
     * @return
     * The updatedDate
     */
    public String getUpdatedDate() {
        return updatedDate;
    }

    /**
     *
     * @param updatedDate
     * The updated_date
     */
    public void setUpdatedDate(String updatedDate) {
        this.updatedDate = updatedDate;
    }

    /**
     *
     * @return
     * The createdDate
     */
    public String getCreatedDate() {
        return createdDate;
    }

    /**
     *
     * @param createdDate
     * The created_date
     */
    public void setCreatedDate(String createdDate) {
        this.createdDate = createdDate;
    }

    /**
     *
     * @return
     * The publishedDate
     */
    public String getPublishedDate() {
        return publishedDate;
    }

    /**
     *
     * @param publishedDate
     * The published_date
     */
    public void setPublishedDate(String publishedDate) {
        this.publishedDate = publishedDate;
    }

    /**
     *
     * @return
     * The materialTypeFacet
     */
    public String getMaterialTypeFacet() {
        return materialTypeFacet;
    }

    /**
     *
     * @param materialTypeFacet
     * The material_type_facet
     */
    public void setMaterialTypeFacet(String materialTypeFacet) {
        this.materialTypeFacet = materialTypeFacet;
    }

    /**
     *
     * @return
     * The kicker
     */
    public String getKicker() {
        return kicker;
    }

    /**
     *
     * @param kicker
     * The kicker
     */
    public void setKicker(String kicker) {
        this.kicker = kicker;
    }

    /**
     *
     * @return
     * The subheadline
     */
    public String getSubheadline() {
        return subheadline;
    }

    /**
     *
     * @param subheadline
     * The subheadline
     */
    public void setSubheadline(String subheadline) {
        this.subheadline = subheadline;
    }

    /**
     *
     * @return
     * The desFacet
     */
    public List<String> getDesFacet() {
        return desFacet;
    }

    /**
     *
     * @param desFacet
     * The des_facet
     */
    public void setDesFacet(List<String> desFacet) {
        this.desFacet = desFacet;
    }

    /**
     *
     * @return
     * The orgFacet
     */
    public List<String> getOrgFacet() {
        return orgFacet;
    }

    /**
     *
     * @param orgFacet
     * The org_facet
     */
    public void setOrgFacet(List<String> orgFacet) {
        this.orgFacet = orgFacet;
    }

    /**
     *
     * @return
     * The perFacet
     */
    public List<String> getPerFacet() {
        return perFacet;
    }

    /**
     *
     * @param perFacet
     * The per_facet
     */
    public void setPerFacet(List<String> perFacet) {
        this.perFacet = perFacet;
    }

    /**
     *
     * @return
     * The geoFacet
     */
    public List<String> getGeoFacet() {
        return geoFacet;
    }

    /**
     *
     * @param geoFacet
     * The geo_facet
     */
    public void setGeoFacet(List<String> geoFacet) {
        this.geoFacet = geoFacet;
    }

    /**
     *
     * @return
     * The relatedUrls
     */
    public Object getRelatedUrls() {
        return relatedUrls;
    }

    /**
     *
     * @param relatedUrls
     * The related_urls
     */
    public void setRelatedUrls(Object relatedUrls) {
        this.relatedUrls = relatedUrls;
    }

    /**
     *
     * @return
     * The multimedia
     */
    public List<Multimedium> getMultimedia() {
        return multimedia;
    }

    /**
     *
     * @param multimedia
     * The multimedia
     */
    public void setMultimedia(List<Multimedium> multimedia) {
        this.multimedia = multimedia;
    }
}

Multimedium.java

public class Multimedium {
    @Expose
    private String url;
    @Expose
    private String format;
    @Expose
    private Integer height;
    @Expose
    private Integer width;
    @Expose
    private String type;
    @Expose
    private String subtype;
    @Expose
    private Object caption;
    @Expose
    private Object copyright;

    /**
     *
     * @return
     * The url
     */
    public String getUrl() {
        return url;
    }

    /**
     *
     * @param url
     * The url
     */
    public void setUrl(String url) {
        this.url = url;
    }

    /**
     *
     * @return
     * The format
     */
    public String getFormat() {
        return format;
    }

    /**
     *
     * @param format
     * The format
     */
    public void setFormat(String format) {
        this.format = format;
    }

    /**
     *
     * @return
     * The height
     */
    public Integer getHeight() {
        return height;
    }

    /**
     *
     * @param height
     * The height
     */
    public void setHeight(Integer height) {
        this.height = height;
    }

    /**
     *
     * @return
     * The width
     */
    public Integer getWidth() {
        return width;
    }

    /**
     *
     * @param width
     * The width
     */
    public void setWidth(Integer width) {
        this.width = width;
    }

    /**
     *
     * @return
     * The type
     */
    public String getType() {
        return type;
    }

    /**
     *
     * @param type
     * The type
     */
    public void setType(String type) {
        this.type = type;
    }

    /**
     *
     * @return
     * The subtype
     */
    public String getSubtype() {
        return subtype;
    }

    /**
     *
     * @param subtype
     * The subtype
     */
    public void setSubtype(String subtype) {
        this.subtype = subtype;
    }

    /**
     *
     * @return
     * The caption
     */
    public Object getCaption() {
        return caption;
    }

    /**
     *
     * @param caption
     * The caption
     */
    public void setCaption(Object caption) {
        this.caption = caption;
    }

    /**
     *
     * @return
     * The copyright
     */
    public Object getCopyright() {
        return copyright;
    }

    /**
     *
     * @param copyright
     * The copyright
     */
    public void setCopyright(Object copyright) {
        this.copyright = copyright;
    }
}

I know it's a lot of code to go through but I'm looking for any help to solve this problem so I'm trying to be as clear as I can.

Upvotes: 3

Views: 1362

Answers (1)

AndyRoid
AndyRoid

Reputation: 5057

I was able to get a solution by implementing a custom JsonDeserializer and then adding that to the RestAdapter like so:

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");
        }
    }
}

Inside Result.java I added the following constructor (note you can add more parameters for things like section, subsection, etc.):

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

Then for my RestAdapter I do the following:

private NYTimesService() {

    Gson gson = new GsonBuilder()
            .registerTypeAdapter(Result.class, new ResultsDeserializerJson()).create();

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

I created a custom application to get this working and the repository is open sourced here:

SampleNYTimesApp Repo

This is still somewhat of a hackish solution, and I feel like it is not the most optimal, but I was able to get the following in the time I figured out the solution:

enter image description here

Upvotes: 2

Related Questions