Android Gson Details (IV)

Type Adapter

Type Adapter is an abstract class provided by Gson since version 2.0 (source annotations say 2.1) to take over some type of serialization and deserialization process. It contains two annotation methods, write(JsonWriter,T) and read(JsonReader), and other methods are final methods and finally invoke these two abstract methods.

public abstract class TypeAdapter<T> {
    public abstract void write(JsonWriter out, T value) throws IOException;
    public abstract T read(JsonReader in) throws IOException;
    //Other final methods are not posted, including `toJson', `toJsonTree', `toJson', and `nullSafe'.
}

Note: Type Adapter and Json Serializer and Json Deserializer need to be used in conjunction with GsonBuilder. RegiserType Adapter or GsonBuilder. RegiserType Hierarchy Adapter, which will not be repeated below.

Use examples:

User user = new User("Monster kidou", 24);
user.emailAddress = "ikidou@example.com";
Gson gson = new GsonBuilder()
        //Register TypeAdapter for User
        .registerTypeAdapter(User.class, new UserTypeAdapter())
        .create();
System.out.println(gson.toJson(user));

Definition of UserType Adapter:

public class UserTypeAdapter extends TypeAdapter<User> {

    @Override
    public void write(JsonWriter out, User value) throws IOException {
        out.beginObject();
        out.name("name").value(value.name);
        out.name("age").value(value.age);
        out.name("email").value(value.email);
        out.endObject();
    }

    @Override
    public User read(JsonReader in) throws IOException {
        User user = new User();
        in.beginObject();
        while (in.hasNext()) {
            switch (in.nextName()) {
                case "name":
                    user.name = in.nextString();
                    break;
                case "age":
                    user.age = in.nextInt();
                    break;
                case "email":
                case "email_address":
                case "emailAddress":
                    user.email = in.nextString();
                    break;
            }
        }
        in.endObject();
        return user;
    }
}

When we registered the Type Adapter for User.class, as long as we operated on the @Serialized Name, Field Naming Strategy, Since, Until, Expos, which were introduced earlier in User.class, all of them lost their colour and lost their effect, we would only call the User Type Adapter. write (JsonWriter, User) method that we implemented, and I would write as I wanted.

In another scenario, the first article talks about Gson's fault-tolerant mechanism, such as converting string "24" to int's 24, but what if an empty string is returned to you in some cases? Although this is a server-side problem, we're just doing a demonstration here.

Type int will make a mistake, right. According to what we described above, can I register a Type Adapter to take over the serialization and deserialization process?

Gson gson = new GsonBuilder()
        .registerTypeAdapter(Integer.class, new TypeAdapter<Integer>() {
            @Override
            public void write(JsonWriter out, Integer value) throws IOException {
                out.value(String.valueOf(value)); 
            }
            @Override
            public Integer read(JsonReader in) throws IOException {
                try {
                    return Integer.parseInt(in.nextString());
                } catch (NumberFormatException e) {
                    return -1;
                }
            }
        })
        .create();
System.out.println(gson.toJson(100)); // Result: "100"
System.out.println(gson.fromJson("\"\"",Integer.class)); // Results: -1

