How to parse various answers from server and Retrofit - rest

The server can answer like:
{ "data1":"value", "data2":"value" }
or:
{ "error":"text" }
or:
{ "json":"{ "error":"text" }" }
How to parse various answers from the server using retrofit.
Maybe I should make a rich POJO like:
class MyAnswer {
String data1;
String data2;
String error;
// etc
}

I recommend you to use Google's Gson library to Serialize/Deserialize Json strings to POJO's and deserialize back.
Retrofit2 also supports Gson as a converter.
Add compile 'com.squareup.retrofit2:converter-gson' to your build.gradle and create Retrofit instance like below:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
Define your Java classes and annotate them with Gson's SerializedName tag.
public class MyAnswer {
#SerializedName("data1")
public String data1;
#SerializedName("data2")
public String data2;
#SerializedName("error")
public String error;
}
Then you can get your POJO on the onResponse method:
#Override
public void onResponse(Call<ExampleClass> call, Response<ExampleClass> response) {
ExampleClass exampleClass = response.body();
......
}
You can also deserialize yourself from Json:
Gson gson = new GsonBuilder().create();
ExampleClass ec = gson.fromJson(jsonString, ExampleClass.class);
Or serialize to Json:
ExampleClass ec = new ExampleClass();
ec.data1 = "Some text";
ec.data2 = "Another text";
Gson gson = new GsonBuilder().create();
String jsonString = gson.toJson(ec);
You can also create nested/complex structures with Gson.
For more information, visit their user guide.

Related

Spring boot using wrong message convertor

I have a rest endpoint like below which is supposed to accept an XML input, do some processing on it and then return a response in XML as well.
#RequestMapping(value = "/rest/v1/test/listener", method = RequestMethod.POST)
public ResponseEntity<MyResponseType> processBooking(#RequestBody MyRequest myRequest) throws JAXBException {
MyResponseType response = myService.process(myRequest);
// ... do something with it and generate 'response'
return new ResponseEntity<>(response, HttpStatus.OK);
}
And MyRequest class looks like below which is autogenerated via jaxb and an external xsd which I cannot change (details omitted from the class)
/**
* MyRequest
*/
public class MyRequest {
#XmlElement(required = true)
#XmlSchemaType(name = "string")
protected SomeEnum someEnum;
...
#XmlType(name = "SomeEnum")
#XmlEnum
public enum SomeEnum {
ACTIVITY,
DEPOSIT,
EQUIPMENT,
FEE,
MISC,
PROTECTION,
RENTAL,
TAX,
DISCOUNT;
public static SomeEnum fromValue(String v) {
return valueOf(v);
}
public String value() {
return name();
}
}
}
The problem is that when I try to run it, I get the following error message
2018-04-09 11:47:59.378 WARN 2702 --- [ main]
.w.s.m.s.DefaultHandlerExceptionResolver : Failed to read HTTP
message:
org.springframework.http.converter.HttpMessageNotReadableException:
JSON parse error: Can not construct instance of MyRequest.SomeEnum: no
String-argument constructor/factory method to deserialize from String
value ('MISC'); nested exception is
com.fasterxml.jackson.databind.JsonMappingException: Can not construct
instance of MyRequest.SomeEnum: no String-argument constructor/factory
method to deserialize from String value ('MISC')
A sample xml that I send as input is
<myRequest>
...
<advertiserAssignedId>19ABC12331</advertiserAssignedId>
<listingExternalId>ABC123</listingExternalId>
<unitExternalId>ABC123</unitExternalId>
<someEnum>
<name>MISC</name>
<feeType>MISC</feeType>
...
</someEnum>
...
</myRequest>
You have to specify what is your endpoint consuming using the consumes attribut .
When you post the request to your endpoint, don't forget to set the Content-type header to application/xml

Retrofit with soap api

