Multiple converters with Retrofit 2

后端 未结 4 1005
萌比男神i
萌比男神i 2020-12-13 10:30

I have a HATEOAS (HAL) REST service and managed to talk to it with the code below (using halarious as a conversion engine) but when I try to merge the converters (stal

相关标签:
4条回答
  • 2020-12-13 10:53

    You need to return null from the Converter.Factory if the type does not match. Keep the Class<?> around in a field to compare it against.

    @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 Type that is passed in.

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

    0 讨论(0)
  • 2020-12-13 10:56

    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:

    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:

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

    to

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

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

    0 讨论(0)
  • 2020-12-13 11:06
    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);
        }
    }
    
    0 讨论(0)
  • 2020-12-13 11:09

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

    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 GenericArrayType with GenericComponentType as CustomElement.class and for second method type will be just CustomElement.class

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

    0 讨论(0)
提交回复
热议问题