New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2.0-beta2 adding extra quotes to multipart string form values #1210

Closed
bperin opened this Issue Oct 19, 2015 · 34 comments

Comments

@bperin

bperin commented Oct 19, 2015

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

@JakeWharton

This comment has been minimized.

Show comment
Hide comment
@JakeWharton

JakeWharton Oct 19, 2015

Collaborator

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?

Collaborator

JakeWharton commented Oct 19, 2015

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?

@bperin

This comment has been minimized.

Show comment
Hide comment
@bperin

bperin Oct 19, 2015

@JakeWharton multipart/form-data I think.

bperin commented Oct 19, 2015

@JakeWharton multipart/form-data I think.

@JakeWharton

This comment has been minimized.

Show comment
Hide comment
@JakeWharton

JakeWharton Oct 19, 2015

Collaborator

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.

Collaborator

JakeWharton commented Oct 19, 2015

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.

@mhousser

This comment has been minimized.

Show comment
Hide comment
@mhousser

mhousser Oct 19, 2015

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.

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.

@mhousser

This comment has been minimized.

Show comment
Hide comment
@mhousser

mhousser Oct 19, 2015

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.

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.

@JakeWharton

This comment has been minimized.

Show comment
Hide comment
@JakeWharton

JakeWharton Oct 19, 2015

Collaborator

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.

Collaborator

JakeWharton commented Oct 19, 2015

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.

@mhousser

This comment has been minimized.

Show comment
Hide comment
@mhousser

mhousser Oct 19, 2015

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...?

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...?

@mhousser

This comment has been minimized.

Show comment
Hide comment
@mhousser

mhousser Oct 19, 2015

    Gson gson = new GsonBuilder() .create();

    return new Retrofit.Builder()
            .baseUrl(Env.GetApiBaseUrl())
            .addConverterFactory(GsonConverterFactory.create(gson))
            .client(getHttpClient())
            .build();
    Gson gson = new GsonBuilder() .create();

    return new Retrofit.Builder()
            .baseUrl(Env.GetApiBaseUrl())
            .addConverterFactory(GsonConverterFactory.create(gson))
            .client(getHttpClient())
            .build();
@mhousser

This comment has been minimized.

Show comment
Hide comment
@mhousser

mhousser Oct 19, 2015

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.

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.

@JakeWharton

This comment has been minimized.

Show comment
Hide comment
@JakeWharton

JakeWharton Oct 19, 2015

Collaborator

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.

Collaborator

JakeWharton commented Oct 19, 2015

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.

@mhousser

This comment has been minimized.

Show comment
Hide comment
@mhousser

mhousser Oct 19, 2015

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?

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?

@JakeWharton

This comment has been minimized.

Show comment
Hide comment
@JakeWharton

JakeWharton Oct 19, 2015

Collaborator

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).

Collaborator

JakeWharton commented Oct 19, 2015

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).

@mhousser

This comment has been minimized.

Show comment
Hide comment
@mhousser

mhousser Oct 19, 2015

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 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..

@JakeWharton

This comment has been minimized.

Show comment
Hide comment
@JakeWharton

JakeWharton Oct 19, 2015

Collaborator

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 #763 just with another built-in Java primitive.

Collaborator

JakeWharton commented Oct 19, 2015

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 #763 just with another built-in Java primitive.

@bperin

This comment has been minimized.

Show comment
Hide comment
@bperin

bperin Oct 19, 2015

@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.

bperin commented Oct 19, 2015

@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.

@JakeWharton

This comment has been minimized.

Show comment
Hide comment
@JakeWharton

JakeWharton Oct 19, 2015

Collaborator

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
#1210 (comment).

Collaborator

JakeWharton commented Oct 19, 2015

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
#1210 (comment).

@mhousser

This comment has been minimized.

Show comment
Hide comment
@mhousser

mhousser Oct 19, 2015

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?

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?

@mhousser

This comment has been minimized.

Show comment
Hide comment
@mhousser

mhousser Oct 19, 2015

Oh, I see, so:

private static final MediaType MEDIA_TYPE = MediaType.parse("text/plain");

Oh, I see, so:

