改造2 - 将@Body中的模型作为FormData发送,而不是JSON

时间:2016-10-19 15:39:58

标签: android json urlencode retrofit2

我正在尝试将以下java模型发送为表单编码正文 WITHOUT 包装{}。我已经尝试了一些我能找到的模型 NOT 作为JSON,但使用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
);

如果有帮助,这是我们的设置

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

2 个答案:

答案 0 :(得分:0)

如果我没弄错的话

  

对于application / x-www-form-urlencoded,HTTP消息的正文   发送到服务器本质上是一个巨大的查询字符串 - 名称/值   对由&符号(&amp;)分隔,名称与之分开   等于符号(=)的值。

How to send Form data in retrofit2 android

答案 1 :(得分:0)

原来我必须创建自己的键值对转换器,它扩展了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);
    }
}

这是我们的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();
    }
}

在您的“改造”构建器中添加.addConverterFactory(RF2_KeyValuePairConverter.create(gson)),这会将您的回复转换为键/值对