Retrofit自定义反序列化器开发:响应信封封装处理需求
Hey Tomislav, this is a super common pain point when working with APIs that wrap every response in a unique top-level envelope. Let’s break down how to create a reusable custom deserializer to handle this cleanly, without writing boilerplate for each endpoint.
Step 1: Define a Generic Envelope Class
First, notice all your responses share the same inner structure: ErrorCode, ErrorDescription, and Result—only the top-level key changes (like GetUserResult or GetPicturesResult). We can abstract this into a generic class to reuse the structure across all endpoints:
public class ApiEnvelope<T> { private int ErrorCode; private String ErrorDescription; private T Result; // Getters and setters (or use Lombok to skip writing these) public int getErrorCode() { return ErrorCode; } public void setErrorCode(int errorCode) { ErrorCode = errorCode; } public String getErrorDescription() { return ErrorDescription; } public void setErrorDescription(String errorDescription) { ErrorDescription = errorDescription; } public T getResult() { return Result; } public void setResult(T result) { Result = result; } }
Step 2: Build the Custom JsonDeserializer
Next, we’ll write a deserializer that ignores the dynamic top-level key and parses the inner envelope into our generic ApiEnvelope class. Since each response only has one top-level key, we can safely grab the first entry in the JSON object:
import com.google.gson.*; import java.lang.reflect.Type; public class ApiEnvelopeDeserializer<T> implements JsonDeserializer<ApiEnvelope<T>> { @Override public ApiEnvelope<T> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { // Extract the single top-level key (e.g., "GetUserResult") and its value JsonObject topLevelObject = json.getAsJsonObject(); String topKey = topLevelObject.keySet().iterator().next(); JsonElement envelopeContent = topLevelObject.get(topKey); // Let Gson handle parsing the inner envelope into our generic class return context.deserialize(envelopeContent, typeOfT); } }
Step 3: Configure Gson and Retrofit
Now we need to tell Gson to use our custom deserializer for all ApiEnvelope instances, then wire this Gson setup into Retrofit:
// Create a Gson instance with our custom deserializer attached Gson gson = new GsonBuilder() .registerTypeAdapter(ApiEnvelope.class, new ApiEnvelopeDeserializer<>()) .create(); // Build your Retrofit client with this Gson converter Retrofit retrofit = new Retrofit.Builder() .baseUrl("YOUR_API_BASE_URL") .addConverterFactory(GsonConverterFactory.create(gson)) .build();
Step 4: Define Your Retrofit Interface
Finally, your API interface can return the generic ApiEnvelope with the actual data type you expect. No more worrying about the dynamic top-level key—Gson handles that under the hood:
public interface MyApiService { @POST("getUser") Call<ApiEnvelope<User>> getUser(); @POST("getPictures") Call<ApiEnvelope<List<Picture>>> getPictures(); @POST("getComment") Call<ApiEnvelope<Comment>> getComment(); }
Bonus: Simplify Error Handling (Optional)
If you want to avoid checking ErrorCode every time you make a call, you can add a helper method to extract the result or throw an error automatically:
public static <T> T getResultOrThrow(ApiEnvelope<T> envelope) throws ApiException { if (envelope.getErrorCode() != 0) { throw new ApiException(envelope.getErrorCode(), envelope.getErrorDescription()); } return envelope.getResult(); }
Then use it in your callback to clean up the code:
myApiService.getUser().enqueue(new Callback<ApiEnvelope<User>>() { @Override public void onResponse(Call<ApiEnvelope<User>> call, Response<ApiEnvelope<User>> response) { if (response.isSuccessful()) { try { User user = getResultOrThrow(response.body()); // Do something with the user data } catch (ApiException e) { // Handle API-specific errors (e.g., show error message) } } } @Override public void onFailure(Call<ApiEnvelope<User>> call, Throwable t) { // Handle network failures } });
This approach keeps your code DRY and avoids repeating envelope-parsing logic for every endpoint.
内容的提问来源于stack exchange,提问作者Tomislav29

