Serialize Query-Parameter in Retrofit - android

Imagine the following request:
#POST("/recipes/create")
void createRecipe(#Query("recipe") Recipe recipe, Callback<String> callback);
I would like to have toJson(recipe) but unfortunately my request is just calling toString() for my recipe which does not work at all.
I could override the toString inside of Recipe but I'd rather have a general solution.
I cannot use #Body as I need to specify, what I'm sending (i need to have "recipe=json(theRecipe)".
I also cannot change the serialization to add "recipe=" as I'm not in charge of the server.
At the moment I'm using a QueryMap Map where I put in a serialized object. Although this works, it's not a very nice solution in my opinion.
Can I somehow intercept the retrofit-adapter?

This is now possible when registering a custom Converter.Factory that overrides the stringConverter method, which is called when resolving parameters. The Github issue that #William referred to 2 years ago doesn't seem to be updated since support was added.
Method Javadoc:
Returns a Converter for converting type to a String, or null if type cannot be handled by this factory. This is used to create converters for types specified by #Field, #FieldMap values, #Header, #HeaderMap, #Path, #Query, and #QueryMap values.
The example below delegates to Gson, but in the same way any type of conversion can be applied to the parameters.
Example: GsonStringConverterFactory
class GsonStringConverterFactory
extends Converter.Factory {
private final transient Gson gson;
GsonStringConverterFactory(final Gson gson) {
this.gson = gson;
}
#Override
public Converter<?, String> stringConverter(final Type type, final Annotation[] annotations, final Retrofit retrofit) {
final TypeAdapter typeAdapter;
typeAdapter = gson.getAdapter(TypeToken.get(type));
return new StringConverter<>(typeAdapter);
}
private static class StringConverter<T>
implements Converter<T, String> {
private final TypeAdapter<T> typeAdapter;
private StringConverter(final TypeAdapter<T> typeAdapter) {
this.typeAdapter = typeAdapter;
}
#Override
public String convert(final T value)
throws IOException {
/* This works in our case because parameters in this REST api are always some kind of scalar
* and the toJson method converts these to simple json types. */
final String jsonValue;
jsonValue = typeAdapter.toJson(value));
if (jsonValue.startsWith("\"") && jsonValue.endsWith("\"") {
/* Strip enclosing quotes for json String types */
return jsonValue.substring(1, jsonValue.length() - 1);
} else {
return jsonValue;
}
}
}
}
Registering the converter:
To register the custom converter, your Retrofit builder could look something like this:
new Retrofit.Builder().baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.addConverterFactory(new GsonStringConverterFactory(gson))
.build();

I don't think it supports this right now in some nice way. Check this answer by one of the authors: https://github.com/square/retrofit/issues/291
The suggested method from that answer is to create a custom type that overrides the toString() method, because Retrofit internally uses String.valueOf(value) to convert query or path parameters to strings.
So, you could have something like this:
class Recipe {
public int id;
public String title;
#Override
public String toString() {
// You can use a GSON serialization here if you want
// This is not a robust implementation
return Integer.toString(id) + "-" + title;
}
}

As #Rolf mentioned, there is a way to set customConverter.Factory
Example:
public class QueryConverterFactory extends Converter.Factory {
public static QueryConverterFactory create() {
return new QueryConverterFactory();
}
private QueryConverterFactory() {
}
#Nullable
#Override
public Converter<?, String> stringConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
if (type == Date.class) {
return DateQueryConverter.INSTANCE;
}
return null;
}
private static final class DateQueryConverter implements Converter<Date, String> {
static final DateQueryConverter INSTANCE = new DateQueryConverter();
private static final ThreadLocal<DateFormat> DF = new ThreadLocal<DateFormat>() {
#Override
public DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
#Override
public String convert(Date date) {
return DF.get().format(date);
}
}
}
You can add converters for your own types.

You can use retrofit.RestAdapter.Builder().setConverter(...) to pass a custom json converter.

Related

Why to use #Body annotation when a class object can be passed in method's argument?

in the method getLocation(), what difference will it make if i simply pass a TableParameter object?
public interface MasterAPI {
#POST("Master/getLocation")
public Call<List<LocationGetTab>> getLocation(#Body TableParameter
parameter);
}
Class TableParameter is so defined:
public class TableParameter {
#SerializedName("State")
int State;
#SerializedName("district")
int District;
#SerializedName("LocType")
String locType;
public TableParameter(int district, int state, String locType) {
District = district;
State = state;
this.locType = locType;
}
}
#Body annotation is used when you are passing your object as a argument and those all parameter which are defined into model class those all data passes to your service as an individual argument as a request parameter.
Please read whole Retrofit Document it helps a lot to you.
Hope you understand.

Gson remove unnecessary nested object fields

I am trying to serialize an object. I have the following structure:
Class A{
String aField1;
String aField2;
B bObj;
}
Class B{
String bField1;
String bField2;
String bField3;
}
I am trying to serialze class A and B objects to send them to server.
When I am serializing Class A object, it gives me
{
aField1: "abc",
aField2: "def",
B: {
bField1: "mnp",
bField2: "qrt",
bField3: "xyz",
}
}
And serializing Class B obj:
{
bField1: "mnp",
bField2: "qrt",
bField3: "xyz",
}
But I want Class A object like this:
{
aField1: "abc",
aField2: "def",
B: {
bField1: "mnp"
}
}
I am currently using GSON library to accomplish this.
I want to remove extra key value pairs when interacting with server. How can I do this?
Change your Serialize class like this way.
public class A implements Serializable{
String aField1;
String aField2;
B bObj;
class B{
String bField1;
String bField2;
String bField3;
}
}
Just remove the extra fields. It will not make any problem.
You can mark bField2 and bField3 as transient or use the annotation #Expose(serialize = false).
Or you can customize your serialization exclusion strategy.
Sample code:
GsonBuilder builder = new GsonBuilder();
Type type = new TypeToken <A>(){}.getType();
builder.addSerializationExclusionStrategy(
new ExclusionStrategy() {
#Override
public boolean shouldSkipField(FieldAttributes fieldAttributes) {
return fieldAttributes.getName().equals("bField2") ||
fieldAttributes.getName().equals("bField3");
}
#Override
public boolean shouldSkipClass(Class<?> aClass) {
return false;
}
}
);

Deserializing ArrayList<Interface> using GSON

I have a class Shop
public class Shop {
private String name;
private ArrayList<Fruitable> fruits;
public String toJson() {
Gson gson = new Gson();
return gson.toJson(this, Shop.class)
}
}
Apple and Orange are the two classes that implements Fruitable interface.
Fruitable:
public interface Fruitable {
String getColor();
}
Apple:
public class Apple implements Fruitable {
private int Id;
#Override
public String getColor() {
return "red";
}
}
Orange:
public class Orange implements Fruitable {
private int Id;
#Override
public String getColor() {
return "orange";
}
}
I can serialize Shop using GSON, however, I cannot deserialize it properly.
When I deserialize, the ArrayList<Fruitable> has objects, but the objects are null.
This is because GSON requires a concrete instance to deserialze into.
I know I can implement JsonDeserializer in Shop.
However, can I implement some deserializing mechanism, inside Apple and Orange so they will deserialize themselves?
If that is not possible, and I have to write a deserializer in Shop, can I write code just to deserialize the fruits property and not the other properties,i.e. GSON will deserialize the remaining properties assuming they are concrete instances?
Ok so you should use GSON annotations they will make your life a whole lot easier.
An example:
public class Deal{
#SerializedName("name")
String name;
#SerializedName("times")
List<DealTime> times;
}
The json coming back will roughly look this:
{
name:dave,
times[
{
end:400,
start:200
},
{
end:30,
start:500
}
]
}
Then your DealTime class will have the following:
public class DealTime{
#SerializedName("end")
int end;
#SerializedName("start")
int start;
}
All you have to do is cast the json coming back with GSON using the Deal object.
Hope this helps.

RxJava and Retrofit - Raising custom exceptions depending on server response

I would like Retrofit to raise custom exceptions depending on the server response. For example in the following structure:
{
"code":0,
"message":"OK",
"data":{....}
}
I would like to raise an exception for subscribers if code is anything other than 0. How is it possible using Retrofit and Rx? I would much prefer to write this logic only once and have it applied to all observables returned by retrofit.
I would like to raise an exception for subscribers if code is anything other than 0. How is it possible using Retrofit and Rx?
You can use a Observable.flatMap operator:
api.request().flatMap(response -> {
if (response.getCode() != 0) {
return Observable.error(new Exception("Remote error occurred"));
}
return Observable.just(response);
});
I would much prefer to write this logic only once and have it applied to all observables returned by retrofit.
Unfortunately, there is not way to do it using retrofit and rx-java. You have to write the code above for every retrofit call. The only thing you can do is to use Observable.compose method and reduce the amount of boilerplate you actually have to write.
api.request().compose(new ResponseTransformer<Response>());
And here is the ResponseTransformer class:
public static class ResponseTransformer<T extends Response> implements Observable.Transformer<T, T> {
#Override
public Observable<T> call(final Observable<T> observable) {
return observable.flatMap(response -> {
if (response.getCode() != 0) {
return Observable.error(new Exception("Remote error occurred"));
}
return Observable.just(response);
});
}
}
UPDATE
Well, as I said, there is no way to avoid boilerplate code using only retrofit and rxjava, but you can workaround it with dynamic proxies (note that you don't need to call compose anymore):
final Api api = restAdapter.create(Api.class);
final ClassLoader loader = api.getClass().getClassLoader();
final Class<?>[] interfaces = api.getClass().getInterfaces();
final Api proxy = (Api) Proxy.newProxyInstance(loader, interfaces, new ResponseInvocationHandler(api));
proxy.request().subscribe(response -> {
System.out.println("Success!");
});
ResponseInvocationHandler class:
public static class ResponseInvocationHandler implements InvocationHandler {
private final Object target;
public ResponseInvocationHandler(final Object target) {
this.target = target;
}
#Override
#SuppressWarnings({"unchecked"})
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
final Object result = method.invoke(target, args);
if (result instanceof Observable) {
return Observable.class.cast(result).compose(new ResponseTransformer<>());
}
return result;
}
}
I would suggest a different approach.
You will need to implement a custom OkHttp client with custom Interceptor.
OkHttpClient client = new OkHttpClient();
client.interceptors().add(new MyInterceptor());
mAdapter = new RestAdapter.Builder().setEndpoint(Consts.ENDPOINT).setClient(new OkClient(client))
.setLogLevel(RestAdapter.LogLevel.BASIC).build();
In your interceptor depending on the code returned you can proceed normally or throw an exception.
Something like this:
public class MyInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
if(response.code() == 0) {
throw new RuntimeException("Something went wrong!");
}
return response;
}
}
1 custom Observable.Operator :
public class YourOperator implements Observable.Operator{
public void onNext(Data data){
if (data.code != 0 ){
//raise your custom Exception
}
}
public void onError(Throwable e){
//handler Exception
}
}
2 user like:
api.youRequest()
.lift(new YourOperator())
.subscribe(....);