private static final MediaType MEDIA_TYPE = MediaType.parse("text/plain");

@bperin

This comment has been minimized.

Show comment
Hide comment
@bperin

bperin Oct 19, 2015

@mhousser Content-Type: text/plain; charset=UTF-8

bperin commented Oct 19, 2015

@mhousser Content-Type: text/plain; charset=UTF-8

@mhousser

This comment has been minimized.

Show comment
Hide comment
@mhousser

mhousser Oct 19, 2015

Hmm. Having trouble. Back end still receiving strings with quotes in them..

Hmm. Having trouble. Back end still receiving strings with quotes in them..

@mhousser

This comment has been minimized.

Show comment
Hide comment
@mhousser

mhousser Oct 19, 2015

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.

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.

@rikochet

This comment has been minimized.

Show comment
Hide comment
@rikochet

rikochet Nov 1, 2015

@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!

rikochet commented Nov 1, 2015

@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!

@JakeWharton

This comment has been minimized.

Show comment
Hide comment
@JakeWharton

JakeWharton Nov 19, 2015

Collaborator

Looks like this is a dupe of #763!

Collaborator

JakeWharton commented Nov 19, 2015

Looks like this is a dupe of #763!

@ramesh586

This comment has been minimized.

Show comment
Hide comment
@ramesh586

ramesh586 Nov 26, 2015

Still this issue not cleared from my side how you are closed it

Still this issue not cleared from my side how you are closed it

@JakeWharton

This comment has been minimized.

Show comment
Hide comment
@JakeWharton

JakeWharton Nov 26, 2015

Collaborator

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
#1210 (comment).

Collaborator

JakeWharton commented Nov 26, 2015

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
#1210 (comment).

@pedrofraca

This comment has been minimized.

Show comment
Hide comment
@pedrofraca

pedrofraca Dec 2, 2015

What about to do in that way?

RequestBody caption = RequestBody.create(MediaType.parse("text/plain"), new String("caption"));

What about to do in that way?

RequestBody caption = RequestBody.create(MediaType.parse("text/plain"), new String("caption"));

@regisoliveira

This comment has been minimized.

Show comment
Hide comment
@regisoliveira

regisoliveira Dec 8, 2015

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

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

This comment has been minimized.

Show comment
Hide comment
@Bajranghudda1

Bajranghudda1 Dec 18, 2015

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<String, RequestBody> 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"]}}

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<String, RequestBody> 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"]}}

@pflammertsma

This comment has been minimized.

Show comment
Hide comment
@pflammertsma

pflammertsma Jan 18, 2016

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.

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.

@jcodesdotme

This comment has been minimized.

Show comment
Hide comment
@jcodesdotme

jcodesdotme May 11, 2016

"Solution2" in this answer solved my issue regarding to this.

"Solution2" in this answer solved my issue regarding to this.

@DenisShov

This comment has been minimized.

Show comment
Hide comment
@DenisShov

DenisShov Jun 7, 2016

Use this:
compile 'com.squareup.retrofit2:converter-scalars:2.0.1'
Check this:
http://stackoverflow.com/a/36907435/3844201

DenisShov commented Jun 7, 2016

Use this:
compile 'com.squareup.retrofit2:converter-scalars:2.0.1'
Check this:
http://stackoverflow.com/a/36907435/3844201

@NhamPhanDinh

This comment has been minimized.

Show comment
Hide comment
@NhamPhanDinh

NhamPhanDinh Jul 17, 2016

In version 2.1.0 still not work

In version 2.1.0 still not work

@Yazon2006

This comment has been minimized.

Show comment
Hide comment
@Yazon2006

Yazon2006 Dec 19, 2016

I'm using another one solution. 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 solution from regisdaniel but I think that it's little a bit more elegant.

Yazon2006 commented Dec 19, 2016

I'm using another one solution. 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 solution from regisdaniel but I think that it's little a bit more elegant.

@tpatterson

This comment has been minimized.

Show comment
Hide comment
@tpatterson

tpatterson Feb 12, 2018

Do like DenisShov said. Works great for me:

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

tpatterson commented Feb 12, 2018

Do like DenisShov said. Works great for me:

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment