1. Scenarios and solutions for Json parsing

1.1 scenario

1. The first chestnut (without null field parsing)

val jsonString = "{\"name\":\"woochen\",\"obj\":{\"address\":\"china\"},\"list\":[{\"address\":\"china\"}]}"
Copy the code
data class TestBean(
    val name:String="".val obj:InnerBean=InnerBean(),
    val list:List<InnerBean> = listOf()
){
    data class InnerBean(
        val address:String="")}Copy the code

Note: Initial field assignments in entity classes only apply if the JSON field has not been parsed

val result = gson.fromJson<TestBean>(jsonString, TestBean::class.java)
println(Analysis results:${result}")
Copy the code

Results:

TestBean(name=woochen, obj=InnerBean(address= China), list=[InnerBean(address= China)])Copy the code

2 More chestnuts (data parsing with null fields) if the server does not specifically handle this, we might receive json like this

val jsonString = "{\"name\":null,\"obj\":null,\"list\":null}"
Copy the code

We are still using the original entity class to parse, and the result is this

TestBean(name=null, obj=null, list=null)
Copy the code

Seemingly fine, we proceed to execute this code

result.name.length
Copy the code

Compilation passes, and an exception is thrown when executed

java.lang.NullPointerException
Copy the code

The smart guy might say, well, that’s fine

result.name?.length
Copy the code

Turns out, however, it doesn’t work…

Since Gson generated the instance by using the no-argument construction reflection of the class, it successfully completed the parsing process, but Kotlin’s null test resulted in null-pointer problems when used.

1.2 Solutions

Solution 1: Let the server assign an initial value to all fields to avoid null

If you can use this solution, this article is of no use to you and is not recommended to continue reading.

Solution 2: Use nullable type entity class resolution

data class TestBean(
    valname:String? ="".valobj:InnerBean? =InnerBean(),val list:List<InnerBean>? = listOf()
){
    data class InnerBean(
        valaddress:String? ="")}Copy the code

After this parsing, when we use a field, when we use it as a non-null parameter, we need to add? Or!!!!! Personally, I think it’s a bit troublesome

Solution 3: Reassign the NULL value directly during parsing. Part 3 shows how to do this

/ / entity class
data class TestBean(
    val name:String="".val obj:InnerBean=InnerBean(),
    val list:List<InnerBean> = listOf()
){
    data class InnerBean(
        val address:String="")}/ / target json
val jsonString = "{\"name\":null,\"obj\":null,\"list\":null}"

// Parse the result
TestBean(name=, obj=InnerBean(address=), list=[])
Copy the code

2. Gson source code analysis

To implement scenario 2, let’s do a simple source code analysis to see how Gson converts a JSON string into a concrete entity class. If you only need specific code implementation, skip this section and go straight to Part 3

The following only contains the core code part, the source code part is Java code, the project code is Kotlin, please forgive any discomfort when reading (PS: using Kotlin really do not want to write Java code).

val result = gson.fromJson<TestBean>(jsonString, TestBean::class.java)
Copy the code
  public <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
    //1. Get the type of Class
    Object object = fromJson(json, (Type) classOfT);
    return Primitives.wrap(classOfT).cast(object);
  }
  
  public <T> T fromJson(String json, Type typeOfT) throws JsonSyntaxException {
    if (json == null) {
      return null;
    }
    //2. Get the Reader object from the string data passed in.
    StringReader reader = new StringReader(json);
    T target = (T) fromJson(reader, typeOfT);
    return target;
  }

  public <T> T fromJson(Reader json, Type typeOfT) throws JsonIOException, JsonSyntaxException {
    JsonReader jsonReader = newJsonReader(json);
    //3. Wrap Reader objects to parse {,null,[, etc
    T object = (T) fromJson(jsonReader, typeOfT);
    assertFullConsumption(object, jsonReader);
    return object;
  }

 public <T> T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, JsonSyntaxException {... reader.peek(); isEmpty =false;
      TypeToken<T> typeToken = (TypeToken<T>) TypeToken.get(typeOfT);
      //4. Obtain the corresponding adapter according to the type
      TypeAdapter<T> typeAdapter = getAdapter(typeToken);
      //5. Parses json with adapters and Reader objects and returns the specified object
      T object = typeAdapter.read(reader);
      returnobject; . }Copy the code

As you can see from the code above, step 4 is the key step that determines how the type is converted

 public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {...for (TypeAdapterFactory factory : factories) {
        // run the factories () command
        TypeAdapter<T> candidate = factory.create(this, type);
        if(candidate ! =null) {
          call.setDelegate(candidate);
          typeTokenCache.put(type, candidate);
          returncandidate; }}... }Copy the code

