Nested JSON Object (Retrofit) - java

I'm using Retrofit to send picture request and receive this Json
{"response":{
"face":{
"value":"true",
"confidence":55
},
"gender":{
"value":"male",
"confidence":73
},
...
}}
and I'm receiving it with Retrofit....
RestAdapter adapter = new RestAdapter.Builder()
.setLogLevel(RestAdapter.LogLevel.FULL)
.setEndpoint(END_POINT)
.build();
Mylistenerr listener = adapter.create(Mylistenerr.class);
File photo = new File(picturePath);
TypedFile image = new TypedFile("multipart/image/jpg", photo);
listener.setUserImage(
image,
new Callback<respostring>() {
#Override
public void success(respostring rp, Response arg1) {}
#Override
public void failure(RetrofitError arg0) {
pd.hide();
pd.dismiss();
Log.d("ERROR:", arg0.getLocalizedMessage());
}
});
}
private static class respostring {
private Content face;
private Content gender;
respostring() {}
}
class Content
{
private int confidence;
private String value;
Content(){}
public int getconf(){
return this.confidence;
}
public String getvalue(){
return this.value;
}
}
My interface
public interface Mylistenerr {
#Multipart
#POST("/public/test")
void setUserImage(
#Part("image") TypedFile file,
Callback<respostring> response);
}
but there is retrofit error. Is there something I miss here?

I'd recommend you using Gson for json deserialization instead since retrofit supports it very well. Then you can just create classes like this:
Your face class:
public class Face implements Serializable {
#SerializedName("value")
public boolean value;
#SerializedName("confidence")
public int confidence;
}
Your gender class:
public class Gender implements Serializable {
#SerializedName("value")
public String value;
#SerializedName("confidence")
public int confidence;
}
your response class:
public class YourResponseType implements Serializable {
#SerializedName("face")
public Face face;
#SerializedName("gender")
public Gender gender;
}
Then you can actually make retrofit doing the rest for you:
listener.setUserImage(image, new Callback<YourResonseType>() {...});
Hope that helps!

Related

Android: Api call using RxJava returning null response

I'm using Retrofit to make API call, When I handle the response I get an error, I'm trying to get data from this API call on this page https://my-json-server.typicode.com/jayraic/demo/db
Attempt to invoke interface method 'java.lang.Object java.util.List.get(int)' on a null object reference
Retrofit base call
public class NBADataFactory {
private final static String BASE_URL = "https://my-json-server.typicode.com/jayraic/demo/";
public final static String DB_URL = "https://my-json-server.typicode.com/jayraic/demo/db";
public static NBAService create() {
Retrofit retrofit = new Retrofit.Builder().baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
return retrofit.create(NBAService.class);
}
Retrofit Call
private void fetchTeamList() {
NBAApplication nbaApplication = NBAApplication.create(context);
NBAService nbaService = nbaApplication.getNbaService();
Disposable disposable = nbaService.fetchTeam(NBADataFactory.DB_URL)
.subscribeOn(nbaApplication.subscribeScheduler())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<NBAResponse>() {
#Override
public void accept(NBAResponse nbaResponse) {
changeTeamDataSet(nbaResponse.getTeamList());
teamProgress.set(View.GONE);
teamLabel.set(View.GONE);
teamRecycler.set(View.VISIBLE);
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) {
messageLabel.set(context.getString(R.string.error_loading_people));
teamProgress.set(View.GONE);
teamLabel.set(View.VISIBLE);
teamRecycler.set(View.GONE);
}
});
compositeDisposable.add(disposable);
}
private void changeTeamDataSet(List<Team> teams) {
teamList.addAll(teams);
setChanged();
notifyObservers();
}
Response Model
public class NBAResponse {
List<Team> teamList;
public List<Team> getTeamList() {
return teamList;
}
}
Team Model
public class Team implements Serializable {
#SerializedName("id") public String id;
#SerializedName("full_name") public String full_name;
#SerializedName("win") public String win;
#SerializedName("losses") public String losses;
#SerializedName("players") public List<Player> players;
}
I'm trying this out and stuck at this issue, any help or direction to right path would be appreciated.
The response json has teams as outer array name, but in your response Pojo class has its name teamList. Either of below should solve your problem
List<Team> teams;
or
#SerializedName("teams")
List<Team> teamList;

Dynamic Request body REST API Method using swagger

