我如何使Swift中的枚举类型Decodable可解码?
我如何使Swift中的枚举类型Decodable可解码?
enum PostType: Decodable {\n init(from decoder: Decoder) throws {\n // 在这里放什么?\n }\n case Image\n enum CodingKeys: String, CodingKey {\n case image\n }\n}\n
\n要如何完成这段代码?\n另外,假设我将case
更改为以下形式:\ncase image(value: Int)\n
\n我该如何使其符合 Decodable 协议?\n以下是我的完整代码(目前无法运行):\nlet jsonData = \"\"\"\n{\n \"count\": 4\n}\n\"\"\".data(using: .utf8)!\n do {\n let decoder = JSONDecoder()\n let response = try decoder.decode(PostType.self, from: jsonData)\n print(response)\n } catch {\n print(error)\n }\n }\n}\nenum PostType: Int, Codable {\n case count = 4\n}\n
\n另外,它将如何处理像这样的枚举?\nenum PostType: Decodable {\n case count(number: Int)\n}\n
在Swift中,如果遇到未知的枚举值,会抛出一个`.dataCorrupted`错误。如果你的数据来自服务器,它可能在任何时候发送一个未知的枚举值(服务器端的错误、API版本中添加的新类型,你希望之前版本的应用程序能够优雅地处理该情况等),你最好事先准备好,并以"防御性编码"的方式安全地解码你的枚举。
下面是一个关于如何做到这一点的示例,包括有或没有关联值的情况:
enum MediaType: Decodable {
case audio
case multipleChoice
case other
// case other(String) -> 我们也可以这样给枚举参数化
init(from decoder: Decoder) throws {
let label = try decoder.singleValueContainer().decode(String.self)
switch label {
case "AUDIO": self = .audio
case "MULTIPLE_CHOICES": self = .multipleChoice
default: self = .other
// default: self = .other(label)
}
}
}
struct Question {
[...]
let type: MediaType
enum CodingKeys: String, CodingKey {
[...]
case type = "type"
}
}
extension Question: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
[...]
type = try container.decode(MediaType.self, forKey: .type)
}
}
感谢,你的回答更容易理解。这个答案也帮助了我,谢谢。可以通过让枚举从String继承来改进它,这样你就不需要在字符串上进行切换。简单明了的回答。谢谢,这个解决方案很完美!
问题的出现原因:
根据问题中的代码,问题是关于如何使带有关联类型的枚举符合Swift中的Codable协议。Codable是一个用于编码和解码数据的协议,可以方便地将数据序列化为JSON、Plist等格式,或将其反序列化为对象。
解决方法:
为了使带有关联类型的枚举符合Codable协议,需要对枚举进行扩展,并实现Codable协议中的init(from:)和encode(to:)方法。在init(from:)方法中,通过Decoder的container(forKey:)方法获取到对应的值,再根据值的类型来初始化枚举。在encode(to:)方法中,通过Encoder的container(forKey:)方法获取到对应的容器,然后根据枚举的类型将值编码进容器中。
以下是解决问题的完整代码:
enum PostType: Codable {
case count(number: Int)
case title(String)
}
extension PostType {
private enum CodingKeys: String, CodingKey {
case count
case title
}
enum PostTypeCodingError: Error {
case decoding(String)
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
if let value = try? values.decode(Int.self, forKey: .count) {
self = .count(number: value)
return
}
if let value = try? values.decode(String.self, forKey: .title) {
self = .title(value)
return
}
throw PostTypeCodingError.decoding("Whoops! \(dump(values))")
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .count(let number):
try container.encode(number, forKey: .count)
case .title(let value):
try container.encode(value, forKey: .title)
}
}
}
以上代码中,通过扩展PostType枚举并实现Codable协议中的方法,使得PostType枚举可以进行编码和解码操作。
以下是使用示例代码:
import Foundation // 导入Foundation框架以使用JSONEncoder/JSONDecoder
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let decoder = JSONDecoder()
let count = PostType.count(number: 42)
let countData = try encoder.encode(count)
let countJSON = String(data: countData, encoding: .utf8)!
print(countJSON)
// {
// "count" : 42
// }
let decodedCount = try decoder.decode(PostType.self, from: countData)
let title = PostType.title("Hello, World!")
let titleData = try encoder.encode(title)
let titleJSON = String(data: titleData, encoding: .utf8)!
print(titleJSON)
// {
// "title": "Hello, World!"
// }
let decodedTitle = try decoder.decode(PostType.self, from: titleData)
以上代码中,首先创建了一个JSONEncoder和JSONDecoder对象,用于编码和解码操作。然后,创建了带有关联类型的枚举实例count和title,并将它们分别编码为JSON格式的数据。最后,通过解码操作将JSON数据解码为对应的枚举实例。
通过在枚举中扩展Codable协议中的方法,可以使带有关联类型的枚举符合Codable协议,从而可以方便地进行编码和解码操作。
问题的原因是在Swift中,如果想要将一个枚举类型(enum)通过编码(encoding)和解码(decoding)转换成JSON数据,需要对枚举类型进行一些特殊处理。解决方法是根据枚举的情况选择合适的编码方式或者自定义编码和解码方法。
对于只包含原始值(raw value)的枚举,可以使用Swift中的Int
或String
作为原始值类型,并且在枚举声明中遵循Codable
协议。这样,在编码和解码的过程中,枚举的原始值会被隐式地赋予一个对应的编码值。
例如,以下是一个使用Int
原始值类型的枚举的示例:
enum PostType: Int, Codable {
case image, blob
}
在这个示例中,image
会被编码为0
,blob
会被编码为1
。
对于只包含原始值的枚举,还可以使用String
原始值类型,原理类似。
如果枚举包含与之关联的值(associated values),则需要编写自定义的编码和解码方法。可以参考苹果官方文档中的Encoding and Decoding Custom Types部分。
在Swift 5.5及以上的版本中,甚至可以直接对包含关联值的枚举进行编码和解码,无需编写额外的代码。编码和解码过程中,关联值会被映射到一个字典(dictionary)中,同时需要为每个关联值指定一个参数标签。
以下是一个使用关联值的枚举的示例:
enum Rotation: Codable {
case zAxis(angle: Double, speed: Int)
}
在这个示例中,使用了一个包含两个关联值的枚举类型Rotation
。通过合适的编码和解码方法,可以将该枚举类型转换成JSON数据或从JSON数据中解码出来。
需要注意的是,在iOS 13.3及以上版本中,枚举类型可以单独进行编码和解码,不需要嵌入到结构体中。但是在之前的版本中,仍然需要将枚举类型嵌入到结构体中才能正常进行编码和解码。
总结起来,要使一个枚举类型在Swift中能够进行编码和解码,可以使用原始值类型并遵循Codable
协议,或者编写自定义的编码和解码方法。在Swift 5.5及以上的版本中,还可以直接对包含关联值的枚举进行编码和解码。