kotlin gson deserialization default value failure in depth explanation

  • 2021-10-27 09:11:19
  • OfStack

Principle of Gson deserialization

Brief introduction of principle

gson deserialization is mainly divided into two processes:

Create an object according to TypeToken Parse data from json string and assign values to object attributes

Object creation

ConstructorConstructor.get

Try to get the parameterless constructor first If it fails, try the constructor for List, Map, etc. Finally, use the Unsafe. newInstance scoop (this scoop does not call the constructor, resulting in all object initialization code not calling)

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 raw type 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 the parameterless constructor 
 ObjectConstructor<T> defaultConstructor = newDefaultConstructor(rawType);
 if (defaultConstructor != null) {
  return defaultConstructor;
 }

 //  Get List<T>,Map<T> And so on, for the List,Map The situation of 
 ObjectConstructor<T> defaultImplementation = newDefaultImplementationConstructor(type, rawType);
 if (defaultImplementation != null) {
  return defaultImplementation;
 }

 // unSafe Construct the object without calling any constructor 
 // finally try unsafe
 return newUnsafeAllocator(type, rawType);
 }

ConstructorConstructor.newDefaultConstructor

Call Class. getDeclaredConstructor to get the parameterless constructor

private <T> ObjectConstructor<T> newDefaultConstructor(Class<? super T> rawType) {
 try {
  //  Get the parameterless constructor 
  final Constructor<? super T> constructor = rawType.getDeclaredConstructor();
  if (!constructor.isAccessible()) {
  accessor.makeAccessible(constructor);
  }

ConstructorConstructor.newUnsafeAllocator

Call UnSafe. newInstance to create an object The constructor is not called, so all initialized code will not be 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);
  }
  }
 };
 }

Conclusion

For the Gson inverse sequence to work properly and produce the expected results, the class must have a parameterless constructor

kotlin Constructor Default Parameters and Parameterless Constructors

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")
 }
}

Decompiled Java code

Contains two constructors, one is the full-parameter constructor we declared, and the other is the auxiliary constructor generated by kotlin Does not contain parameterless constructors

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);
 }
}

gson deserialized output

Code:


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

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

null
Process finished with exit code 0

Parameters contain default parameters

kotlin code


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

Decompile Java code

In addition to the above two constructors, there is one more parameterless constructor (logically speaking, this is also 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);
 }

 //  Parametric constructor 
 public User() {
  this(0, (String)null, 3, (DefaultConstructorMarker)null);
 }
}

gson deserialized output

Code:


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

Output: In line with expectations

init
sss
Process finished with exit code 0

Best Practice

Practice1

Property is declared in the constructor, and all parameters take default values Indeterminate parameters are declared nullable

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

Practice2

Just go back to the writing of Java


private <T> ObjectConstructor<T> newDefaultConstructor(Class<? super T> rawType) {
 try {
  //  Get the parameterless constructor 
  final Constructor<? super T> constructor = rawType.getDeclaredConstructor();
  if (!constructor.isAccessible()) {
  accessor.makeAccessible(constructor);
  }
0

Summarize


Related articles: