I am using Retrofit and Parceler libraries in order to communicate with my server.
The server has the following two API methods:
GET /api/metadata/{id} that returns the following JSON
{
"id": 1,
"active": true,
"location": {
"type": "Point",
"coordinates": [
30.0000,
30.0000
]
}
}
POST /api/metadata/{id} that expects the following JSON
{
"id": 1,
"active": true,
"location_latitude": 30.0000,
"location_longitude": 30.0000
}
That is so for historic reasons and cannot change.
In my android application, I declare Retrofit in the following way:
public interface ServerApi {
#GET("/api/metadata/{id}")
Metadata getMetadata(#Path("id") int id);
#POST("/api/metadata/{id}")
Metadata updateMetadata(#Path("id") int id, #Body Metadata metadata);
}
Parcel classes are defined in the following way:
Metadata:
#Parcel
public class Metadata {
#SerializedName("id")
private Integer id;
#SerializedName("location")
private GeometryPoint location;
#SerializedName("location_latitude")
private float locationLatitude;
#SerializedName("location_longitude")
private float locationLongitude;
public void setId(Integer id) {
this.id = id;
}
public void setLocationLatitude(float locationLatitude) {
this.locationLatitude = locationLatitude;
}
public void setLocationLongitude(float locationLongitude) {
this.locationLongitude = locationLongitude;
}
public void setLocation(GeometryPoint location) {
this.location = location;
}
public Integer getId() {
return id;
}
public float getLocationLatitude() {
return locationLatitude;
}
public float getLocationLongitude() {
return locationLongitude;
}
public GeometryPoint getLocation() {
return location;
}
}
GeometryPoint:
#Parcel
public class GeometryPoint {
#SerializedName("type")
private String type;
#SerializedName("coordinates")
private float[] coordinates;
public void setType(String type) {
this.type = type;
}
public void setCoordinates(float[] coordinates) {
this.coordinates = coordinates;
}
public String getType() {
return type;
}
public float[] getCoordinates() {
return coordinates;
}
}
I would like to use Metadata class throughout my application. I would like to query the server, receive Metadata, update it, and send it to the server. Obviously, the formats of metadata differ between the GET and POST. As such, I'd like to have GET coverted to POST format upon receiving it.
My question is whether it is possible to somehow declare annotations so that Retrofit and Parceler would be aware of location parameter, deserialize it from JSON but write it to Metadata class via setLocation() method where I could break it down into `location_latitude' and 'location_longitude'.
This is some pseudocode of the desired Metadata class:
#Parcel
public class Metadata {
#SerializedName("id")
private Integer id;
// I'd like not having location variable defined at all
// but use some annotation magic :)) to tell GSON to deserialize
// JSON and call setLocation() when it tries to process location
// parameter of the server response
/*
#SerializedName("location")
private GeometryPoint location;
*/
#SerializedName("location_latitude")
private float locationLatitude;
#SerializedName("location_longitude")
private float locationLongitude;
public void setId(Integer id) {
this.id = id;
}
public void setLocationLatitude(float locationLatitude) {
this.locationLatitude = locationLatitude;
}
public void setLocationLongitude(float locationLongitude) {
this.locationLongitude = locationLongitude;
}
public void setLocation(GeometryPoint location) {
this.location_latitude = location.getCoordinates()[1];
this.location_longitude = location.getCoordinates()[0];
}
public Integer getId() {
return id;
}
public float getLocationLatitude() {
return locationLatitude;
}
public float getLocationLongitude() {
return locationLongitude;
}
// No need for getLocation method
}
Or am I just being silly (I literally picked up Retrofit, Parceler, and GSON awareness yesterday) and should create two metadata classes MetadataExternal and MetadataInternal to use for receiving and sending to the server?
Related
I'm working with an API where all data are wrapped in a custom object (see below), so I cannot use moshi to convert the retrofit body direct to my models. What is the best way to work with moshi in this case?
#COLLECTIONS ENDPOINT
{
"status": 200,
"data": [
{
"id": 28122,
"name": "Abandonei",
"counts": {
"books": 3
}
},
{
"id": 21091,
"name": "Lendo",
"counts": {
"books": 6
}
},
],
"errors": [],
"pagination": {
"after": 2,
"hasNextPage": true
}
}
The same json structure is used in all api endpoints, the default fields are:
{
"status": 200,
"data": [],
"errors": [],
"pagination": {
"after": 1,
"hasNextPage": true
}
}
My Collection model:
public class BookCollection {
public long id;
public String name;
public ArrayList<Book> books;
public BookCollection(long id, String name) {
this.id = id;
this.name = name;
}
}
To avoid create a parent class to each model, I have implemented a way to use a class that receive a generic type.
To put this to work I've changed the Moshi class to Gson.
My model:
public class BookCollection {
public long id;
public String name;
public ArrayList<Book> books;
public BookCollection(long id, String name) {
this.id = id;
this.name = name;
}
}
The wrapper class used to unwrap the json data:
public class ApiWrapper<T> {
public final int status;
public final T data;
public final List<ApiError> errors = new ArrayList<>();
public ApiWrapper(int status, T data, List<ApiError> errors) {
this.status = status;
this.data = data;
this.errors.addAll(errors);
}
}
The Errors class, referenced in the class above:
public class ApiError {
public int code;
public String message;
public String error;
}
Usage:
public interface NetAPI {
#GET("me/collections")
Call<ResponseBody> getCollections(#Header("Authorization") String auth);
}
public class CollectionViewModel extends ViewModel {
private final MutableLiveData<List<Collection>> collections = new MutableLiveData<>();
private final MutableLiveData<Boolean> loading = new MutableLiveData<>();
private final MutableLiveData<Boolean> collectionError = new MutableLiveData<>();
private Call<ResponseBody> call;
private void fetchCollections() {
loading.setValue(true);
call = Api.getInstance().getCollections(TOKEN);
call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
try {
collectionError.setValue(false);
//THE SECRET
Gson gson = new Gson();
ApiWrapper<List<Collection>> apiResponse = null;
apiResponse = gson.fromJson(response.body().string(), new TypeToken<ApiWrapper<List<Collection>>>(){}.getType());
collections.setValue(apiResponse.data);
loading.setValue(false);
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.e(getClass().getSimpleName(), "Error loading data", t);
collectionError.setValue(true);
loading.setValue(false);
}
});
}
}
At this way I can reuse my ApiWrapper class to any model (Books, Users, Login, etc).
Thanks.
You will need to setup gson/moshi to use your classes that you have created for the json to object mapping. Here is an example of what those java classes would look like. You can use data classes in kotlin as well. For moshi, you will have to create the adapter to help with the json to object mapping.
publci class CollectionResponse {
public int status;
public List<BookCollection> data;
public List<Error> errors;
public Pagination pagination;
}
public class Pagination {
public int after;
public boolean hasNextPage;
}
public class BookCollection {
public long id;
public String name;
public Count counts;
}
public Count {
public int books;
}
public class Error {
}
I'm using Jackson as part of a spring boot app. I am turning JSON into Java, and I am getting this error. I did some research, but I still don't understand what is going wrong or how to fix it.
Here is the JSON fragment:
"dataBlock": {
"sections": [
{
"info": "",
"prompt": "",
"name": "First Section",
"sequence": 0,
"fields": [],
"gatingConditions": [],
"guid": "480d160c-c34f-4022-97b0-e8a1f28c49ae",
"id": -2
}
],
"prompt": "",
"id": -1,
"name": ""
}
So my Java object for this "dataBlock" element:
public class DataBlockObject {
private int id;
private String prompt;
private String name;
private List<SectionObject> sections;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getPrompt() {
return prompt;
}
public void setPrompt(String prompt) {
this.prompt = prompt;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<SectionObject> getSections() {
return sections;
}
public void setSections(List<SectionObject> sections) {
this.sections = sections;
}
}
And the Section object is this:
public class SectionObject {
private int id;
private String name;
private String prompt;
private String info;
private int sequence;
private List<FieldObject> fields;
private List<GatingConditionObject> gatingConditions;
private String guid;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPrompt() {
return prompt;
}
public void setPrompt(String prompt) {
this.prompt = prompt;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
public int getSequence() {
return sequence;
}
public void setSequence(int sequence) {
this.sequence = sequence;
}
public List<FieldObject> getFields() {
return fields;
}
public void setFields(List<FieldObject> fields) {
this.fields = fields;
}
public List<GatingConditionObject> getGatingConditions() {
return gatingConditions;
}
public void setGatingConditions(List<GatingConditionObject> gatingConditions) {
this.gatingConditions = gatingConditions;
}
public String getGuid() {
return guid;
}
public void setGuid(String guid) {
this.guid = guid;
}
}
So it seems to me that Jackson would make a DataBlockObject, map the obvious elemenets, and create an array that I have clearly marked as a List named sections. -- just like the JSON shows.
Now the error is:
Caused by: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "sections" (class com.gridunity.workflow.bean.json.SectionObject), not marked as ignorable (8 known properties: "gatingConditions", "sequence", "prompt", "fields", "id", "info", "guid", "name"])
Now according to that error it would seem that one of my 8 elements should be named "sections" - But that's not one of my elements. It clearly has a problem with my List of Sections, but I cant figure out what it is.
Can someone explain WHY this is happening, especially sence it looks like I have my structure correct, and how to fix this. I have seen this on other posts:
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
But that seems incredibly wrong as I know all of my properties.
It looks like the JSON itself has another sections field in one or more of the dataBlock.sections items. If you don't have control over the construction of the JSON object, you'll need to add a #JsonIgnoreProperties annotation on the SectionObject class so that when the JSON object has fields that aren't specified in the POJO, it won't throw an error during deserialization.
#JsonIgnoreProperties(ignoreUnknown = true)
public class SectionObject {
// class members and methods here
}
I am using Spring's RestTemplate to convert a JSON response from the RiotAPI into my BasicSummoner object. I believe the issue is with converting the JSON response into my object. After calling getForObject() all of the object's fields are null/empty. Any help is appreciated as this is my first Spring project and first time using Riot's API.
I have verified that the JSON resonse is correct and looks like this:
{
"riotschmick": {
"id": 585897,
"name": "RiotSchmick",
"profileIconId": 782,
"summonerLevel": 30,
"revisionDate": 1469155559000
}
}
My request looks like this:
public BasicSummoner requestBasicSummoner() {
RestTemplate template = new RestTemplate();
String mes = "https://na.api.pvp.net/api/lol/na/v1.4/summoner/by-name/RiotSchmick?api_key=<my-api-key>";
BasicSummoner summoner = template.getForObject(mes, BasicSummoner.class);
log.info(summoner.toString());
return summoner;
}
And the object BasicSummoner looks like this:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
#JsonIgnoreProperties(ignoreUnknown = true)
public class BasicSummoner {
private long id;
private String name;
private int profileIconId;
private long revisionDate;
private long summonerLevel;
public BasicSummoner() {
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getProfileIconId() {
return profileIconId;
}
public void setProfileIconId(int profileIconId) {
this.profileIconId = profileIconId;
}
public long getRevisionDate() {
return revisionDate;
}
public void setRevisionDate(long revisionDate) {
this.revisionDate = revisionDate;
}
public long getSummonerLevel() {
return summonerLevel;
}
public void setSummonerLevel(long summonerLevel) {
this.summonerLevel = summonerLevel;
}
#Override
public String toString() {
return "id=" + id + ", name=" + name + " , summoner level=" + summonerLevel;
}
}
Your JSON is not a single Object, but an Object inside another Object.
This means that to use your code as it is now, you need to unwrap the inner Object, or change the structure to something else.
The response seems to fit a Map<String, BasicSummoner>
When I do rest class I have received below response.
Response:
{
"_index": "a_index",
"total":9,
"_type": "e",
"_id": "BSKnamtd_8-egMNvh",
"_data": 2.076404564,
"_secure": {
"email": "abcd1#gmail.com"
}
}
To set this response. I have created pojo class as shown blow.
public class data implements Serializable {
private static final long serialVersionUID = 644188100766481108L;
private String _index;
private Integer total;
private String _type;
private String _id;
private Double _data;
private Source _secure;
public String getIndex() {
return _index;
}
public void setIndex(String _index) {
this._index = _index;
}
public Integer getTotal() {
return total;
}
public void setTotal(Integer total) {
this.total = total;
}
public String getType() {
return _type;
}
public void setType(String _type) {
this._type = _type;
}
public String getId() {
return _id;
}
public void setId(String _id) {
this._id = _id;
}
public Double getData() {
return _data;
}
public void setData(Double _data) {
this._data = _data;
}
public Source getSecure() {
return _secure;
}
public void setSecure(Source _secure) {
this._secure = _secure;
}
}
When I hit the restClient call, I am getting only "total" value remaining values getting as null. "total" variable not having underscore("") remaining variables have "" so that I am facing Issue..?
Please help how to solve this issue.
The Json object is
...
"_index": "a_index",
"total":9,
...
And the properties for those two properties are:
public void setIndex(String _index) {
this._index = _index;
}
public void setTotal(Integer total) {
this.total = total;
}
As you can see, the java property for the Json _index property is index (setIndex).
index is different from _index, so when the Json object is mapped it gets null, because it can't find the property _index.
In the other hand you have that the java property for the Json total property is total (setTotal).
In this case the properties in Json and Java have the same name, so it gets the value loaded.
Java class property name is invisible to object mapper (it is private). What matters in your case is getter/setter name. It is used to map JSON object property name to Java class (POJO).
You have two options I can think of. Dirty one is just to change setter names to set_index (), set_type(), etc so that they correspond to JSON property names, or you can do it right:
Change names of Java class properties and methods to standard convention so that code is nice and readabl.
Annotate properties with #JsonProperty to express different that default mapping between JSON and Java object.
From Annotation Type JsonProperty Java docs:
Default value ("") indicates that the field name is used as the
property name without any modifications, but it can be specified to
non-empty value to specify different name. Property name refers to
name used externally, as the field name in JSON objects.
Example with annotation:
public class test {
#JsonProperty("_index")
private String index;
private Integer total;
#JsonProperty("_type")
private String type;
public String getIndex() { return index; }
public void setIndex(String index){ this.index = index; }
public Integer getTotal() { ... }
public void setTotal(Integer total) { ... }
public String getType() { ... }
public void setType(String type) { ... }
....
}
I am working on facebook application. When i queried for https://graph.facebook.com/me/video.watches?offset=0&limit=1000 I am getting a List of Watched Movies. For eg. I am pasting only one movie out of that list here.
{
"data": [
{
"id": "664878940211923",
"data": {
"tv_show": {
"id": "108611845829948",
"url": "https://www.facebook.com/pages/Popeye-the-Sailor/108611845829948",
"type": "video.tv_show",
"title": "Popeye the Sailor"
}
},
"type": "video.watches",
},
Here is the POJO Class I created to convert this to Java.
import java.util.List;
public class FBUserVideoWatches {
private List<Data> data;
public List<Data> getData() {
return data;
}
public void setData(List<Data> data) {
this.data = data;
}
public class Data{
private long id;
private TVData data;
private String type;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public TVData getData() {
return data;
}
public void setData(TVData data) {
this.data = data;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
public class TVData{
private TV_Shows tv_shows;
public TV_Shows getTv_shows() {
return tv_shows;
}
public void setTv_shows(TV_Shows tv_shows) {
this.tv_shows = tv_shows;
}
}
public class TV_Shows{
private long id;
private String url;
private String type;
private String title;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
}
Here is how i convert the json to java.
FBUserVideoWatches fbUserVideoWatches = gson.fromJson(response.getBody(), FBUserVideoWatches.class);
for (Data data : fbUserVideoWatches.getData()) {
System.out.println(data.getId());//This only works and I am getting values.
if(null != data.getData()){
if(null!=data.getData().getTv_shows()){
System.out.print(data.getData().getTv_shows().getTitle());
}
if(null!=data.getData().getTv_shows()){
System.out.print(data.getData().getTv_shows().getType());
}
}
}
When I use getter methods to get data from java class I am getting ID 664878940211923 & "type": "video.watches" as shown above. The members inside "tv_show" I am unable to access. I think some where I went wrong in creating POJO. I am unable to find that mistake. Please help me what corrections is necessary to make that work. Hope my question is clear. Thanks in Advance.
You were doing mistake in TVData Class your forgot serialized name on instance property coz its in diffferent in json and TV data class
#SerializedName(value="tv_show")
private TV_Shows tv_shows;
Assuming your json is like this
{
"data":[
{
"id":"664878940211923",
"data":{
"tv_show":{
"id":"108611845829948",
"url":"https://www.facebook.com/pages/Popeye-the-Sailor/108611845829948",
"type":"video.tv_show",
"title":"Popeye the Sailor"
}
},
"type":"video.watches"
},
{
"id":"664878940211923",
"data":{
"tv_show":{
"id":"108611845829948",
"url":"https://www.facebook.com/pages/Popeye-the-Sailor/108611845829948",
"type":"video.tv_show",
"title":"Popeye the Sailor"
}
},
"type":"video.watches"
}
]
}
Parsing model will like below according you to your json format
public class FBUserVideoWatches {
private List<Data> data;
public List<Data> getData() { return data; }
public void setData(List<Data> data) { this.data = data;}
}
public class Data {
private TVData data;
private String id;
private String type;
// setter/getter here
}
public class TVData {
#SerializedName(value="tv_show")
private Show show;
// setter/getter here
}
public class Show {
private String id;
private String url;
private String type;
private String title;
// setter/getter here
}
finally with Google Gson parse your object as like below
Gson gson = new Gson();
FBUserVideoWatches fbUserVideoWatches =gson.fromJson(json_string, FBUserVideoWatches.class);