Android foundation building - learning notes of Gson framework

Posted by dbradbury on Tue, 08 Feb 2022 07:51:54 +0100

1 Preface

In Android development, the Gson framework is often used to serialize Java objects and deserialize json data.

This paper will gradually and deeply show the use of Gson framework through practical examples, hoping to help students develop better.

2 text

2.1 basic use

Use tojason () and fromjason () of Gson object to serialize Java objects and deserialize json data.
Open the code of Gson class, and you can see that there are multiple groups of tojason () and fromjason () methods. The calling relationship between them is shown in the following figure (please click the large figure to view):


After reading the above call diagram, let's take a look at the use of tojason () and fromjason () methods.

Simple class

First, define a simple class, Person class:

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

The test code is as follows:

public class Test01 {
    public static void main(String[] args) {
        Gson gson = new Gson();
        // serialize
        Person person = new Person("willwaywang6", 18);
        String personJson = gson.toJson(person);
        System.out.println(personJson);

        // Deserialization
        String json = "{\"name\":\"willwaywang6\",\"age\":18}";
        Person p = gson.fromJson(json, Person.class);
        System.out.println(p);
    }
}

The printing results are as follows:

{"name":"willwaywang6","age":18}
Person{name='willwaywang6', age=18}

You can see that by creating a Gson object, you can realize basic serialization and deserialization by using tojason () and fromjason ().

Generic class

So, for generic classes, is it still applicable to use the above method?

We introduce a generic class:

class Response<T> {
    public int code;
    public String message;
    public T data;

    @Override
    public String toString() {
        return "Response{" +
                "code=" + code +
                ", message='" + message + '\'' +
                ", data=" + data +
                '}';
    }
}

Such a generic class is often used to receive the data returned by the server in development.

First, perform the serialization test, and use Person as the argument of the generic type parameter:

Gson gson = new Gson();
// serialize
Person person = new Person("willwaywang6", 18);
Response<Person> response = new Response<>();
response.code = 0;
response.message = "success";
response.data = person;
String responseJson = gson.toJson(response);
System.out.println(responseJson);

The printing results are as follows:

{"code":0,"message":"success","data":{"name":"willwaywang6","age":18}}

As you can see, serialization is no problem for classes that contain generics.

Next, look at deserialization:

Gson gson = new Gson();
// Deserialization
String json = "{\"code\":0,\"message\":\"success\",\"data\":{\"name\":\"willwaywang6\",\"age\":18}}";
Response<Person> r = gson.fromJson(json, Response.class);
System.out.println(r);

The printing results are as follows:

Response{code=0, message='success', data={name=willwaywang6, age=18.0}}

Seems normal, too?

No, no, no, it seems a little wrong? We know that the data type of age is int, but how does it print out age=18.0?

What's going on?

To find out, we're in system out. println(r); Make a breakpoint in this line and debug it.


Looking at our screenshot, the type of age has indeed become Double. The key is the data field of the Response object. Its type is LinkedTreeMap, not the Person type we expect.

Some students may think that the output is just an incorrect type of age, or is it deserialized normally? Don't you have to do anything else?

To illustrate the mistake of this idea, let's add another line of code:

Person p2 = r.data;

The purpose is to retrieve the Person object in the data field of the Response object for further use in the program.
Run the program and you will see the following error messages:

java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to com.example.lib.basic.Person
	at com.example.lib.basic.Test02.main(Test02.java:31)

Type conversion exception: cannot convert LinkedTreeMap type to Person type.

The occurrence of this exception is obvious. From the debug screenshot, we can know that the current type of the data field is LinkedTreeMap. Here, it is not allowed to convert the LinkedTreeMap type to the Person type.

Therefore, public < T > t fromjson (string json, class < T > classof T) cannot be directly used to deserialize json into generic objects.

Resolve exceptions deserialized to generic classes

We use the method of public < T > t from Jason (string JSON, type typeof T) to solve the problem of failure in deserialization.

