【问题标题】:Serialize Query-Parameter in Retrofit在改造中序列化查询参数
【发布时间】:2015-02-13 22:47:42
【问题描述】:

想象以下请求:

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

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

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

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

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

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

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

【问题讨论】:

    标签: android gson retrofit


    【解决方案1】:

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

    方法 Javadoc:

    返回用于将类型转换为字符串的转换器,如果此工厂无法处理类型,则返回 null。这用于为@Field、@FieldMap 值、@Header、@HeaderMap、@Path、@Query 和@QueryMap 值指定的类型创建转换器。

    下面的示例委托给 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();
    

    【讨论】:

      【解决方案2】:

      我不认为它现在以某种很好的方式支持这一点。检查其中一位作者的答案: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;
        }
      }
      

      【讨论】:

      【解决方案3】:

      正如@Rolf 提到的,有一种方法可以设置customConverter.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);
              }
          }
      }
      

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

      【讨论】:

      • 请问使用 ThreadLocal 包装器的目的是什么?
      • 我怎样才能为 POST requestBody 做同样的事情?
      • @sbearben SimpleDateFormat 不是线程安全的。通过将每个实例包装在 ThreadLocal 中,可以保证每个线程都能看到自己的实例。
      【解决方案4】:

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

      【讨论】:

      • 感谢您的快速回复。我已经设置了转换器。问题是转换器仅在@ Body 内部使用。我需要在@Query 中进行序列化
      猜你喜欢
      • 2019-05-15
      • 2016-08-12
      • 2020-08-07
      • 1970-01-01
      • 1970-01-01
      • 2018-02-11
      • 1970-01-01
      • 2020-05-28
      相关资源
      最近更新 更多