sequence

“JSON is a self-describing, language-independent, lightweight text data exchange format that is often used for data storage and transfer. GSON allows us to quickly serialize and deserialize JSON data between objects.”

GSON’s toJson() and fromJson() methods, which are the most basic ways to use GSON, are straightforward and not much to say. But when asked how GSON is fault-tolerant of JSON data and how flexible it is to serialize and deserialize it, it misses the point.

JSON data fault tolerance, the simplest way is to keep the data at the front and back end consistent, there is no fault tolerance problem, but in the real scene, it is not as good as we expected.

Take two simple examples: the name of the User class. Some interfaces return the Key value of name and some return the Key value of username. For example, if the age field returns a string such as “18” and a Java object parses it to an Int, GSON has some type fault tolerance, so it can be successfully parsed. But if the age field returns a string such as “”, how can it not throw an exception? And set it to the default value 0, okay?

In this article, we’ll take a closer look at how GSON performs fault-tolerant parsing of data.

GSON fault tolerance

Regular use of GSON

GSON is an official JSON parsing library from Google. It is commonly used to serialize Java objects toJson data using toJson() or to deserialize JSON data to Java objects using fromJson().

/ / the serialization
val user = User()
user.userName = "Android Development Architecture"
user.age = 18
user.gender = 1
val jsonStr = Gson().toJson(user)
Log.i("lzx"."json:$jsonStr")
/ / json: {" age ": 18," gender ": 1," userName ":" Android development architecture "}

// deserialize
val newUser = Gson().fromJson(jsonStr,User::class.java)
Log.i("lzx"."userName:${newUser.userName}")
// userName:Android development architecture
Copy the code

GSON is handy, and most of the time doesn’t require us to do anything extra. The only thing you might care about is generic erasure, which is nothing more than a difference in parameters.

Using GSON only involves these two methods when the data is fairly standard, but for some special scenarios, it’s not that simple.

GSON annotations

GSON provides annotations as a way to configure the simplest flexibility. Here are two annotations @serializedName and @expose.

@serializedName can be used to configure the name of a JSON field. The most common scenarios vary from language to language. Some use underline and some use camel name.

Using the User class as an example, the Java object defines the User name as userName and the JSON data returned as user_name. This difference can be resolved with @serializedName.

class User{
    @SerializedName("user_name")
    var userName :String? = null
    var gender = 0
    var age = 0
}
Copy the code

In some cases, different interfaces may return different User names in the same User object, for example, name, user_name, and username. Such differences can also be resolved by using @serializedName.

There is also an alternate field in @SerializedName, where multiple parse names can be configured for the same field.

class User{
    @SerializedName(value = "user_name",alternate = arrayOf("name","username"))
    var userName :String? = null
    var gender = 0
    var age = 0
}
Copy the code

Take a look at @expose, which is used to configure some exception fields.

In the serialization and deserialization process, there are always some fields that are local to the business and do not need to be serialized from JSON or be serialized when passing JSON data.

This situation is easily solved with @expose. By exposing this field, you are literally participating in serialization and deserialization. When using the @ Expose, so regular new Gson () is no longer applicable, need GsonBuilder cooperate. ExcludeFieldsWithoutExposeAnnotation () method is used.

@expose has two configuration items, serialize and deserialize, which specify whether this field should be included in serialization and deserialization, both of which default to True.

class User{
    @SerializedName(value = "user_name",alternate = arrayOf("name"."username"))
    @Expose
    var userName :String? = null
    @Expose
    var gender = 0
    var age = 0

    @Expose(serialize = true,deserialize = false)
    var genderDesc = ""
}

fun User.gsonTest(a){
    / / the serialization
    val user = User()
    user.userName = "Android Development Architecture"
    user.age = 18
    user.gender = 1
    user.genderDesc = "Male"

    val gson = GsonBuilder()
            .excludeFieldsWithoutExposeAnnotation()
            .create()

    val jsonStr = gson.toJson(user)
    Log.i("cxmydev"."json:$jsonStr")
    / / json: {" gender ": 1," genderDesc ":" male "and" user_name ":" ChengXiang ink film "}

    val newUser = gson.fromJson(jsonStr,User::class.java)
    Log.i("cxmydev"."genderDesc:${newUser.genderDesc}")
    // genderDesc:
}
Copy the code

