2015-10-29 9 views
14

У меня есть HATEOAS (HAL) REST службы и удалось поговорить с ним с ниже код (используя как двигатель преобразования), но когда я пытаюсь merge the converters (stallone и stallone2), приложение всегда будет забрать первый конвертер, а не тот, который подходит для типа ответа, который, конечно, приводит к ошибке.Несколько преобразователей с Модернизированный 2

Как я могу избежать повторных модификаций, которые отличаются друг от друга только мелким шрифтом?

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) { 
     } 
    } 
} 

Опять же, проблема заключается в том, что при попытке сократить выше, как это:

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

вы получите исключение на Response<LoginResponse> response2 = ... линия:

Исключение в потоке "главный" java.lang.ClassCastException: com.example.retrofit.DiscoveryResponse не может быть приведен к com.example.retrofit.LoginResponse

+1

Что такое 'GsonRequestBodyConverter'? – naXa

ответ

18

Вы должны вернуть null из Converter.Factory, если тип не совпадает. Держите Class<?> в поле, чтобы сравнить его.

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

Это позволит использовать несколько экземпляров, поскольку каждый из них применим только к его собственному типу.

При этом, однако, вы, вероятно, может сойти с рук только с помощью одного преобразователя и вытягивать класс от 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... 
} 

Вы можете посмотреть на Wire конвертер в репо, который делает это для полного примера.

0
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

Я сделал почти такой же, как @ 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; 
     } 
    } 
} 

Тип поступает из интерфейса дооснащения, например, если у вас есть:

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

Для метода getCustomElements() тип будет GenericArrayType с GenericComponentType как CustomElement.class, а для второго типа метода будет только CustomElement.class

Не уверен, что это лучшее решение, но для меня это работает. Надеюсь, поможет.

0

В моем случае мне нужно было сериализовать и десериализовать только один класс в XML. Для всего остального мне нужен был Джон.Так что я зарегистрировал свои адаптеры, как это:

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

, так как я не мог продлить SimpleXmlConverterFactory (к сожалению) мне пришлось использовать свой собственный класс и измените следующую строку:

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

к

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

Таким образом, только ответы и запросы типа NeedToBeXML преобразуются в XML - и все остальное JSON.

Смежные вопросы