Although Retrofit has handled the JSON conversion perfectly with Converter-Gson in network requests, it is inevitable that we will have to do the JSON conversion manually in real development. In this case, you need to use Google’s official JSON conversion framework -Gson. In fact, Converter – Gson is also based on GSON. Gson is pretty well packaged, but there are a lot of caveats when it comes to usage. Next, start from the beginning and trust me, spend 20 minutes reading this article and you will have a complete grasp of the various poses of Gson.
Introduced a.
Gson is sometimes used by other packages that are directly included in other dependencies, such as Retrofit’s Converter-gson. But Gson can also be introduced separately:
Implementation 'com. Google. Code. Gson: gson: 2.8.6'Copy the code
Two. Basic usage
2.1 Entity Class to JSON
There’s nothing to say about this, just one sentence:
Gson().toJson(entity)
Copy the code
2.2 Json to Entity Class
There’s only one way to do it
Gson().fromJson("<json>",Entity::class.java)
Copy the code
This seems simple enough, but there are pits, too, for example, if the Entity here is a BaseEntity, which contains the stereotype.
Can YOU convert Json? (More on that later)
2.3 Android Studio generates entity classes
Now you have the following Json data:
{
"code":0,
"message":"",
"content":{
"city":"cheng du",
"weather":"sunny",
"tem":16,
"date":"2020-10-19"
}
}
Copy the code
Is a simple weather data, where code and message are the status code and exception information returned by the server. Code =0 indicates that the request is normal. It is easy to understand the meaning of other fields.
To use this Json data, we first need to create the entity class. Fortunately, Android Studio already has a plugin for one-click generation. Search for “Json” in the plugin.
For now, my project uses Kotlin entirely, so I’ll just install the first plug-in as follows: Create a random.kt file for Kotlin, such as TestEntity in my case, and right-click ->Generate– >Kotlin data class from JSON
Create an entity class with the following content (note manually adding the Serializable interface)
import java.io.Serializable
data class TestEntity(
val code: Int,
val content: Content,
val message: String
) : Serializable
data class Content(
val city: String,
val date: String,
val tem: Int,
val weather: String
) : Serializable
Copy the code
2.4 Actual operations Perform the conversion
I added the following method to MainActivity:
val TAG="JsonTest" private fun start() { val json="{\n" + " \"code\":0,\n" + " \"message\":\"\",\n" + " \"content\":{\n" + " \"city\":\"cheng du\",\n" + " \"weather\":\"sunny\",\n" + " \"tem\":16,\n" + " \"date\":\"2020-10-19\"\n" + " }\n" + "} "try {/ / json entity class val entity = Gson (). FromJson (json, TestEntity: : class. Java) enter the d (TAG," converting json entity class - complete ") Log.d(TAG," city: ${entity.content.city}") log.d (TAG," weather: ${entity.content.weather}") log.d (TAG," temperature: ${entity.content.city}") ${entity.content.tem}") log. d(TAG," time: ${entity.content.date}") val json2=Gson().tojson (entity) log.d (TAG," entity class converted toJson -- done ") log.d (TAG,json2)}catch (e:Exception){log. d(TAG,"json format error ")}}Copy the code
This method executes the result
Advanced usage – dealing with stereotypes in entity classes
Generally, in network requests, although the contents of each interface are different, the outermost layer has the same format. For example, the outer layer of JSON data above contains code and Message. This is the server’s unified response to all interface requests, so usually in development, we need to deal with the outermost data uniformly, which needs to use the stereotype to wrap the entity class.
Again, based on the previous JSON data, first create a BaseEntity class with the following contents:
import java.io.Serializable
data class BaseEntity<T>(
val code: Int = 0,
val message: String = "",
val content: T?
) : Serializable
Copy the code
This entity class is an outer wrapper around the data returned by all interfaces, and then uses stereotypes to represent the different data in the Content. I usually give a default value for non-null data in the entity class. I try to avoid null Pointers, but this is just a personal habit. Then create an entity WeatherEntity for the json data of the layer:
import java.io.Serializable
class WeatherEntity(
val city: String = "",
val date: String = "",
val tem: Int = 0,
val weather: String = ""
) : Serializable
Copy the code
So our entire entity class has changed from TestEntity to —->BaseEntity
. Is it possible to convert json the same way as before? Try modifying the previous start method:
As you can see, the Kotlin compilation fails when the data entity class contains a stereotype. In this case, there is another way to pass in the entity class Type to the fromJson to do the JSON conversion. Change the code for the transformation in the start method to the following:
val type = object : TypeToken<BaseEntity<WeatherEntity>>() {}.type
val entity: BaseEntity<WeatherEntity>? = Gson().fromJson<BaseEntity<WeatherEntity>>(json, type)
Copy the code
That will do
Advanced usage – encountered generic type erasure problems and solutions
#####4.1 Background Create an abstract HttpCallback class. The code is shown in the following figure. The abstract HttpCallback class looks familiar if you use Rxjava+Retrofit for network requests
/** * HttpCallback<T> { String) {} // Request success. Abstract Fun onSuccess(Entity: T) // Server response error. Open Fun onHttpError(code: Int, message: "throwable: throwable") {} Open fun onHttpError(code: Int, message: String) {// Uniform error handling}}Copy the code
Briefly, in this callback, onNext takes the data returned by the server, processes it and converts it to JSON, converts the JSON data into the entity object corresponding to the template, and then passes the object out in onSuccess. Of course, there are many errors and null values in the middle of the judgment and processing. After setting up the callback in MainActivity, you only need to care about the entity object in onSuccess and other error messages, which is very convenient. We now modify the start method as follows:
Private fun start() {// Set anonymous class callBack object val callBack = object: HttpCallback<WeatherEntity>() { override fun onSuccess(entity: WeatherEntity) {log.d (TAG, "JSON converted to entity class -- done ") log.d (TAG," city: ${entity.city}") log.d (TAG, "weather: ${entity.weather}") log. d(TAG, "temperature: ${entity.tem}") log. d(TAG," time: ${entity.date}")} Override fun onResponseError(code: Int, message: String) { super.onResponseError(code, message) } override fun onHttpError(code: Int, message: String) { super.onHttpError(code, Simulation data message)}} / / val data = "{\ n" + "\" code \ ": 0, \ n" + "\" message \ ": \" \ ", \ n "+" \ "content \" : {\ n "+" \"city\":\"cheng du\",\n" + " \"weather\":\"sunny\",\n" + " \"tem\":16,\n" + " \"date\":\"2020-10-19\"\n" + " }\n" + "}" CallBack. OnNext (data)}Copy the code
#####4.2 Problem introduction In the above code, it is really convenient to use in Mactivity, there is a unified place for JSON conversion and unified error handling. Just pass in the stereotype and process the result in onSuccess. Now the question is, how do we convert JSON into the entity object corresponding to the T stereotype in HttpCallback?
Is it ok to use the previous method to convert again? Modify the onNext code as follows:
Fun onNext(data: String) {val json = data val type = object: TypeToken<BaseEntity<T>>() {}.type val bean = Gson().fromJson<BaseEntity<T>>(json, type) if (bean.content ! = null) {onSuccess(bean.content)} else {onResponseError(-1)}}Copy the code
Gson reported an error:
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to WeatherEntity
Copy the code
Because the generic types in the abstract HttpCallback class have been erased, Gson does not know what object to convert the data to. Forced conversion to WeatherEntity crashes. Below, I will introduce two solutions. The primary one is the one I wrote at the beginning, and the advanced one is a solution given by a senior in the company after consulting him. #####4.3 Primary solution: Gson().fromjson is the core method for converting json to an entity-class object, focusing on the following two overwrites
public <T> T fromJson(String json, Type typeOfT) throws JsonSyntaxException {
if (json == null) {
return null;
}
StringReader reader = new StringReader(json);
T target = (T) fromJson(reader, typeOfT);
return target;
}
Copy the code
The conversion is done by Class
public <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
Object object = fromJson(json, (Type) classOfT);
return Primitives.wrap(classOfT).cast(object);
}
Copy the code
Here’s my onNext code:
Fun onNext(data: String) {// If data needs to be decrypted, get json data by decrypting val json = data try{// Get BaseEntity val type = object for the first time: TypeToken<BaseEntity<Any>>() {}.type = Gson().fromjson <BaseEntity<Any>>(json, type) Log.d(TAG, Code :${basebean.code} message:${basebean.message}") // The second transform gets content // The generic Class val classT = (javaClass. GenericSuperclass as ParameterizedType). ActualTypeArguments [0] as Class < T > / / for the first time the transformed the content of a json string val Val bean = Gson().fromjson (contentJson, classT) log.d (TAG, OnSuccess (bean)}catch (e:Exception){onResponseError(-1, "json format error ")}}Copy the code
The comments are clear enough, but here’s the idea: After we get json first, because all the data in the network request is wrapped through BaseEntity: The first step is to convert json to BaseEntity object baseBean, Get code and message do some exception checking and convert basebean.content via Gson().tojson (basebean.content) to the corresponding JSON string contentJson step 2 Get the corresponding Class of T, do the secondary conversion through fromJson, and finally get the entity Class object we want, and pass it to onSuccess
/ / get a generic Class val classT = (javaClass. GenericSuperclass as ParameterizedType.) actualTypeArguments [0] as Class < T > Var contentJson = Gson().tojson (basebean.content) Gson().fromJson(contentJson, classT)Copy the code
Of course, this is just shorthand, but there are also some code judgments, non-null judgments, that need to be reported as exceptions.
This is enough to solve the problem of generic type erasure, but you can see that you have converted JSON to an object twice and an object to JSON once. If every interface had to do this for every request, it would be a waste of memory, so let’s take a look at the corporate approach and do it in one conversion. #####4.4 Advanced solution HttpCallback
/** */ abstract class HttpCallback<T> {val TAG = "JsonTest"; String) {/ / if need decrypt the data The json data obtained by decrypting val json = data val classT = (javaClass. GenericSuperclass as ParameterizedType).actualTypeArguments[0] as Class<T> val jsonType = ResultParameterType(classT) val baseBean = try { Gson().fromJson<BaseEntity<T>>(json, jsonType) } catch (e: Exception) { null } if(baseBean?.content ! = null && basebean.code == 0){onSuccess(basebean.content)}} T) open fun onResponseError(code: Int, message: String) { Throwable) {} Open fun onHttpError(code: Int, message: String) {// ResultParameterType(private val type: type): ParameterizedType {Override fun getRawType(): Type { return BaseEntity::class.java } override fun getOwnerType(): Type? { return null } override fun getActualTypeArguments(): Array<Type> { return arrayOf(type) } } }Copy the code
And you can see it’s done just once, perfect.
However, I don’t fully understand the code, and I’m still learning it. My brother told me that I need to figure out what arguments the Gson().fromjson method needs, either Class or Type. You just need to have the parameters it needs passed to it, either from BaseEntity or from T, but the end result is different.
In short, the way of learning is long and encouraging.