java 带有 Retrofit 2 的多个转换器

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/33404734/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 21:37:30  来源:igfitidea点击:

Multiple converters with Retrofit 2

javagenericsgsonretrofithateoas

提问by Gabor

I have a HATEOAS (HAL)REST service and managed to talk to it with the code below (using halariousas a conversion engine) but when I try to merge the converters(stalloneand stallone2), the app will always pick up the first converter, instead of the one that is appropriate for the response type which of course leads to an error.

我有一个HATEOAS (HAL)REST 服务,并设法使用下面的代码(使用halarious作为转换引擎)与它对话,但是当我尝试合并转换器stallonestallone2)时,应用程序将始终选择第一个转换器,而不是适合响应类型的一种,这当然会导致错误。

How could I avoid duplicate retrofits that are only different in a small type detail?

我怎样才能避免重复的改造,这些改造只是在小类型细节上有所不同?

public interface Stallone {
   @GET("/discovery")
   Call<DiscoveryResponse> discover();
   @POST()
   Call<LoginResponse> login(@Url String url, @Body LoginRequest secret);
}
   public static void main(String... args) throws IOException {
      // Initialize a converter for each supported (return) type
      final Stallone stallone = new Retrofit.Builder()
         .baseUrl(BASE)
         .addConverterFactory(HALConverterFactory.create(DiscoveryResponse.class))
         .build().create(Stallone.class);
      final Stallone stallone2 = new Retrofit.Builder()
         .baseUrl(BASE)
         .addConverterFactory(HALConverterFactory.create(LoginResponse.class))
         .build().create(Stallone.class);

      // Follow the HAL links
      Response<DiscoveryResponse> response = stallone.discover().execute();
      System.out.println(response.code() + " " + response.message());
      Assert.assertNotNull(response.body());
      String loginPath = response.body().getLogin();
      Assert.assertEquals(loginPath, "/login");

      // Follow another link
      if (loginPath.startsWith("/"))
         loginPath = loginPath.substring(1);
      Response<LoginResponse> response2 =
         stallone2.login(loginPath,
                        new LoginRequest(AUTH0TOKEN, null)).execute();
      System.out.println(response2.code() + " " + response2.message());
      Assert.assertNotNull(response2.body());

      String setupPath = response2.body().getSetup();
      Assert.assertEquals(setupPath, "/setup");

      System.out.println("All OK!");
   }
public final class HALConverterFactory extends Converter.Factory {

   private final Gson gson;

   public static HALConverterFactory create(Class<?> type) {
      return new HALConverterFactory(type);
   }

   private HALConverterFactory(Class<?> type) {
      if (!HalResource.class.isAssignableFrom(type))
         throw new NullPointerException("Type should be a subclass of HalResource");
      GsonBuilder builder = new GsonBuilder();
      builder.registerTypeAdapter(HalResource.class, new HalSerializer());
      builder.registerTypeAdapter(HalResource.class, new HalDeserializer(type));
      builder.setExclusionStrategies(new HalExclusionStrategy());
      this.gson = builder.create();
   }

   @Override
   public Converter<ResponseBody, ?> fromResponseBody(Type type, Annotation[] annotations) {
      return new HALResponseBodyConverter<>(gson);
   }

   @Override public Converter<?, RequestBody> toRequestBody(Type type, Annotation[] annotations) {
      return new GsonRequestBodyConverter<>(gson, type);
   }
}
final class HALResponseBodyConverter<T extends HalResource>
   implements Converter<ResponseBody, T> {
   private final Gson gson;

   HALResponseBodyConverter(Gson gson) {
      this.gson = gson;
   }

   @Override public T convert(ResponseBody value) throws IOException {
      BufferedSource source = value.source();
      try {
         String s = source.readString(Charset.forName("UTF-8"));
         return (T) gson.fromJson(s, HalResource.class);
      } catch (Exception e) {
         throw new RuntimeException(e);
      } finally {
         closeQuietly(source);
      }
   }

   private static void closeQuietly(Closeable closeable) {
      if (closeable == null) return;
      try {
         closeable.close();
      } catch (IOException ignored) {
      }
   }
}

Again, the problem is thatwhen you try to shorten the above like this:

同样,问题是当您尝试像这样缩短上述内容时:

  final Stallone stallone = new Retrofit.Builder()
     .baseUrl(BASE)
.addConverterFactory(HALConverterFactory.create(DiscoveryResponse.class))
     .addConverterFactory(HALConverterFactory.create(LoginResponse.class))
     .build().create(Stallone.class);

you'll get an exception at the Response<LoginResponse> response2 = ...line:

你会在这一Response<LoginResponse> response2 = ...行得到一个例外:

Exception in thread "main" java.lang.ClassCastException: com.example.retrofit.DiscoveryResponse cannot be cast to com.example.retrofit.LoginResponse

线程“main”中的异常 java.lang.ClassCastException:com.example.retrofit.DiscoveryResponse 无法转换为 com.example.retrofit.LoginResponse

回答by Jake Wharton

You need to return nullfrom the Converter.Factoryif the type does not match. Keep the Class<?>around in a field to compare it against.

你需要返回nullConverter.Factory如果类型不匹配。将Class<?>周围保持在一个字段中以进行比较。

@Override
public Converter<ResponseBody, ?> fromResponseBody(Type type, Annotation[] annotations) {
  if (!this.type.equals(type)) {
    return null;
  }
  return new HALResponseBodyConverter<>(gson);
}

