首页 > 解决方案 > 使用 GSON 和 Retrofit 解析嵌套的多态对象

问题描述

我正在尝试显示具有不同类型 ViewHolders 的消息列表,即文本、ImageText、视频等。我从 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 $76.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()

通知>元数据>正文可以是多态的。我有一组延伸到 BaseMessageListItem 的类(用于 ImageItem、ImageWithTextItem、VideoItem 等)。

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()

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

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

标签: androidparsinggson

解决方案


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

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()

}

推荐阅读