Retrofit: 2.0-beta2 adding extra quotes to multipart string form values

Created on 19 Oct 2015  Â·  37Comments  Â·  Source: square/retrofit

retrofit 2.0 seems to be double quoting strings in multi part post requsts.

for example this interface

public interface ApiInterface {

    @Multipart
    @POST("user/login/")
    Call<SessionToken> userLogin(@Part("username") String username, @Part("password") String password);
}

server side will print the key value pairs as

username : "brian"
password : "password"

instead of

username : brian
password : password

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

Most helpful comment

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();

All 37 comments

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 [email protected] 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 [email protected] 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 items);
We get request like...............

{"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 (\").....??

Note:- But backend still can remove these extra (\") from requeset.

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 getVerty(
@Part("id") Sting id,
@PartMap() Map phot,
@Part("name") String name);

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:

https://stackoverflow.com/a/36907435/716237

Is their a official fix available?

public interface ApiConfig {
@Multipart
@POST("createOnlineSession")
Call saveSessionData(
@Part("sessionUnit") RequestBody sessionUnit,
@Part("sessionType") RequestBody sessionType,
@Part("sessionClass") RequestBody sessionClass,
@Part("sessionStudent") ArrayList sessionStudent,
@Part("sessionSubject") RequestBody sessionSubject,
@Part("teacherInstruction") RequestBody teacherInstruction,
@Part MultipartBody.Part file,
@Part("schoolId") RequestBody schoolId,
@Part("branchId") RequestBody branchId,
/@Part("createdBy") CreatedByModel createdBy,/
@Part("sessionName") RequestBody sessionName,
@Part("sessionDate") RequestBody sessionDate,
@Part("videoName") RequestBody videoName
);

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

Was this page helpful?
0 / 5 - 0 ratings