Retrofit Moshi如何将不同字段的对象数组转换为POJO
Retrofit Moshi How to convert an array of objects with different fields to POJO
我真的很难思考如何将以下 JSON 转换为 Moshi 数据 class。我无法更改 API 的工作方式,因为 API 不是我的。
JSON在下面:
{
"relationships": [
{
"id": "66a36824-04e6-452c-b9d2-679ac589cb8a",
"type": "artist",
"attributes": {
"name": "Nyunyu",
"imageUrl": null,
"biography": [],
"createdAt": "2021-04-19T21:59:45+00:00",
"updatedAt": "2021-04-19T21:59:45+00:00",
"version": 1
}
},
{
"id": "a050afdf-7377-4fe6-8317-fe7b38d1e000",
"type": "cover_art",
"attributes": {
"description": "",
"volume": null,
"fileName": "b0d63a0b-dd95-468c-b3e1-098e1414f6e9.jpg",
"createdAt": "2021-05-24T18:35:46+00:00",
"updatedAt": "2021-05-24T18:35:46+00:00",
"version": 1
}
}
]
}
我发现无法转换,因为如果你有一个相同对象的数组,那么每个对象必须共享相同的结构,对吧!但是在这种情况下,我被迫这样做。注意他们有完全不同的字段。
@JsonClass(generateAdapter = true)
data class Attributes (
@Json(name = "name") val name : String,
@Json(name = "imageUrl") val imageUrl : String,
@Json(name = "biography") val biography : List<String>,
@Json(name = "createdAt") val createdAt : String,
@Json(name = "updatedAt") val updatedAt : String,
@Json(name = "version") val version : Int
)
@JsonClass(generateAdapter = true)
data class Attributes (
@Json(name = "description") val description : String,
@Json(name = "volume") val volume : String,
@Json(name = "fileName") val fileName : String,
@Json(name = "createdAt") val createdAt : String,
@Json(name = "updatedAt") val updatedAt : String,
@Json(name = "version") val version : Int
)
当然,它无法编译,因为我们不能有两个同名的 classes。那么遇到这种情况该怎么办呢?
您需要一个 Moshi 自定义转换器,可能需要创建 2 个 class,例如 ArtistAttributes
和 CoverArtAttributes
,它们从基础 [=14= 继承了它们的一些共同属性] class。然后在自定义转换器中检查类型 json 属性,如果它等于 covert_art
,则创建一个 CoverArtAttributes object,否则创建另一个。
那么在你 parent class 你应该有这样的东西:
data class Relationship constructor (
@Json(name = "id") val id : String,
@Json(name = "type") val type : String,
@Json(name = "attributes") val attributes : Attributes,
)
class AttributesAdapter {
@ToJson
fun toJson(Attributes attributes): String {
//ignore if you don't need opposite conversion
}
@FromJson
fun fromJson(String json): Attributes {
val moshi = Moshi.Builder().build()
val jsonObject = JSONObject(json)
val type = jsonObject.getString("type")
val adapter = when (type) {
"artist" -> {
moshi.adapter(ArtistAttributes::class.java)
}
"cover_art" -> {
moshi.adapter(CoverArtAttributes::class.java)
}
else -> throw IllegalArgumentException("unhandled type")
}
return adapter.fromJson(json)
}
}
然后你需要将AttributesAdapter
添加到parent Moshi实例并用它来转换JSON.
PS: The above code has not been tested, you probably will need to make
adjustments to make it work but it can give you some hints.
有关此主题的更多信息,请在 Internet 上搜索 Moshi custom adapters
,周围有一些有用的资源
看完MatPag的回答后,我决定使用接口来实现我的目标。这是我使用 PolymorphicJsonAdapterFactory
的最终结果
- 在您的应用模块中包含 Moshi 适配器依赖项 Gradle。
implementation "com.squareup.moshi:moshiadapters:$moshi_version"
- 创建共享相同字段的基本接口
interface BaseRelationship {
val type: String
}
- 创建您的数据类 实现步骤 2 中的接口
@JsonClass(generateAdapter = true)
data class RelArtist(
@Json(name = "id") val id: String,
@Json(name = "type") override val type: String,
@Json(name = "attributes") val attributes: RelArtistAttr?
): BaseRelationship
@JsonClass(generateAdapter = true)
data class RelArtistAttr(
@Json(name = "name") val name : String?,
@Json(name = "imageUrl") val imageUrl : String?,
@Json(name = "biography") val biography : List<String>,
@Json(name = "createdAt") val createdAt : String?,
@Json(name = "updatedAt") val updatedAt : String?,
@Json(name = "version") val version : Int?
)
@JsonClass(generateAdapter = true)
data class RelCoverImage(
@Json(name = "id") val id: String,
@Json(name = "type") override val type: String,
@Json(name = "attributes") val attributes: RelCoverImageAttr?
) : BaseRelationship
@JsonClass(generateAdapter = true)
data class RelCoverImageAttr(
@Json(name = "description") val description: String?,
@Json(name = "volume") val volume: String?,
@Json(name = "fileName") val fileName: String?,
@Json(name = "createdAt") val createdAt: String?,
@Json(name = "updatedAt") val updatedAt: String?,
@Json(name = "version") val version: Int?
)
- 在您的
Retrofit
实例中,执行此操作。
val moshi = Moshi.Builder()
.add(PolymorphicJsonAdapterFactory.of(BaseRelationship::class.java, "type")
.withSubtype(RelArtist::class.java, "artist")
.withSubtype(RelCoverImage::class.java, "cover_art")
)
.build()
val retrofit = Retrofit.Builder()
.baseUrl(MangadexService.BASE_URL)
.client(httpClient)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
return retrofit.create(MangadexService::class.java)
第 5 步。在您的 API 调用中,根据您的基本接口内部的内容将其转换为适当的类型
val relationship = it.relationships.firstOrNull { it.type == "cover_art" }
val coverImageRelation = relationship as? RelCoverImage
// Do things with your downcasted object here. . .
我真的很难思考如何将以下 JSON 转换为 Moshi 数据 class。我无法更改 API 的工作方式,因为 API 不是我的。
JSON在下面:
{
"relationships": [
{
"id": "66a36824-04e6-452c-b9d2-679ac589cb8a",
"type": "artist",
"attributes": {
"name": "Nyunyu",
"imageUrl": null,
"biography": [],
"createdAt": "2021-04-19T21:59:45+00:00",
"updatedAt": "2021-04-19T21:59:45+00:00",
"version": 1
}
},
{
"id": "a050afdf-7377-4fe6-8317-fe7b38d1e000",
"type": "cover_art",
"attributes": {
"description": "",
"volume": null,
"fileName": "b0d63a0b-dd95-468c-b3e1-098e1414f6e9.jpg",
"createdAt": "2021-05-24T18:35:46+00:00",
"updatedAt": "2021-05-24T18:35:46+00:00",
"version": 1
}
}
]
}
我发现无法转换,因为如果你有一个相同对象的数组,那么每个对象必须共享相同的结构,对吧!但是在这种情况下,我被迫这样做。注意他们有完全不同的字段。
@JsonClass(generateAdapter = true)
data class Attributes (
@Json(name = "name") val name : String,
@Json(name = "imageUrl") val imageUrl : String,
@Json(name = "biography") val biography : List<String>,
@Json(name = "createdAt") val createdAt : String,
@Json(name = "updatedAt") val updatedAt : String,
@Json(name = "version") val version : Int
)
@JsonClass(generateAdapter = true)
data class Attributes (
@Json(name = "description") val description : String,
@Json(name = "volume") val volume : String,
@Json(name = "fileName") val fileName : String,
@Json(name = "createdAt") val createdAt : String,
@Json(name = "updatedAt") val updatedAt : String,
@Json(name = "version") val version : Int
)
当然,它无法编译,因为我们不能有两个同名的 classes。那么遇到这种情况该怎么办呢?
您需要一个 Moshi 自定义转换器,可能需要创建 2 个 class,例如 ArtistAttributes
和 CoverArtAttributes
,它们从基础 [=14= 继承了它们的一些共同属性] class。然后在自定义转换器中检查类型 json 属性,如果它等于 covert_art
,则创建一个 CoverArtAttributes object,否则创建另一个。
那么在你 parent class 你应该有这样的东西:
data class Relationship constructor (
@Json(name = "id") val id : String,
@Json(name = "type") val type : String,
@Json(name = "attributes") val attributes : Attributes,
)
class AttributesAdapter {
@ToJson
fun toJson(Attributes attributes): String {
//ignore if you don't need opposite conversion
}
@FromJson
fun fromJson(String json): Attributes {
val moshi = Moshi.Builder().build()
val jsonObject = JSONObject(json)
val type = jsonObject.getString("type")
val adapter = when (type) {
"artist" -> {
moshi.adapter(ArtistAttributes::class.java)
}
"cover_art" -> {
moshi.adapter(CoverArtAttributes::class.java)
}
else -> throw IllegalArgumentException("unhandled type")
}
return adapter.fromJson(json)
}
}
然后你需要将AttributesAdapter
添加到parent Moshi实例并用它来转换JSON.
PS: The above code has not been tested, you probably will need to make adjustments to make it work but it can give you some hints.
有关此主题的更多信息,请在 Internet 上搜索 Moshi custom adapters
,周围有一些有用的资源
看完MatPag的回答后,我决定使用接口来实现我的目标。这是我使用 PolymorphicJsonAdapterFactory
- 在您的应用模块中包含 Moshi 适配器依赖项 Gradle。
implementation "com.squareup.moshi:moshiadapters:$moshi_version"
- 创建共享相同字段的基本接口
interface BaseRelationship {
val type: String
}
- 创建您的数据类 实现步骤 2 中的接口
@JsonClass(generateAdapter = true)
data class RelArtist(
@Json(name = "id") val id: String,
@Json(name = "type") override val type: String,
@Json(name = "attributes") val attributes: RelArtistAttr?
): BaseRelationship
@JsonClass(generateAdapter = true)
data class RelArtistAttr(
@Json(name = "name") val name : String?,
@Json(name = "imageUrl") val imageUrl : String?,
@Json(name = "biography") val biography : List<String>,
@Json(name = "createdAt") val createdAt : String?,
@Json(name = "updatedAt") val updatedAt : String?,
@Json(name = "version") val version : Int?
)
@JsonClass(generateAdapter = true)
data class RelCoverImage(
@Json(name = "id") val id: String,
@Json(name = "type") override val type: String,
@Json(name = "attributes") val attributes: RelCoverImageAttr?
) : BaseRelationship
@JsonClass(generateAdapter = true)
data class RelCoverImageAttr(
@Json(name = "description") val description: String?,
@Json(name = "volume") val volume: String?,
@Json(name = "fileName") val fileName: String?,
@Json(name = "createdAt") val createdAt: String?,
@Json(name = "updatedAt") val updatedAt: String?,
@Json(name = "version") val version: Int?
)
- 在您的
Retrofit
实例中,执行此操作。
val moshi = Moshi.Builder()
.add(PolymorphicJsonAdapterFactory.of(BaseRelationship::class.java, "type")
.withSubtype(RelArtist::class.java, "artist")
.withSubtype(RelCoverImage::class.java, "cover_art")
)
.build()
val retrofit = Retrofit.Builder()
.baseUrl(MangadexService.BASE_URL)
.client(httpClient)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
return retrofit.create(MangadexService::class.java)
第 5 步。在您的 API 调用中,根据您的基本接口内部的内容将其转换为适当的类型
val relationship = it.relationships.firstOrNull { it.type == "cover_art" }
val coverImageRelation = relationship as? RelCoverImage
// Do things with your downcasted object here. . .