Note: When testing empty strings, they must be """instead of ", "representing no JSON strings, ""representing only those in json.

You said that this takeover would involve two troubles. I just want to take care of the serialization (or de-serialization) process. I don't care about the other process. Isn't there any simpler way? Of course! The following are Json Serializer and Json Deserializer.

Json Serializer and Json Deserializer

Json Serializer and Json Deserializer do not have to implement the serialization and deserialization process like Type Adapter. You can choose to use Json Serializer only to take over the serialization process, and Json Deserializer only to take over the deserialization process, as the above requirements can use the following code.

Gson gson = new GsonBuilder()
        .registerTypeAdapter(Integer.class, new JsonDeserializer<Integer>() {
            @Override
            public Integer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
                try {
                    return json.getAsInt();
                } catch (NumberFormatException e) {
                    return -1;
                }
            }
        })
        .create();
System.out.println(gson.toJson(100)); //Result: 100
System.out.println(gson.fromJson("\"\"", Integer.class)); //Result-1

Here's an example of how all numbers are serialized into strings

JsonSerializer<Number> numberJsonSerializer = new JsonSerializer<Number>() {
    @Override
    public JsonElement serialize(Number src, Type typeOfSrc, JsonSerializationContext context) {
        return new JsonPrimitive(String.valueOf(src));
    }
};
Gson gson = new GsonBuilder()
        .registerTypeAdapter(Integer.class, numberJsonSerializer)
        .registerTypeAdapter(Long.class, numberJsonSerializer)
        .registerTypeAdapter(Float.class, numberJsonSerializer)
        .registerTypeAdapter(Double.class, numberJsonSerializer)
        .create();
System.out.println(gson.toJson(100.0f));//Result: "100.0"

Note: RegiserType Adapter must use packaging type, so int.class,long.class,float.class and double.class are not feasible. At the same time, we can't use the parent class to replace the subtype above, which is why we should register separately instead of using Number.class directly.

The above shows that registerType Adapter can't work, that is, there are other ways to do it? Of course! Instead of registerType Hierarchy Adapter, you can use Number.class instead of registering one by one.

The difference between registerType Adapter and registerType Hierarchy Adapter:

registerTypeAdapter registerTypeHierarchyAdapter
Supporting generics
Supporting inheritance or not
Note: If a serialized object itself has a generic type and registers the corresponding Type Adapter, then Gson.toJson(Object,Type) must be called to explicitly tell the type of the Gson object.

Type type = new TypeToken<List<User>>() {}.getType();
TypeAdapter typeAdapter = new TypeAdapter<List<User>>() {
   //slightly
};
Gson gson = new GsonBuilder()
        .registerTypeAdapter(type, typeAdapter)
        .create();
List<User> list = new ArrayList<>();
list.add(new User("a",11));
list.add(new User("b",22));
//Notice that there is an additional type parameter
String result = gson.toJson(list, type);

Type Adapter Factory

Type Adapter Factory, as the name implies, is used to create a factory class of Type Adapter. By comparing Type, we can determine whether there is a corresponding Type Adapter. If not, we return null and use it in conjunction with GsonBuilder. RegiserType Adapter Factory.

Gson gson = new GsonBuilder()
    .registerTypeAdapterFactory(new TypeAdapterFactory() {
        @Override
        public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
            return null;
        }
    })
    .create();

IV. Annotation @JsonAdapter

Json Adapter is more special than the previous annotations of Serialized Name, Field Naming Strategy, Since, Until, Expos. The other annotations are used on POJO fields, and this one is used on POJO classes, receiving a parameter, and must be Type Adpater, Json Serializer or Json Deserializer.

It is said that Json Serializer and Json Deserializer should cooperate with GsonBuilder. RegiserType Adapter, but it is too troublesome to register every time. Json Adapter is to solve this pain point.

How to use it (for example, User):

@JsonAdapter(UserTypeAdapter.class) //Add to Class
public class User {
    public User() {
    }
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public User(String name, int age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
    }
    public String name;
    public int age;
    @SerializedName(value = "emailAddress")
    public String email;
}

User Type Adapter is no longer registered with GsonBuilder.
Note: @JsonAdapter only supports Type Adapter or Type Adapter Factory

Gson gson = new Gson();
User user = new User("Monster kidou", 24, "ikidou@example.com");
System.out.println(gson.toJson(user));
//Result:{"name":"Monster kidou","age":24,"email":"ikidou@example.com"}
//To distinguish the result, the email field is deliberately compared with the@SerializedNameDifferent settings in annotations

Note: The priority of JsonAdapter is higher than that of GsonBuilder. RegiserType Adapter.

V. Comparison of Type Adapter with Json Serializer and Json Deserializer

TypeAdapter JsonSerializer,JsonDeserializer
Introduced version 2.01.x
Stream API support does not support *, and JsonElement needs to be generated ahead of time
Less memory footprint than TypeAdapter
Efficiency is lower than Type Adapter
Range of action serialization and deserialization serialization or deserialization

6. Type Adapter Example

Note: Type Adapter here refers to Type Adapter, Json Serializer and Json Deserializer.
Here, the Type Adapter talks about the problem that null strings may occur when converting numeric values in the form of strings into ints automatically. Here's another requirement:

The data field type returned by the server is not fixed. For example, the successful data request is a List, and the unsuccessful data request is a String type. How can the front end handle it when using generic parsing?
In fact, this problem is mainly caused by the server side. There is no guarantee of data consistency in the interface design. The correct data return posture: the same interface should not change the return type under any circumstances, either do not return, or return empty values, such as null, [], {}.

But here's the solution:
Programme 1:

Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(List.class, new JsonDeserializer<List<?>>() {
    @Override
    public List<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        if (json.isJsonArray()){
            //Here I am responsible for the analysis.
            Gson newGson = new Gson();
            return newGson.fromJson(json,typeOfT);
        }else {
            //Returns an empty List that does not match the interface type
            return Collections.EMPTY_LIST;
        }
    }
}).create();

Programme II:

Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(List.class, new JsonDeserializer<List<?>>() {
    @Override
    public List<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        if (json.isJsonArray()) {
            JsonArray array = json.getAsJsonArray();
            Type itemType = ((ParameterizedType) typeOfT).getActualTypeArguments()[0];
            List list = new ArrayList<>();
            for (int i = 0; i < array.size(); i++) {
                JsonElement element = array.get(i);
                Object item = context.deserialize(element, itemType);
                list.add(item);
            }
            return list;
        } else {
            //Returns an empty List that does not match the interface type
            return Collections.EMPTY_LIST;
        }
    }
}).create();

Attention should be paid to:

The registerTypeHierarchyAdapter method must be used, otherwise it will not be valid for a subclass of List, but if List is used in POJO, registerTypeAdapter can be used.
In the case of arrays, we need to create a new Gson instead of using context directly. Otherwise, gson will call our custom Json Deserializer to make recursive calls. Scheme 2 does not recreate Gson, so we need to extract the type of E in the List, and then deserialize it to register the type Adaper manually for E.
Recommend scheme 2 in terms of efficiency, eliminating the process of re-instantiating Gson and registering other Type Adapters.

Reference resources

http://www.jianshu.com/p/3108f1e44155

Keywords: JSON less

Added by jbachris on Sat, 15 Jun 2019 01:43:29 +0300