Closed bperin closed 8 years ago
This is because it's running through the JSON converter. There's no special support for String
and I'm not sure I want to add it.
What content type would you expect these parts to use?
@JakeWharton multipart/form-data I think.
That's what the entire request uses, but each part also has a content type associated with it. In this case, it's going to be set to application/json
alongside the extra quotes.
Same issue. Huge roadblock to my 2-day upgrade to Retrofit 2.0-beta2.
I opened a similar issue, although I encountered this same problem during Enum serialization (with @SerializedName
annotations) having extra quotes being received by back end server.
@JakeWharton - Our only expectation is that quotes would not suddenly appear to back-end after Retrofit 2.0 upgrade.
My Retrofit endpoint is defined as such:
@Multipart
@POST("images")
Call<ImageResponse> createImage(
@Part("image") RequestBody image,
@Part("lat") double lat,
@Part("lng") double lng,
@Part("type") ImageType type,
@Part("description") String description);
Before Retrofit 2.0, the string test
would arrive to the server as test
. Now it's arriving with extra quotes: "test"
. (ImageType
enum value also has troublesome extra quotes, but the String example is much more simple so I'll focus on that.)
Really don't know how to proceed.
Specifically, my back end system is built on Laravel's Lumen microframework, and POST values are accessed via $request->input('field_name');
.
These values now contain the quotes in them. Other mobile clients/etc don't send these extra quotes, so a hack-y 'strip off quotes on server' solution isn't an option.
You can write your own Converter for String
and other primitives and put them on the wire in whatever format you want. As I said, right now they're going through the JSON converter that you are presumably using.
I'm serializing using plain old Gson just like I was with Retrofit 1.9. Nothing fancy.
You're saying with Retrofit 2.0 we have to create a type converter for serializing Strings...?
Gson gson = new GsonBuilder() .create();
return new Retrofit.Builder()
.baseUrl(Env.GetApiBaseUrl())
.addConverterFactory(GsonConverterFactory.create(gson))
.client(getHttpClient())
.build();
I would certainly categorize this as unexpected behaviour, an obstacle to upgrading to 2.0, and generally incompatible with the expectations put forth by of other REST libraries' (including Retrofit 1.9's) out-of-the-box String serialization.
Pull requests welcome. But as with most of the issues filed on Retrofit, you are trivializing the problem and assuming everyone wants the same behavior as you.
Is it not fair to say that Retrofit 1.9 and 2.0, when using the same API endpoint annotations and the same converter (Gson), should send the same data to the server?
No. There are fundamental changes in the converter pipeline that's used. Some special cases have been removed (like String
currently) while others have been added (RequestBody
, ResponseBody
, and Void
).
I like the word "currently" - it gives me hope that the no-magical-quotes behaviour will come back..
In the meantime I suppose I have no choice but to build a JakeWhartonHowCouldYouDoThisToMeConverterFactory
class..
I would actually argue that the absence of quotes from 1.x was the more magical behavior of the two. It's very clear what's happening in 2.x and you actually have the power to control it by placing another converter before the JSON one. This is the same issue as https://github.com/square/retrofit/issues/763 just with another built-in Java primitive.
@JakeWharton I'll mess around with custom converter but I would imagine as more people upgrade to 2.0 this will come up more than once. On a side note thanks for a great library, I moved over from Spring Android a while ago besides this issue it's been great.
There's a ToStringConverterFactory multiple places in the tests of the library. Should be able to just copy/paste that guy for now.
On Mon, Oct 19, 2015 at 3:55 PM bperin notifications@github.com wrote:
@JakeWharton https://github.com/JakeWharton I'll mess around with custom converter but I would imagine as more people upgrade to 2.0 this will come up more than once. On a side note thanks for a great library, I moved over from Spring Android a while ago besides this issue it's been great.
— Reply to this email directly or view it on GitHub https://github.com/square/retrofit/issues/1210#issuecomment-149328185.
Ok, here's the route I'm going at the moment:
return new Retrofit.Builder()
.baseUrl(Env.GetApiBaseUrl())
.addConverterFactory(GsonStringConverterFactory.create(gson))
.addConverterFactory(GsonConverterFactory.create(gson))
.client(getHttpClient())
.build();
And then inside GsonStringConverterFactory
:
@Override
public Converter<?, RequestBody> toRequestBody(Type type, Annotation[] annotations)
{
if (type == String.class)
return new GsonStringRequestBodyConverter<>(gson, type);
return null;
}
I'm just copying GsonRequestBodyConverter
into my magical GsonStringRequestBodyConverter
class, I guess. I only need to change the MEDIA_TYPE
, I imagine.
What do you recommend I change it to?
Oh, I see, so:
private static final MediaType MEDIA_TYPE = MediaType.parse("text/plain");
@mhousser Content-Type: text/plain; charset=UTF-8
Hmm. Having trouble. Back end still receiving strings with quotes in them..
Ok. Finally got Strings (Enums are still my next issue..) to work. Certainly not out-of-the-box any more.
Firstly:
return new Retrofit.Builder()
.baseUrl(Env.GetApiBaseUrl())
.addConverterFactory(new GsonStringConverterFactory())
.addConverterFactory(GsonConverterFactory.create(gson))
.client(getHttpClient())
.build();
As per @JakeWharton 's suggestion:
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;
}
}
Confirmed that back end server is receiving proper string without quotes inside it.
@mhousser cheers for the Converter Factory! I switched from @FormUrlEncoded to @Multipart so that I may POST files, and all of a sudden these annoying quotes were appearing around my strings!
Thanks for putting together a work-around, much appreciated!
Looks like this is a dupe of #763!
Still this issue not cleared from my side how you are closed it
Did you look at the PR? There's a scalars converter you can use.
On Thu, Nov 26, 2015 at 8:08 AM ramesh586 notifications@github.com wrote:
Still this issue not cleared from my side how you are closed it
— Reply to this email directly or view it on GitHub https://github.com/square/retrofit/issues/1210#issuecomment-159908206.
What about to do in that way?
RequestBody caption = RequestBody.create(MediaType.parse("text/plain"), new String("caption"));
For those that are still facing this problem, I changed my Interface from:
@Multipart
@POST("api/comm/orders")
Call<Void> postOrder(@Part("orderID") String orderID, @Part("orderNumber") Long orderNumber, @Part("order\"; filename=\"orderData.dat ") RequestBody order);
to
@Multipart
@POST("api/comm/orders")
Call<Void> postOrder(@Part("orderID") RequestBody orderID, @Part("orderNumber") RequestBody orderNumber, @Part("order\"; filename=\"orderData.dat ") RequestBody order);
Then, declared the RequestBodies as below:
RequestBody requestBodyOrderID = RequestBody.create(MediaType.parse("text/plain"), pedido.getPedidoID());
RequestBody requestBodyOrderNumber = RequestBody.create(MediaType.parse("text/plain"), String.valueOf(pedido.getNumero()));
RequestBody requestBodyOrderDataFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
Call<Void> postOrderCall = ordersClient.postOrder(requestBodyOrderID, requestBodyOrderNumber, requestBodyOrderDataFile);
postOrderCall.execute();
The above solution working fine in simple primitive values but it still add extra ("\"") in your request, when we pass an array or a List of items as multipart...
If we send an array or list like...
getVerty(........, @Part("items_info") List
{"status":"1","message":{"nfc_tag_id":"U-123456-123-12/346","time":"2015-12-16 16:54:04","resident_at_home":"Y","items_info":"[{\"cna_number\":\"\",\"item_bar_code\":\"SKN6466A\",\"item_type\":\"SP\",\"time\":\"2015-12-16 16:54:10\"}]"}}
We can see there is still extra (\").....??
But you can send an array or list also like this...
RequestBody rb;
LinkedHashMap<String, RequestBody> mp= new LinkedHashMap<>();
for(int i=0;i<itemBeam.getItems_info().size();i++)
{
rb=RequestBody.create(MediaType.parse("text/plain"), itemBeam.getItems_info().get(i).getItem_bar_code());
mp.put("item_bar_code["+i+"]", rb);
rb=RequestBody.create(MediaType.parse("text/plain"),itemBeam.getItems_info().get(i).getItem_type());
mp.put("item_type["+i+"]",rb);
rb=RequestBody.create(MediaType.parse("text/plain"),itemBeam.getItems_info().get(i).getCna_number());
mp.put("cna_number["+i+"]",rb);
rb=RequestBody.create(MediaType.parse("text/plain"),itemBeam.getItems_info().get(i).getTime());
mp.put("times["+i+"]",rb);
}
Now pass your map in the callback function ....
getVerty("11221", mp, "Bajrang Hudda");
And change your interface like this.....
@Multipart
@POST("vertical")
Call
That's it..... now your request will be like.... {"status":"1","message":{"nfc_tag_id":"U-123456-123-12/346","time":"2015-12-17 10:04:29","resident_at_home":"Y","item_bar_code":["MC140517193S","9804456119139","CN04G4817161653H01AV","SP40D71289"],"item_type":["O","O","O","SP"],"cna_number":["","","",""],"times":["2015-12-17 10:04:33","2015-12-17 10:04:38","2015-12-17 10:04:46","2015-12-17 10:04:53"]}}
I've extended on @mhousser's converter, and included enums and all primary types:
https://gist.github.com/pflammertsma/b9cb0c4688bbd335ab66
I agree with @JakeWharton that these sorts of things are by design. Retrofit 2 really behaves well in using the defined converter for everything, including multipart data. It simply appears that the API I'm communicating with doesn't respect the Content-Type of each part, assuming it's always "text/plain".
This solution worked for me, but I'd emphasize that the underlying problem is really the API.
"Solution2" in this answer solved my issue regarding to this.
Use this: compile 'com.squareup.retrofit2:converter-scalars:2.0.1' Check this: http://stackoverflow.com/a/36907435/3844201
In version 2.1.0 still not work
I use another one solution. Worked with Retrofit 2.1.0. (Rx adapter is optional here)
My retrofit interface looks like this:
@POST("/children/add")
Single<Child> addChild(@Body RequestBody requestBody);
And in ApiManager I use it like this:
@Override
public Single<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 solution from @regisdaniel but I think that this one is little a bit more elegant.
Do like DenisShov said. Works great for me:
Is their a official fix available?
public interface ApiConfig {
@Multipart
@POST("createOnlineSession")
Call
this is my request but may data reaching on server for arraylist is in [ '{"_id":"5c98936d37b84518c878a79f","fullName":"Asad Siddiqui","stuId":"EPS000119S001"}','{"_id":"5c98936d37b84518c878a79f","fullName":"Asad Siddiqui","stuId":"EPS000119S001"}']
why these ' ' quotes here by this my this list json {\"_id\":\"5c98936d37b84518c878a79f\",\"fullName\":\"Asad Siddiqui\",\"stuId\":\"EPS000119S001\"} converting this \ with back slash format the format i pasted here is removed here in this comment i don't know why please help me out
@JakeWharton please look in this
Strange that this exists even today, and only this works
@osumoclement Did you tried ScalarsConverterFactory
before GsonConverterFactory
. It already contains the idea that the SO link above put forward.
Yes, and that didn't work. I ended up using a custom String Converter
in between ScalarsConverterFactory
and GsonConverterFactory
, like this:
private static Retrofit.Builder builder = new Retrofit.Builder() .baseUrl(getAPI_BASE_URL()) .addConverterFactory(ScalarsConverterFactory.create()) .addConverterFactory(CustomRetrofitStringConverter.create()) .addConverterFactory(GsonConverterFactory.create());
@osumoclement Really? I am running into the same issue now and trying with ScalarsConverterFactory
. The logic to convert String
to RequestBody
and vice-versa is already there is ScalarsConverterFactory
so I think it is redundant.
I checked the logs with Interceptor and seems like it is working for me.
I think it depends on how else you're using Retrofit/GSON in other parts of your code. In my case, I was doing a MultiPartBody request as below:
List<MultipartBody.Part> jsonbody = new ArrayList<>();
ProgressRequestBody reqFile = new ProgressRequestBody((File) value, API.this);
multipartbody.add(MultipartBody.Part.createFormData(key.toString(), ((File) value).getName(), reqFile));
jsonbody.add(MultipartBody.Part.createFormData((String) key, (String) value));
call = genericCreateService.sendCreateRequest((String) action.get("url"), multipartbody, jsonbody);
I tried many variations of solutions suggested online, including ScalarsConverterFactory
but eventually just settled for the "hacky" solution of having a custom String Converter
. Also, I had encountered a similar case where GSON would also double quote dates if I used Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
so this was yet another hack:
else if (value instanceof Date) {
//this is hacky as hell and ideally shouldn't be there
//gson's setdateformat is double quoting dates for some reason, so I am unquoting them
jsonbody.add(MultipartBody.Part.createFormData((String) key,
(gson.toJson(value)).substring(1, gson.toJson(value).length() - 1)));
}
For those that are still facing this problem, I changed my Interface from:
@Multipart @POST("api/comm/orders") Call<Void> postOrder(@Part("orderID") String orderID, @Part("orderNumber") Long orderNumber, @Part("order\"; filename=\"orderData.dat ") RequestBody order);
to
@Multipart @POST("api/comm/orders") Call<Void> postOrder(@Part("orderID") RequestBody orderID, @Part("orderNumber") RequestBody orderNumber, @Part("order\"; filename=\"orderData.dat ") RequestBody order);
Then, declared the RequestBodies as below:
RequestBody requestBodyOrderID = RequestBody.create(MediaType.parse("text/plain"), pedido.getPedidoID()); RequestBody requestBodyOrderNumber = RequestBody.create(MediaType.parse("text/plain"), String.valueOf(pedido.getNumero())); RequestBody requestBodyOrderDataFile = RequestBody.create(MediaType.parse("multipart/form-data"), file); Call<Void> postOrderCall = ordersClient.postOrder(requestBodyOrderID, requestBodyOrderNumber, requestBodyOrderDataFile); postOrderCall.execute();
@regisoliveira This was so useful for me. It even worked with the @Body annotation.
@POST("/access")
Call
Thank you. I hope it gets fixed soon...
What if I'd like to send single string using POST method:
package com.somePackage.client.web.business.feature.control;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.Path;
import retrofit2.http.POST;
import com.somePackage.client.web.business.feature.entity.featureModel;
import com.somePackage.client.web.business.feature.entity.featureExtensiveModel;
public interface someFeatureApi {
@GET("someFeature/{deviceId}")
Call<featureModel> getDeviceId(@Path("deviceId") String deviceId);
@POST("someFeature/deviceData")
Call<featureExtensiveModel> getDeviceData(@Body String deviceId);
}
But the deviceId
must be specifically String
, because it's passed as string to this getDeviceData
method somewhere else.
And when I set the deviceId
type inside of getDeviceData
method to DeviceIdType
instead of String
, the IntelliJ IDEA linter shows warning that it's potentially error producing, because types don't match, because somewhere else in some class, this getDeviceData
method, as a I already mentioned before, is called with deviceId
parameter being String
.
retrofit 2.0 seems to be double quoting strings in multi part post requsts.
for example this interface
server side will print the key value pairs as
instead of
The second way is how it would show up in retrofit 1.9 and any other rest client.
stack link https://stackoverflow.com/questions/33205855/retrofit-2-0-beta-2-is-adding-literal-quotes-to-multipart-values?noredirect=1#comment54246402_33205855