GenderDesc = true; genderDesc = true; genderDesc = true; genderDesc = true; genderDesc = true; Does not participate in deserialize (deserialize = false).

Note that once you start using @expose, all fields need to be marked explicitly to see if they are “exposed.” In the example above, the age field doesn’t have the @expose annotation, so it doesn’t exist during either serialization or deserialization.

That’s all for two important field annotations in GSON. Used correctly, they can solve 80% of GSON’s data parsing problems.

GsonBuilder is flexible in parsing

As you saw in the previous example, there are two ways to construct a Gson object, new Gson() and using GsonBuilder construction.

Either way you can construct a Gson object, but in this Builder object, there are some quick ways to parse JSON more flexibly.

For example, GSON will not parse null fields by default, but we can serialize them to null fields using the.serializenulls () method.

/ / the serialization
val user = User()
user.age = 18
user.gender = 1

val jsonStr = GsonBuilder().create().toJson(user)
Log.i("lzx"."json:$jsonStr")
// json:{"age":18,"gender":1}

val jsonStr1 = GsonBuilder().serializeNulls().create().toJson(user)
Log.i("lzx"."json1:$jsonStr1")
// json1:{"age":18,"gender":1,"userName":null}
Copy the code

GsonBuilder also provides more operations:

  1. .serializenulls () : field serialized to NULL.
  2. .setDateformat () : Sets the date format, for example, setDateFormat(” YYYY-MM-DD “).
  3. DisableInnerClassSerialization () : serialization inner class is forbidden.
  4. GenerateNonExcutableJson () : parse shall not directly generate JSON, can more)]} ‘the four characters.
  5. .disableHTMLescaping () : Disables transfer of HTML tags
  6. .setPrettyPrinting() : formats output

Annotations and some of the methods provided in GSON Builder are convenient apis that GSON provides for specific scenarios, but more complex scenarios are beyond them.

TypeAdapter

If the rules described above do not meet the requirements of the business, it does not matter, Gson has a big trick, is to use TypeAdapter.

TypeAdapter is a generic term for a class that is indeed an abstract class in the GSON library, but is not a class in GSON usage.

To use TypeAdapter, we need to use registerTypeAdapter() in the GsonBuilder class. Let’s first look at the method implementation of this class.

 public GsonBuilder registerTypeAdapter(Type type, Object typeAdapter) {
    $Gson$Preconditions.checkArgument(typeAdapter instanceofJsonSerializer<? > || typeAdapterinstanceofJsonDeserializer<? > || typeAdapterinstanceofInstanceCreator<? > || typeAdapterinstanceofTypeAdapter<? >);if (typeAdapter instanceofInstanceCreator<? >) { instanceCreators.put(type, (InstanceCreator) typeAdapter); }if (typeAdapter instanceofJsonSerializer<? > || typeAdapterinstanceofJsonDeserializer<? >) { TypeToken<? > typeToken = TypeToken.get(type); factories.add(TreeTypeAdapter.newFactoryWithMatchRawType(typeToken, typeAdapter)); }if (typeAdapter instanceofTypeAdapter<? >) { factories.add(TypeAdapters.newFactory(TypeToken.get(type), (TypeAdapter)typeAdapter)); }return this;
  }
Copy the code

As you can see, the registration method requires a data type that supports Both JsonSerializer and JsonDeserializer in addition to TypeAdapter. InstanceCreator has too few usage scenarios to cover.

TypeAdapter(abstract class), JsonSerializer(interface), JsonDeserializer(interface) can be understood as the general meaning of TypeAdapter, they are specific what the difference?

