I wanna post request with body in retrofit, wherein the brackets there is another brackets
expect body request: {"attributes":{"data":"FOOBAR"},"deviceId":171,"type":"custom"}
I've tried with backslash and I always get a bad request.
Activity.java
StringBuilder stringBuilder = new StringBuilder("{\"data\":\"");
stringBuilder.append(commandInput.getText());
stringBuilder.append("\"}");
Tasker task = new Tasker(idUnit, "custom", stringBuilder.toString());
Call<Tasker> call2 = mApiService.postCommand(task);
call2.enqueue(new Callback<Tasker>() {
#Override
public void onResponse(Call<Tasker> call, Response<Tasker> response) {
Toast.makeText(CommandActivity.this, getString(R.string.command_sent), Toast.LENGTH_SHORT).show();
}
#Override
public void onFailure(Call<Tasker> call, Throwable t) {
Toast.makeText(CommandActivity.this, getString(R.string.command_failed), Toast.LENGTH_SHORT).show();
}
});
Tasker.java
public class Tasker {
private long deviceId;
private String type;
private String attributes;
public Tasker(long deviceId, String type, String attributes) {
this.deviceId = deviceId;
this.type = type;
this.attributes = attributes;
}
}
Expect result payload:
{"attributes":{"data":"FOOBAR"},"deviceId":171,"type":"custom"}
Actual output payload:
{"attributes":"{\"data\":\"FOOBAR\"}","deviceId":171,"type":"custom"}
You need some class or Map object to hold the attributes, not String attributes
Also, try to use JsonObject class rather than just a StringBuilder
Related
I need to deserialize the response of an external REST API which is usually a JSON object but if an error occurred it will be an array of JSON objects. The API is lacking in documentation so the way I determine if error occurred or not is by the HTTP Status of the response. The problem is that non-error responses have different structure per API route (user response, product response etc.). Another problem is in my application we use an abstract class for external API response with such fields as error, hasError. I solved the problem as follows:
public abstract class AbsractApiResponse {
public abstract String getError();
public abstract void setError(String error);
public abstract String getDescription();
public abstract void setDescription(String error);
}
public class ErrorResponse {
private String errorCode; // getters/setters
private String message; // getters/setters
}
public class UserResponse extends AbsractApiResponse {
private String error; // getters/setters
private boolean hasError; // getters/setters
private boolean description; // getters/setters
private String userName;
private String userEmail;
}
public <R extends AbsractApiResponse> R deserializeResponse(
String apiResponse, Class<R> responseType, boolean isHttpError)
throws JsonProcessingException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
R response;
Object deserialized = objectMapper.readValue(apiResponse, Object.class);
if (isHttpError && deserialized instanceof List) {
TypeReference<List<ErrorResponse>> errorResponseType = new TypeReference<>() {};
List<ErrorResponse> responseErrors = objectMapper.convertValue(deserialized,
errorResponseType);
Constructor<R> constructor = responseType.getDeclaredConstructor();
response = constructor.newInstance();
ErrorResponse firstError = responseErrors.get(0);
String errorDescription = responseErrors.stream().map(ErrorResponse::toString).collect(Collectors.joining());
response.setError(firstError.getMessage());
response.setDescription(errorDescription);
} else {
response = objectMapper.convertValue(deserialized, responseType);
}
return response;
}
With this approach I would have to add fields like error/hasError etc. to every class which represents a response which isn't that bad I guess. Another red flag for me is the use of reflection (responseType.getDeclaredConstructor()) and the 4 checked exceptions that go with it. I'm wondering, if there's a better way to solve this?
I do not recommend to merge error response together with business objects. You can return given response class in case of success and throw an exception in case of error. This is what I think would be the cleanest way.
If you do not want to throw an exception you can implement wrapper class which contains response and error objects. In case error field is set we know there was a problem. It could look like below:
interface ApiResponse {
}
#Data
class ResponseWrapper<R extends ApiResponse> {
private R response;
private Error error;
public boolean hasErrors() {
return Objects.nonNull(error);
}
}
#Data
class Error {
private String error;
private String description;
}
#Data
class ErrorResponse {
private String errorCode;
private String message;
}
#Data
class UserResponse implements ApiResponse {
private String userName;
private String userEmail;
}
And generic implementation of that method could look like:
class JsonDecoder {
private final ObjectMapper objectMapper = ...;
public <R extends ApiResponse> ResponseWrapper<R> deserializeResponse(String apiResponse, Class<R> responseType, boolean isHttpError)
throws JsonProcessingException {
ResponseWrapper<R> response = new ResponseWrapper<>();
if (isHttpError) {
response.setError(deserializeError(apiResponse));
} else {
response.setResponse(objectMapper.readValue(apiResponse, responseType));
}
return response;
}
private Error deserializeError(String apiResponse) throws JsonProcessingException {
final TypeReference<List<ErrorResponse>> errorResponseType = new TypeReference<>() {};
List<ErrorResponse> errors = objectMapper.readValue(apiResponse, errorResponseType);
ErrorResponse firstError = errors.get(0);
String errorDescription = errors.stream().map(ErrorResponse::toString).collect(Collectors.joining());
Error error = new Error();
error.setError(firstError.getMessage());
error.setDescription(errorDescription);
return error;
}
}
I have a problem with parsing my custom response because the I have a response with Localization properties.
I am recieving a response that looks something like this:
[
{
"id": "dummyID1",
"name.en_US": "dummyNameEn1",
"name.fi_FI": "dummyNameFi1"
},
{
"id": "dummyID2",
"name.en_US": "dummyNameEn2",
"name.fi_FI": "dummyNameFi2"
},
{
"id": "dummyID3",
"name.en_US": "dummyNameEn3",
"name.fi_FI": "dummyNameFi3"
}...
]
And to parse that I have created a custom class Device.java:
public class Device {
public String id;
public LocalizedString name;
public Device(String id, LocalizedString name) {
this.id = id;
this.name = name;
}
//Getters and setters
}
Now here we have a custom object named LocalizedString.java:
public class LocalizedString implements Parcelable {
public static final Creator<LocalizedString> CREATOR = new Creator<LocalizedString>() {
#Override
public LocalizedString createFromParcel(Parcel in) {
return new LocalizedString(in);
}
#Override
public LocalizedString[] newArray(int size) {
return new LocalizedString[size];
}
};
private String en_US;
private String fi_FI;
public LocalizedString(String en, String fi) {
this.en_US = en;
this.fi_FI = fi;
}
protected LocalizedString(Parcel in) {
en_US = in.readString();
fi_FI = in.readString();
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(en_US);
dest.writeString(fi_FI);
}
//Getters, setters
}
Now in my response I want to create a list of Device's but it does not seem to understand how the ´LocalizedString´ works. Since my request is returning a <List<Device>> I cannot really customly parse it either.
Here is how I try to parse it:
Call<List<Device>> call = getMainActivity().getRestClient().getDevices();
call.enqueue(new Callback<List<Device>>() {
#Override
public void onResponse(Call<List<Device>> call, Response<List<Device>> response) {
if (isAttached()) {
if (response.isSuccessful()) {
// get data
List<Device> items = response.body();
}
}
}
#Override
public void onFailure(Call<List<Device>> call, Throwable t) {
if (isAttached()) {
Logger.debug(getClass().getName(), "Could not fetch installation document devices past orders", t);
getMainActivity().showError(R.string.error_network);
}
}
});
And:
#GET("document/devices")
Call<List<Device>> gettDevices();
What am I supposed to do in this situation to bind the name to the Device and later be able to either get en_US or fi_FI.
Better you can write it like this
public class Device {
#SerializedName("id")
public String id;
#SerializedName("name.en_US")
public String en;
#SerializedName("name.fi_FI")
public String fi;
public Device(String id, String english, String fi) {
this.id = id;
this.en = english;
this.fi = fi;
}
//Getters and setters
}
If you can control the source of the JSON, then a modification of that JSON structure is easy to solve your problem.
If you can not, the one way we can use to solve your problem is to use Jackson and custom deserializer:
public class DeviceDeserializer extends StdDeserializer<Device> {
public DeviceDeserializer() {
this(null);
}
public DeviceDeserializer(Class<?> vc) {
super(vc);
}
#Override
public Device deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
String id = getStringValue(node, "id");
String en = getStringValue(node, "name.en_EN");
String fi = getStringValue(node, "name.fi_FI");
LocalizedString localized = new LocalizedString(en, fi);
return new Device(id, localizedString);
}
private String getStringValue(JsonNode node, String key) {
// Throws exception or use null is up to you to decide
return Optional.ofNullable(node.get("id"))
.map(JsonNode::asText)
.orElse(null);
}
}
Manually register the deserializer yourself or using the annotation:
#JsonDeserialize(using = DeviceDeserializer.class)
public class Device {
...
Note that you must enable retrofit jackson converter plugin: (see the Retrofit Configuration part)
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com")
.addConverterFactory(JacksonConverterFactory.create())
.build();
Read this: Get nested JSON object with GSON using retrofit
How to convert given json response
{
"name" : "John",
"surname" : "Doe",
"location" : {
"name" : "Paris",
"desc" : "Welcome to Paris"
}
}
into
class Person
{
String name;
String surname;
Location location; // new Location(String name, String desc)
}
It's all about nested Location class that is inside Person class
Use #Expose or #SerializedName annotation like
class Person
{
#SerializedName("name")
String name;
#SerializedName("surname")
String surname;
#SerializedName("location")
Location location; // new Location(String name, String desc)
}
and Location class like
class Location
{
#SerializedName("name")
String name;
#SerializedName("desc")
String desc;
}
Add getter and setter method for accessing data
Use a combination of Gson + Retrofit.
First of all use the annotation #SerializedName("yourFieldName") that Retrofit provides in the fields of your model class.
Init your Gson configuration with a RuntimeTypeAdapterFactory:
RuntimeTypeAdapterFactory<Person> itemFactory = RuntimeTypeAdapterFactory
.of(Person.class) // The field that defines the type
.registerSubtype(Location.class, "location")
.registerSubtype(YourSubclass.class) // if the flag equals the class name, you can skip the second parameter.
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(itemFactory)
.create();
Then you init Retrofit:
Retrofit.Builder builder = new Retrofit.Builder();
builder.baseUrl(BASE_URL);
builder.addConverterFactory(GsonConverterFactory.create(gson));
Retrofit retrofit = builder.build();
First get an API and get its JSON output by Advance REST Client which is a Chrome Extension. Now put that output to JSON to POJO converter
and you'll get your POJO Classes. Paste them to your project. Make an interface`
/**
* Get Data
*
* #param body Holds the JSON payloads
* #return Formatted data
*/
#POST("JobSpotAPI/getUserInterviewSchedule")
Call<POJOClass> getData(#Body JsonObject body);
and setup a Client
public class RetrofitClient {
private static Retrofit retrofit = null;
public static Retrofit getClient(String baseUrl) {
if (retrofit==null) {
retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
Create a Recycler view setup its adapter and all. Just add a few new methods like-
public void updateAnswers(List<Item> items) {
mItems = items;
notifyDataSetChanged();
}
private Item getItem(int adapterPosition) {
return mItems.get(adapterPosition);
}
Setup Utility class to call the interface.
public class ApiUtils {
public static final String BASE_URL = "https://base_url/";
public static Interface_name methodName() {
return RetrofitClient.getClient(BASE_URL).create(Interface_name.class);
}}
Declare the Interface in your Activity
Interface_name obj = ApiUtils.methodName();
If you have some payloads then attach them to you request. Before that you need a JSON string to pass to API request. To make a JSON Payload.
private JsonObject makeJsonObjectPayload() {
JsonObject requestBean = new JsonObject();
requestBean.addProperty("key", value);
requestBean.addProperty("key", value);
requestBean.addProperty("key", value);
requestBean.addProperty("key", value);
return requestBean;
Pass the API request
obj.getData(makeJsonObjectPayload()).enqueue(new Callback<POJOClass>() {
#Override
public void onResponse(Call<POJOClass> call, Response<POJOClass> response) {
if(response.isSuccessful()) {
mAdapter.updateAnswers(response.body().getItems());
Log.d("MainActivity", "posts loaded from API");
}else {
int statusCode = response.code();
// handle request errors depending on status code
}
}
#Override
public void onFailure(Call<UserDataPOJOClass> call, Throwable t) {
//showErrorMessage();
Log.d("API ERROR",""+t.getMessage());
Log.d("MainActivity", "error loading from API");
}
});
I try to send the json object to rest services but I get some error like this:
http://localhost:8080/api/v1/cardLimit 400 (Bad Request);
Wrap to JSON
public class GameLimit implements Serializable {
private static final long serialVersionUID = 1L;
private LimitType firstLimit;
private LimitType secondLimit;
public LimitType getFirstLimit() {
return firstLimit;
}
public void setFirstLimit(LimitType firstLimit) {
this.firstLimit = firstLimit;
}
public LimitType getSecondLimit() {
return secondLimit;
}
public void setSecondLimit(LimitType secondLimit) {
this.secondLimit = secondLimit;
}
}
public class LimitType implements Serializable{
private static final long serialVersionUID = 1L;
private BigDecimal limit;
private String type;
private String status;
public BigDecimal getLimit() {
return limit;
}
public void setLimit(BigDecimal limit) {
this.limit = limit;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}
Limit request object
public class LimitReq extends GameLimit {
private String key;
public String getKey() {
return key;
}
}
RestController:
#RequestMapping(value = "/GameLimit", method = RequestMethod.POST)
public Response setCardLimit(#RequestBody GameLimitReq request) throws Exception {
return limitService.updateGameLimit(request);
}
TypeScript client:
changeLimits(firstLimit: IWidgetLimit, secondLimit: IWidgetLimit, key: string): ng.IPromise<any> {
return this.$http.post(this.apiPrefix + '/GameLimit', {
'firstLimit': {
limit: firstLimit.limit,
type: firstLimit.type,
status: firstLimit.status
},
'secondLimit': {
limit: secondLimit.limit,
type: secondLimit.type,
status: secondLimit.status,
},
key: key
}).then(function (response: any) {
return response.data;
}.bind(this));
}
Seeing this question and answer a 400 error indicates that your json is malformed.
From your code snippets, it seems that the line limitService.updateGameLimit(request); should provide the JSON, yet it is not included in the code snipets. Once you have the output of that method, you can run it through JsonLint to check the syntax. Then repairs can be made from there.
From your typescript client it seems that this is supplying invalid json. While I am not totally versed in typescript this certainly has some malformed JSON even if there are implied quotes. At the very least there should be double quotes around firstLimit, secondLimit, and key.
It's because your json is not being formed properly.
And there are multiple reasons for that
Your Keys are supposed to be strings, and wrapped in quotes. eg: use "type" instead of type.
You have a comma at the end of the line
status: secondLimit.status,
Remove that comma.
After you are done with it, validate a sample output on jsonlint.com or a similar service. It will help you figure out errors.
I want to interact with a RESTful webservice that responds only in JSON.
Any successful response from the server has this syntax:
{
"code": int code,
"data": object or list of objects
}
while on error response:
{
"code": int code,
"error": string,
"details": string
}
So I made two classes in my Android project like this (for GSON reflection):
public class ErrorEntity {
private String details;
private String error;
private int code;
public ErrorEntity() {
// Stub constructor
}
public String getDetails() {
return details;
}
public String getError() {
return error;
}
public int getCode() {
return code;
}
}
For a successful response I made a generic because I don't want to parse JSON data on overridden parseNetworkResponse:
public class SuccessfulEntity<T> {
private T data;
private int code;
public SuccessfulEntity() {
// Stub content
}
public T getData() {
return data;
}
public int getCode() {
return code;
}
}
Now, because my RESTful server requires some custom headers, I need to make a Request subclass, but I don't know from which class I need to inherit.
I saw this question: Send POST request with JSON data using Volley and though to do something like that.
Basically, I want to make a new class (VolleyRestClient) which has GET, POST, DELETE methods and API routings, and with this class make all requests I need to do.
Methods of this class need to make a new custom request and parse new objects response like SuccessfulEntity and ErrorEntity, and then parsing data in service/thread that make the VolleyRestClient call.
How can I do that?
After a long fight with generics and type erasure, I finally did it.
So I'm posting this for whoever has the same issue like me and needs a solution without freaking out.
My ErrorEntity and my SuccessfulEntity are still the same, but I created a new interface called RepositoryListener, like this:
public interface RepositoryListener {
public abstract void onErrorResponse(int code, String details);
public abstract void onSuccessfulResponse(int code, Object obj);
public abstract void onSuccessfulResponse2(int code, List<Object> obj);
}
Then I made a class, VolleyRestClient, like this:
public class VolleyRestClient extends RestClient {
private final DefaultRetryPolicy mRetryPolicy;
private final RequestQueue mQueue;
private final Gson gson = new Gson();
public VolleyRestClient(Context context) {
// Default retry policy
mRetryPolicy = new DefaultRetryPolicy(2000, 3, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT);
mQueue = Volley.newRequestQueue(context);
}
public RequestQueue getQueue() {
// Method to push requests for image download
return mQueue;
}
#Override
public void GET(boolean obj, boolean needAuth, String url, Type type,
RepositoryListener listener) {
// Choose which listener to construct
Response.Listener<myResponse> mListener = obj ?
// This uses objects
makeSuccessfulListener(listener, type) :
// This uses list of objects
makeSuccessfulListener2(listener, type);
myRequest mRequest =
new myRequest(Request.Method.GET, needAuth, url,
mListener, makeErrorListener(listener));
mRequest.setRetryPolicy(mRetryPolicy);
mQueue.add(mRequest);
}
#Override
public void POST(boolean needAuth, String url, String body, Type type, RepositoryListener listener) {
myRequest mRequest = new myRequest(Request.Method.POST, needAuth, url, body,
makeSuccessfulListener(listener, type), makeErrorListener(listener));
mRequest.setRetryPolicy(mRetryPolicy);
mQueue.add(mRequest);
}
#Override
public void DELETE(boolean needAuth, String url, Type type, RepositoryListener listener) {
myRequest mRequest =
new myRequest(Request.Method.DELETE, needAuth, url,
makeSuccessfulListener(listener, type), makeErrorListener(listener));
mRequest.setRetryPolicy(mRetryPolicy);
mQueue.add(mRequest);
}
private Response.Listener<myRequest> makeSuccessfulListener
(final RepositoryListener listener, final Type type) {
// TODO: test this method and implement lists
if (listener == null) {
return null;
} else {
return new Response.Listener<myRequest>() {
#Override
public void onResponse(myRequest response) {
SuccessfulEntity<Object> obj = gson.fromJson(response.getBody(), type);
listener.onSuccessfulResponse(response.getCode(), obj.getData());
}
};
}
}
private Response.Listener<myRequest> makeSuccessfulListener2
(final RepositoryListener listener, final Type type) {
// TODO: test lists
if (listener == null) {
return null;
} else {
return new Response.Listener<myRequest>() {
#Override
public void onResponse(myReqyest response) {
SuccessfulEntity<List<Object>> obj = gson.fromJson(response.getBody(), type);
listener.onSuccessfulResponse2(response.getCode(), obj.getData());
}
};
}
}
private Response.ErrorListener makeErrorListener(final RepositoryListener listener) {
return new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
try {
String jError = new String(error.networkResponse.data);
ErrorEntity mError = gson.fromJson(jError, ErrorEntity.class);
// Invoke listener closure
listener.onErrorResponse(error.networkResponse.statusCode, mError.getDetails());
} catch (Exception e) {
listener.onErrorResponse(404, e.getMessage());
}
}
};
}
}
This is very dependant by my needs, but I'll explain the general concept.
So I have a custom request, as explained in my question, and I want to parse it to the correct data type.
To be more specific, I could have a JSONArray data only on GET requests (paginated elements, etc...) so I need to find a way to distinguish between this two cases (of course, I know in which cases I'll get a List or an Object).
We cannot simply create POJO from Json within a generic class using its type (because Java Type Erasure), so we need object type upfront.
But what we can do is:
in our custom request, on parseNetworkResponse, do something like that:
#Override
protected Response<myResponse> parseNetworkResponse(NetworkResponse response) {
try {
// Using server charset
myResponse mResponse = new myResponse();
mResponse.setCode(response.statusCode);
mResponse.setBody(new String(response.data,
HttpHeaderParser.parseCharset(response.headers)));
// Return new response
return Response.success(mResponse, HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
// Normally use 'utf-8'
return Response.error(new ParseError(e));
}
}
In other words, copy the raw string response body onto a new object myResponse;
Response body will be eventually parsed in VolleyRestClient with the appropriate type passed as a GET/DELETE/POST argument;
makeSuccessfulListener and makeSuccessfulListener2 construct a Response.Listener from a RepositoryListener, which has 3 methods to override: onSuccessfulResponse for objects data, onSuccessfulResponse2 for list of objects data, onErrorResponse for 4XX/5XX errors;
Our data object/list will be parsed to more generics type (List and Object) and then passed to our custom listener RepositoryListener.
A full example for this approach:
public void getNewLogin(String nickname, String password,
final TextView author, final TextView title, final TextView text) {
String json =
(new StringBuilder()
.append("{ \"nickname\": \"")
.append(nickname)
.append("\", \"password\": \"")
.append(password)
.append("\" }")).toString();
mRest.POST(false, "http://192.168.0.104:8000/api/session", json,
new TypeToken<SuccessfulEntity<Login>>(){}.getType(),
new RepositoryListener() {
#Override
public void onSuccessfulResponse2(int code, List<Object> obj) {
// Nothing happens here
}
#Override
public void onSuccessfulResponse(int code, Object obj) {
UserSession mInstance = UserSession.getInstance(null);
Login newLogin = (Login) obj;
title.setText(newLogin.getToken());
mInstance.setToken(newLogin.getToken());
Log.i("onSuccessfulResponse", mInstance.getToken());
Log.i("onSuccessfulResponse", mInstance.getmAuthorizationToken());
if (newLogin.getUser() != null) {
author.setText(newLogin.getUser().getNickname());
text.setText(newLogin.getUser().getUniversity());
}
}
#Override
public void onErrorResponse(int code, String error) {
Log.i("onErrorResponse", error);
}
});
mRest is a VolleyRestClient object, which performs a POST request to that address with Type constructed by Gson TypeToken (remember, our body is a SuccessfulEntity).
Since we'll have an Object data for sure, we'll just override onSuccessfulResponse, cast data object to the same type T of SuccessfulEntity used in TypeToken, and do our dirty work.
I don't know if I was clear, this approach works, if some of you needs some clarification, just ask :)