headers example ejemplo java generics gson retrofit hateoas

java - example - retrofit post json



Convertidores mĂșltiples con Retrofit 2 (4)

Tengo un servicio REST HATEOAS (HAL) y halarious hablar con él con el código a continuación (usando el motor de conversión), pero cuando intento fusionar los convertidores ( stallone y stallone2 ), la aplicación siempre recogerá el primer convertidor. , en lugar del apropiado para el tipo de respuesta que, por supuesto, conduce a un error.

¿Cómo podría evitar las modificaciones duplicadas que solo son diferentes en un detalle pequeño?

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

De nuevo, el problema es que cuando intentas acortar lo anterior de esta manera:

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

obtendrá una excepción en la línea Response<LoginResponse> response2 = ... :

Excepción en el hilo "main" java.lang.ClassCastException: com.example.retrofit.DiscoveryResponse no se puede convertir a com.example.retrofit.LoginResponse


Debe devolver un null desde Converter.Factory si el tipo no coincide. Mantenga la Class<?> Alrededor de un campo para compararla.

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

Esto permitirá que se utilicen múltiples instancias porque cada una solo se aplica a su propio tipo.

Dicho esto, sin embargo, probablemente pueda salirse con la suya utilizando solo un único convertidor y extrayendo la clase del Type que se pasa.

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

Puede ver el convertidor de alambre en el repositorio que hace esto para un ejemplo completo.


En mi caso, necesitaba serializar y deserializar solo una clase a XML. Para todo lo demás necesitaba a Json. Así que registré mis adaptadores así:

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

ya que no podía extender SimpleXmlConverterFactory (desafortunadamente) tuve que usar mi propia clase y cambiar la siguiente línea:

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

a

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

De esta manera solo las respuestas y solicitudes de tipo NeedToBeXML se convierten a XML, y todo lo demás JSON.


Hice casi lo mismo que @ jake-wharton dijo en https://.com/a/33459073/2055854 pero añadí algunos cambios:

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; } } }

El tipo proviene de la interfaz de actualización, por ejemplo, si tiene:

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

Para el método getCustomElements() tipo será GenericArrayType con GenericComponentType como CustomElement.class y para el segundo tipo de método será solo CustomElement.class

No estoy seguro de si es la mejor solución pero para mí funciona. Espero eso ayude.


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