See the following test code:

Gson gson = new Gson();
// Deserialization
String json = "{\"code\":0,\"message\":\"success\",\"data\":{\"name\":\"willwaywang6\",\"age\":18}}";
Type responseType = new TypeToken<Response<Person>>() {
}.getType();
Response<Person> r = gson.fromJson(json, responseType);
System.out.println(r);
System.out.println(r.data);
try {
    Person p2 = r.data;
} catch (Exception exception) {
    exception.printStackTrace();
}

As you can see, we first obtained the Type responseType object:

Type responseType = new TypeToken<Response<Person>>() {
}.getType();

Then, the responseType is passed into the fromjson (string JSON, type typeof) method:

Response<Person> r = gson.fromJson(json, responseType);

Run the following program to view the results:

Response{code=0, message='success', data=Person{name='willwaywang6', age=18}}
Person{name='willwaywang6', age=18}

In order to have a more detailed view, it's better to interrupt and check the returned results.


No problem!

Here, the basic use of gson framework is completed.

However, in the actual development, it is not enough to be satisfied with the basic use of gson. Gson also provides us with some configurations to solve the problems in actual development.

2.2 using the annotations provided by the Gson framework

@SerializedName annotation

Take a look at the definition of @ SerializedName annotation:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface SerializedName {

  /**
   * @return the desired name of the field when it is serialized or deserialized
   * Returns the expected field name when serializing or deserializing
   */
  String value();
  /**
   * @return the alternative names of the field when it is deserialized
   * Returns an optional field name when deserializing
   */
  String[] alternate() default {};
}

The following is an example:

Now there is a simple Response class:

class Response {
    
    public int code;
    public String message;
    public String data;

    @Override
    public String toString() {
        return "Response{" +
                "code=" + code +
                ", message='" + message + '\'' +
                ", data=" + data +
                '}';
    }
}

The requirement is to serialize a Response object into json and pass it to the server for parsing. The json required by the server is as follows:

{
	"code": 0,
	"msg": "ok",
	"content": "some data"
}

If we don't do anything, serializing the Response object can't meet the requirements.

Gson gson = new Gson();
// serialize
Response response = new Response(0, "ok", "some data");
String responseJson = gson.toJson(response);
System.out.println(responseJson);

The printing results are as follows:

{"code":0,"message":"ok","data":"some data"}

In this case, you need to use the value element of the @ SerializedName annotation. Add annotations on the message and data fields respectively, as shown below:

class Response {

    public int code;
    @SerializedName("msg")
    public String message;
    @SerializedName("content")
    public String data;
}

Run the program again and the results are as follows:

{"code":0,"msg":"ok","content":"some data"}

You can see that the demand is met.

Having finished serialization, we have encountered a need for deserialization. We hope to

{"code":0,"msg":"ok","content":"some data"}

Deserialize a Response object. Note that the Response class does not use any annotations at this time.

class Response {
    public int code;
    public String message;
    public String data;
}

The test code is as follows:

Gson gson = new Gson();
// Deserialization
String json = "{\"code\":0,\"msg\":\"ok\",\"content\":\"some data\"}";
Response r = gson.fromJson(json, Response.class);
System.out.println(r);

The printing results are as follows:

Response{code=0, message='null', data=null}

Obviously, deserialization failed.

The solution is to add comments on the message and data fields respectively, as shown below:

class Response {
    public int code;
    @SerializedName("msg")
    public String message;
    @SerializedName("content")
    public String data;
}

Run the program again to see the results:

Response{code=0, message='ok', data=some data}

To summarize:

  • When a Java object is serialized into a json string, the field in the class that declares the annotation will use the value element value specified by the @ SerializedName annotation as the corresponding key in the json string;

  • When the json string is deserialized into a Java object, taking the value element value specified by the @ SerializedName annotation as the value value of the key will be assigned to the field in the class that declares the annotation.

