在 Retrofit 中序列化查询参数

Serialize Query-Parameter in Retrofit

想象一下以下请求:

@POST("/recipes/create")
void createRecipe(@Query("recipe") Recipe recipe, Callback<String> callback);

我想要 toJson(recipe) 但不幸的是我的请求只是为我的食谱调用 toString() 根本不起作用。

我可以覆盖 Recipe 中的 toString,但我宁愿有一个通用的解决方案。

我不能使用@Body,因为我需要指定我发送的内容(我需要"recipe=json(theRecipe)"。

我也无法更改序列化以添加 "recipe=",因为我不负责服务器。

目前我正在使用 QueryMap 地图,我在其中放入了一个序列化对象。虽然这可行,但在我看来这不是一个很好的解决方案。

我能以某种方式拦截改装适配器吗?

您可以使用 retrofit.RestAdapter.Builder().setConverter(...) 传递自定义 json 转换器。

我不认为它现在以某种好的方式支持它。检查其中一位作者的回答:https://github.com/square/retrofit/issues/291

该答案中建议的方法是创建一个覆盖 toString() 方法的自定义类型,因为 Retrofit 在内部使用 String.valueOf(value) 将查询或路径参数转换为字符串。

所以,你可以有这样的东西:

class Recipe {
  public int id;
  public String title;

  @Override
  public String toString() {
    // You can use a GSON serialization here if you want
    // This is not a robust implementation
    return Integer.toString(id) + "-" + title;
  }
}

注册自定义 Converter.Factory 覆盖解析参数时调用的 stringConverter 方法时,现在可以做到这一点。 @William 2 年前提到的 Github issue 自从添加支持后似乎没有更新。

方法 Javadoc:

Returns a Converter for converting type to a String, or null if type cannot be handled by this factory. This is used to create converters for types specified by @Field, @FieldMap values, @Header, @HeaderMap, @Path, @Query, and @QueryMap values.

下面的示例委托给 Gson,但同样可以将任何类型的转换应用于参数。

示例:GsonStringConverterFactory

class GsonStringConverterFactory
    extends Converter.Factory {

    private final transient Gson gson;

    GsonStringConverterFactory(final Gson gson) {

        this.gson = gson;
    }

    @Override
    public Converter<?, String> stringConverter(final Type type, final Annotation[] annotations, final Retrofit retrofit) {

        final TypeAdapter typeAdapter;
        typeAdapter = gson.getAdapter(TypeToken.get(type));

        return new StringConverter<>(typeAdapter);
    }

    private static class StringConverter<T>
            implements Converter<T, String> {

        private final TypeAdapter<T> typeAdapter;

        private StringConverter(final TypeAdapter<T> typeAdapter) {

            this.typeAdapter = typeAdapter;
        }

        @Override
        public String convert(final T value)
                throws IOException {

            /* This works in our case because parameters in this REST api are always some kind of scalar 
             * and the toJson method converts these to simple json types. */
            final String jsonValue;
            jsonValue = typeAdapter.toJson(value));

            if (jsonValue.startsWith("\"") && jsonValue.endsWith("\"") {
                /* Strip enclosing quotes for json String types */
                return jsonValue.substring(1, jsonValue.length() - 1);
            } else {
                return jsonValue;
            }
        }
    }
}

正在注册转换器:

要注册自定义转换器,您的 Retrofit 构建器可能如下所示:

    new Retrofit.Builder().baseUrl(BASE_URL)
                          .addConverterFactory(GsonConverterFactory.create(gson))
                          .addConverterFactory(new GsonStringConverterFactory(gson))
                          .build();

正如@Rolf 提到的,有一种方法可以设置自定义Converter.Factory 示例:

    public class QueryConverterFactory extends Converter.Factory {
    public static QueryConverterFactory create() {
        return new QueryConverterFactory();
    }

    private QueryConverterFactory() {
    }

    @Nullable
    @Override
    public Converter<?, String> stringConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
        if (type == Date.class) {
            return DateQueryConverter.INSTANCE;
        }
        return null;
    }

    private static final class DateQueryConverter implements Converter<Date, String> {
        static final DateQueryConverter INSTANCE = new DateQueryConverter();

        private static final ThreadLocal<DateFormat> DF = new ThreadLocal<DateFormat>() {
            @Override
            public DateFormat initialValue() {
                return new SimpleDateFormat("yyyy-MM-dd");
            }
        };

        @Override
        public String convert(Date date) {
            return DF.get().format(date);
        }
    }
}

您可以为自己的类型添加转换器。