Factories are passed in in the construct of Gson, which opens the API for setting up factories

//GsonBuilder.java
 public GsonBuilder registerTypeAdapterFactory(TypeAdapterFactory factory) {
    factories.add(factory);
    return this;
  } 
  
public Gson create(a) {
    List<TypeAdapterFactory> factories = new ArrayList<TypeAdapterFactory>(this.factories.size() + this.hierarchyFactories.size() + 3);
    factories.addAll(this.factories);
    // The factory we added was flipped
    Collections.reverse(factories);
    List<TypeAdapterFactory> hierarchyFactories = new ArrayList<TypeAdapterFactory>(this.hierarchyFactories);
    Collections.reverse(hierarchyFactories);
    factories.addAll(hierarchyFactories);
    addTypeAdaptersForDate(datePattern, dateStyle, timeStyle, factories);
    return new Gson(excluder, fieldNamingPolicy, instanceCreators,
        serializeNulls, complexMapKeySerialization,
        generateNonExecutableJson, escapeHtmlChars, prettyPrinting, lenient,
        serializeSpecialFloatingPointValues, longSerializationPolicy,
        datePattern, dateStyle, timeStyle,
        this.factories, this.hierarchyFactories, factories);
  } 

//Gson.Java
  Gson(final Excluder excluder, final FieldNamingStrategy fieldNamingStrategy,
      finalMap<Type, InstanceCreator<? >> instanceCreators,boolean serializeNulls,
      boolean complexMapKeySerialization, boolean generateNonExecutableGson, boolean htmlSafe,
      boolean prettyPrinting, boolean lenient, boolean serializeSpecialFloatingPointValues,
      LongSerializationPolicy longSerializationPolicy, String datePattern, int dateStyle,
      int timeStyle, List<TypeAdapterFactory> builderFactories,
      List<TypeAdapterFactory> builderHierarchyFactories,
      List<TypeAdapterFactory> factoriesToBeAdded) {
       List<TypeAdapterFactory> factories = new ArrayList<TypeAdapterFactory>();

    // built-in type adapters that cannot be overridden
    factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);
    factories.add(ObjectTypeAdapter.FACTORY);
    factories.add(excluder);

    / /!!!!!! Here is the factory we added ourselves
    factories.addAll(factoriesToBeAdded);
    // type adapters for basic platform typesfactories.add(TypeAdapters.STRING_FACTORY); factories.add(TypeAdapters.INTEGER_FACTORY); factories.add(TypeAdapters.BOOLEAN_FACTORY); factories.add(TypeAdapters.BYTE_FACTORY); . }Copy the code

FactoriesToBeAdded is set up by registerTypeAdapterFactory factory, they will be added to the Gson default in the front of the adapter, and factories are adpter traversal matching from the very beginning, So we can implement the requirements by overriding the specified type of adapter from our own Adapter logic.

So let’s go to one of the default adapters and look at the parsing logic. Here’s the default adapter for parsing strings, TypeAdapters.STRING_FACTORY

//TypeAdapters.STRING_FACTORY
  public static final TypeAdapter<String> STRING = new TypeAdapter<String>() {
    @Override
    public String read(JsonReader in) throws IOException {
      JsonToken peek = in.peek();
      if (peek == JsonToken.NULL) {
        in.nextNull();
        / /!!!!!! Returns NULL when the field value is NULL
        return null;
      }
      /* coerce booleans to strings for backwards compatibility */
      if (peek == JsonToken.BOOLEAN) {
        return Boolean.toString(in.nextBoolean());
      }
      return in.nextString();
    }
    @Override
    public void write(JsonWriter out, String value) throws IOException { out.value(value); }};Copy the code

For example, if we want the string to be null, parse and return “” instead of NULL

class StringNullAdapter : TypeAdapter<String>() {
        @Throws(IOException::class)
        override fun read(reader: JsonReader): String {
            if (reader.peek() === JsonToken.NULL) {
                reader.nextNull()
                // Only here
                return ""
            }
            return reader.nextString()
        }

        @Throws(IOException::class)
        override fun write(writer: JsonWriter, value: String?). {
            if (value == null) {
                writer.nullValue()
                return
            }
            writer.value(value)
        }
    }
Copy the code

3. Gson non-empty TypeAdapter implementation