Careful, have you noticed that there is an alternate element in the @ SerializedName annotation? This is also useful, but it only works in the process of deserialization.

Use a scenario to illustrate the use of the alternate element.

Now the server has issued three sets of json strings:

{
	"code": 0,
	"msg": "ok",
	"content": "some data"
}
{
	"code": 1,
	"msg": "server bang",
	"result": "blablabla"
}
{
	"code": 2,
	"msg": "server bang",
	"result_data": "blablabla"
}

Take a look at the above three json strings. Their key s are different, namely content, result and result_data, on the client side, they all correspond to the Response class, so the client needs to serialize them into Response objects.

class Response {
    public int code;
    @SerializedName("msg")
    public String message;
    @SerializedName("content")
    public String data;
}

Let's take a test and see the results:

Gson gson = new Gson();
String json1 = "{\"code\":0,\"msg\":\"ok\",\"content\":\"some data\"}";
String json2 = "{\"code\":1,\"msg\":\"server bang\",\"result\":\"blablabla\"}";
String json3 = "{\"code\":2,\"msg\":\"server bang\",\"result_data\":\"blablabla\"}";
System.out.println(gson.fromJson(json1, Response.class));
System.out.println(gson.fromJson(json2, Response.class));
System.out.println(gson.fromJson(json3, Response.class));

Print results:

Response{code=0, message='ok', data=some data}
Response{code=1, message='server bang', data=null}
Response{code=2, message='server bang', data=null}

You can see that the last two json strings did not successfully deserialize the data field of the Java object.

In this case, you need to use the alternate element of the @ SerializedName annotation.

class Response {
    public int code;
    @SerializedName("msg")
    public String message;
    @SerializedName(value = "content", alternate = {"result", "result_data"})
    public String data;
}

Run again to see the results:

Response{code=0, message='ok', data=some data}
Response{code=1, message='server bang', data=blablabla}
Response{code=2, message='server bang', data=blablabla}

As you can see, the deserialization was successfully completed.

If such a json string appears, how will it be deserialized?

{
	"code": 4,
	"msg": "server bang",
	"content": "some data",
	"result": "blablabla",
	"reslut_data": "plaplapla"
}
Gson gson = new Gson();
// Deserialization in case of multiple alternatives
String json4 = "{\"code\":4,\"msg\":\"server bang\",\"content\":\"some data\",\"result\":\"blablabla\",\"result_data\":\"plaplapla\"}";
String json5 = "{\"code\":5,\"msg\":\"server bang\",\"result\":\"blablabla\",\"result_data\":\"plaplapla\",\"content\":\"some data\"}";
String json6 = "{\"code\":6,\"msg\":\"server bang\",\"result_data\":\"plaplapla\",\"content\":\"some data\",\"result\":\"blablabla\"}";
System.out.println(gson.fromJson(json4, Response.class));
System.out.println(gson.fromJson(json5, Response.class));
System.out.println(gson.fromJson(json6, Response.class));

The printing results are as follows:

Response{code=4, message='server bang', data=plaplapla}
Response{code=5, message='server bang', data=some data}
Response{code=6, message='server bang', data=blablabla}

Check the print results. When multiple elements in the alternate array appear in the json string, the last one assigned to the field in the class declaring the annotation will be selected.

To summarize:

When the json string is deserialized into a Java object, the elements in the array of alternate elements specified by the @ SerializedName annotation as the value value of the key will be assigned to the field in the class that declares the annotation.

When multiple elements in the alternate array appear in the json string, the last assignment will be selected to the field in the class that declares the annotation.

@Expose annotation

Using the Expose annotation, you can clearly specify whether a field can be serialized or deserialized.

Look at the following classes:

public class Person {
    @Expose
    String firstName;
    @Expose(deserialize = false)
    String lastName;
    @Expose(serialize = false)
    int age;
    @Expose(serialize = false, deserialize = false)
    String password;
    String phoneNumber;
}

First, explain how each field participates in serialization and deserialization:

