Swift Codable 进阶:从基础到复杂类型
我们都知道,Codable = Encodable + Decodable。
Swift 给了我们一套几乎“傻瓜式”的序列化 / 反序列化方案。
但是,除了基础类型(String, Int, Double, Bool 等),一旦遇到 枚举、自定义 struct、Date、URL,事情就没那么轻松了。
下面我就用几个小故事 + 示例,聊聊我踩过的坑和应对方法。
1. 基础类型 —— “最省心”的部分
struct User: Codable {
var id: Int
var name: String
}
JSON 只要对得上 key,就能自动 decode:
{ "id": 1, "name": "Alice" }
这里没啥坑,但后续就开始复杂了。
2. 枚举 (enum) —— “多选一”的麻烦
我在写一个任务管理 App 时,遇到接口里有个 status 字段:
{ "status": "done" }
最自然的写法是:
enum Status: String, Codable {
case todo
case doing
case done
}
这样就能自动解码了。
但坑在于:后端有时候会传奇怪值。
比如 "status": "archived",我的枚举里没有,就会解码失败。
解决办法:
✅ 写 init(from:)容错
enum Status: String, Codable {
case todo, doing, done, unknown
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let raw = try container.decode(String.self)
self = Status(rawValue: raw) ?? .unknown
}
}
这样就算来了 "archived",也能落到 .unknown。
3. 自定义 struct —— “嵌套的考验”
比如一个活动对象,里面有主办方信息:
{
"title": "Swift Meetup",
"organizer": {
"name": "Capton",
"url": "https://example.com"
}
}
我们定义两个 struct:
struct Organizer: Codable {
var name: String
var url: URL
}
struct Event: Codable {
var title: String
var status: Status
var date: Date
var organizer: Organizer
}
这时候 Codable 会自动帮我们把 organizer 映射成嵌套 struct,非常优雅。
但是,一旦 url 变成 "not_a_url",就又踩坑了。
URL解码意外的情况
当测试环境的接口里,url 字段变成了:
"url": "not_a_valid_url"
这时候问题就来了:
-
JSONDecoder 会尝试用 URL(string:) 来初始化。
-
但 not_a_valid_url 显然不是合法地址,于是 decode 直接抛错。
如果我用 do-catch,就会在 catch 里收到 DecodingError.dataCorrupted,拿不到这个资源对象:
do {
let resource = try JSONDecoder().decode(Resource.self, from: data)
print(resource)
} catch {
print("解码失败:\(error)")
}
虽然 App 不会崩,但数据缺失的问题很麻烦,尤其是业务里 url 可能是非必填字段。
所以我干脆自己写了个“安全 URL 包装器”:
struct SafeURL: Codable {
var value: URL?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let urlString = try container.decode(String.self)
value = URL(string: urlString)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(value?.absoluteString ?? "")
}
}
这样一来,就算后端传来奇怪的字符串,SafeURL.value 也只是 nil,
不会影响整个模型的解码成功。
此时Organizer变为了
struct Organizer: Codable {
var name: String
var url: SafeURL
}
4. Date —— “时间格式的战争”
小故事:我和后端的“时间格式的战争”
有一次我在写一个活动 App,要从接口拿到活动时间。
于是我很天真地写了个 model:
struct Event: Codable {
var title: String
var status: Status
// 其他字段
}
接口返回的是这样一段 JSON:
{
"title": "Swift 社区见面会",
"date": "2025-09-27 14:30:00"
}
我信心满满地跑起来,结果直接抛错。
因为 Swift 默认把 Date 编解码成 时间戳(Double),而后端给的是个字符串。
好嘛,我的 App 和后端同事就开始了“时间大战”:
-
我说:“你们给时间戳吧,我这边直接就能解了。”
-
后端说:“不行,我们 Java 统一用字符串,别搞特殊。”
于是我只能乖乖写解码策略:
let decoder = JSONDecoder()
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
decoder.dateDecodingStrategy = .formatted(formatter)
这下才算是把时间读进来。
后来我干脆封装了一个 DateFormatter 工具,项目里全局用,不然每个地方都写一次太麻烦了。
5. 混合案例 —— “大而全”的对象
结合上面所有情况:
{
"title": "Swift Workshop",
"status": "done",
"date": "2025-09-27 14:30:00",
"organizer": {
"name": "Capton",
"url": "not_a_valid_url"
}
}
模型:
struct Event: Codable {
var title: String
var status: Status
var date: Date
var organizer: Organizer
}
配合:
-
自定义 Status 容错
-
DateDecodingStrategy 格式化
-
SafeURL 包装
就能完美解码,即使后端数据再怎么“放飞自我”。
代码demo
CodableDemo.playground.zip (重命名去除 .zip)
6. 我的经验总结
-
enum:加个 .unknown 容错,防止后端乱传值。
-
struct 嵌套:没坑,但子 struct 要考虑容错。
-
Date:永远确认前后端的时间格式,最好写统一的解码器。
-
URL:写 SafeURL,让模型健壮性更高。
-
复杂 JSON:别怕写 init(from:),关键场景手写反而最安全。
📌 一句话总结:
Codable 能帮我们 80%,剩下的 20% 靠“自定义 + 容错”。