//NotNullAdapterFactory.kt
class NotNullAdapterFactory : TypeAdapterFactory {
    override fun <T> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
        val rawType = type.rawType as Class<T>
        return if (rawType == String::class.java) {
            StringNullAdapter() as TypeAdapter<T>
        } else if (rawType == Int: :class.java || rawType == Integer::class.java) {
            IntNullAdapter() as TypeAdapter<T>
        }else if (rawType == Boolean: :class.java) {
            BooleanNullAdapter() as TypeAdapter<T>
        }else if (rawType == Long: :class.java) {
            LongNullAdapter() as TypeAdapter<T>
        }else {
            null}}class StringNullAdapter : TypeAdapter<String>() {
        @Throws(IOException::class)
        override fun read(reader: JsonReader): String {
            if (reader.peek() === JsonToken.NULL) {
                reader.nextNull()
                return ""
            }
            return reader.nextString()
        }

        @Throws(IOException::class)
        override fun write(writer: JsonWriter, value: String?). {
            if (value == null) {
                writer.nullValue()
                return
            }
            writer.value(value)
        }
    }

    class IntNullAdapter : TypeAdapter<Int>() {
        @Throws(IOException::class)
        override fun read(reader: JsonReader): Int {
            if (reader.peek() === JsonToken.NULL) {
                reader.nextNull()
                return 0
            }
            return reader.nextInt()
        }

        @Throws(IOException::class)
        override fun write(writer: JsonWriter, value: Int?). {
            if (value == null) {
                writer.nullValue()
                return
            }
            writer.value(value)
        }
    }

    class BooleanNullAdapter : TypeAdapter<Boolean>() {
        @Throws(IOException::class)
        override fun read(reader: JsonReader): Boolean {
            if (reader.peek() === JsonToken.NULL) {
                reader.nextNull()
                return false
            }
            return reader.nextBoolean()
        }

        @Throws(IOException::class)
        override fun write(writer: JsonWriter, value: Boolean?). {
            if (value == null) {
                writer.nullValue()
                return
            }
            writer.value(value)
        }
    }

    class LongNullAdapter : TypeAdapter<Long>() {
        @Throws(IOException::class)
        override fun read(reader: JsonReader): Long {
            if (reader.peek() === JsonToken.NULL) {
                reader.nextNull()
                return 0
            }
            return reader.nextLong()
        }

        @Throws(IOException::class)
        override fun write(writer: JsonWriter, value: Long?). {
            if (value == null) {
                writer.nullValue()
                return
            }
            writer.value(value)
        }
    }

}
Copy the code

Among them, the object types, and array type is more special, can be the default ReflectiveTypeAdapterFactory CollectionTypeAdapterFactory related file copy out. The modification method is the same as above

The final use is this

val gsonBulder = GsonBuilder()
val f: Field = gsonBulder::class.java.getDeclaredField("instanceCreators")
f.isAccessible = true
val instanceCreator = f.get(gsonBulder) as Map<Type, InstanceCreator<*>>
val constructorConstructor = ConstructorConstructor(instanceCreator)
/ / attention to intercept the order, the source of the above, by adding the factories will be registerTypeAdapterFactory inversion, so finally added will be the first traversal.
gsonBulder.registerTypeAdapterFactory(ReflectiveTypeAdapterFactory(constructorConstructor, FieldNamingPolicy.IDENTITY, Excluder.DEFAULT,JsonAdapterAnnotationTypeAdapterFactory(constructorConstructor)))
            .registerTypeAdapterFactory(CollectionTypeAdapterFactory(constructorConstructor))
            .registerTypeAdapterFactory(NotNullAdapterFactory())
val gson = gsonBulder.create()
Copy the code

Note:

  1. The above code, the author in the project test available.
  2. CollectionTypeAdapterFactory ReflectiveTypeAdapterFactory related code is too long and relatively simple, there is no post, according to the way the adpter NotNullAdapterFactory changes.
  3. NotNullAdapterFactory does not override all default types, but is used in my project.
  4. If you use the ReflectiveTypeAdapterFactory can intercept the default object parsing, it applies only to the custom object, automatic loading is an Integer, and Long is unable to properly resolved, such as if to parse gson appeared, You can refer to the code in NotNullAdapterFactory for judgment.

All done, paste plan two again

/ / entity class
data class TestBean(
    val name:String="".val obj:InnerBean=InnerBean(),
    val list:List<InnerBean> = listOf()
){
    data class InnerBean(
        val address:String="")}/ / target json
val jsonString = "{\"name\":null,\"obj\":null,\"list\":null}"

// Parse the result
TestBean(name=, obj=InnerBean(address=), list=[])
Copy the code

My level is limited, if there is an error, please leave a message. If this article has helped you at all, please give it a thumbs up