[英]how to flatten nested JSON into single class using retrofit and gson converter?
我有一個來自服務器的嵌套 JSON,如您所見,位置中有一個嵌套數據
{
"id": "18941862",
"name": "Pizza Maru",
"url": "https://www.zomato.com/jakarta/pizza-maru-1-thamrin?utm_source=api_basic_user&utm_medium=api&utm_campaign=v2.1",
"location": {
"address": "Grand Indonesia Mall, East Mall, Lantai 3A, Jl. M.H. Thamrin No. 1, Thamrin, Jakarta",
"locality": "Grand Indonesia Mall, Thamrin",
"city": "Jakarta",
"city_id": 74,
"latitude": "-6.1954467635",
"longitude": "106.8216102943",
"zipcode": "",
"country_id": 94,
"locality_verbose": "Grand Indonesia Mall, Thamrin, Jakarta"
},
"currency": "IDR"
}
我正在使用 retrofit 並使用 gson 轉換器。 通常我需要制作 2 個數據 class 到 map JSON 到 POJO。 所以我需要制作 Restaurant class 和 Location class,但我需要將 json object 扁平化為單個 Restaurant class,就像這樣
data class Restaurant : {
var id: String
var name: String
var url: String
var city: String
var latitude: Double
var longitude: Double
var zipcode: String
var currency: String
}
如果我使用 retrofit 和 gson 轉換器,該怎么做?
java 或 kotlin 都可以
此解決方案是解決此問題的靈丹妙葯,值得贊賞。
先拿這個Kotlin文件:
/**
* credits to https://github.com/Tishka17/gson-flatten for inspiration
* Author: A$CE
*/
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FIELD)
annotation class Flatten(val path: String)
class FlattenTypeAdapterFactory(
private val pathDelimiter: String = "."
): TypeAdapterFactory {
override fun <T: Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T> {
val delegateAdapter = gson.getDelegateAdapter(this, type)
val defaultAdapter = gson.getAdapter(JsonElement::class.java)
val flattenedFieldsCache = buildFlattenedFieldsCache(type.rawType)
return object: TypeAdapter<T>() {
@Throws(IOException::class)
override fun read(reader: JsonReader): T {
// if this class has no flattened fields, parse it with regular adapter
if(flattenedFieldsCache.isEmpty())
return delegateAdapter.read(reader)
// read the whole json string into a jsonElement
val rootElement = defaultAdapter.read(reader)
// if not a json object (array, string, number, etc.), parse it
if(!rootElement.isJsonObject)
return delegateAdapter.fromJsonTree(rootElement)
// it's a json object of type T, let's deal with it
val root = rootElement.asJsonObject
// parse each field
for(field in flattenedFieldsCache) {
var element: JsonElement? = root
// dive down the path to find the right element
for(node in field.path) {
// can't dive down null elements, break
if(element == null) break
// reassign element to next node down
element = when {
element.isJsonObject -> element.asJsonObject[node]
element.isJsonArray -> try {
element.asJsonArray[node.toInt()]
} catch(e: Exception) { // NumberFormatException | IndexOutOfBoundsException
null
}
else -> null
}
}
// lift deep element to root element level
root.add(field.name, element)
// this keeps nested element un-removed (i suppose for speed)
}
// now parse flattened json
return delegateAdapter.fromJsonTree(root)
}
override fun write(out: JsonWriter, value: T) {
throw UnsupportedOperationException()
}
}.nullSafe()
}
// build a cache for flattened fields's paths and names (reflection happens only here)
private fun buildFlattenedFieldsCache(root: Class<*>): Array<FlattenedField> {
// get all flattened fields of this class
var clazz: Class<*>? = root
val flattenedFields = ArrayList<Field>()
while(clazz != null) {
clazz.declaredFields.filterTo(flattenedFields) {
it.isAnnotationPresent(Flatten::class.java)
}
clazz = clazz.superclass
}
if(flattenedFields.isEmpty()) {
return emptyArray()
}
val delimiter = pathDelimiter
return Array(flattenedFields.size) { i ->
val ff = flattenedFields[i]
val a = ff.getAnnotation(Flatten::class.java)!!
val nodes = a.path.split(delimiter)
.filterNot { it.isEmpty() } // ignore multiple or trailing dots
.toTypedArray()
FlattenedField(ff.name, nodes)
}
}
private class FlattenedField(val name: String, val path: Array<String>)
}
然后像這樣將它添加到Gson
:
val gson = GsonBuilder()
.registerTypeAdapterFactory(FlattenTypeAdapterFactory())
.create()
Retrofit.Builder()
.baseUrl(baseUrl)
...
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
使用您的示例,您可以像這樣解析 pojo:
// prefer constructor properties
// prefer val over var
// prefer added @SerializedName annotation even to same-name properties:
// to future proof and for easier proguard rule config
data class Restaurant(
@SerializedName("id") val id: String,
@SerializedName("name") val name: String,
@SerializedName("url") val url: String,
@Flatten("location.city") val city: String,
@Flatten("location.latitude") val latitude: Double,
@Flatten("location.longitude") val longitude: Double,
@Flatten("location.zipcode") val zipcode: String,
@SerializedName("currency") var currency: String
)
您甚至可以在數組中寫下路徑,例如@Flatten("friends.0.name")
= 獲取第一個朋友的名字。 有關更多信息,請訪問此存儲庫
但是請注意,我將TypeAdapter
為僅讀取/使用 json 個對象。 如果你也想用它來寫 json ,你可以實現write()
。
不客氣。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.