前言
昨天写代码的时候遇到个枚举要转 JSON 的需求,本来想手写序列化,结果发现 Swift 枚举的 Codable 支持比我想象得要强大!不管是简单枚举还是带关联值得复杂枚举,基本都能自动处理。今天就来聊聊这个功能,挺实用的。
最简单的情况
给枚举加个 Codable,编译器自动帮你搞定:
有原始值的枚举
enum Direction: String, Codable {
case north, south, east, west
}
// 堆代码 duidaima.com
let direction: Direction = .north
let jsonData = try JSONEncoder().encode(direction)
print(String(data: jsonData, encoding: .utf8)!) // 输出:"north"
解码就是反过来,JSON 字符串直接对应枚举值。
普通枚举
没原始值的话会变成这样:
enum Status: Codable {
case success, failure
}
let status: Status = .success
print("编码后:{\"success\":{}}") // 输出格式
有关联值的枚举
这个有意思,关联值会变成嵌套结构:
enum Command: Codable {
case load(key: String)
case store(key: String, value: Int)
}
let command: Command = .store(key: "userConfig", value: 42)
// 编码后:{"store":{"value":42,"key":"userConfig"}}
没标签的话就用 _0、_1:
enum UserRole: Codable {
case member(String, Int)
}
let role: UserRole = .member("张三", 5)
// 编码后:{"member":{"_0":"张三","_1":5}}
自定义规则
改键名
有时候想改 JSON 的键:
enum APIStatus: Codable {
case success
case failure(reason: String)
enum CodingKeys: String, CodingKey {
case success = "ok"
case failure = "error"
}
}
// success 变成了 ok,failure 变成了 error
关联值键名
关联值的键也能改,规则是 "枚举值 + CodingKeys":
enum NetworkRequest: Codable {
case get(url: String)
case post(url: String, body: Data)
enum PostCodingKeys: String, CodingKey {
case url = "endpoint"
case body = "payload"
}
}
// url 变成 endpoint,body 变成 payload
完全自定义
有时候自动生成地格式不够用,比如对接某些奇怪的 API。那就自己写:
enum APIResponse: Codable {
case success(data: String)
case error(message: String)
func encode(to encoder: Encoder)throws {
var container = encoder.singleValueContainer()
switchself {
case .success(let data):
try container.encode(["status": "ok", "data": data])
case .error(let message):
try container.encode(["status": "error", "message": message])
}
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let response = try container.decode([String: String].self)
iflet data = response["data"] {
self = .success(data: data)
} elseiflet message = response["message"] {
self = .error(message: message)
} else {
throwDecodingError.dataCorrupted(
DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "格式不对")
)
}
}
}
// 生成:{"status":"ok","data":"用户数据"}
几个坑
性能:复杂枚举序列化会慢,关联值多了特别明显。
兼容:加新枚举值时注意下,老版本可能解码失败。
最好做错误处理:
do {
let decoded = try JSONDecoder().decode(Command.self, from: jsonData)
} catch {
print("解码出错:\(error)")
}
写在最后
枚举 Codable 确实挺方便,大部分场景自动生成就够了。需要自定义时也不麻烦,配合网络请求、数据存储能省不少事。你用过枚举 Codable 吗?有没有踩过坑?评论区聊聊!