The pit when gson deserializes into data class

foreword

In Android development, gson is a very commonly used three-party library for processing json. It is maintained by Google and has always been relatively stable, at least when it is developed in Java.

However, gson's support for Kotlin's data class is not perfect, and there will be some pitfalls. Let's take a look below


The normal situation of gson deserialization into data class

In kotlin we use data class to act as a data class, for example:

data class User(
    val name: String,
    val age: Int,
)

Using gson to parse json into objects is also very simple, as follows

fun main() {
    
    
    val jsonStr = """{"name":"喻志强","age":0}"""
    val gson = GsonBuilder().create()
    val user = gson.fromJson(jsonStr, User::class.java)
    println("user = ${
      
      user}")
}

insert image description here

Then we get a User object, and the data is ok. I have also written some tips and packages about using gson before. If you are interested, you can take a look at:
Some tips used by Gson
The above situation is based on the normal json data. But when the json data is not so normal, pits are generated, let's take a look


The attribute value cannot be null, and a null value appears after deserialization

Still the above example, the data class is as follows

data class User(
    val name: String,
    val age: Int,
)

If the name in the json data is gone, as follows

fun main() {
    
    
    val jsonStr = """{"age":0}"""
    val gson = GsonBuilder().create()
    val user = gson.fromJson(jsonStr, User::class.java)
    println("user = ${user}")
}

The parsed objects are as follows. At
insert image description here
first glance, it seems that there is nothing wrong with it. There is no name field. Isn’t the value of name just null?

Then if you don't pay attention when writing code, write code similar to name operation, for example

fun main() {
    
    
    val jsonStr = """{name:null,age:18}"""
    val gson = GsonBuilder().create()
    val user = gson.fromJson(jsonStr, User::class.java)
    println("user = ${
      
      user}")
    val substring = user.name.substring(0, 1)
    /*获取name的第一个字符*/
    println("substring = ${
      
      substring}")
}

Doesn't it report an error when it runs
insert image description here
? The name is null, but if you think about it carefully, this is wrong. This obviously conflicts with the syntax of kotlin. We all know that if the value of an attribute in the data class can be null, we need to use it? To identify, as follows

insert image description here
In this way, if we operate on the name when writing the code, the IDE will prompt us to add a question mark, which can effectively avoid the occurrence of null pointer exceptions.
insert image description here
Add the ? sign to run it, and indeed no error will be reported.
insert image description here
However, when the name in our data class does not add a question mark, a null value will also appear. This is the first pit, which does not match expectations.
The reason is that gson uses java reflection to build objects, that is to say, gson does not know the data class, so it will not satisfy kotlin's null safety feature, so strange null pointer exceptions are easy to occur.

The way to solve this problem is very simple, just add a ? That's it, I personally don't like to use the question mark of null security. It's a bit disgusting to write. Question marks are added everywhere. Although it can effectively avoid null pointer exceptions, and in some business scenarios, some fields are It must have a value, and it cannot be null. If it is null, it means that the data is abnormal. Just throw an exception directly during deserialization.
The pitfall of gson is that it cannot tell you that the data is abnormal during deserialization . Only when the value of this field is used, NPE will appear, and it will be too late at this time...


What? The default value of the data class does not take effect?

In some scenarios, we want the data to have some default values, or in order to solve the above null value problem, we want to give name a default value, hoping that if there is no name field in json, we can use the default value of name ,as follows:

data class User(
    val name: String="喻志强",
    val age: Int,
)

Then run it again

fun main() {
    
    
    val jsonStr = """{age:28}"""
    val gson = GsonBuilder().create()
    val user = gson.fromJson(jsonStr, User::class.java)
    println("user = ${
      
      user}")
    val substring = user.name.substring(0, 1)
    /*获取name的第一个字符*/
    println("substring = ${
      
      substring}")
}

still error
insert image description here

It can be found that the default value does not take effect, and it is still null. When you hit a breakpoint, you will know that the reason is that gson did not find it when looking for the parameterless construction of User. Finally, by directly creating the object, there is no construction at all, and the construction does not go UnsafeAllocator.create(). The default value will naturally not take effect
insert image description here

It is also very simple to solve this problem. We assign an initial value to each attribute, so that a constructor without parameters will be generated, and this problem will not occur. Let’s try it

data class User(
    val name: String="喻志强",
    val age: Int=0,
)

run again

fun main() {
    
    
    val jsonStr = """{age:18}"""
    val gson = GsonBuilder().create()
    val user = gson.fromJson(jsonStr, User::class.java)
    println("user = ${
      
      user}")
    val substring = user.name.substring(0, 1)
    println("substring = ${
      
      substring}")
}

insert image description here

Well, this seems to be ok. This is also my own way in daily development. I am used to giving each attribute a default value, which is easier to deal with when writing code.
But is it foolproof to give the default value? Of course not.
Let’s look at the following code again.

fun main() {
    
    
    val jsonStr = """{name:null,age:18}"""
    val gson = GsonBuilder().create()
    val user = gson.fromJson(jsonStr, User::class.java)
    println("user = ${
      
      user}")
    val substring = user.name.substring(0, 1)
    println("substring = ${
      
      substring}")
}

We have given default values ​​to all attributes of the data class, but when the value of an attribute in json is explicitly null, the null value will still cover the original default value, which falls into the first pit again. Statement It cannot be null when writing code, and there is no exception prompt when writing code, but NPE will appear when running as a result.

There is really no good way to deal with this kind of situation where the returned data is not standardized. The only good way to deal with it is to force the backend to change it...

insert image description here
But I also found a better solution. It is enough to write the backend by myself and learn crud. I don’t need to ask for help. That’s how I am...

insert image description here

Ok, let’s not install it anymore, let’s summarize the two main problems that may arise when gson is combined with data class

  • The value cannot be null when the attribute is declared, and the result is null after deserialization, which is not as expected
  • The default value may not take effect and may be overwritten by null

In fact, there are some solutions to the above problems, check it yourself, but I don't like it very much, because the problem is not solved from the root cause.

If you want to solve it from the root cause, it’s very simple. Instead of using gson, you can use moshi or jackson. They all do separate processing for kotlin. There are also official documents for specific usage. Here is the github address first.

moshi:https://github.com/square/moshi

Jackson:jackson-module-kotlin

The next blog is about the basic use and actual combat of moshi. If you are interested, you can learn more about it.

The basic use and practice of moshi, a modern JSON library friendly to kotlin


If you think this article is helpful to you, please give it a thumbs up. It can help more developers. If there are any mistakes in the article, please correct me. For reprinting, please indicate that you are reposting from Yu Zhiqiang’s blog, thank you !

Guess you like

Origin blog.csdn.net/yuzhiqiang_1993/article/details/123953523