Principle of Gson deserialization

The principle is briefly

Gson deserialization is divided into two main processes:

  1. Create objects based on TypeToken
  2. Parses the data from the JSON string and assigns values to the object properties

Object creation

ConstructorConstructor.get

  • Try to get the no-argument constructor first
  • On failure, try the constructor for List, Map, and so on
  • Finally, use unbroadening. NewInstance pocket (this pocket will not call the constructor, resulting in no object initialization code being called)
public <T> ObjectConstructor<T> get(TypeToken<T> typeToken) {
    final Type type = typeToken.getType();
    final Class<? super T> rawType = typeToken.getRawType();

    // first try an instance creator

    @SuppressWarnings("unchecked") // types must agree
    final InstanceCreator<T> typeCreator = (InstanceCreator<T>) instanceCreators.get(type);
    if (typeCreator ! = null) {return new ObjectConstructor<T>() {
        @Override public T construct() {
          return typeCreator.createInstance(type); }}; } // Next try rawtype match for instance creators
    @SuppressWarnings("unchecked") // types must agree
    final InstanceCreator<T> rawTypeCreator =
        (InstanceCreator<T>) instanceCreators.get(rawType);
    if(rawTypeCreator ! = null) {return new ObjectConstructor<T>() {
        @Override public T construct() {
          return rawTypeCreator.createInstance(type); }}; } // get ObjectConstructor<T> defaultConstructor = newDefaultConstructor(rawType);if(defaultConstructor ! = null) {returndefaultConstructor; } // get List<T>,Map<T>, etc. For the List, the Map of ObjectConstructor < T > defaultImplementation = newDefaultImplementationConstructor (type, rawType);
    if(defaultImplementation ! = null) {returndefaultImplementation; // unsafe. // finally try unSafereturn newUnsafeAllocator(type, rawType);
  }
Copy the code

ConstructorConstructor.newDefaultConstructor

  • Call the Class. No arguments constructor getDeclaredConstructor
private <T> ObjectConstructor<T> newDefaultConstructor(Class<? Super T> rawType) {try {final Constructor<? super T> constructor = rawType.getDeclaredConstructor();if(! constructor.isAccessible()) { accessor.makeAccessible(constructor); }Copy the code

ConstructorConstructor.newUnsafeAllocator

  • Call broadening. NewInstance to create the object
  • The constructor is not called, so all initialized code is not called
private <T> ObjectConstructor<T> newUnsafeAllocator(
      final Type type, final Class<? super T> rawType) {
    return new ObjectConstructor<T>() {
      private final UnsafeAllocator unsafeAllocator = UnsafeAllocator.create();
      @SuppressWarnings("unchecked")
      @Override public T construct() {
        try {
        // 
          Object newInstance = unsafeAllocator.newInstance(rawType);
          return (T) newInstance;
        } catch (Exception e) {
          throw new RuntimeException(("Unable to invoke no-args constructor for " + type + "."
              + "Registering an InstanceCreator with Gson for this type may fix this problem."), e); }}}; }Copy the code

conclusion

  • For the Gson antisequence to work properly and make the result as expected, the class must have a no-parameter constructor

The relationship between the kotlin constructor default argument and the no-argument constructor

There is no default value in the parameter

Kotlin code

  • Id has no default value
class User(val id: Int, val name: String = "sss") {
    init {
        println("init")}}Copy the code

Decompiled Java code

  • Contains two constructors, the full-parameter constructor we declare and the auxiliary constructor generated by Kotlin
  • Does not contain a no-argument constructor
public final class User {
   private final int id;
   @NotNull
   private final String name;
   
   public User(int id, @NotNull String name) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      super();
      this.id = id;
      this.name = name;
      String var3 = "init"; System.out.println(var3); } / /$FF: synthetic method
   public User(int var1, String var2, int var3, DefaultConstructorMarker var4) {
      if((var3 & 2) ! = 0) { var2 =""; } this(var1, var2); }}Copy the code

Gson deserializes the output

Code:

 @Test
    fun testJson() {
        val user = Gson().fromJson("{}", User::class.java)
        print(user.name)
    }
Copy the code

Output: Not expected (the name we declared as non-empty actually results in null)

null
Process finished with exit code 0
Copy the code

Parameters that contain default parameters

Kotlin code

class User(val id: Int=1, val name: String = "sss") {
    init {
        println("init")}}Copy the code

Decompile Java code

  • In addition to the above two constructors, there is an additional constructor with no arguments (logically speaking, this is also as expected)
public final class User {
   private final int id;
   @NotNull
   private final String name;

   public User(int id, @NotNull String name) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      super();
      this.id = id;
      this.name = name;
      String var3 = "init"; System.out.println(var3); } / /$FF: synthetic method
   public User(int var1, String var2, int var3, DefaultConstructorMarker var4) {
      if((var3 & 1) ! = 0) { var1 = 1; }if((var3 & 2) ! = 0) { var2 =""; } this(var1, var2); } // The no-argument constructor publicUser() { this(0, (String)null, 3, (DefaultConstructorMarker)null); }}Copy the code

Gson deserializes the output

Code:

 @Test
    fun testJson() {
        val user = Gson().fromJson("{}", User::class.java)
        print(user.name)
    }
Copy the code

Output: As expected

init
sss
Process finished with exit code 0
Copy the code

Best Practice

Practice1

  • Property is declared in the constructor, and all arguments take default values
  • Indeterminate arguments are declared nullable
class User(val id: Int=1 , val name: String = "sss") {
    init {
        println("init")}}Copy the code

Practice2

  • Just go back to Java
class User {
    val id: Int = 1
    val name: String = "sss"

    init {
        println("init")}}Copy the code