Principle of Gson deserialization
The principle is briefly
Gson deserialization is divided into two main processes:
- Create objects based on TypeToken
- 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