我想知道是否有一种可行的方法可以通过使用自定义解码器初始化程序和/或多个容器和编码键来跨多个模型共享公共属性。这是我想要映射到相应的可编码模型的 JSON 对象:
JSON 对象
我想要映射到的属性Codable is 'sprites'.
正如你所看到的,很多属性都像back_default、back_female 等..可以在其他模型之间共享,例如精灵->其他->dream_world,精灵->其他->官方艺术作品 and in 精灵->版本->第一代->红蓝
我的目标是能够使用一个共享模型并自定义他的解码器(也许通过使用多个容器),因此我可以说,例如:如果当前要解码的密钥是“官方艺术作品“仅解码此模型对象公开的所有 CodingKey 的子集(也许我需要使用另一个子容器?),因此我将只有 1 个密钥的解码字段官方艺术作品, 2 个密钥的解码字段梦想世界等等。
[EDIT]
目前我想出的解决方案是other关键是这个,但有些属性是共享的,我认为有一个更好的解决方案(如果我理解我正确地做到了这一点,我可以将其应用到versions模型的关键):
struct PokemonSpritesModel: Codable {
//MARK: Properties
var back_default: String?
var back_female: String?
var back_shiny: String?
var back_shiny_female: String?
var front_default: String?
var front_female: String?
var front_shiny: String?
var front_shiny_female: String?
var other: PokemonSpritesModelOther?
var versions: PokemonSpritesModelVersions?
}
struct PokemonSpritesModelOther: Codable {
//MARK: Properties
var dreamWorld: PokemonSpritesModelOtherDreamWorld?
var officialArtwork: PokemonSpritesModelOtherOfficialArtwork?
private enum CodingKeys: String, CodingKey {
case officialArtwork = "official-artwork", dreamWorld = "dream_world"
}
struct PokemonSpritesModelOtherDreamWorld: Codable {
//MARK: Properties
var front_default: String?
var front_female: String?
}
struct PokemonSpritesModelOtherOfficialArtwork: Codable {
//MARK: Properties
var front_default: String?
}
}
我可以使用之前的模型并将其扩展为所有其他 json 对象来表示,但这将需要编写大量重复代码,我认为有比我目前正在做的更好的方法。我读过很多 SO 和 Medium 博客文章,但它们对我没有帮助。
我不确定我所要求的是否可以做Swift.
[EDIT 2]
我认为最好从头开始,让其他人了解我正在努力实现的目标。目前我对该 JSON 结构的可编码是这样的:
struct PokemonDetailsModel: Decodable {
// MARK: Properties
var name: String?
var base_experience: Int?
var order: Int?
var sprites: PokemonSpritesModel?
var stats: [PokemonStatsModel]?
var types: [PokemonTypesModel]?
}
struct PokemonSpritesModel: Decodable {
//MARK: Properties
// ======== SET Of keys that are repeated
var back_default: String?
var back_female: String?
var back_shiny: String?
var back_shiny_female: String?
var front_default: String?
var front_female: String?
var front_shiny: String?
var front_shiny_female: String?
// ======== SET Of keys that are repeated
var other: PokemonSpritesModelOther?
var versions: PokemonSpritesModelVersions?
}
struct PokemonSpritesModelOther: Decodable {
//MARK: Properties
var dreamWorld: PokemonSpritesModelOtherDreamWorld?
var officialArtwork: PokemonSpritesModelOtherOfficialArtwork?
private enum CodingKeys: String, CodingKey {
case officialArtwork = "official-artwork", dreamWorld = "dream_world"
}
struct PokemonSpritesModelOtherDreamWorld: Decodable {
//MARK: Properties
var front_default: String?
var front_female: String?
}
struct PokemonSpritesModelOtherOfficialArtwork: Decodable {
//MARK: Properties
var front_default: String?
}
}
struct PokemonSpritesModelVersions: Decodable {
//MARK: Properties
var generation_i: PokemonSpritesModelVersionsGenerationsI?
var generation_ii: PokemonSpritesModelVersionsGenerationsII?
// var generation_iii: PokemonSpritesModelVersionsGenerationsIII?
// var generation_iv: PokemonSpritesModelVersionsGenerationsIV?
// var generation_v: PokemonSpritesModelVersionsGenerationsV?
// var generation_vi: PokemonSpritesModelVersionsGenerationsVI?
// var generation_vii: PokemonSpritesModelVersionsGenerationsVII?
// var generation_viii: PokemonSpritesModelVersionsGenerationsVIII?
private enum CodingKeys: String, CodingKey {
case generation_i = "generation-i",
generation_ii = "generation-ii",
// generation_iii = "generation-iii",
// generation_iv = "generation-iv",
// generation_v = "generation-v",
// generation_vi = "generation-vi",
// generation_vii = "generation-vii",
// generation_viii = "generation-viii"
}
struct PokemonSpritesModelVersionsGenerationsI: Decodable {
//MARK: Properties
var red_blue: PokemonSpritesModelVersionsGenerationsColors?
var yellow: PokemonSpritesModelVersionsGenerationsColors?
private enum CodingKeys: String, CodingKey {
case red_blue = "red-blue", yellow
}
}
struct PokemonSpritesModelVersionsGenerationsII: Decodable {
//MARK: Properties
var crystal: PokemonSpritesModelVersionsGenerationsColors?
var gold: PokemonSpritesModelVersionsGenerationsColors?
var silver: PokemonSpritesModelVersionsGenerationsColors?
}
struct PokemonSpritesModelVersionsGenerationsColors: Decodable {
//****** Notes: Some of the keys here (i marked them with an asterisk) are not part of the Set of keys market in the 'sprites' json object that are shared across different models
var back_default: String?
var back_shiny: String?
var back_gray: String? // *
var back_female: String?
var back_shiny_female: String?
var front_default: String?
var front_shiny: String?
var front_gray: String? // *
var front_female: String?
var front_shiny_female: String?
}
我省略了其他几代结构,因为概念是相同的。
通过这种方法,一切正常,但问题是我始终使用结构“PokemonSpritesModelVersionsGenerationsColors”中的完整键集(我也应该使用“sprites”键集,但我不知道如何推断该集并使精灵结构仍然工作,另外我还应该在“PokemonSpritesModelVersionsGenerationsColors”中添加标有*的新灰色。
正如您所说,重复几种类型的键是sprites结构体(other and versions应该排除 .json 对象,但正如我之前所说,应该添加标有 * 的精灵)。
根据我所做的研究,有两种可能的解决方案可以完成我想做的事情:
-
创建一个单独的DTO层(代表服务器 API 返回的数据的模型),然后有另一个领域层(应用程序的型号)。每个域模型将过滤相应 DTO 模型的键集,以便域层模型将与服务器响应匹配(DTO 模型与服务器响应不匹配,因为它不会仅使用必要子集所需的键)
-
使用某种组合自定义解码器初始化程序 + 多个容器通过编码键子类型枚举(也许借助枚举关联值)+也许其他东西。
我想避免第一种方法,主要是因为有两个层,除了域模型中包含的字段和业务逻辑来过滤必要的键之外,它们基本上是相同的。我正在考虑继续采用第二种方法。
我想我心里有一个关于如何做到这一点的想法,但我无法将所有部分放在一起。我主要阅读了这些博客文章:
- https://matteomanferdini.com/codable/
- https://lostmoa.com/blog/CodableConformanceForSwiftEnumsWithMultipleAssociatedValuesOfDifferentTypes/
- 使用关联值仅解码键的子集? (https://forums.swift.org/t/codable-synthesis-for-enums-with-linked-values/41493)
- 多个容器用作鉴别器来仅解码密钥的子集? (https://forums.swift.org/t/automatic-codable-conformance-for-enums-with-linked-values-that-themselves-conform-to-codable/11499)
- 检查解码器当前正在解码什么类型的密钥并仅创建该密钥?https://stackoverflow.com/a/53270057/2685716)
- 多个容器? (https://stackoverflow.com/a/57788293/2685716)
我认为第 1 篇和第 2 篇博客文章是我可以从中汲取一些想法来构建这个 sprites Json Codable 结构的文章。从第 1 篇博客文章来看,这是代码中可能更有趣的部分:
extension Launch: Decodable {
enum CodingKeys: String, CodingKey {
case timeline
case links
case rocket
case flightNumber = "flight_number"
case missionName = "mission_name"
case date = "launch_date_utc"
case succeeded = "launch_success"
case launchSite = "launch_site"
enum RocketKeys: String, CodingKey {
case rocketName = "rocket_name"
}
enum SiteKeys: String, CodingKey {
case siteName = "site_name_long"
}
enum LinksKeys: String, CodingKey {
case patchURL = "mission_patch"
}
}
}
从第 2 篇博客文章中,这可能是我正在寻找的代码,它从顶部/根键(我们的 sprites json 对象?)开始,一直向下直到我们需要解码的 Json 对象的底部。
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let key = container.allKeys.first
switch key {
case .empty:
self = .empty
case .editing:
let subview = try container.decode(
EditSubview.self,
forKey: .editing
)
self = .editing(subview: subview)
case .exchangeHistory:
let connection = try container.decode(
Connection?.self,
forKey: .exchangeHistory
)
self = .exchangeHistory(connection: connection)
case .list:
var nestedContainer = try container.nestedUnkeyedContainer(forKey: .list)
let selectedId = try nestedContainer.decode(UUID.self)
let expandedItems = try nestedContainer.decode([Item].self)
self = .list(
selectedId: selectedId,
expandedItems: expandedItems
)
default:
throw DecodingError.dataCorrupted(
DecodingError.Context(
codingPath: container.codingPath,
debugDescription: "Unabled to decode enum."
)
)
}
}
所以我将尝试以最简单的方式总结我想做的事情:我的想法是创建属于全局键集的键子集的多个容器(其中一些将是嵌套容器,可能是无键容器)不同模型之间共享的。比在相同的结构中或者在单独的结构中更好(也许我还需要在其中移动与其关联的枚举键的子集),我将有一个自定义解码器初始值设定项,它将仅创建在此枚举子集中定义的键键)。
[EDIT 3]
@RobNapier 是的,我想使用相同的语法。我仍然不知道什么是最简单或正确的方法。您能否提供一个完整的示例,说明您的 [String:URL] 方法,使我能够通过重用共享密钥来解码以下 json?
{
"sprites":
{
"back_default": "some text",
"back_female": "some text",
"back_shiny": "some text",
"back_shiny_female": "some text",
"front_default": "some text",
"front_female": "some text",
"front_shiny": "some text",
"front_shiny_female": "some text",
"other": {
"dream_world": {
"front_default": "some text",
"front_female": "some text"
},
"official-artwork": {
"front_female": "some text"
}
}
}
}