Field nameParticipate in serialization?Participate in deserialization?
firstName
lastName×
age×
password××
phoneNumber××

The test code is as follows:

// serialize
Person person = new Person("zhichao", "wang", 18, "123456", "13912345678");
Gson gson = new Gson();
System.out.println(gson.toJson(person));
// Deserialization
String json = "{\"firstName\":\"zhichao\",\"lastName\":\"wang\",\"age\":18,\"password\":\"123456\",\"phoneNumber\":\"13912345678\"}";
System.out.println(gson.fromJson(json, Person.class));

Operation results:

{"firstName":"zhichao","lastName":"wang","age":18,"password":"123456","phoneNumber":"13912345678"}
Person{firstName='zhichao', lastName='wang', age=18, password='123456', phoneNumber='13912345678'}

We don't see the effect of using @ Expose annotation. All fields are involved in the process of serialization and deserialization.

This is because the method of building the Gson object and the excludeFieldsWithoutExposeAnnotation() configuration are not added. Modify the code for creating the Gson object as follows:

Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();

Run the program again as follows:

{"firstName":"zhichao","lastName":"wang"}
Person{firstName='zhichao', lastName='null', age=18, password='null', phoneNumber='null'}

Check the table above again:

Field nameParticipate in serialization?Participate in deserialization?
firstName
lastName×
age×
password××
phoneNumber××

The fields involved in serialization include firstName and lastName, so the serialized json string is:

{"firstName":"zhichao","lastName":"wang"}

The fields involved in deserialization include firstName and age, so the object print after deserialization is:

Person{firstName='zhichao', lastName='null', age=18, password='null', phoneNumber='null'}

@Differences between Expose and transient Keywords:

Fields with @ Expose annotation declared can separately control whether to serialize and deserialize;
The fields modified with the transient keyword can neither be serialized nor deserialized.

2.3 using the configuration provided by the Gson framework

Use serializeNulls() configuration to force serialization of null values

Now there is a request class RequestBean:

public class RequestBean {
    /**
     * Article id
     */
    public int id;
    /**
     * Article title
     */
    public String title;
    /**
     * true if the user likes it; false if the user doesn't like it; null if the user has no selection
     */
    public Boolean action;
}

It is required that the values of the three fields must be serialized and reported to the server.

This requirement is not very difficult. Look at the code:

Gson gson = new Gson();
RequestBean requestBean = new RequestBean();
requestBean.id = 1;
requestBean.title = "a state scholar of no equal";
System.out.println(gson.toJson(requestBean));

The printing results are as follows:

{"id":1,"title":"a state scholar of no equal"}

However, the serialized result is not signed with the requirement, because it does not contain the value of the action field, and the server will certainly find it.

At this time, you need to use the serializeNulls() configuration to modify the code to obtain the Gson object, as follows:

Gson gson = new GsonBuilder().serializeNulls().create();

Use setPrettyPrinting() configuration to format json string printing

Now there is a UserBean class:

public class UserBean {
    public String userid;
    public String username;
    public String password;
    public int age;
    public int height;
    public double salary;
    public String address;
}

You need to print the serialized json string of its object:

UserBean userBean = new UserBean();
userBean.userid = "204895272048";
userBean.username = "Ultraman";
userBean.age = 1000;
userBean.height = 300;
userBean.password = "1234567890";
userBean.salary = 30000.0;
userBean.address = "outer space, unknown";
Gson gson = new Gson();
System.out.println(gson.toJson(userBean));

Print results:

{"userid":"204895272048","username":"Ultraman","password":"1234567890","age":1000,"height":300,"salary":30000.0,"address":"outer space, unknown"}

Seeing that the print result is not formatted, the expectation is as follows:


Modify the way to get the Gson object by using setPrettyPringint() configuration:

Gson gson = new GsonBuilder().setPrettyPrinting().create();

Print results:

