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
- Custom OkHttp Interceptor
- 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 the
code
Determines 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)