使用 GSON 和 Retrofit 解析嵌套多态对象

Parsing Nested Polymorphic Objects with GSON and Retrofit

我正在尝试显示具有不同类型 ViewHolder(即文本、图像文本、视频等)的消息列表。我从 API 中以这种格式获得了这些对象的列表:

{
 "message":"success",
 "total_pages":273,
 "current_page":1,
 "page_size":10,
 "notifications":[
  {
     "id":4214,
     "notification_message":"test notification 1",
     "meta_data":{
        "messageId":"19819189",
        "viewHolderType":"textOnly",
        "body":{
           "time":"10-06-21T02:31:29,573",
           "type":"notification",
           "title":"Hi, Welcome to the NT experience",
           "description":"This is the welcome message",
           "read":true
        }
     }
  },
  {
     "id":9811,
     "notification_message":"test vss notification",
     "meta_data":{
        "messageId":"2657652",
        "viewHolderType":"textWithImage",
        "body":{
           "time":"11-06-21T02:31:29,573",
           "type":"promotions",
           "title":"Your Package - Premium",
           "description":"Thank you for subscribing to the package. Your subscription entitles you to Premium 365 Days Plan (worth .61)",
           "headerImage":"www.someurl.com/image.jpg",
           "read":true
        }
     }
  }
 ]
}

现在我必须从客户端模块的网络模块中解析这个列表,客户端模块将只使用 meta_data 中的对象。为此,我创建了以下 类:

open class BaseMessageListItem

internal data class MessageListResponse(
@field:SerializedName("current_page")
val current_page: Int,

@field:SerializedName("notifications")
val notifications: List<MessageListItem>,

@field:SerializedName("message")
val message: String,

@field:SerializedName("page_size")
val page_size: Int,

@field:SerializedName("total_page")
val total_page: Int
)

internal data class MessageListItem(
@field:SerializedName(“id”)
val id: String,

@field:SerializedName("notification_message")
val notification_message: String,

 @field:SerializedName("meta_data")
val meta_data: MessageListMetaDataItem,
)


internal data class MessageListMetaDataItem(
@field:SerializedName("messageId")
val messageId: String = "",

@field:SerializedName("viewHolderType")
val viewHolderType: String = "",

@field:SerializedName("body")
val body: BaseMessageListItem = BaseMessageListItem() 
)

internal data class ImageMessageListItem(
@field:SerializedName("description")
val description: String,

@field:SerializedName("headerImage")
val headerImage: String,

@field:SerializedName("read")
val read: Boolean,

@field:SerializedName("time")
val time: String,

@field:SerializedName("title")
val title: String,

@field:SerializedName("type")
val type: String
): BaseMessageListItem()

internal data class TextMessageListItem(
@field:SerializedName("description")
val description: String,

@field:SerializedName("read")
val read: Boolean,

@field:SerializedName("time")
val time: String,

@field:SerializedName("title")
val title: String,

@field:SerializedName("type")
val type: String
): BaseMessageListItem()

通知>meta_data>正文可以是多态的。我有一组 类(用于 ImageItem、ImageWithTextItem、VideoItem 等)扩展到 BaseMessageListItem。

private var runtimeTypeAdapterFactory: RuntimeTypeAdapterFactory<BaseMessageListItem> = RuntimeTypeAdapterFactory
    .of(BaseMessageListItem::class.java, "viewHolderType")
    .registerSubtype(ImageMessageListItem::class.java, MessageListItemTypes.TEXT_WITH_IMAGE.value)
    .registerSubtype(TextMessageListItem::class.java, MessageListItemTypes.TEXT_ONLY.value)

private var gson: Gson = GsonBuilder()
    .registerTypeAdapterFactory(runtimeTypeAdapterFactory)
    .create()

我尝试使用 RuntimeTypeAdapterFactory 中的 viewHolderType 解析它,但由于它不是 BaseMessageListItem 的 属性,因此无法解析它。

任何人都有处理这种类型的经验JSON,请分享任何指示。

我可能理解错了,但我想建议一种不同的方法。我假设您想直接从 API 响应中获得 ViewHolder 类型。

我想推荐两种方法:

  • 首先,如果可以修改 API 响应,我建议将 viewHolderType 从 String 更改为 Int,这样您就可以清楚地了解您的映射,然后您可以直接对比。
  • 其次,我建议在您的数据 class 中保留另一个键,它根据收到的 viewHolderType 设置值,如下所示。
internal data class MessageListMetaDataItem(
        @field:SerializedName("messageId")
        val messageId: String = "",

        @field:SerializedName("viewHolderType")
        val viewHolderType: String = "",

        @field:SerializedName("body")
        val body: BaseMessageListItem = BaseMessageListItem()
) {
    val viewHolderMapping: Int
    get() = when(viewHolderType){
        "textOnly" -> MessageListItemTypes.TEXT_ONLY
        "textWithImage" -> MessageListItemTypes.TEXT_WITH_IMAGE
        else -> MessageListItemTypes.UNKNOWN_TYPE
    }
}

RuntimeTypeAdapterFactory 要求将 viewHolderType 字段放入 body 对象中。为了解决这个问题,你有 补丁 RuntimeTypeAdapterFactory(它甚至没有作为已编译的 JAR 发布,但仍作为源代码保留在 public 存储库中,可以自由修改),或者修复你的 class 层次结构以提升缺少的字段,因为它只能与同一嵌套级别的字段一起使用。

internal var gson: Gson = GsonBuilder()
        .registerTypeAdapterFactory(
                RuntimeTypeAdapterFactory.of(BaseMessageListMetaDataItem::class.java, "viewHolderType")
                        .registerSubtype(TextWithImageMessageListMetaDataItem::class.java, "textWithImage")
                        .registerSubtype(TextOnlyMessageListMetaDataItem::class.java, "textOnly")
        )
        .create()

internal data class MessageListItem(
        @field:SerializedName("meta_data")
        val metaData: BaseMessageListMetaDataItem<*>?,
)

internal abstract class BaseMessageListMetaDataItem<out T>(
        @field:SerializedName("viewHolderType")
        val viewHolderType: String?,
        @field:SerializedName("body")
        val body: T?
) where T : BaseMessageListMetaDataItem.Body {

    internal abstract class Body

}

internal class TextOnlyMessageListMetaDataItem
    : BaseMessageListMetaDataItem<TextOnlyMessageListMetaDataItem.Body>(null, null) {

    internal data class Body(
            @field:SerializedName("title")
            val title: String?
    ) : BaseMessageListMetaDataItem.Body()

}

internal class TextWithImageMessageListMetaDataItem
    : BaseMessageListMetaDataItem<TextWithImageMessageListMetaDataItem.Body>(null, null) {

    internal data class Body(
            @field:SerializedName("title")
            val title: String?,
            @field:SerializedName("headerImage")
            val headerImage: String?
    ) : BaseMessageListMetaDataItem.Body()

}