如何使用retrofit和gson转换器将嵌套JSON扁平化为单个类?

m3eecexj  于 2022-11-06  发布在  其他
关注(0)|答案(1)|浏览(146)

我有一个来自服务器的嵌套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个数据类来将JSONMap到POJO。因此我需要创建Restaurant类和Location类,但我需要将json对象扁平化为单个的Restaurant类,如下所示

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 

}

如何做到这一点,如果我使用的是改型和gson转换器?
java或Kotlin都可以

j0pj023g

j0pj023g1#

这个解决方案是解决这个问题的银,怎么欣赏都不为过。
先看这个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") = get first friend 's name。
但是,请注意,我将TypeAdapter剥离为只读取/使用json对象。如果您也想使用write()来编写json,则可以实现它。
不客气。

相关问题