{
  "userid": "204895272048",
  "username": "Ultraman",
  "password": "1234567890",
  "age": 1000,
  "height": 300,
  "salary": 30000.0,
  "address": "outer space, unknown"
}

Use disableHtmlEscaping() configuration to remove html character escape

Now there is a simple class:

public class SimpleBean {
    public String title;
    public String message;
}

The test code is as follows:

SimpleBean bean = new SimpleBean();
bean.title = "Mr Bush's House";
bean.message = "a > b";
Gson gson = new Gson();
System.out.println(gson.toJson(bean));

The printing results are as follows:

{"title":"Mr Bush\u0027s House","message":"a \u003e b"}

You can see that the 'character is escaped to \ u0027 and the > character is escaped to \ u003e.

Such content is directly displayed on the mobile phone screen, and the user cannot understand it. Therefore, it must be changed. You can use disablehtmlescapping() to modify the code to obtain the Gson object as follows:

Gson gson = new GsonBuilder().disableHtmlEscaping().create();

After re running, the results are as follows:

{"title":"Mr Bush's House","message":"a > b"}

Use setexclusionstrategies (exclusionstrategies... Strategies) configuration to add flexible exclusion policies

The previous @ Expose mainly set whether to serialize and deserialize for a single field. Here, you can uniformly configure the exclusion policy through setexclusionstrategies (exclusionstrategies... Strategies).

The following is a detailed demonstration:

This is a simple class:

public class Student {
    public String name;
    public int age;
    public boolean gender;
    public Date birthday;
    public String _school;
}

We intend to exclude the fields of boolean type and Date type in both serialization and deserialization

Gson gson = new GsonBuilder().setExclusionStrategies(new ExclusionStrategy() {
    @Override
    public boolean shouldSkipField(FieldAttributes f) {
        return false;
    }
    @Override
    public boolean shouldSkipClass(Class<?> clazz) {
        return clazz == Date.class || clazz == boolean.class;
    }
}).create();
// serialize
Student student = new Student();
student.name = "willwaywang6";
student.age = 18;
student.gender = true;
student.birthday = new Date(System.currentTimeMillis());
student._school = "college";
System.out.println(gson.toJson(student));
// Deserialization
String json = "{\"name\":\"willwaywang6\",\"age\":18,\"gender\":true,\"birthday\":\"May 30, 2021 1:58:25 PM\",\"_school\":\"college\"}";
Student s = gson.fromJson(json, Student.class);
System.out.println(s);

Print results:

{"name":"willwaywang6","age":18,"_school":"college"}
Student{name='willwaywang6', age=18, gender=false, birthday=null, _school='college'}

If you want to exclude serialization and deserialization based on the field name, annotation and modifier, you can handle it in the shouldSkipField method. The modified code is as follows:

public boolean shouldSkipField(FieldAttributes f) {
	// Exclude names with "" Fields at the beginning
    return f.getName().startsWith("_");
}

Rerun to view the results:

{"name":"willwaywang6","age":18}
Student{name='willwaywang6', age=18, gender=false, birthday=null, _school='null'}

You can see:_ The school field is not involved in serialization and deserialization.

2.4 serialization and deserialization of custom Gson

This refers to the way through registerTypeAdapter:

public GsonBuilder registerTypeAdapter(Type type, Object typeAdapter) {
  $Gson$Preconditions.checkArgument(typeAdapter instanceof JsonSerializer<?>
      || typeAdapter instanceof JsonDeserializer<?>
      || typeAdapter instanceof InstanceCreator<?>
      || typeAdapter instanceof TypeAdapter<?>);
  if (typeAdapter instanceof InstanceCreator<?>) {
    instanceCreators.put(type, (InstanceCreator) typeAdapter);
  }
  if (typeAdapter instanceof JsonSerializer<?> || typeAdapter instanceof JsonDeserializer<?>) {
    TypeToken<?> typeToken = TypeToken.get(type);
    factories.add(TreeTypeAdapter.newFactoryWithMatchRawType(typeToken, typeAdapter));
  }
  if (typeAdapter instanceof TypeAdapter<?>) {
    factories.add(TypeAdapters.newFactory(TypeToken.get(type), (TypeAdapter)typeAdapter));
  }
  return this;
}