I have use case were I need to get requestBody based on selection of field.below is same code which I was able get the dynamic responseBody Based on selection ProtocolType.Is there is any way that swagger can read the RequestBody Dynamically.
Controller.Java
#ApiOperation(value = "Protocol Account", tags = {"ProtocolAccount"})
#RequestMapping(value = "/protocolAccount/{protocolName}",
method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody public ProtocolAccount getProtocol(#PathVariable String protocolName)
{
return service.getProtocol(protocolName);
}
Service.Java
public ProtocolAccount getProtocol(String protocolName){
ProtocolAccount protocolAccount=new ProtocolAccount();
Object object=ProtocolType.fromMap(protocolName);
protocolAccount.setProtocol(object);
return protocolAccount;
}
POJOs
public class ProtocolAccount
{
String Id;
private Object protocolType
}
public class Protocol{
private String port;
}
public class FTPProtocol extends Protocol{
/*Some Fields*/
}
public class SFTPProtocol extends Protocol{
/*Some Fields*/
}
Enumeration
public enum ProtocolType
{
SFTP("SFTP"), FTPS("FTPS"), AS2("AS2"), FTP("FTP");
private final String value;
private static final EnumMap<ProtocolType,
Object>map = new EnumMap<ProtocolType, Object>(ProtocolType.class);
static{
map.put(ProtocolType.SFTP, new SFTPProtocol());
map.put(ProtocolType.FTP, new FTPProtocol());
map.put(ProtocolType.FTPS,new FTPSProtocol());
}
ProtocolType(String v){
value=v;
}
public static ProtocolType fromValue(String val){
return EnumSet.allOf(ProtocolType.class)
.stream().filter(e->e.value.equals(val))
.findFirst().orElseThrow(()->new IllegalArgumentException(val));
}
public String value(){
return value;
}
public static Object fromMap(String value)
{
return map.get(ProtocolType.fromValue(value));
}
}

Unable to make restTemplate call with Generics for nested Json

I am trying to make a restTemplate call for API testing. The json returned is a nested one with multiple levels.
{
"code": 200,
"data": {
"result": {
"publicId": "xyz"
}
}
}
I have the following classes acting as wrapper :
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public abstract class RestCallResponse<T> {
private int code;
protected RestCallResponse(int code) {
this.code = code;
}
protected RestCallResponse(){}
#JsonProperty("data")
public Map<?, ?> getRestCallResponse() {
return ImmutableMap.of("result", getResult());
}
#JsonIgnore
protected abstract T getResult();
public int getCode() {
return code;
}
}
And then a SuccessRestResponse class extending this abstract class :
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE)
public class SuccessRestResponse<T> extends RestCallResponse<T> {
#JsonProperty("result")
private T result;
public SuccessRestResponse() {
}
public SuccessRestResponse(T result) {
super(HttpStatus.SC_OK);
this.result = result;
}
protected T getResult() {
return this.result;
}
}
Then finally I have the actual data POJO :
public final class CreatedResponse {
#JsonProperty
private final EntityId publicId;
public CreateCreativeResponse(EntityId publicId) {
this.publicId = publicId;
}
}
In the test case, I am making a call as such :
ResponseEntity<SuccessRestResponse<CreatedResponse>> newResponse =
restTemplate.exchange(requestEntity, new ParameterizedTypeReference<SuccessRestResponse<CreatedResponse>>() {});
But I am getting the following error :
nested exception is org.springframework.http.converter.HttpMessageNotReadableException: Could not read document: null value in entry: result=null (through reference chain: com.inmobi.helix.test.api.dao.SuccessRestResponse["data"]);
Any suggestions? Where am I going wrong?
I solved the problem with a workaround. Still don't know what's wrong with the above piece of code though.
I got rid of the class RestCallResponse<T> and edited the field members in SuccessRestResponse<T> to look like this :
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public class SuccessRestResponse<T> {
private int code;
private Map<String, T> data;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public Map<String, T> getData() {
return data;
}
#JsonIgnore
public T getResult() {
return data.get("result");
}
public void setData(Map<String, T> data) {
this.data = data;
}
}
This corresponds to the nested json while deserialization.
P.S. - Would still like to know what went wrong in my above code
though. As in, why did class hierarchy not work for me.

Gson + AutoValue error while trying to implement own deserializer

