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