如何为多个类似的 API JSON 响应结构的多个数据类编写 Kotlin 基类 - 已关闭

发布于 01-15 02:25 字数 2284 浏览 3 评论 0原文

所有 API JSON 响应都将具有以下结构:

{
    "status": <Integer>
    "data": <Object or List of Objects>
    "message": <String>
}

唯一更改的属性是“数据”,它可以是任何对象或对象列表。 那么有没有办法创建像

open class BaseResponse<T> (
    @SerializedName("status")
    val status: Int,
    @SerializedName("data")
    abstract val `data`: T,
    @SerializedName("message")
    val message: String
)

响应类这样的 BaseResponse 类

data class HelloResponse (
    override val `data`: Hello
) : BaseResponse<Hello> {
    data class Hello (
        @SerializedName("hello")
        val hello: String
    )
}


data class HellosResponse (
    override val `data`: List<Hello>
) : BaseResponse<List<Hello>> {
    data class Hello (
        @SerializedName("hello")
        val hello: String
    )
}

我真正想要的是仅覆盖 data 属性,这样我就不必编写我编写的每个响应子数据类的状态和消息属性。我不想在子类中写入状态和消息并将其传递给基类,因为我仍然会写入这两个属性,因此与创建具有状态和消息的数据类没有区别。

所以不能像

data class HelloResponse (
    val status: Int,
    override val `data`: Hello,
    val message: String
) : BasicResponse<Hello>(status, `data`, message) {
    data class Hello (
        @SerializedMessage("hello")
        val hello: String
    )
}

编辑:自己的答案

那么我意识到HelloResponse实际上是一种浪费,因为我只是用它来访问实际的Hello类。 所以我所做的就是直接在Retrofit2服务中使用Base类。 例如:

fun hello(): Call<BaseResponse<Hello>>

or

fun hellos(): Call<BaseResponse<List<Hello>>>

你必须在使用 BaseResponse 的任何地方直接指定类型。也许创建 typeallias

或者您可以创建别名

typealias HelloResponse = BaseResponse<Hello>

typealias HellosResponse = BaseResponse<List<Hello>>

要使用 Gson 手动反序列化 json 字符串,您需要使用 TypeToken 参数而不是类类型。

val hello = Gson().fromJson<BaseResponse<Hello>>(jsonStr, object: TypeToken<BaseResponse<Hello>>(){}.type)

如果使用

val hello = Gson().fromJson<BaseResponse<Hello>>(jsonStr, BaseResponse::class.java)

data 属性不会转换为 Hello 而是转换为 LinkedHashMap

注意: Retrofit2的GsonConverterFactory内部使用了TypeToken,所以没问题。

All the API JSON responses would have the following structure:

{
    "status": <Integer>
    "data": <Object or List of Objects>
    "message": <String>
}

the only property that changes is the 'data', which can be any object or list of object.
So is there a way to create a BaseResponse class like

open class BaseResponse<T> (
    @SerializedName("status")
    val status: Int,
    @SerializedName("data")
    abstract val `data`: T,
    @SerializedName("message")
    val message: String
)

and the response classes

data class HelloResponse (
    override val `data`: Hello
) : BaseResponse<Hello> {
    data class Hello (
        @SerializedName("hello")
        val hello: String
    )
}


data class HellosResponse (
    override val `data`: List<Hello>
) : BaseResponse<List<Hello>> {
    data class Hello (
        @SerializedName("hello")
        val hello: String
    )
}

What i really want is to only override the data property, so that i don't have to write status and message property for each Response sub data class i write. I dont want to write status and message in my sub class and pass it to base class, cause i'd still write both the properties, so no difference than creating a data class with status and message.

so cannot be like

data class HelloResponse (
    val status: Int,
    override val `data`: Hello,
    val message: String
) : BasicResponse<Hello>(status, `data`, message) {
    data class Hello (
        @SerializedMessage("hello")
        val hello: String
    )
}

Edit: Own Answer

Well I realized that the HelloResponse is actually a waste since i'm only using it to access the actual Hello class.
So what i did was to use the Base class directly in Retrofit2 service.
Eg:

fun hello(): Call<BaseResponse<Hello>>

or

fun hellos(): Call<BaseResponse<List<Hello>>>

Well you have to directly specify the type with BaseResponse everywhere you use it. Maybe create typeallias

Or you can create alias

typealias HelloResponse = BaseResponse<Hello>

typealias HellosResponse = BaseResponse<List<Hello>>

To manually deserialize json string with Gson, you need to use TypeToken parameter instead of class type.

val hello = Gson().fromJson<BaseResponse<Hello>>(jsonStr, object: TypeToken<BaseResponse<Hello>>(){}.type)

If you use

val hello = Gson().fromJson<BaseResponse<Hello>>(jsonStr, BaseResponse::class.java)

The data property doesn't convert to Hello instead converts to LinkedHashMap

Note:
Retrofit2's GsonConverterFactory uses TypeToken internally, so no problem.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

软糖2025-01-22 02:25:56

如果您不想为数据子类编写 statusmessage 属性,那么您不能期望子类具有带有 status的构造函数>消息神奇地。

我强烈建议您将 BaseResponse 抽象化并创建子类,如下所示

abstract class BaseResponse<T> {
    @SerializedName("status")
    abstract val status: Int

    @SerializedName("message")
    abstract val message: String

    @SerializedName("data")
    abstract val `data`: T
}

data class HelloResponse (
    override val status: Int,
    override val message: String,
    override val `data`: Hello,
) : BaseResponse<Hello>() {
    data class Hello (
        @SerializedMessage("hello")
        val hello: String
    )
}

您可以通过牺牲数据类的方式实现它,而无需为子类声明编写重写 val 属性。但是,您将失去数据类提供的所有奖励。

abstract class BaseResponse<T> {
    @SerializedName("status")
    abstract val status: Int

    @SerializedName("message")
    abstract val message: String

    @SerializedName("data")
    abstract val `data`: T
}

class HelloResponse: BaseResponse<Hello>() {
    data class Hello (
        @SerializedMessage("hello")
        val hello: String
    )
}

温馨提示,如果类属性名称和 json 属性名称相同,则不需要使用 @SerializedName 注解。

If you don't want to write status and message properties for data subclasses then you cannot expect subclass to have a constructor with status and message magically.

I strongly suggest you to make BaseResponse abstract and make subclasses like following

abstract class BaseResponse<T> {
    @SerializedName("status")
    abstract val status: Int

    @SerializedName("message")
    abstract val message: String

    @SerializedName("data")
    abstract val `data`: T
}

data class HelloResponse (
    override val status: Int,
    override val message: String,
    override val `data`: Hello,
) : BaseResponse<Hello>() {
    data class Hello (
        @SerializedMessage("hello")
        val hello: String
    )
}

You can achieve it in a way you don't need to write override val properties for subclass declarations by sacrificing data classes. However you lose all bounties provided by data class.

abstract class BaseResponse<T> {
    @SerializedName("status")
    abstract val status: Int

    @SerializedName("message")
    abstract val message: String

    @SerializedName("data")
    abstract val `data`: T
}

class HelloResponse: BaseResponse<Hello>() {
    data class Hello (
        @SerializedMessage("hello")
        val hello: String
    )
}

Just a kind reminder, you don't need to use @SerializedName annotation if class property name and json property name are same.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文