I have a Retrofit interface that I combine with RxJava. All my retrofit calls return Observable. Those "SomePojo" classes, I generate them online using Schema2Pojo sites.
I have a problem when making the following api call:
https://developers.themoviedb.org/3/search/2Y9y2LReFZdHFHbFA
As you can see, it is an array with two different types of objects, that I called "Media" and "Credit". These two classes I generated using Google's autovalue as follows:
#AutoValue
public abstract class Media implements Parcelable {
#SerializedName(value = "title", alternate = {"name"})
public abstract String title();
#Nullable
#SerializedName("vote_average")
public abstract String voteAverage();
#Nullable
#SerializedName("backdrop_path")
public abstract String backdropPath();
#Nullable
public abstract String adult();
public abstract String id();
#Nullable
public abstract String overview();
#Nullable
#SerializedName("original_language")
public abstract String originalLanguage();
#Nullable
#SerializedName("genre_ids")
public abstract List<String> genreIds();
#Nullable
#SerializedName(value = "release_date", alternate = {"first_air_date"})
public abstract String releaseDate();
#Nullable
#SerializedName(value = "original_title", alternate = {"original_name"})
public abstract String originalTitle();
#Nullable
#SerializedName("vote_count")
public abstract String voteCount();
#Nullable
#SerializedName("poster_path")
public abstract String posterPath();
#Nullable
public abstract String video();
#Nullable
#SerializedName("media_type")
public abstract String mediaType();
#Nullable
public abstract String popularity();
#Nullable
#SerializedName("origin_country")
public abstract List<String> originalCountry();
public static Media create(String title, String voteAverage, String backdropPath,
String adult, String id, String overview, String originalLanguage,
List<String> genreIds, String releaseDate, String originalTitle,
String voteCount, String posterPath, String video, String mediaType,
String popularity, List<String> originalCountry) {
return new AutoValue_Media(title, voteAverage, backdropPath, adult, id, overview,
originalLanguage, genreIds, releaseDate, originalTitle, voteCount, posterPath,
video, mediaType, popularity, originalCountry);
}
public static TypeAdapter<Media> typeAdapter(Gson gson) {
return new AutoValue_Media.GsonTypeAdapter(gson);
}
}
And:
#AutoValue
public abstract class Credit implements Parcelable {
public abstract String id();
#SerializedName("credit_id")
public abstract String creditId();
#Nullable
public abstract String department();
public abstract String name();
#Nullable
#SerializedName(value = "job", alternate = {"character"})
public abstract String job();
#Nullable
#SerializedName("profile_path")
public abstract String profilePath();
#Nullable
public abstract String order();
#Nullable
#SerializedName("cast_id")
public abstract String castId();
public static Credit create(String id, String creditId, String department, String name, String job,
String profilePath, String order, String castId) {
return new AutoValue_Credit(id, creditId, department, name, job, profilePath, order, castId);
}
public static TypeAdapter<Credit> typeAdapter(Gson gson) {
return new AutoValue_Credit.GsonTypeAdapter(gson);
}
}
To resolve the problem created by the array with two different kind of objects, I made the POJO return by this call implement its own JsonDeserializer:
public class MediaListPojo {
#SerializedName("results")
private List<Media> movies;
private List<Credit> credits;
private Dates dates;
private String page;
private String total_pages;
private String total_results;
public List<Media> getMedia() {
return movies;
}
public void setMovies(List<Media> movies) {
this.movies = movies;
}
public List<Credit> getCredits() {return credits;}
public void setCredits(List<Credit> credits) {this.credits = credits;}
public Dates getDates() {
return dates;
}
public void setDates(Dates dates) {
this.dates = dates;
}
public String getPage() {
return page;
}
public void setPage(String page) {
this.page = page;
}
public String getTotal_pages() {
return total_pages;
}
public void setTotal_pages(String total_pages) {
this.total_pages = total_pages;
}
public String getTotal_results() {
return total_results;
}
public void setTotal_results(String total_results) {
this.total_results = total_results;
}
#Override
public String toString() {
return "MediaListPojo{" +
"movies=" + movies +
", credits=" + credits +
", dates=" + dates +
", page='" + page + '\'' +
", total_pages='" + total_pages + '\'' +
", total_results='" + total_results + '\'' +
'}';
}
public static class MediaListPojoDeserializer implements JsonDeserializer<MediaListPojo> {
#Override
public MediaListPojo deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
MediaListPojo mediaListPojo = new Gson().fromJson(json, MediaListPojo.class);
JsonObject jsonObject = json.getAsJsonObject();
if (jsonObject.has("results")) {
JsonArray jsonArray = jsonObject.getAsJsonArray("results");
List<Credit> credits = new ArrayList<>();
Credit credit;
for (JsonElement element : jsonArray) {
JsonObject current = element.getAsJsonObject();
if (current.get("media_type").getAsString().equals("person")) {
credit = new Gson().fromJson(current, Credit.class);
credits.add(credit);
}
}
mediaListPojo.setCredits(credits);
}
return mediaListPojo;
}
}
}
The main idea behind this json deserializer is: "Use the default type adapter for this class and then set the Credit objects using this JsonDeserializer"
However, for some reason, I get the following error while deserializing:
java.lang.RuntimeException: Failed to invoke public Media() with no args
...
Caused by: java.lang.InstantiationException: Can't instantiate abstract class Media
at java.lang.reflect.Constructor.newInstance(Native Method)
It shouldn't try to instantiate the abstract superclass, but use AutoValue's generated Type Adapter.
This is how I built my retrofit instance:
class Creator {
public static MovieService newMovieService() {
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new AutoValueGson_MyAdapterFactory())
.registerTypeAdapter(MediaListPojo.class, new MediaListPojo.MediaListPojoDeserializer())
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
.create();
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(NetworkUtil.makeQueryInterceptor("api_key", BuildConfig.MY_API_KEY))
.build();
Retrofit retrofit = new Retrofit.Builder()
.client(client)
.baseUrl(MovieService.ENDPOINT)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
return retrofit.create(MovieService.class);
Can you help me understand what I did wrong?
Well, I found the solution 5 minutes after posting the question, but since I think other people might struggle with this as well. I'll share the solution:
Basically, inside of my JsonDeserializer, I was using a new instance of a Gson object when, in fact, this is a mistake.
The typeadapterfactory that I registered while creating my retrofit instance is where all of the other TypeAdapters live.
Therefore, calling
Gson gson = new Gson();
Doesn't supply the type adapters that I needed to deserialize the rest of the object.
I hope it helps.
you need to register your TypeAdapterFactory like so when creating your instance of Gson. for me I did this in my dagger 2 module like below.
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(GsonAdapterFactory.create())
.create();
hope this helps.

Gson property order in android

I have integrated Gson to create the json used in a request for an android application.
Here is my model class
public class TwitterUser {
#Expose
public String gid;
public String icon_url;
public Boolean is_app_user;
#Expose
public String displayName;
public TwitterUser(String l, String i, String url, Boolean app_user) {
gid = i;
displayName = l;
icon_url = url;
is_app_user = app_user;
}
public TwitterUser(String l, String i) {
gid = i;
displayName = l;
}
public String getGid() {
return gid;
}
public void setGid(String gid) {
this.gid = gid;
}
public String getIcon_url() {
return icon_url;
}
public void setIcon_url(String icon_url) {
this.icon_url = icon_url;
}
public Boolean getIs_app_user() {
return is_app_user;
}
public void setIs_app_user(Boolean is_app_user) {
this.is_app_user = is_app_user;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
Here is how i create the json request
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
gson.toJson(twitterUser));
But when I send the request to the server - the order will be rejected. I have to change the request's field order to stay:
gid
displayName
but gson creates other way around, is there any way to achieve this.
Gson doesn't support definition of property order out of the box, but there are other libraries that do. Jackson allows defining this with #JsonPropertyOrder, for example.
But of course Gson has it's way so you can do it by creating your very own Json serializer:
public class TwitterUserSerializer implements JsonSerializer<TwitterUser> {
#Override
public JsonElement serialize(TwitterUser twitterUser, Type type, JsonSerializationContext context) {
JsonObject object = new JsonObject();
object.add("gid", context.serialize(twitterUser.getGid());
object.add("displayName", context.serialize(twitterUser.getDisplayName());
// ...
return object;
}
}
Then of course you need to pass this serializer to Gson during Setup like this:
Gson gson = new GsonBuilder().registerTypeAdapter(TwitterUser.class, new TwitterUserSerializer()).excludeFieldsWithoutExposeAnnotation().create();
String json = gson.toJson(twitterUser);
See also:
Gson User Guide - Custom serializers and deserializers

Categories