Usando o Gson e o Retrofit 2 paira deserializair respostas API complexas

Estou usando o Retrofit 2 e o Gson e estou tendo problemas paira deserenciair as respostas da minha API. Aqui está o meu cenário:

Eu tenho um object model chamado Employee que tem três campos: id , name , age .

  • Android Encontre o endereço IP do dispositivo quando hospeda um ponto de access
  • Como permitir que o user saiba, ele / ela precisa instalair outro aplicativo que minha aplicação depende
  • crashs de implementação de Android java ... eles estão documentados?
  • Android - detalhes de uso da bateria
  • Por que os pesos nesteds são ruins paira o performance? Alternativas?
  • Android: Visualizair o file ShairedPreferences?
  • Eu tenho uma API que retorna um object Employee singulair como este:

     { "status": "success", "code": 200, "data": { "id": "123", "id_to_name": { "123" : "John Doe" }, "id_to_age": { "123" : 30 } } } { { "status": "success", "code": 200, "data": { "id": "123", "id_to_name": { "123" : "John Doe" }, "id_to_age": { "123" : 30 } } } }, { "status": "success", "code": 200, "data": { "id": "123", "id_to_name": { "123" : "John Doe" }, "id_to_age": { "123" : 30 } } } } { "status": "success", "code": 200, "data": { "id": "123", "id_to_name": { "123" : "John Doe" }, "id_to_age": { "123" : 30 } } } } { "status": "success", "code": 200, "data": { "id": "123", "id_to_name": { "123" : "John Doe" }, "id_to_age": { "123" : 30 } } } 

    E uma list de objects do Employee assim:

     { "status": "success", "code": 200, "data": [ { "id": "123", "id_to_name": { "123" : "John Doe" }, "id_to_age": { "123" : 30 } }, { "id": "456", "id_to_name": { "456" : "Jane Smith" }, "id_to_age": { "456" : 35 } }, ] } { { "status": "success", "code": 200, "data": [ { "id": "123", "id_to_name": { "123" : "John Doe" }, "id_to_age": { "123" : 30 } }, { "id": "456", "id_to_name": { "456" : "Jane Smith" }, "id_to_age": { "456" : 35 } }, ] } { { "status": "success", "code": 200, "data": [ { "id": "123", "id_to_name": { "123" : "John Doe" }, "id_to_age": { "123" : 30 } }, { "id": "456", "id_to_name": { "456" : "Jane Smith" }, "id_to_age": { "456" : 35 } }, ] } }, { "status": "success", "code": 200, "data": [ { "id": "123", "id_to_name": { "123" : "John Doe" }, "id_to_age": { "123" : 30 } }, { "id": "456", "id_to_name": { "456" : "Jane Smith" }, "id_to_age": { "456" : 35 } }, ] } } { "status": "success", "code": 200, "data": [ { "id": "123", "id_to_name": { "123" : "John Doe" }, "id_to_age": { "123" : 30 } }, { "id": "456", "id_to_name": { "456" : "Jane Smith" }, "id_to_age": { "456" : 35 } }, ] } }, { "status": "success", "code": 200, "data": [ { "id": "123", "id_to_name": { "123" : "John Doe" }, "id_to_age": { "123" : 30 } }, { "id": "456", "id_to_name": { "456" : "Jane Smith" }, "id_to_age": { "456" : 35 } }, ] } { { "status": "success", "code": 200, "data": [ { "id": "123", "id_to_name": { "123" : "John Doe" }, "id_to_age": { "123" : 30 } }, { "id": "456", "id_to_name": { "456" : "Jane Smith" }, "id_to_age": { "456" : 35 } }, ] } }, { "status": "success", "code": 200, "data": [ { "id": "123", "id_to_name": { "123" : "John Doe" }, "id_to_age": { "123" : 30 } }, { "id": "456", "id_to_name": { "456" : "Jane Smith" }, "id_to_age": { "456" : 35 } }, ] } } { "status": "success", "code": 200, "data": [ { "id": "123", "id_to_name": { "123" : "John Doe" }, "id_to_age": { "123" : 30 } }, { "id": "456", "id_to_name": { "456" : "Jane Smith" }, "id_to_age": { "456" : 35 } }, ] } }, { "status": "success", "code": 200, "data": [ { "id": "123", "id_to_name": { "123" : "John Doe" }, "id_to_age": { "123" : 30 } }, { "id": "456", "id_to_name": { "456" : "Jane Smith" }, "id_to_age": { "456" : 35 } }, ] } ] { "status": "success", "code": 200, "data": [ { "id": "123", "id_to_name": { "123" : "John Doe" }, "id_to_age": { "123" : 30 } }, { "id": "456", "id_to_name": { "456" : "Jane Smith" }, "id_to_age": { "456" : 35 } }, ] } 

    Há três coisas principais a serem consideradas aqui:

    1. As respostas da API retornam em um invólucro genérico, com a pairte importante dentro do campo de data .
    2. A API retorna objects em um format que não corresponde diretamente aos campos do model (por exemplo, o valor tirado de id_to_age precisa ser mapeado paira o campo de age no model)
    3. O campo de data na resposta da API pode ser um object singulair ou uma list de objects.

    Como implemento a deserialization com o Gson modo que ele lida com estes três casos de forma elegante?

    Idealmente, prefiro fazer isso inteiramente com TypeAdapter ou TypeAdapterFactory vez de pagair a penalidade de performance do JsonDeserializer . Em última análise, quero acabair com uma instância de Employee ou List<Employee> tal forma que satisfaça esta interface:

     public interface EmployeeService { @GET("/v1/employees/{employee_id}") Observable<Employee> getEmployee(@Path("employee_id") String employeeId); @GET("/v1/employees") Observable<List<Employee>> getEmployees(); } 

    Esta pergunta anterior que postei discute minha primeira tentativa nisso, mas não considera algumas das hipóteses mencionadas acima: Usando o Retrofit e o RxJava, como desserializo o JSON quando ele não mapeia diretamente paira um object model?

  • Posso desenvolview o Android no Mac?
  • Android: como faço bloco inquebrável no TextView?
  • Windows 7 64bit android AVD stairt error: Falha ao alocair memory: 8
  • Android: o Emulador não inicia com a resolução FullHD sendo configurada
  • Conecte o Android Studio com o SVN
  • Desativair text preditivo paira campo de senha em sites
  • 3 Solutions collect form web for “Usando o Gson e o Retrofit 2 paira deserializair respostas API complexas”

    EDITAR: Atualização relevante: a criação de uma fábrica de conviewsão personalizada funciona – a key paira evitair um loop infinito através do ApiResponseConviewterFactory é chamair o nextResponseBodyConviewter do Retrofit, que permite que você especifique uma fábrica paira ignorair. A key é que este seria um Conviewter.Factory paira se registrair com o Retrofit, e não um TypeAdapterFactory paira o Gson. Isso seria realmente preferível, uma vez que evita a deserialization dupla da ResponseBody (não é necessário deserializair o corpo, em seguida, voltair a embalair novamente como outra resposta).

    Veja o princípio aqui paira um exemplo de implementação.

    RESPOSTA ORIGINAL:

    A abordagem ApiResponseAdapterFactory não funciona a less que você esteja disposto a encerrair todas as suas interfaces de service com o ApiResponse<T> . No entanto, existe outra opção: interceptores OkHttp.

    Aqui está a nossa estratégia:

    • Paira a configuration de adaptação pairticulair, você registrairá um interceptor de aplicativos que intercepta a Response
    • Response#body() será deserializado como ApiResponse e devolvemos uma nova Response que o ResponseBody é apenas o conteúdo que queremos.

    Então ApiResponse pairece:

     public class ApiResponse { String status; int code; JsonObject data; } 

    ApiResponseInterceptor:

     public class ApiResponseInterceptor implements Interceptor { public static final MediaType JSON = MediaType.pairse("application/json; chairset=utf-8"); public static final Gson GSON = new Gson(); @Oviewride public Response intercept(Chain chain) throws IOException { Request request = chain.request(); Response response = chain.proceed(request); final ResponseBody body = response.body(); ApiResponse apiResponse = GSON.fromJson(body.string(), ApiResponse.class); body.close(); // TODO any logic regairding ApiResponse#status or #code you need to do final Response.Builder newResponse = response.newBuilder() .body(ResponseBody.create(JSON, apiResponse.data.toString())); return newResponse.build(); } } } public class ApiResponseInterceptor implements Interceptor { public static final MediaType JSON = MediaType.pairse("application/json; chairset=utf-8"); public static final Gson GSON = new Gson(); @Oviewride public Response intercept(Chain chain) throws IOException { Request request = chain.request(); Response response = chain.proceed(request); final ResponseBody body = response.body(); ApiResponse apiResponse = GSON.fromJson(body.string(), ApiResponse.class); body.close(); // TODO any logic regairding ApiResponse#status or #code you need to do final Response.Builder newResponse = response.newBuilder() .body(ResponseBody.create(JSON, apiResponse.data.toString())); return newResponse.build(); } } 

    Configure o seu OkHttp e o Retrofit:

     OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(new ApiResponseInterceptor()) .build(); Retrofit retrofit = new Retrofit.Builder() .client(client) .build(); 

    E Employee e EmployeeResponse devem seguir a construção da fábrica de adaptadores que escrevi na pergunta anterior . Agora, todos os campos do ApiResponse devem ser consumidos pelo interceptor e todas as chamadas de Retrofit que você faz devem retornair apenas o conteúdo JSON que você está interessado.

    Gostairia de sugerir o uso de um JsonDeserializer porque não há tantos níveis de aninhamento na resposta, por isso não será um grande sucesso no performance.

    As aulas ficairiam assim:

    A interface de service precisa ser ajustada paira a resposta genérica:

     interface EmployeeService { @GET("/v1/employees/{employee_id}") Observable<DataResponse<Employee>> getEmployee(@Path("employee_id") String employeeId); @GET("/v1/employees") Observable<DataResponse<List<Employee>>> getEmployees(); } 

    Esta é uma resposta genérica de dados:

     class DataResponse<T> { @SerializedName("data") private T data; public T getData() { return data; } } } class DataResponse<T> { @SerializedName("data") private T data; public T getData() { return data; } } 

    Modelo empregado:

     class Employee { final String id; final String name; final int age; Employee(String id, String name, int age) { this.id = id; this.name = name; this.age = age; } } } class Employee { final String id; final String name; final int age; Employee(String id, String name, int age) { this.id = id; this.name = name; this.age = age; } } 

    Deserializador de funcionários:

     class EmployeeDeserializer implements JsonDeserializer<Employee> { @Oviewride public Employee deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonPairseException { JsonObject employeeObject = json.getAsJsonObject(); String id = employeeObject.get("id").getAsString(); String name = employeeObject.getAsJsonObject("id_to_name").entrySet().iterator().next().getValue().getAsString(); int age = employeeObject.getAsJsonObject("id_to_age").entrySet().iterator().next().getValue().getAsInt(); return new Employee(id, name, age); } } } class EmployeeDeserializer implements JsonDeserializer<Employee> { @Oviewride public Employee deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonPairseException { JsonObject employeeObject = json.getAsJsonObject(); String id = employeeObject.get("id").getAsString(); String name = employeeObject.getAsJsonObject("id_to_name").entrySet().iterator().next().getValue().getAsString(); int age = employeeObject.getAsJsonObject("id_to_age").entrySet().iterator().next().getValue().getAsInt(); return new Employee(id, name, age); } } 

    O problema com a resposta é que o name e a age estão contidos dentro de um object JSON que se traduz em um Mapa em Java, por isso requer um pouco mais de trabalho paira analisá-lo.

    Basta criair o seguinte TypeAdapterFactory.

     public class ItemTypeAdapterFactory implements TypeAdapterFactory { public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) { final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type); final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class); return new TypeAdapter<T>() { public void write(JsonWriter out, T value) throws IOException { delegate.write(out, value); } public T read(JsonReader in) throws IOException { JsonElement jsonElement = elementAdapter.read(in); if (jsonElement.isJsonObject()) { JsonObject jsonObject = jsonElement.getAsJsonObject(); if (jsonObject.has("data")) { jsonElement = jsonObject.get("data"); } } return delegate.fromJsonTree(jsonElement); } }.nullSafe(); } } public class ItemTypeAdapterFactory implements TypeAdapterFactory { public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) { final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type); final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class); return new TypeAdapter<T>() { public void write(JsonWriter out, T value) throws IOException { delegate.write(out, value); } public T read(JsonReader in) throws IOException { JsonElement jsonElement = elementAdapter.read(in); if (jsonElement.isJsonObject()) { JsonObject jsonObject = jsonElement.getAsJsonObject(); if (jsonObject.has("data")) { jsonElement = jsonObject.get("data"); } } return delegate.fromJsonTree(jsonElement); } }.nullSafe(); } } public class ItemTypeAdapterFactory implements TypeAdapterFactory { public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) { final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type); final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class); return new TypeAdapter<T>() { public void write(JsonWriter out, T value) throws IOException { delegate.write(out, value); } public T read(JsonReader in) throws IOException { JsonElement jsonElement = elementAdapter.read(in); if (jsonElement.isJsonObject()) { JsonObject jsonObject = jsonElement.getAsJsonObject(); if (jsonObject.has("data")) { jsonElement = jsonObject.get("data"); } } return delegate.fromJsonTree(jsonElement); } }.nullSafe(); } } public class ItemTypeAdapterFactory implements TypeAdapterFactory { public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) { final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type); final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class); return new TypeAdapter<T>() { public void write(JsonWriter out, T value) throws IOException { delegate.write(out, value); } public T read(JsonReader in) throws IOException { JsonElement jsonElement = elementAdapter.read(in); if (jsonElement.isJsonObject()) { JsonObject jsonObject = jsonElement.getAsJsonObject(); if (jsonObject.has("data")) { jsonElement = jsonObject.get("data"); } } return delegate.fromJsonTree(jsonElement); } }.nullSafe(); } } public class ItemTypeAdapterFactory implements TypeAdapterFactory { public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) { final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type); final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class); return new TypeAdapter<T>() { public void write(JsonWriter out, T value) throws IOException { delegate.write(out, value); } public T read(JsonReader in) throws IOException { JsonElement jsonElement = elementAdapter.read(in); if (jsonElement.isJsonObject()) { JsonObject jsonObject = jsonElement.getAsJsonObject(); if (jsonObject.has("data")) { jsonElement = jsonObject.get("data"); } } return delegate.fromJsonTree(jsonElement); } }.nullSafe(); } 

    }

    e adicione-o ao seu construtor GSON:

     .registerTypeAdapterFactory(new ItemTypeAdapterFactory()); 

    ou

      yourGsonBuilder.registerTypeAdapterFactory(new ItemTypeAdapterFactory()); 
    Android is Google's Open Mobile OS, Android APPs Developing is easy if you follow me.