The function of this method is to configure Gson for custom serialization and deserialization.

The whole method is posted here to let you see that although the name of the method is called registerTypeAdapter, there are more than typeadapters that can be passed in. Specifically, there are four types of objects that can be passed in:

  • JsonSerializer type;
  • JsonDeserializer type;
  • InstanceCreator type;
  • TypeAdapter type.

Each type of application needs to be developed flexibly, but it needs to be used separately in all practical applications.

Custom serialization using JsonSerializer

Here is the process of building a buyer, selecting goods, finally generating an order, serializing it and sending it to the server.

Let's take a look at several classes that will be used:

/**
 * Buyers
 */
public class Shopper {
    public String userid;
    public String username;
}
/**
 * commodity
 */
public class Commodity {
    public String id;
    public String name;
}
/**
 * order
 */
public class Order {
    public String orderid;
    public String userid;
    public String username;
    public List<Commodity> commodities;
}

Here is the demo code:

Shopper shopper = new Shopper("userid_1234", "willwaywang6");
List<Commodity> commodities = Arrays.asList(
        new Commodity("id_12345", "Huawei P50"),
        new Commodity("id_23456", "Huawei P60")
);
Order order = new Order("order_1111", shopper.userid, shopper.username, commodities);
Gson gson = new GsonBuilder().setPrettyPrinting().create();
System.out.println(gson.toJson(order));

The operation results are as follows:

{
  "orderid": "order_1111",
  "userid": "userid_1234",
  "username": "willwaywang6",
  "commodities": [
    {
      "id": "id_12345",
      "name": "Huawei P50"
    },
    {
      "id": "id_23456",
      "name": "Huawei P60"
    }
  ]
}

Here, we seem to have completed the requirements.

However, after a while, the server came over and said, "can you modify the data format of the order submitted by the client? Now the reported data is redundant, and the server will explode. Help to modify it like this:"

{
	"orderid": "order_1111",
	"userid": "userid_1234",
	"username": "willwaywang6",
	"commodities": [
		"id_12345", "id_23456"
	]
}

The first thought is that for a single Commodity object, only the id field is serialized. The modification code is as follows:

JsonSerializer<Commodity> jsonSerializer = new JsonSerializer<Commodity>() {
    @Override
    public JsonElement serialize(Commodity src, Type typeOfSrc, JsonSerializationContext context) {
        JsonObject result = new JsonObject();
        result.addProperty("id", src.id);
        return result;
    }
};
Gson gson = new GsonBuilder().setPrettyPrinting().registerTypeAdapter(Commodity.class, jsonSerializer).create();
System.out.println(gson.toJson(order));

The operation results are as follows:

{
  "orderid": "order_1111",
  "userid": "userid_1234",
  "username": "willwaywang6",
  "commodities": [
    {
      "id": "id_12345"
    },
    {
      "id": "id_23456"
    }
  ]
}

Does not meet the requirements.

In fact, you should get the whole list < commodity > object, then take out the id in it and store it in an array. The modification code is as follows:

JsonSerializer<List<Commodity>> jsonSerializer = new JsonSerializer<List<Commodity>>() {
    @Override
    public JsonElement serialize(List<Commodity> src, Type typeOfSrc, JsonSerializationContext context) {
        JsonArray jsonArray = new JsonArray();
        for (Commodity commodity : src) {
            jsonArray.add(commodity.id);
        }
        return jsonArray;
    }
};
// Note that there are generic types here. To get the type in this way.
Type type = new TypeToken<List<Commodity>>() {
}.getType();
Gson gson = new GsonBuilder().setPrettyPrinting().registerTypeAdapter(type, jsonSerializer).create();
System.out.println(gson.toJson(order));