I am trying to create Retrofit instance with the soap API. But I don't have an idea how to create. I have checked all the websites. I have got the code for creating the Retrofit instance with Rest API.
Also, I am having WSDL file. I am able to create required POJO class with that but I don't know how to use for a service call.
Kindly anyone please suggest some code or how to use that.
For Rest call, I am creating Retrofit instance like
#GET("users/{user}/repos")
Call<List<User>> listUsers(#Path("user") String user);
You should first make ApiClient class as follow:
public class ApiClient {
public static final String BASE_URL = "http://54.255.249.65/socialcommerce/rest/V1/";
private static Retrofit retrofit = null;
public static Retrofit getClient() {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}
In the controller class you should use the above class as follow:
ApiInterface apiInterface = ApiClientInstagram.getClient().create(ApiInterface.class);
Call<InstagramDetail> call = apiInterface.getInstagramDetail(token);
Log.d("tag", call.request().url().toString());
call.enqueue(new Callback<InstagramDetail>() {
#Override
public void onResponse(Call<InstagramDetail> call, Response<InstagramDetail> response) {
String code = String.valueOf(response.code());
if (code.equals("200")) {
response.body();
} else {
Toast.makeText(getApplicationContext(), "Backend Error", Toast.LENGTH_LONG).show();
}
}
#Override
public void onFailure(Call<InstagramDetail> call, Throwable t) {
Toast.makeText(getApplicationContext(), t.getMessage(), Toast.LENGTH_LONG).show();
}
});

Retrofit 2.0-beta-2 is adding literal quotes to MultiPart values

Went to upgrade to Retrofit 2.0 and running into this weird problem.
I have a method to log a user in
public interface ApiInterface {
#Multipart
#POST("user/login/")
Call<SessionToken> userLogin(#Part("username") String username, #Part("password") String password);
}
When I look at the key value POST params on the server side they print like this
username : "brian"
password : "password"
The same method using retrofit 1.9 the K:V pairs look like
username : brian
password : password
It's adding literal quotes to the POST variables
If I use any other rest client the variables print like the second way without the quotes.
Here is how I build the Retrofit instance with an interceptor
OkHttpClient client = new OkHttpClient();
client.interceptors().add(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
// Customize the request
Request request = original.newBuilder()
.header("Accept", "application/json")
.header("Authorization", myPrefs.accessToken().getOr(""))
.method(original.method(), original.body())
.build();
Response response = chain.proceed(request);
// Customize or return the response
return response;
}
});
Ok2Curl.set(client);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(apiEndpoint)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build();
I imagine i'm doing something wrong with the converter but not sure what.
Has anyone else ran into this problem yet? I know its in beta but it's pretty widly used.
This is because it's running through the JSON converter.
Solution1:
use RequestBody instead of String
public interface ApiInterface {
#Multipart
#POST("user/login/")
Call<SessionToken> userLogin(#Part("username") RequestBody username, #Part("password") RequestBody password);
}
Build RequestBody:
RequestBody usernameBody = RequestBody.create(MediaType.parse("text/plain"), usernameStr);
RequestBody passwordBody = RequestBody.create(MediaType.parse("text/plain"), passwordStr);
Launch network operation:
retrofit.create(ApiInterface.class).userLogin(usernameBody , passwordBody).enqueue()....
Solution2: Create a custom ConverterFactory to dispose String part value.
For: Retrofit2 final release not beta. (com.squareup.retrofit2:retrofit:2.0.0)
Create your StringConverterFactory:
public class StringConverterFactory extends Converter.Factory {
private static final MediaType MEDIA_TYPE = MediaType.parse("text/plain");
public static StringConverterFactory create() {
return new StringConverterFactory();
}
#Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
if (String.class.equals(type)) {
return new Converter<ResponseBody, String>() {
#Override
public String convert(ResponseBody value) throws IOException {
return value.string();
}
};
}
return null;
}
#Override
public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
if(String.class.equals(type)) {
return new Converter<String, RequestBody>() {
#Override
public RequestBody convert(String value) throws IOException {
return RequestBody.create(MEDIA_TYPE, value);
}
};
}
return null;
}
}
Add to your retrofit instance:
retrofit = new Retrofit.Builder()
.baseUrl(SERVER_URL)
.client(client)
.addConverterFactory(StringConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
Attention: StringConverterFactory should add before GsonConverterFactory!
then you can use String as part value directly.
You can find more information about this issue in https://github.com/square/retrofit/issues/1210
I have the same problem, and how it solved:
1) Add to build.gradle:
compile 'com.squareup.retrofit2:converter-scalars:2.1.0' // Remember to add the same version
2) Add one line here:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(URL_BASE)
.addConverterFactory(ScalarsConverterFactory.create()) // this line
.addConverterFactory(GsonConverterFactory.create(gson))
.client(getUnsafeOkHttpClient())
.build();
What about to do in that way?
RequestBody caption = RequestBody.create(MediaType.parse("text/plain"), new String("caption"));
Here is how to resolve it,
Firstly:
return new Retrofit.Builder()
.baseUrl(Env.GetApiBaseUrl())
.addConverterFactory(new GsonStringConverterFactory())
.addConverterFactory(GsonConverterFactory.create(gson))
.client(getHttpClient())
.build();
Create a CustomConverter like this one, this is needed by Retrofit 2, unless some fix the "feature" added in v2.
public class GsonStringConverterFactory extends Converter.Factory {
private static final MediaType MEDIA_TYPE = MediaType.parse("text/plain");
#Override
public Converter<?, RequestBody> toRequestBody(Type type, Annotation[] annotations) {
if (String.class.equals(type))// || (type instanceof Class && ((Class<?>) type).isEnum()))
{
return new Converter<String, RequestBody>() {
#Override
public RequestBody convert(String value) throws IOException {
return RequestBody.create(MEDIA_TYPE, value);
}
};
}
return null;
}
}
I've found another one solution except those. Worked with Retrofit 2.1.0. (Rx adapter is optional here)
My retrofit interface looks like this:
#POST("/children/add")
Observable<Child> addChild(#Body RequestBody requestBody);
And in ApiManager I use it like this:
#Override
public Observable<Child> addChild(String firstName, String lastName, Long birthDate, #Nullable File passportPicture) {
MultipartBody.Builder builder = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("first_name", firstName)
.addFormDataPart("last_name", lastName)
.addFormDataPart("birth_date", birthDate + "");
//some nullable optional parameter
if (passportPicture != null) {
builder.addFormDataPart("certificate", passportPicture.getName(), RequestBody.create(MediaType.parse("image/*"), passportPicture));
}
return api.addChild(builder.build());
}
It is similar to Solution1 from Loyea but I think that it's little a bit more elegant.
If your UI is showing your responses with quotes, you can use getAsString instead of toString
I don't know if it is too late, but we can also send requests with RequestBody.
Example:
public interface ApiInterface {
#Multipart
#POST("user/login/")
Call<SessionToken> userLogin(#Part("username") String username, #Part("password") String password);
}
We can convert as below:
public interface ApiInterface {
#Multipart
#POST("user/login/")
Call<SessionToken> userLogin(#Part("username") RequestBody username, #Part("password") String password);
}

begin array but was string GSON

I have a Web Service REST :
#Path("/Vehicles")
public class Vehicles{
#GET
#Path("/Cars")
#Produces(aplicattion/json)
public String Cars() {
Car[] cars = Consulting my database...
Gson gson = new Gson();
return gson.toJson(cars);
}
I consume the web service:
try {
HttpClient httpClient = new DefaultHttpClient();
HttpGet get = new HttpGet(
"http://localhost:8080/Concessionaire/rest/Vehicles/Cars");
HttpResponse resp = httpClient.execute(get);
String respGET = EntityUtils.toString(resp.getEntity());
Gson gson = new Gson();
Cars[] c = gson.fromJson(respGET,Cars[].class);
}catch(Exception e){
}
But appears this exception: Expected BEGIN_ARRAY but was String at line 1 colum 6
What is the problem ?
Your method returns a String
public String Cars()
The client code expects a Car array
Cars[] c = gson.fromJson(respGET,Cars[].class);
Gson expects the BEGIN_ARRAY event while parsing the json but instead finds a String. To fix it, send a Cars[] using the jersey Response class and change the return type to Response.
return Response.ok(myCarsArray).build();

Send byte array from web service to client

I want to send a byte array from a web service to a client that requests an operation exposed via the service. In my method, I read an image into a byte array. I think place this byte array into a wrapper POJO. This is the return type for the operation.
#Override
public ImageWrapper getImage() {
File imageFile = new File("C:\\images\\car.jpg");
ImageWrapper wrapper = null;
try {
BufferedImage img = ImageIO.read(imageFile);
ByteArrayOutputStream baos = new ByteArrayOutputStream(1000);
ImageIO.write(img, "jpg", baos);
baos.flush();
byte[] result = baos.toByteArray();
baos.close();
wrapper = new ImageWrapper();
wrapper.setContent(result);
System.out.println("Service image wrapper: " + wrapper);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return wrapper;
}
I can receive the ImageWrapper object in the client ok. It has a different id to the ImageWrapper instance that is created by the web service on the server, as I would expect. But, the problem is that when I try to get the byte[] array from the ImageWrapper, it is null... Any ideas why? The wrapper class looks like:
package soap.service.model;
public class ImageWrapper {
private byte[] content;
public void setContent(byte[] content) {
this.content = content;
}
public byte[] getImg() {
return this.content;
}
}
and the client looks like:
import java.net.MalformedURLException;
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.ws.Service;
import soap.service.model.ImageWrapper;
import soap.service.sei.ImageSei;
public class ImageClient {
public static void main(String... args) throws MalformedURLException {
URL url = new URL("http://localhost:8080/image?wsdl");
QName qname = new QName("http://impl.service.soap/", "ImageImplService");
Service service = Service.create(url, qname);
ImageSei sei = service.getPort(ImageSei.class);
ImageWrapper iw = sei.getImage();// This is ok
System.out.println(iw.getImg()); // * This is null
}
}
========================================================================
Update Even if I change the byte array in ImageWrapper to a String, it
still comes back as 'null' in the client. I have my web service set to use
'Document' style also.
Your interface object (the one getting serialized and being transfered) does not contain public data (only a method to get private data). Your byte[] should be a public field or property to be included in the serialized data