JSON Parsing with Retrofit

I started using Retrofit recently. I don't know much about it. I have googled this issue and no answers suite my problem.
This is JSON response
{
"results": [
{
"description_eng": "This is second time testing",
"img_url": "-",
"title_eng": "Second test"
},
{
"description_eng": "Hello 1 2 3, I am testing.",
"img_url": "https://fbcdn-sphotos-f-a.akamaihd.net/hphotos-ak-xpa1/t31.0-8/s720x720/10838273_816509855058935_6428556113200361121_o.jpg",
"title_eng": "Test"
}
]
}
This is Feed Class
public class Feed {
public List<Results> results;
class Results{
String description_eng,img_url,title_eng;
}
}
This is the interface
public interface GetApi {
#GET("/api.json")
public void getData(Callback<List<Feed>> response);
}
I got json_illegal_syntax Exception.
This is how I solved this problem, by creating empty constructors.
Feed.class
public class Feed{
private List<Result> results;
public Feed(){}
public List<Result> getFeed(){
return this.results;
}
public void setFeed(List<Result> results) {
this.results = results;
}
}
Result.class
public class Result{
private String description_eng;
private String img_url;
private String title_eng;
public Result(){}
//getters and setters
}
GetApi.class
public interface GetApi {
#GET("/api.json")
public void getData(Callback<Feed> response);
}
Retrofit uses Gson by default to convert HTTP bodies to and from JSON. If you want to specify behavior that is different from Gson's defaults (e.g. naming policies, date formats, custom types), provide a new Gson instance with your desired behavior when building a RestAdapter.
Gson can not automatically deserialize the pure inner classes since their no-args constructor also need a reference to the containing Object which is not available at the time of deserialization. You can address this problem by either making the inner class static or by providing a custom InstanceCreator for it. Here is an example:
public class A {
public String a;
class B {
public String b;
public B() {
// No args constructor for B
}
}
}
NOTE: The above class B can not (by default) be serialized with Gson.
You should read more about GSON library
#Ye Min Htut Actually, even better is to write Feed class with generics.
public class FeedList<T>{
private List<T> feeds;
public Feed() {
}
public List<T> getFeed(){
return this.feeds;
}
public void setFeed(List<T> results) {
this.feeds = feeds;
}
}
and if there is something similar with only one object, than you can remove List part and get/set single object.
Now you can call FeedList<Result> or with whatever object you want.
GetApi.class
public interface GetApi {
#GET("/api.json")
public void getData(Callback<FeedList<Result>> response);
}
#Ye Min Htut I will suggest you to make Model/POJO classes using ROBOPOJO Generator It will generate All model classes for you, you don't need to make it by your self, Will also help you in future while creating model class. It just need JSON string and click will make your job done

Categories

Resources