The operation results are as follows:

{
  "orderid": "order_1111",
  "userid": "userid_1234",
  "username": "willwaywang6",
  "commodities": [
    "id_12345",
    "id_23456"
  ]
}

Ha ha, this time it meets the requirements of the server.

Custom deserialization using JsonDeserializer

The requirement this time is to deserialize the json string given by the background into an object:

{
	"name": "Huawei P100",
	"weight": 1,
	"timestamp": 1622360677020
}

The corresponding classes are:

public class Goods {
    public String name;
    public int weight;
    public long timestamp;
}

The test code is as follows:

String goodsJson = "{\"name\":\"Huawei P100\",\"weight\":1,\"timestamp\":1622360677020}";
Gson gson = new GsonBuilder().create();
Goods goods = gson.fromJson(goodsJson, Goods.class);
System.out.println(goods);

Print results:

Goods{name='Huawei P100', weight=1, timestamp=1622360677020}

Deserialized normally.

However, sometimes such json strings are returned in the background:

{
	"name": "Huawei P100",
	"weight": "",
	"timestamp": 1622360677020
}
String goodsJson = "{\"name\":\"Huawei P100\",\"weight\":\"\",\"timestamp\":1622360677020}";

Run the program again and report an error:

Exception in thread "main" com.google.gson.JsonSyntaxException: java.lang.NumberFormatException: empty String
	at com.google.gson.internal.bind.TypeAdapters$7.read(TypeAdapters.java:228)
	at com.google.gson.internal.bind.TypeAdapters$7.read(TypeAdapters.java:218)
	at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:131)
	at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:222)
	at com.google.gson.Gson.fromJson(Gson.java:932)
	at com.google.gson.Gson.fromJson(Gson.java:897)
	at com.google.gson.Gson.fromJson(Gson.java:846)
	at com.google.gson.Gson.fromJson(Gson.java:817)
	at com.example.lib._04_typeadapter.Test02.demo2(Test02.java:26)
	at com.example.lib._04_typeadapter.Test02.main(Test02.java:13)
Caused by: java.lang.NumberFormatException: empty String
	at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1842)
	at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
	at java.lang.Double.parseDouble(Double.java:538)
	at com.google.gson.stream.JsonReader.nextInt(JsonReader.java:1202)
Caused by: java.lang.NumberFormatException: empty String

Here you can use the JSON deserializer to resolve this exception. The modification code is as follows:

JsonDeserializer<Integer> jsonDeserializer = new JsonDeserializer<Integer>() {
    @Override
    public Integer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        try {
            return json.getAsInt();
        } catch (Exception exception) {
            return 0;
        }
    }
};
Gson gson = new GsonBuilder().registerTypeAdapter(int.class, jsonDeserializer).create();

After modifying the program, run the following:

Goods{name='Huawei P100', weight=0, timestamp=1622360677020}

As you can see, the exception is solved here.

It should be noted that the first parameter of the registerTypeAdapter() method passes int.class, which corresponds to the int type field of the Goods class; If the field type of the Goods class is Integer, the first parameter of the registerTypeAdapter method needs to pass Integer class.

Custom deserialization using InstanceCreator

The requirement has a shopercontext class:

public class ShopperContext {
    public String userid;
    public String username;
    public Context context;

    public ShopperContext(Context context) {
        this.context = context;
    }
}

It is required that the shopercontext object contains the value of the Context type field after serialization.

Don't think too much first. Use the previous method to deserialize directly:

String json = "{\"userid\":\"id_12345\",\"username\":\"willwaywang6\"}";
Gson gson = new GsonBuilder().create();
ShopperContext shopperContext = gson.fromJson(json, ShopperContext.class);
System.out.println(shopperContext);

Print as follows:

ShopperContext{userid='id_12345', username='willwaywang6', context=null}

You can see that the context field is null, which does not meet the requirements.

InstanceCreator is needed at this time. The modification code is as follows:

