Reputation: 3843
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
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:
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:
Upvotes: 2