改装Moshi如何将具有不同字段的对象数组转换为POJO



我很难思考如何将以下JSON转换为Moshi数据类。我也不能改变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
)

当然,它不能编译,因为我们不能有两个同名的类。那么在这种情况下我该怎么办呢?

您需要一个Moshi自定义转换器,可能需要创建两个类,如ArtistAttributesCoverArtAttributes,它们从基本Attributes类继承了一些公共属性。然后在自定义转换器中检查json类型属性,如果它等于covert_art,则创建一个CoverArtAttributes对象,否则创建另一个。

那么在你的家长课堂上,你应该有这样的东西:

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添加到父Moshi实例中,并使用它来转换JSON。

PS:上述代码尚未经过测试,您可能需要制作调整以使其发挥作用,但它可以给你一些提示。

有关此主题的更多信息,请在Internet上搜索Moshi custom adapters,这里有一些关于的有用资源

在阅读了MatPag的答案后,我决定使用接口来实现我的目标。这是我使用PolymorphicJsonAdapterFactory的最终结果

  1. 在应用程序模块Gradle中包含Moshi适配器依赖项
implementation "com.squareup.moshi:moshiadapters:$moshi_version"
  1. 创建共享同一字段的基本界面
interface BaseRelationship {
val type: String
}
  1. 在步骤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?
)
  1. 在您的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. . .

最新更新