// Simulate a Context object
final Context context = new Context();
InstanceCreator<ShopperContext> instanceCreator = new InstanceCreator<ShopperContext>() {
    @Override
    public ShopperContext createInstance(Type type) {
        return new ShopperContext(context);
    }
};
Gson gson = new GsonBuilder().registerTypeAdapter(ShopperContext.class, instanceCreator).create();

Run the program again to see the results:

ShopperContext{userid='id_12345', username='willwaywang6', context=com.example.lib._04_typeadapter.Context@6267c3bb}

Another example:

There is a ShopperSingleton class:

public class ShopperSingleton {

    public String userid;
    public String username;

    private static ShopperSingleton singleton = new ShopperSingleton();

    private ShopperSingleton() {

    }

    public static ShopperSingleton getInstance() {
        return singleton;
    }

    @Override
    public String toString() {
        return "ShopperSingleton@" + hashCode() + "{" +
                "userid='" + userid + '\'' +
                ", username='" + username + '\'' +
                '}';
    }
}

This is a singleton class.

It is required to assign the value to the field of this singleton after deserialization, instead of creating a new instance every time.

Take the previous method and code the code first:

String json = "{\"userid\":\"id_12345\",\"username\":\"willwaywang6\"}";
Gson gson = new GsonBuilder().create();
// Printing before deserialization
System.out.println(ShopperSingleton.getInstance());
ShopperSingleton shopperContext = gson.fromJson(json, ShopperSingleton.class);
// Printing after deserialization
System.out.println(shopperContext);

Print as follows:

ShopperSingleton@1066516207{userid='null', username='null'}
ShopperSingleton@777874839{userid='id_12345', username='willwaywang6'}

The singleton of ShopperSingleton was broken before and after serialization.

Use InstanceCreator to solve:

InstanceCreator<ShopperSingleton> instanceCreator = new InstanceCreator<ShopperSingleton>() {
    @Override
    public ShopperSingleton createInstance(Type type) {
        return ShopperSingleton.getInstance();
    }
};
Gson gson = new GsonBuilder().registerTypeAdapter(ShopperSingleton.class, instanceCreator).create();
ShopperSingleton shopperContext = gson.fromJson(json, ShopperSingleton.class);

Print as follows:

ShopperSingleton@705927765{userid='null', username='null'}
ShopperSingleton@705927765{userid='id_12345', username='willwaywang6'}

You can see that there is one object before and after serialization, which meets the requirements.

Use TypeAdapter to customize serialization and deserialization

The requirements are as follows:
For the Point class:

public class Point {
    public int x;
    public int y;
}

When serializing, it is a json string such as "1,1"; During deserialization, "1,1" can be converted to a Point object.

Look directly at the code:

Gson gson = new GsonBuilder().registerTypeAdapter(Point.class, new TypeAdapter<Point>() {
    @Override
    public void write(JsonWriter writer, Point value) throws IOException {
        if (value == null) {
            writer.nullValue();
            return;
        }
        String xy = value.x + "," + value.y;
        writer.value(xy);
    }
    @Override
    public Point read(JsonReader reader) throws IOException {
        if (reader.peek() == JsonToken.NULL) {
            reader.nextNull();
            return null;
        }
        String xy = reader.nextString();
        String[] parts = xy.split(",");
        int x = Integer.parseInt(parts[0]);
        int y = Integer.parseInt(parts[1]);
        return new Point(x, y);
    }
}).create();
// serialize
Point point = new Point(1, 1);
System.out.println(gson.toJson(point));
// Deserialization
// Special note: it is "\" 2,2 \ ", not" 2,2 ", otherwise deserialization will fail
String json = "\"2,2\"";
Point p = gson.fromJson(json, Point.class);
System.out.println(p);

3 finally

This article starts with the basic use of Gson, then shows the common annotations and configurations in Gson, and finally introduces the use of registerAdapter.

I hope I can help you.

reference resources

Topics: Android gson