TypeAdapter contains two main methods, the write() and read() methods, which take over serialization and deserialization, respectively. Sometimes, we don’t need to handle either of these cases. For example, we only need to deserialize() the JsonDeserializer interface. Instead, we implement the serialize() method of the JsonSerializer interface, which makes our take-over more flexible and controllable.

Note that TypeAdapter is called a big move because it invalidates all the configurations described earlier. However, not all rules need to be implemented by ourselves after using TypeAdapter. Note that the first parameter to the registerTypeAdapter() method is typed, and it only takes over for a specific type.

For example, when a “” JSON field encounters a field of type Int, parsing fails and an exception is thrown.

/ / the serialization
val user = User()
user.age = 18
user.gender = 1

val jsonStr = "{\"gender\ :\",\"user_name\ :\"Android development architecture \"}"

val newUser = GsonBuilder().create().fromJson(jsonStr,User::class.java)
Log.i("cxmydev"."gender:${gender}")
Copy the code

In the example above, the gender field should be an Int value, and the gender in the JSON string for “”, such code, run will throw JsonSyntaxException: Java. Lang. A NumberFormatException: Empty String exception.

class IntegerDefault0Adapter : JsonDeserializer<Int> {
    override fun deserialize(json: JsonElement? , typeOfT:Type? , context:JsonDeserializationContext?).: Int {
        try {
            returnjson!! .getAsInt() }catch (e: NumberFormatException) {
            return 0}}}Copy the code

The default value 0 is returned when the conversion to Int is abnormal. Then join it using the registerTypeAdapter() method.

val newUser = GsonBuilder()
        .registerTypeAdapter(Int: :class.java, IntegerDefault0Adapter())
        .create().fromJson(jsonStr,User::class.java)
Log.i("cxmydev"."gender : ${newUser.gender}")
// gender : 0
Copy the code

When TypeAdapter is used, JSON parsing is no longer a problem. TypeAdapter applies to many scenarios, can be implemented according to specific requirements, here is not too much introduction.

A few more TypeAdapter details are added.

The difference between 1. RegisterTypeHierarchyAdapter ()

Look at the source code, attentive friend should be discovered, registered TypeAdapter, and registerTypeHierarchyAdapter () method, it and registerTypeAdapter () method is what’s the difference?

The difference is whether the type class that takes over supports inheritance. For example, in the previous example, we only took ints, while numeric types and others such as Long, Float, Double, etc. were not hit. What if we register Number as the parent of these Number types? Using registerTypeAdapter() will also not be hit because the types do not match.

At this point you can use the registerTypeHierarchyAdapter () method to register, it is support inheritance.

2. Use of the TypeAdapterFactory factory class

The registerXxx() method can be used as a chain call to register various Adapters.

If the trouble, you can also use this Adapter TypeAdapterFacetory factory, cooperate registerTypeAdapterFactory () method, according to different types to return the Adapter.

In fact, just change the way of implementation, there is no big difference.

3. @ JsonAdapter annotation

Unlike @serializedName and @expose, which were introduced earlier, the @JsonAdapter does not apply to fields, but to Java classes.

It specifies an Adapter class, which can be one of TypeAdapter, JsonSerializer, or JsonDeserializer.

The @jsonAdapter annotation is just a more flexible way to configure it.

summary

GSON is easy to use, but it’s based on getting it right. I’ve seen some ugly code, such as writing multiple fields in a Java object in a multi-field scenario and adding a method to return non-null fields from multiple fields. Or an afternoon of “communicating” with the backend developer about the format of a JSON data return.

There’s nothing wrong with sticking to norms, but it’s not lean thinking when you can’t continue your work because of someone else’s problems.

If you don’t abstract, you can’t think deeply, so let’s make a simple summary of today’s content.

GSON provides two easy ways to serialize and deserialize JSON data: toJson() and fromJson().

The @serializedName annotation solves the problem of resolving inconsistent fields and multiple fields.

The @expose annotation solves the problem of field exclusion during field serialization and deserialization.

GsonBuilder provides some handy apis for parsing data, for example

More flexible parsing, using TypeAdapter, can accurately customize the whole process of serialization and deserialization.