I'm trying to send the following java model as form encoded body WITHOUT the wrapping {}. I've tried everything I can find to send a Model NOT as JSON but as form encoded data using Retrofit 2.
// Sends as JSON
#Headers("Content-Type:application/x-www-form-urlencoded")
#POST(SERVICES + USERS)
Observable<UserInfoResponse> signupUser(#Body SignUpParams params);
// Works
#FormUrlEncoded
#POST(SERVICES + USERS)
Observable<UserInfoResponse> signupUser(
#Field("approve") boolean approve,
#Field("daily_newsletter") int newsletter,
#Field("include_order_info") boolean includeOrderInfo,
#Field("is_21") int is21,
#Field("is_guest") int isGuest,
#Field("method") String method,
#Field("email") String email,
#Field("password") String password,
#Field("oauth_token") String oauthToken
);
Here's our setup if it helps
// Dagger Provider
#Provides
#Singleton
#Named(JT_API)
Retrofit provideJTSecureApiRetrofit(OkHttpClient okHttpClient, Gson gson) {
Retrofit retrofit = new Retrofit.Builder().client(okHttpClient)
.baseUrl(jtBaseUrl)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
return retrofit;
}
#Provides
#Singleton
OkHttpClient provideOkHttpClient(JTApp app) {
Interceptor addUrlParams = chain -> {
Request request = chain.request();
HttpUrl url = request.url()
.newBuilder()
.addQueryParameter("app_version", BuildConfig.VERSION_NAME)
.build();
request = request.newBuilder()
.url(url)
.build();
return chain.proceed(request);
};
OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();
okHttpClientBuilder.addInterceptor(addUrlParams);
// this doesn't seem to do anything…
okHttpClientBuilder.addInterceptor(chain -> {
Request original = chain.request();
Request.Builder requestBuilder = original.newBuilder()
.addHeader("Content-Type", "application/x-www-form-urlencoded");
Request request = requestBuilder.build();
return chain.proceed(request);
});
okHttpClientBuilder.readTimeout(JTApp.HTTP_TIMEOUT, TimeUnit.SECONDS)
.connectTimeout(JTApp.HTTP_TIMEOUT, TimeUnit.SECONDS);
return okHttpClientBuilder.build();
}
If i am not mistaken
For application/x-www-form-urlencoded, the body of the HTTP message
sent to the server is essentially one giant query string -- name/value
pairs are separated by the ampersand (&), and names are separated from
values by the equals symbol (=).
How to send Form data in retrofit2 android
Turns out I had to create my own key value pair converter which extends the Retrofit2 Converter.Factory
/**
* Retrofit 2 Key Value Pair Form Data Encoder
*
* This is a copy over of {#link GsonConverterFactory}. This class sends the outgoing response as
* form data vs the gson converter which sends it as JSON. The response is proxied through the
* gson converter factory just the same though
*
* Created by marius on 11/17/16.
*/
public class RF2_KeyValuePairConverter extends Converter.Factory {
private final GsonConverterFactory gsonConverter;
/**
* Create an instance using a default {#link Gson} instance for conversion. Encoding to form data and
* decoding from JSON (when no charset is specified by a header) will use UTF-8.
*/
public static RF2_KeyValuePairConverter create() {
return create(new Gson());
}
/**
* Create an instance using {#code gson} for conversion. Encoding to Form data and
* decoding from JSON (when no charset is specified by a header) will use UTF-8.
*/
public static RF2_KeyValuePairConverter create(Gson gson) {
return new RF2_KeyValuePairConverter(gson);
}
private final Gson gson;
private RF2_KeyValuePairConverter(Gson gson) {
if (gson == null) throw new NullPointerException("gson == null");
this.gson = gson;
this.gsonConverter = GsonConverterFactory.create(gson);
}
#Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
return gsonConverter.responseBodyConverter(type, annotations, retrofit);
}
#Override
public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
return new KeyValueBodyConverter<>(gson);
}
}
And here's our KeyValueBody
public class KeyValuePairConverter extends retrofit2.Converter.Factory implements Converter {
private final Gson gson;
public KeyValuePairConverter(Gson gson) {
this.gson = gson;
}
// Taken from retrofit's GsonConverter
#Override
public Object fromBody(TypedInput body, Type type) throws ConversionException {
String charset = MimeUtil.parseCharset(body.mimeType());
InputStreamReader isr = null;
try {
isr = new InputStreamReader(body.in(), charset);
return gson.fromJson(isr, type);
} catch (IOException e) {
throw new ConversionException(e);
} catch (JsonParseException e) {
throw new ConversionException(e);
} finally {
if (isr != null) {
try {
isr.close();
} catch (IOException ignored) {
}
}
}
}
#Override
public TypedOutput toBody(Object object) {
String json = gson.toJson(object);
//Log.d( "RETROFIT", json );
Type type = new TypeToken<Map<String, Object>>() { } .getType();
// this converts any int values to doubles so we are fixing them back in pojoToTypedOutput
Map<String, Object> map = gson.fromJson(json, type);
String body = pojoToTypedOutput(map, null);
// removes the initial ampersand
return new TypedString(body.substring(1));
}
/**
* Converts object to list of query parameters
* (works with nested objects)
*
* #todo
* query parameter encoding
*
* #param map this is the object map
* #param parentKey this is the parent key for lists/arrays
* #return
*/
public String pojoToTypedOutput(Map<String, Object> map, String parentKey) {
StringBuffer sb = new StringBuffer();
if (map != null && map.size() > 0) {
for (String key : map.keySet()) {
// recursive call for nested objects
if (map.get(key).getClass().equals(LinkedTreeMap.class)) {
sb.append(pojoToTypedOutput((Map<String, Object>) map.get(key), key));
} else {
// parent key for nested objects
Object objectValue = map.get(key);
// converts any doubles that really could be ints to integers (0.0 to 0)
if (objectValue.getClass().equals(Double.class)) {
Double doubleValue = (Double) objectValue;
if ((doubleValue == Math.floor(doubleValue)) && !Double.isInfinite(doubleValue)) {
objectValue = ((Double) objectValue).intValue();
}
}
if (parentKey != null && parentKey.length() != 0) {
sb.append("&").append(key).append("=").append(objectValue);
} else {
sb.append("&").append(parentKey + "[" + key + "]").append("=").append(objectValue);
}
}
}
}
return sb.toString();
}
}
In your Retrofit builder add .addConverterFactory(RF2_KeyValuePairConverter.create(gson)) and this will convert your responses to key/value pairs
Related
I built the request like following:
public static ApiBuilder newInstance(){
GsonBuilder gsonBuilder = new GsonBuilder()
.registerTypeAdapter(Boolean.class, booleanAsIntAdapter)
.registerTypeAdapter(boolean.class, booleanAsIntAdapter)
.registerTypeAdapter(Date.class, (JsonDeserializer<Date>) (json, typeOfT, context)
-> new Date(Long.valueOf(json.getAsString())));
OkHttpClient client = new OkHttpClient();
client.setConnectTimeout(180, TimeUnit.SECONDS);
client.setReadTimeout(240, TimeUnit.SECONDS);
String url = MyApplicationConfiguration.getInstance().get("urlofservice");
RestAdapter restAdapter = new RestAdapter.Builder()
.setRequestInterceptor(
request -> {
request.addHeader(deviceIdHeader, MyApplication.getDeviceId());
}
).setConverter(new GsonConverter(gsonBuilder.create()))
.setEndpoint(url)
.setClient(new OkClient(client)).build();
restAdapter.setLogLevel(RestAdapter.LogLevel.FULL);
return restAdapter.create(ApiExtendedValidation.class);
}
private static final TypeAdapter<Boolean> booleanAsIntAdapter = new TypeAdapter<Boolean>() {
#Override
public void write(JsonWriter out, Boolean value) throws IOException {
if (value == null) {
out.nullValue();
} else {
out.value(value);
}
}
#Override
public Boolean read(JsonReader in) throws IOException {
JsonToken peek = in.peek();
switch (peek) {
case BOOLEAN:
return in.nextBoolean();
case NULL:
in.nextNull();
return null;
case NUMBER:
return in.nextInt() != 0;
case STRING:
return Boolean.parseBoolean(in.nextString());
default:
throw new IllegalStateException("Expected BOOLEAN or NUMBER but was " + peek);
}
}
};
The API interface is:
#Headers("Content-Type: application/json")
#POST("/validate")
Observable<QRValidateResponse> validateQRCode(#Body QRRequest request);
The QRRequest is an object, that contains a String field with a Base64 ecnoded string
Encoding example
byte[] data = rawResult.getText().getBytes(StandardCharsets.ISO_8859_1);
String encodedResult = Base64.encodeToString(data, Base64.DEFAULT);
object example
request{
"lang": "EN",
"content": "SGVsbG8gV29ybGQh="
}
When Retrofit execute the request, the request.content changes like follwing:
SGVsbG8gV29ybGQh\u003d
The Server doesn't recognize the content, so its response in an error.
Why during the request the content changes? Is there a way to avoid it?
SOLUTION
There was an escaping error linked to the use of Gson.
According to this post, solved adding this disableHtmlEscaping() to the GsonBuilder.
I am trying to serialize the request before sending it to the retrofit for webservice calls.
as i am serializing the request , i need to pass json string to retrofit calls in #Body paramenter and due to that
the generated json string results into following json string with the " (Double quotes in front and end ).
"{\"access_token\":\"d80fa6bd6f78cc704104d61146c599bc94b82ca225349ee68762fc6c70d2dcf0\",\"fitness\":[{\"_id\":\"1d051bfe-df30-4fa0-808b-9d7300a608ab\",\"activity_id\":\"877284d3-4f36-4ec0-a536-11563207dc4d\",\"calories\":600.0,\"distance\":40.0,\"intensity\":\"100\",\"timestamp\":\"2018-07-18T12:56:43+00:00\",\"type\":\"Running\",\"utc_offset\":\"+05:30\"},{\"_id\":\"2004ff72-707d-489a-927e-4cdeed410095\",\"activity_id\":\"5ed7c90f-805e-4763-aa62-7f8126c84f06\",\"calories\":600.0,\"distance\":40.0,\"intensity\":\"100\",\"timestamp\":\"2018-07-18T12:56:43+00:00\",\"type\":\"Running\",\"utc_offset\":\"+05:30\"}]}"
as there are double quotes the third party api is unable to parse it successfully.
here is my reqeust serializer code
public class RequestSerializer implements JsonSerializer<Request<?>> {
#Override
public JsonElement serialize(Request<?> request, Type typeOfSrc, JsonSerializationContext context) {
JsonObject jsonObject = new GsonBuilder().create().toJsonTree(request,Request.class).getAsJsonObject();
JsonElement requestList = jsonObject.get("requestList");
jsonObject.remove("requestList");
jsonObject.add("fitness",requestList);
return jsonObject;
}
}
code to call retrofit webservice
GsonBuilder builder = new GsonBuilder();
builder.excludeFieldsWithoutExposeAnnotation();
builder.registerTypeAdapter(Request.class, new RequestSerializer());
Gson gson = builder.create();
String data = gson.toJson(request);
Flowable<Response> fitnessFlowable = new WebRequest().getRemoteClient().create(FitnessApi.class).postFitnessData("5b238abb4d3590001d9b94a8",data);
Using objects eliminates string quoting request in POST #Body,
example as my working code:
class Data{
#SerializedName("access_token")
#Expose
private String access_token;
public String getAccess_token() {
return access_token;
}
public void setAccess_token(String access_token) {
this.access_token = access_token;
}
// Other field defined here
}
#Keep
class Result{
#SerializedName("rc") int rc;
}
#Keep
interface APIFitness{
#Headers("Content-Type: application/json")
#POST("api/save/")
Observable<Result> Save(#Body Data data);
//More methods..
}
using
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("url")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
APIFitness service = retrofit.create(APIFitness.class);
Data data = new Data();
authCode.setAccess_token(token);
service.Save(data)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.newThread())
.subscribe( data -> { } )
I was trying to develop a simple POST API call in Android so I made one thinking that the request content-type was a json. Turns out it is expecting a multipart/form-data format and I'm struggling changing my function.
I'd like to know if there is any library to manage this. If not, I'd like to know how to pass my arguments in a multipart format.
#Override
public boolean post(String poiId, String description, ArrayList<String> tags, Resource resource) {
RequestQueue queue = mRequestQueue;
poiId = "1";
description = "Test post";
final HashMap<String, Object> params = new HashMap<>();
params.put("poiID", poiId);
params.put("description", description);
System.out.println("POI ID " + description);
params.put("tags", tags);
params.put("resource", resource);
RequestFuture<JSONObject> future = RequestFuture.newFuture();
JsonObjectRequest request = new JsonObjectRequest(
Request.Method.POST,
API_POST_URL,
new JSONObject(params),
future, future) {
#Override
public HashMap<String, String> getHeaders() {
System.out.println(PostRepositoryImpl.this.getHeaders());
return PostRepositoryImpl.this.getHeaders();
}
};
queue.add(request);
try {
future.get(TIMEOUT, TIMEOUT_TIME_UNIT); // this will block
}catch (InterruptedException | ExecutionException | TimeoutException e){
e.printStackTrace();
return false;
}
return true;
}
I hardcoded some of the values because I wanted to test with poiID and description
So I want to send these kind of values in my multipart/form-date:
- poiID : String
- description : String
- resource : image
- tags
Is there any way to do this similar to the way I made my json request?
Kind regards
EDIT:
#Override
public boolean post(String poiId, String description, ArrayList<String> tags, Resource resource) {
RequestQueue queue = mRequestQueue;
StringRequest postRequest = new StringRequest(Request.Method.POST, API_POST_URL,
new Response.Listener<String>()
{
#Override
public void onResponse(String response) {
// response
Log.d("Response", response);
}
},
new Response.ErrorListener()
{
#Override
public void onErrorResponse(VolleyError error) {
// error
Log.d("Error.Response", "400");
}
}
) {
#Override
protected HashMap<String, String> getParams()
{
HashMap<String, String> params = new HashMap<String, String>();
params.put("poiID", "Alif");
params.put("description", "http://itsalif.info");
return params;
}
};
queue.add(postRequest);
return true;
}
How do I add the headers?
If it isn't JSON, simply use a StringRequest.
Not sure how to use Future with Volley, so change that accordingly
Then, params are added in an overridden method
Request request = new StringRequest(
Request.Method.POST,
API_POST_URL,
future, future) {
#Override
public HashMap<String, String> getHeaders() {
HashMap<String, String> headers = PostRepositoryImpl.this.getHeaders();
System.out.println(headers);
return headers;
}
#Override
public HashMap<String, String> getParams() {
// TODO: Put your params here
}
};
And for Multipart, see Working POST Multipart Request with Volley and without HttpEntity
Using Retrofit 2, you could do this:
//Lets Suppose this you have this postman or you want to make some request like this
//ServiceCreator (In my case i am using oauth2 so have AccessToken). This is a working and production sample, so you have to make your own changes, but i attach to example all components.
public class APIRestClient {
public static String API_BASE_URL = "http://186.151.238.14/";
private static OkHttpClient.Builder httpClient;
private static Retrofit.Builder builder;
public static Retrofit retrofit;
private static Activity mActivity;
private static AccessToken mToken;
/**
* setupBase URL
* #param _baseActivity
*/
public static void setupBaseUrl(Context _baseActivity){
String tmpBase = SharedPreferenceUtilities.getDomain(_baseActivity);
if (tmpBase != null && tmpBase.length() > 0){
if (tmpBase != API_BASE_URL) {
APIRestClient.API_BASE_URL = tmpBase;
}
}
}
/**
* auth2 Authorization Bearer...token create Service instance
* #param _serviceClass
* #param _baseActivity
* #param <S>
* #return
*/
public static <S> S createService(Class<S> _serviceClass, final Activity _baseActivity) {
AccessToken accessToken = TaskManagementApplication.getInstance().getAccessToken();
if (_baseActivity != null) {
setupBaseUrl(_baseActivity);
}
httpClient = new OkHttpClient.Builder();
httpClient.connectTimeout(30000, TimeUnit.SECONDS)
.readTimeout(30000,TimeUnit.SECONDS);
if (BuildConfig.DEBUG) {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
httpClient.addInterceptor(logging);
httpClient.addNetworkInterceptor(new StethoInterceptor());
}
builder = new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create());
if (accessToken == null){
accessToken = new AccessToken();
accessToken.setAccessToken("");
accessToken.setTokenType("Bearer");
accessToken.setScope("");
accessToken.setRefreshToken("");
accessToken.setClientID("");
accessToken.setClientSecret("");
accessToken.setExpiry(0);
}
if(accessToken != null) {
mActivity = _baseActivity;
mToken = accessToken;
final AccessToken token = accessToken;
httpClient.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request.Builder requestBuilder = original.newBuilder()
.header("Accept", "application/json")
.header("Content-type", "application/json")
.header("Authorization",
token.getTokenType() + " " + token.getAccessToken())
.method(original.method(), original.body());
Request request = requestBuilder.build();
return chain.proceed(request);
}
});
httpClient.authenticator(new Authenticator() {
#Override
public Request authenticate(Route route, Response response) throws IOException {
if(responseCount(response) >= 2) {
// If both the original call and the call with refreshed token failed,
// it will probably keep failing, so don't try again.
LoginUtilities.initLogin(_baseActivity,LoginActivity.LOGININTENTRESULT,null);
return null;
}
// We need a new client, since we don't want to make another call using our client with access token
OAuthInterface tokenClient = createAuthService(OAuthInterface.class,mActivity);
Call<AccessToken> call = tokenClient.getRefreshAccessToken(
Grant_type.REFRESH_TOKEN.toString(),
token.getRefreshToken(),
StringUtilities.API_OAUTH_CLIENTID(_baseActivity),
StringUtilities.API_OAUTH_SECRET(_baseActivity),
"");
try {
retrofit2.Response<AccessToken> tokenResponse = call.execute();
if(tokenResponse.code() == 200) {
AccessToken newToken = tokenResponse.body();
mToken = newToken;
SharedPreferenceUtilities.setAccessToken(mActivity,mToken);
TaskManagementApplication.getInstance().setupToken(mToken);
return response.request().newBuilder()
.header("Authorization", newToken.getTokenType() + " " + newToken.getAccessToken())
.build();
} else {
LoginUtilities.initLogin(_baseActivity,LoginActivity.LOGININTENTRESULT,null);
return null;
}
} catch(IOException e) {
LoginUtilities.initLogin(_baseActivity,LoginActivity.LOGININTENTRESULT,null);
return null;
}
}
});
}
OkHttpClient client = httpClient.build();
Retrofit retrofit = builder.client(client).build();
return retrofit.create(_serviceClass);
}
/**
* not auth create Service instance
* #param _serviceClass
* #param _context
* #param <S>
* #return
*/
private static int responseCount(Response response) {
int result = 1;
while ((response = response.priorResponse()) != null) {
result++;
}
return result;
}
}
//ApiInterface
public interface StudentInterface
{
public static final String ENVIARTAREAAPI = "api/estudiante/entregatarea";
#Multipart
#POST(ENVIARTAREAAPI)
Call<TareaCalificacion> entregatarea(#Part("Descripcion") RequestBody Descripcion,
#Part("IdTarea") RequestBody IdTarea,
#Part("IdEstudiante") RequestBody IdEstudiante);
}
//ApiCall (in your activity, fragment or wetheaver) this should be used when you execute your api call
RequestBody descripcionRequestBody = RequestBody.create(
okhttp3.MediaType.parse("text/plain; charset=utf-8"),
mensageEntregaTmp);
RequestBody idTareaRequestBody = RequestBody.create(
okhttp3.MediaType.parse("text/plain; charset=utf-8"),
String.valueOf(mTarea.getIdTarea()));
RequestBody idEstudianteRequestBody = RequestBody.create(
okhttp3.MediaType.parse("text/plain; charset=utf-8"),
String.valueOf(currUser.getPerfil().getSisId()));
StudentInterface studentInterface = APIRestClient.createService(StudentInterface.class,DetalleTareaActivity.this);
Call<TareaCalificacion> call = studentInterface.entregatarea(
descripcionRequestBody,
idTareaRequestBody,
idEstudianteRequestBody);
call.enqueue(new Callback<TareaCalificacion>() {
#Override
public void onResponse(Call<TareaCalificacion> call, Response<TareaCalificacion> response) {
int statusCode = response.code();
if(statusCode == 200) {
Toast.makeText(getApplicationContext, "Success Request", Toast.LENGTH_SHORT).show();
} else {
//todo some kind of error
}
}
#Override
public void onFailure(Call<TareaCalificacion> call, Throwable t) {
//todo some kind of error
}
});
I have used this to upload photos, so i have to use this sample to do that, thats the reason i did not use Content Type application/json.
Hope that helps how to do.
Some class (pojo) like TareaCalificacion (that is what i expect from the response are just class, that i use with GSON), so TareaCalificacion.java is like:
public class TareaCalificacion {
#SerializedName("sisId")
#Expose
private long sisId;
#SerializedName("sisDescripcion")
#Expose
private String sisDescripcion;
#SerializedName("sisEstado")
#Expose
private String sisEstado;
#SerializedName("sis")
#Expose
private int sis;
#SerializedName("sisUsuario")
#Expose
private String sisUsuario;
#SerializedName("CalificacionObtenida")
#Expose
private double CalificacionObtenida;
#SerializedName("IdEstudiante")
#Expose
private long IdEstudiante;
#SerializedName("IdTarea")
#Expose
private long IdTarea;
#SerializedName("Adjunto")
#Expose
private int Adjunto;
#SerializedName("ObservacionCalificacion")
#Expose
private String ObservacionCalificacion;
#SerializedName("IdCatedratico")
#Expose
private long IdCatedratico;
public TareaCalificacion() {
}
}
Attach some links that could help you if you have doubts:
Retrofit Documentation
Another example using this
Lets me know if that works or if is not clear how to do
Regards.
Basically, my question is simple. I'm using retrofit as a framework for communicating with a server which I don't control. I want to set some sort of tag on my request which gets returned in the response automatically. Any idea on how this could be accomplished?
I found a complex and not cool way to do this.
0. add a tag field in your request and response type
1. Custom okhttp3.RequestBody to add a tag field:
2. Custom okhttp3.ResponseBody to add a tag field:
3. Custom Converter.Factory to set and get tag:
for example I did some change to the GsonResponseBodyConverter and GsonRequestBodyConverter:
TagGsonRequestBodyConverter.java:
#Override public RequestBody convert(T value) throws IOException {
Buffer buffer = new Buffer();
Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
JsonWriter jsonWriter = gson.newJsonWriter(writer);
adapter.write(jsonWriter, value);
jsonWriter.close();
TagRequestBody requestBody = TagRequestBody.create(MEDIA_TYPE, buffer.readByteString());
requestBody = value.tag;//just for example ,you will need to check type here
return requestBody;
}
TagGsonResponseBodyConverter.java:
#Override public T convert(ResponseBody source) throws IOException {
try {
//the ugly part,for that retrofit will wrap the responseBody with ExceptionCatchingRequestBody.(ExceptionCatchingRequestBody extends ResponseBody)
ResponseBody value = source;
if (value.getClass().getSimpleName().equals("ExceptionCatchingRequestBody")){
ResponseBody temp = null;
try {
Field f = source.getClass().getDeclaredField("delegate");
f.setAccessible(true);
temp = (T) f.get(value);
} catch (Exception e) {
e.printStackTrace();
}
value = temp != null?temp:value;
}
T t = adapter.fromJson(source.charStream());
if (value instanceof TagResponseBody) {
t.tag = ((TagResponseBody)value).tag;
}
return t;
} finally {
value.close();
}
}
4. add Interceptor when you create retrofit's okhttpClient,pass tag from TagRequestBody to TagResponseBody:
.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
if (request.body() instanceof TagRequestBody) {
TagResponseBody responseBody = new TagResponseBody(response.body());
responseBody.arg = ((TagRequestBody) request.body()).arg;
response = response.newBuilder()
.body(responseBody)
.build();
}
return response;
}
})
So the tag was hold by :
RequestDataWithTag --CustGsonRequestBodyConverter--> RequestBodyWithTag --Interceptor--> ResponseBodyWithTag --CustGsonResponseBodyConverter--> ResponseDataWithTag
I need send a json to my webservice, json is:
{
"Sala": {
"usuario": "%#",
"adversario": "%#",
"atualizacao": "%#",
"device": "%#",
"device_tipo": "ios"
}
}
. I'm trying do it using Retrofit API 1.8.
When I try send the post throws an exception.
Exception:
com.google.gson.JsonSyntaxException: com.google.gson.stream.MalformedJsonException: Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 7 path $
I'm trying this
public class ChatObject {
private String usuario;
private String adversario;
private String atualizacao;
private String email;
private String device;
private String device_tipo;
Retrofit Interface
#POST("/WsChat/interacao.json")
public void onReceiveMessage(#Body ChatObject obj,
Callback<JsonElement> response);
Implements
public void receiveMessage(){
///{\"Sala\":{\"usuario\":\"%#\",\"adversario\":\"%#\",\"atualizacao\":\"%#\",\"device\":\"%#\",\"device_tipo\":\"ios\"}}
ChatObject chatObject = new ChatObject(BatalhaConfigs.USUARIO_EMAIL,
BatalhaConfigs.ADVERSARIO_EMAIL,
new Date().toString(),
BatalhaConfigs.USUARIO_EMAIL,
AndroidReturnId.getAndroidId(),
"android");
RestAdapter adapter = new RestAdapter.Builder()
.setLogLevel(RestAdapter.LogLevel.FULL)
.setRequestInterceptor(new CustomRequestInterceptor())
.setEndpoint(END_POINT)
.build();
ChatListener listener = adapter.create(ChatListener.class);
listener.onReceiveMessage(chatObject, new Callback<JsonElement>() {
#Override
public void success(JsonElement jsonElement, retrofit.client.Response response) {
Log.i("JSON ELEMENT->", jsonElement.toString());
}
#Override
public void failure(RetrofitError error) {
Log.i("FALHOU->", error.getLocalizedMessage());
}
});
}
com.google.gson.JsonSyntaxException: com.google.gson.stream.MalformedJsonException: Use JsonReader.setLenient(true) is usually thrown when there is some character(s) that malforms the JSON. Exception message itself suggest to make the deserialization more tolerant.
But I suggest you to fix your JSON and trim it from unwanted characters.
You should extend GsonConverter and override fromBody() to make Gson read from the tolerant JsonReader. Then just set it to your RestAdapter. This will attempt to use tolerant JsonReader to deserialize and then close it, if not exception is thrown.
public class LenientGsonConverter extends GsonConverter {
private Gson mGson;
public LenientGsonConverter(Gson gson) {
super(gson);
mGson = gson;
}
public LenientGsonConverter(Gson gson, String charset) {
super(gson, charset);
mGson = gson;
}
#Override
public Object fromBody(TypedInput body, Type type) throws ConversionException {
boolean willCloseStream = false; // try to close the stream, if there is no exception thrown using tolerant JsonReader
try {
JsonReader jsonReader = new JsonReader(new InputStreamReader(body.in()));
jsonReader.setLenient(true);
Object o = mGson.fromJson(jsonReader,type);
willCloseStream = true;
return o;
} catch (IOException e) {
e.printStackTrace();
}finally {
if(willCloseStream) {
closeStream(body);
}
}
return super.fromBody(body, type);
}
private void closeStream(TypedInput body){
try {
InputStream in = body.in();
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Seems its changed slightly with Retrofit 2.0
Here's how I did it:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://whatever.com")
.addConverterFactory(LenientGsonConverterFactory.create(gson))
.build();
A new lenient gson factory:
public final class LenientGsonConverterFactory extends Converter.Factory {
/**
* Create an instance using a default {#link Gson} instance for conversion. Encoding to JSON and
* decoding from JSON (when no charset is specified by a header) will use UTF-8.
*/
public static LenientGsonConverterFactory create() {
return create(new Gson());
}
/**
* Create an instance using {#code gson} for conversion. Encoding to JSON and
* decoding from JSON (when no charset is specified by a header) will use UTF-8.
*/
public static LenientGsonConverterFactory create(Gson gson) {
return new LenientGsonConverterFactory(gson);
}
private final Gson gson;
private LenientGsonConverterFactory(Gson gson) {
if (gson == null) throw new NullPointerException("gson == null");
this.gson = gson;
}
#Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new LenientGsonResponseBodyConverter<>(gson, adapter);
}
#Override
public Converter<?, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new LenientGsonRequestBodyConverter<>(gson, adapter);
}
}
Lenient parsing of responses:
private class LenientGsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
private final Gson gson;
private final TypeAdapter<T> adapter;
LenientGsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
this.gson = gson;
this.adapter = adapter;
}
#Override
public T convert(ResponseBody value) throws IOException {
JsonReader jsonReader = gson.newJsonReader(value.charStream());
jsonReader.setLenient(true);
try {
return adapter.read(jsonReader);
} finally {
value.close();
}
}
}
Lenient creation of requests:
private class LenientGsonRequestBodyConverter<T> implements Converter<T, RequestBody> {
private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
private static final Charset UTF_8 = Charset.forName("UTF-8");
private final Gson gson;
private final TypeAdapter<T> adapter;
LenientGsonRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) {
this.gson = gson;
this.adapter = adapter;
}
#Override
public RequestBody convert(T value) throws IOException {
Buffer buffer = new Buffer();
Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
JsonWriter jsonWriter = gson.newJsonWriter(writer);
jsonWriter.setLenient(true);
adapter.write(jsonWriter, value);
jsonWriter.close();
return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
}
}
I just copied the Retrofit source code and added a line to the request and the response converters jsonWriter.setLenient(true);
Or even easier:
Gson gson = new GsonBuilder()
.setLenient()
.create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://whatever.com")
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
RestAdapter adapterRfqPost = new RestAdapter.Builder()
.setEndpoint(Constants.ENDPOINT)
`enter code here`.setConverter(new ConstantsMethods.StringConverter())
.build();
public static class StringConverter implements Converter {
#Override
public Object fromBody(TypedInput typedInput, Type type) throws ConversionException {
String text = null;
try {
text = fromStream(typedInput.in());
} catch (IOException ignored) {/*NOP*/ }
return text;
}
#Override
public TypedOutput toBody(Object o) {
return null;
}
public static String fromStream(InputStream in) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder out = new StringBuilder();
String newLine = System.getProperty("line.separator");
String line;
while ((line = reader.readLine()) != null) {
out.append(line);
out.append(newLine);
}
return out.toString();
}
}
I struggled around a day getting this error and doing what the "correct answer" of this page said so, but after all I figured out my problem, that was assigning the response from an array that was "int" (also my model class was int) to an textView which of course required me to convert it to string the int value. I didn't even required to do the solution of #Nikola Despotoski at all in my case.
Below code worked for me
Gson gson = new GsonBuilder()
.setLenient()
.create();
final RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(endPoint)
.setConverter(new GsonConverter(gson))
.build();
For using ".setLenient()", need to add below line into app's gradle file.
implementation 'com.google.code.gson:gson:2.7'
if you are using PHP as API please check whether it echoes
only JSON encoded objects otherwise it will throw this type of exception
You should help this code :
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint("http://192.168.10.115/test.php")
.setConverter(new GsonConverter(new Gson()))
.build();
Put jar file :
[gson-2.2.2.jar][1]