This will allow multiple instances to be used because each only applies to its own type.

这将允许使用多个实例,因为每个实例仅适用于其自己的类型。

That said, however, you can probably get away with only using a single converter and pulling the class from the Typethat is passed in.

尽管如此,您可能只使用一个转换器并从Type传入的类中提取类就可以逃脱惩罚。

@Override
public Converter<ResponseBody, ?> fromResponseBody(Type type, Annotation[] annotations) {
  if (!HALResponse.class.isAssignableFrom(type)) {
    return null;
  }
  // TODO create converter with `type` now that you know what it is...
}

You can look at the Wire converter in the repo which does this for a full example.

你可以查看 repo 中的 Wire 转换器,它是一个完整的例子。

回答by ???

package ch.halarious.core;

import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * Custom Hal Deserializer  
 *
 * @author jaren
 */
public class CustomHalDeserializer extends HalDeserializer {

    /**
     * Intialisiert ein HalDeserializer-Objekt
     *
     * @param targetType Typ, den wir eigentlich deserialisieren sollten
     */
    public CustomHalDeserializer(Class<?> targetType) {
        super(targetType);
    }

    class CustomArrayList extends ArrayList implements HalResource{}

    public HalResource deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context, Class<?> targetType) throws JsonParseException {
        // Es handelt sich um ein JSON-Objekt.
        JsonObject jsonObject = json.getAsJsonObject();
        JsonObject embeddedRoot = jsonObject.getAsJsonObject(HalConstants.EMBEDDED_ROOT);

        if(embeddedRoot != null){
            Set<Map.Entry<String, JsonElement>> set = embeddedRoot.entrySet();
            if(set.toArray().length == 1){
                JsonArray ja = embeddedRoot.getAsJsonArray(set.iterator().next().getKey());
                if(ja.isJsonArray()) {
                    CustomArrayList arrayResult = new CustomArrayList();
                    Iterator<JsonElement> i = ja.iterator();
                    while(i.hasNext()){
                        JsonElement je = i.next();
                        arrayResult.add(super.deserialize(je, typeOfT, context, targetType));
                    }
                    return arrayResult;
                }
            }
        }

        return super.deserialize(json, typeOfT, context, targetType);
    }
}

回答by Orest

I did almost the same as @jake-wharton said in https://stackoverflow.com/a/33459073/2055854but added some changes:

我和@jake-wharton 在https://stackoverflow.com/a/33459073/2055854 中所说的几乎一样,但添加了一些更改:

public class GenericConverterFactory<T> extends Converter.Factory {

    private final Class<T> clazz;

    public static GenericConverterFactory create(Class<T> clazz) {
        return new GenericConverterFactory(clazz);
    }

    private GenericConverterFactory(Class<T> clazz) {
        this.clazz = clazz;
    }

    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
        if (!isNeededType(type)) {
            return null;
        }

        // some converter that knows how to return your specific type T
        return new GenericConverter(clazz);
    }

    private boolean isNeededType(Type type) {
        if(type instanceof GenericArrayType) {
            // if type is array we should check if it has the same component as our factory clazz
            // if our factory clazz is not array getComponentType will return null
            return ((GenericArrayType) type).getGenericComponentType().equals(clazz.getComponentType());
        } else if(clazz.getComponentType() == null) {
            // if factory clazz is not array and type is not array too
            // type is just a Class<?> and we should check if they are equal
            return clazz.equals(type);
        } else {
            // otherwise our clazz is array and type is not
            return false;
        }
    }
}

Type is coming from retrofit interface for example if you have:

类型来自改造接口,例如,如果您有:

public interface SomeApi{
     @GET("customelement")
     CustomElement[] getCustomElements();
     @GET("customelement/{id}")
     CustomElement getCustomElement(@Path("id") int id);
}

For method getCustomElements()type will be GenericArrayTypewith GenericComponentTypeas CustomElement.classand for second method type will be just CustomElement.class

对于方法getCustomElements()类型将GenericArrayType使用GenericComponentTypeasCustomElement.class而对于第二个方法类型将只是CustomElement.class

Not sure whether it is the best solution but for me it works. Hope it helps.

不确定这是否是最好的解决方案,但对我来说它有效。希望能帮助到你。

回答by f4b

In my case I needed to serialize and deserialize only one class to XML. For everything else I needed Json. So I registered my adapters like this:

就我而言,我只需要将一个类序列化和反序列化为 XML。对于其他一切,我需要 Json。所以我像这样注册了我的适配器:

retrofit = new Retrofit.Builder()
                .baseUrl(BuildConfig.BASE_URL)
                .addConverterFactory(EditUserXmlConverterFactory.create())
                .addConverterFactory(GsonConverterFactory.create(createGson()))
                .client(httpClient.build())
                .build();

since I could not extend SimpleXmlConverterFactory(unfortunately) I had to use my own class and change the following line:

由于我无法扩展SimpleXmlConverterFactory(不幸的是)我不得不使用我自己的类并更改以下行:

if (!(type instanceof Class)) return null;

to

if (type != NeedToBeXML.class) return null;

This way only responses and requests of type NeedToBeXMLare converted to XML - and everything else JSON.

这样,只有类型的响应和请求NeedToBeXML才能转换为 XML - 以及其他所有类型的JSON。