提问人:stompy 提问时间:11/3/2023 最后编辑:HangarRashstompy 更新时间:11/11/2023 访问量:81
将 JSON 字符串映射到自定义枚举大小写而无需访问 Swift 中的枚举实现的最佳方法?
Best way to map a JSON string to a custom enum case without having access to the enums implementation in Swift?
问:
请考虑以下简单的 JSON:
{
"title": "Some title",
"subtitle": "Some subtitle",
"button_type": "rounded"
}
这是我目前解码buttonType字段的方法:
// I dont have access to this enums implementation as it comes from a 3rd party lib.
enum ButtonType {
case squared
case simple
case rounded
case normal
}
struct ResponseModel: Decodable {
var title: String
var subtitle: String
var type: ButtonType
enum CodingKeys: String, CodingKey {
case title = "title"
case subtitle = "subtitle"
case type = "button_type"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
title = try container.decode(String.self, forKey: .title)
subtitle = try container.decode(String.self, forKey: .subtitle)
let stringType = try container.decode(String.self, forKey: .type)
switch stringType {
case "squared":
type = .squared
case "simple":
type = .simple
case "rounded":
type = .rounded
default:
type = .normal
}
}
}
有没有更漂亮的方法可以完成将字符串解码为自定义枚举,而无需那个讨厌的 switch 语句遍历普通字符串?遗憾的是,我无法访问枚举实现,因为它来自第三方库。如果我这样做了,我只会让枚举符合 String & Codable,并让 Codable 发挥它的魔力,但我没有。
谢谢!
答:
2赞
lorem ipsum
11/3/2023
#1
您可以创建自己的enum
enum MyButtonType: String {
case squared
case simple
case rounded
case normal
var toButtonType: ButtonType {
switch self {
case .squared: return .squared
case .simple: return .simple
case .rounded: return .rounded
case .normal: return .normal
}
}
}
然后更改
var type: ButtonType
自
var type: MyButtonType
当您需要定制时,只需使用enum
responseModel.type.toButtonType
在枚举之后,使其符合您不再需要自定义。String
:
RawRepresentable<String>
init
您也可以手动遵守 String 条目。RawPresentable<String> but it requires all the
extension ButtonType: RawRepresentable {
typealias RawValue = String
var rawValue: String {
switch self {
case .squared:
return "squared"
case .simple:
return "simple"
case .rounded:
return "rounded"
case .normal:
return "normal"
}
}
init?(rawValue: String) {
switch rawValue {
case "squared":
self = .squared
case "simple":
self = .simple
case "rounded":
self = .rounded
case "normal":
self = .normal
default:
return nil
}
}
}
这样,您就不必更改模型中的“类型”。
评论
0赞
stompy
11/3/2023
这似乎工作正常。谢谢!想知道是否有任何方法可以避免必须使用 switch 语句并且必须“复制”相同的枚举才能将其从一种类型转换为另一种类型。
0赞
lorem ipsum
11/3/2023
尝试“扩展 ButtonType:String {}”,看看它是否有效@stompy
0赞
stompy
11/3/2023
在制作这个线程之前,我确实尝试过哈哈!可悲的是,您收到编译器错误,并显示“继承自非协议类型'字符串'”:(
0赞
lorem ipsum
11/3/2023
@stompy我以为会是这样,但这可能是唯一的办法。您可以建议第三方将其添加到将来的版本中。
0赞
stompy
11/3/2023
恐怕你是对的。无论如何,您的方法比像我一样切换普通字符串要干净得多。您的答案已被标记为已接受。谢谢!:)
1赞
Luca Angeletti
11/3/2023
#2
只是另一种解决方案。
即使您不控制定义方式,您仍然可以使用扩展来使其符合。ButtonType
Decodable
第 1 步:符合ButtonType
Decodable
extension ButtonType: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let text = try container.decode(String.self)
switch text {
case "squared": self = .squared
case "simple": self = .simple
case "rounded": self = .rounded
case "normal": self = .normal
default: throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "Cannot initialize ButtonType from invalid String value \(text)"))
}
}
}
第 2 步:在ResponseModel
struct ResponseModel: Decodable {
var title: String
var subtitle: String
var type: ButtonType
enum CodingKeys: String, CodingKey {
case title = "title"
case subtitle = "subtitle"
case type = "button_type"
}
}
评论
0赞
stompy
11/13/2023
我想到了这个,是的!但是,如果我团队中的其他人也想用 Decodable 扩展这个第三方枚举(也许是另一种解码策略或其他什么),这两个扩展都会发生冲突。谢谢!
1赞
Alexander
11/10/2023
#3
已经有两个非常好的答案,总结为:
- 创建自己的枚举来镜像主枚举,并根据需要对其进行序列化。这有保持镜子与主镜子同步的维护负担。
- 使主枚举符合 。这减少了维护工作,但有点风险。您应该避免将您无法控制的类型(例如)与您无法控制的协议(例如)保持一致,因为其他人可能已经编写了冲突的一致性。
Decodable
ButtonType
Decodable
我发现还有另一种技巧非常方便。您可以创建一个 PropertyWrapper,并为其提供自定义的可编码行为。
@propertyWrapper struct CustomDecodableButtonType {
let wrappedValue: ButtonType // 1. Define a wrapper for third party type
}
// 2. Define your own decoding logic for it
extension CustomDecodableButtonType: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let text = try container.decode(String.self)
switch text {
case "squared": self.init(wrappedValue: .squared)
case "simple" : self.init(wrappedValue: .simple)
case "rounded": self.init(wrappedValue: .rounded)
case "normal" : self.init(wrappedValue: .normal)
default: throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "Cannot initialize ButtonType from invalid String value \(text)"))
}
}
}
struct ResponseModel: Decodable {
var title: String
var subtitle: String
// 3. Wrap whichever fields you need.
@CustomDecodableButtonType var type: ButtonType
}
评论
0赞
stompy
11/13/2023
哦,哇。我从未深入研究过涉及属性包装器的解决方案。这非常方便。谢谢!
0赞
Alexander
11/13/2023
@stompy是的,这是一个非常强大的功能。好吧,它本身并不是一个真正的功能,而是另外两个功能(属性包装器和 Codable)之间非常自然的相互作用。为外来类型提供可编码的实现是它真正有用的地方之一。另一个有用的情况是,如果你有一种类型,它以不同的方式编码用于不同的用途(例如,两个不同的 API 想要相同的数据,但有两种不同的格式)。
评论