preface

When a service interface is connected, the data type of the data field is different from that of the failed state.

The interface has been online for a long time and cannot be changed. Therefore, the interface must be compatible with the client

When the request succeeds, data is of type T (some custom data type)

{
	"code" : 0."data": {}}Copy the code

When the request fails, data is a String

{
	"code" : 1000."data" : "Error message"
}
Copy the code

A data field corresponds to two data types and can only be defined as Object. When the request succeeds, Gson resolves the data as LinkedTreeMap by default because it is defined as Object. When using the map, you need to use key to select the value, which is extremely inconvenient.

A different approach

Since it is extremely inconvenient to define data as Object, data can only be defined as T. However, parsing will throw an exception when the request fails (because data is a String).

Is it possible to parse the data to type T when the request succeeds, but otherwise leave the data unparsed or move the value of the data to another field (such as the message field)? Personally, I prefer the latter idea. That is, can you change the JSON for failed requests to the following format?

{
	"code" : 1000."data" : null."message" : "Error message"
}
Copy the code

Implementation scheme

With that in mind, I came up with two approaches

  1. Custom OkHttp Interceptor
  2. Custom Gson TypeAdapter

Custom Interceptor

Just a quick way to do it

  • Only certain interfaces are intercepted
  • Read and parse JSON from Response
  • According to thecodeDetermines whether the request was successful
  • If the request fails, a new JSON is generated and a new Response is returned

This approach feels tedious to use and requires additional parsing of JSON. See this article for details on how Android gracefully handles data returned in the background

Custom TypeAdapter

How do I modify the JSON structure using TypeAdapter? After some searching, I found the answer I was looking for on StackOverflow. Based on the implementation of the above answer, I modified it slightly

public class CustomizedTypeAdapterFactory<C> implements TypeAdapterFactory {
    private final Class<C> mClass;

    public CustomizedTypeAdapterFactory(Class<C> cls) {
        this.mClass = cls;
    }

    @Override
    public final <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        //noinspection unchecked
        return type.getRawType() == mClass
                ? (TypeAdapter<T>) createAdapter(gson, (TypeToken<C>) type)
                : null;
    }

    private TypeAdapter<C> createAdapter(Gson gson, TypeToken<C> type) {
        // Get the TypeAdapter corresponding to this type in Gson
        final TypeAdapter<C> delegate = gson.getDelegateAdapter(this, type);
        return new TypeAdapter<C>() {
            @Override
            public void write(JsonWriter out, C value) throws IOException {
                // Convert value to JSON tree and serialize it
                JsonElement tree = delegate.toJsonTree(value);
                onWrite(value, tree);
                Streams.write(tree, out);
            }

            @Override
            public C read(JsonReader in) throws IOException {
                // Deserialize the read JSON string from the JSON tree
                JsonElement tree = Streams.parse(in);
                onRead(tree);
                returndelegate.fromJsonTree(tree); }}; }protected void onWrite(C value, JsonElement json) {}protected void onRead(JsonElement json) {}}Copy the code

Inherit the TypeAdapterFactory and override the onWrite and onRead methods to modify JSON before serialization and deserialization.

public class BaseResponse<T> {
    private int code;
    private T data;
    private String message; // Add a field. }public class ResponseTypeAdapterFactory extends CustomizedTypeAdapterFactory<BaseResponse> {
    private static final String CODE_OK = "0";
    public static final String DATA = "data";
    public static final String CODE = "code";
    public static final String MSG = "message";

    public ResponseTypeAdapterFactory(a) {
        super(BaseResponse.class);
    }

    @Override
    protected void onRead(JsonElement json) {
        JsonObject jsonObject = json.getAsJsonObject();
        JsonElement code = jsonObject.get(CODE);// Get the code field
        // Call successful, no processing
        if (code == null| |! code.isJsonPrimitive() || CODE_OK.equals(code.getAsString())) {return;
        }
        JsonElement data = jsonObject.get(DATA);
        if(data ! =null) {
            // Move the value of data to messagejsonObject.remove(DATA); jsonObject.add(MSG, data); }}}Copy the code

A custom ResponseTypeAdapterFactory, dedicated to parse BaseResponse this type. Because only the JSON structure of the deserialization needs to be changed, only the onRead method is overridden. The logic of the onRead method is also simple. Simply get the code field and determine if the request failed. If so, move the value of data to message.

Finally, don’t forget to ResponseTypeAdapterFactory registered Gson, confused and configuration rules!!!!!

	Gson gson = new GsonBuilder()
	        .registerTypeAdapterFactory(new ResponseTypeAdapterFactory())
	        .create();
Copy the code

conclusion

Using Gson TypeAdapter, the function of modifying JSON structure according to code is realized, so as to achieve the effect of dynamic parsing JSON. This scheme can be used to solve the problem of inconsistent JSON structure. As an aside, if you encounter inconsistent JSON structure, please be sure to communicate with your background colleagues first and then again!!

reference

  • Gson custom seralizer for one variable (of many) in an object using TypeAdapter
  • Android gracefully processes the data returned in the background
  • Do you really know how to use Gson? Gson User Guide (4)