使用 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()
}
我正在尝试显示具有不同类型 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()
}