文章目录
-
- [一、Codable 是什么?](#一、Codable 是什么?)
- 二、核心组件
- [三、基础用法(80% 的场景)](#三、基础用法(80% 的场景))
-
- [3.1 自动解析(最常用,零额外代码)](#3.1 自动解析(最常用,零额外代码))
- [3.2 可选值处理(应对字段缺失)](#3.2 可选值处理(应对字段缺失))
- 四、自定义映射(CodingKeys)
-
- [场景:JSON 使用下划线,Swift 使用驼峰](#场景:JSON 使用下划线,Swift 使用驼峰)
- 五、日期处理(高频痛点)
- 六、进阶:自定义解码逻辑(`init(from:)`)
- 七、应对"脏数据"(类型不匹配)
- 八、继承与类(注意点)
- [九、编码技巧(生成 JSON 请求体)](#九、编码技巧(生成 JSON 请求体))
- 十、常见陷阱与最佳实践
- [十一、与 Combine 结合(黄金搭档)](#十一、与 Combine 结合(黄金搭档))
- 总结

一、Codable 是什么?
Codable 是 Apple 在 Swift 4.0 中引入的一套原生编解码协议 ,它是一个类型别名(typealias Codable = Encodable & Decodable),让你的 Swift 数据模型能在内存对象 与外部表示格式(如 JSON、Plist、XML、PropertyList)之间相互转换。
在 Codable 出现之前,iOS 开发者普遍使用
JSONSerialization手动解析字典,或者依赖第三方库(如 SwiftyJSON、ObjectMapper)。Codable 的出现,让 Swift 拥有了类型安全、声明式、零依赖的原生解析能力,目前已是 Swift 网络层的标配。
二、核心组件
| 组件 | 作用 |
|---|---|
Encodable |
将 Swift 对象编码为外部数据(如 JSON 二进制) |
Decodable |
将外部数据解码为 Swift 对象 |
Codable |
同时具备编码和解码能力 |
JSONEncoder |
将 Codable 对象编码为 JSON Data |
JSONDecoder |
将 JSON Data 解码为 Codable 对象 |
CodingKeys |
自定义属性与 JSON 字段的映射关系 |
三、基础用法(80% 的场景)
3.1 自动解析(最常用,零额外代码)
假设服务端返回如下 JSON:
json
{
"id": 1001,
"name": "张伟",
"isVIP": true,
"score": 98.5,
"tags": ["学霸", "篮球"]
}
Swift 模型只要声明 Codable,所有属性类型匹配即可自动完成转换:
swift
import Foundation
struct User: Codable {
let id: Int
let name: String
let isVIP: Bool
let score: Double
let tags: [String]
}
// 解码(网络请求的 Data 转模型)
let jsonData = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let user = try decoder.decode(User.self, from: jsonData)
print(user.name) // 输出:张伟
// 编码(模型转 JSON Data)
let encoder = JSONEncoder()
let encodedData = try encoder.encode(user)
let encodedString = String(data: encodedData, encoding: .utf8)!
3.2 可选值处理(应对字段缺失)
如果 JSON 中某些字段可能不存在 或值为 null,使用可选类型:
swift
struct User: Codable {
let id: Int
let name: String
let email: String? // 可选:字段可能缺失
let avatarURL: URL? // URL 类型自动转换
}
四、自定义映射(CodingKeys)
当 Swift 属性名与 JSON 字段名不一致时(最常见的是 snake_case 与 camelCase ),使用 CodingKeys 枚举映射。
场景:JSON 使用下划线,Swift 使用驼峰
json
{
"user_id": 1001,
"first_name": "张",
"last_name": "伟"
}
swift
struct User: Codable {
let userId: Int
let firstName: String
let lastName: String
enum CodingKeys: String, CodingKey {
case userId = "user_id"
case firstName = "first_name"
case lastName = "last_name"
}
}
💡 捷径 :如果 JSON 字段全是 snake_case,可以在
JSONDecoder上设置全局转换策略,省去写CodingKeys的麻烦:
swiftdecoder.keyDecodingStrategy = .convertFromSnakeCase同理,编码时用
encoder.keyEncodingStrategy = .convertToSnakeCase。
五、日期处理(高频痛点)
JSON 中的日期通常是字符串,Swift 中要转为 Date。JSONDecoder 提供了多种内置策略:
| JSON 日期格式 | Decoder 策略 |
|---|---|
"2026-08-06T10:30:00Z" |
.iso8601 |
时间戳(秒)1712345678 |
.secondsSince1970 |
时间戳(毫秒)1712345678000 |
.millisecondsSince1970 |
| 自定义格式 | .formatted(DateFormatter) |
swift
// 内置 ISO8601(最常用)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
// 自定义格式(如 "2026-08-06 10:30:00")
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
decoder.dateDecodingStrategy = .formatted(formatter)
struct Event: Codable {
let title: String
let startTime: Date // 自动按策略转换
}
六、进阶:自定义解码逻辑(init(from:))
当 JSON 结构嵌套复杂 ,或需要额外处理/校验时,手动实现解码器。
场景:服务端返回嵌套对象,但你想扁平化
json
{
"id": 1001,
"user": {
"name": "王芳",
"age": 28
},
"department": "技术部"
}
希望模型只保留 userId、userName 和 department:
swift
struct Employee: Codable {
let userId: Int
let userName: String
let department: String
enum CodingKeys: String, CodingKey {
case userId = "id"
case user // 嵌套容器Key
case department
}
// 子层级 Key
enum UserCodingKeys: String, CodingKey {
case name
case age
}
// 自定义解码
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.userId = try container.decode(Int.self, forKey: .userId)
self.department = try container.decode(String.self, forKey: .department)
// 从嵌套层取出 name
let userContainer = try container.nestedContainer(keyedBy: UserCodingKeys.self, forKey: .user)
self.userName = try userContainer.decode(String.self, forKey: .name)
}
}
场景:字符串转枚举(服务端返回数字枚举)
json
{ "status": 2 }
swift
enum OrderStatus: Int, Codable {
case pending = 1
case shipped = 2
case delivered = 3
}
struct Order: Codable {
let status: OrderStatus // 自动将 Int 2 转为 .shipped
}
如果是字符串枚举 ("shipped"),直接声明 String, Codable 即可自动转换。
七、应对"脏数据"(类型不匹配)
服务端可能返回 "123" 表示 ID,而模型需要 Int。Codable 默认会抛错,需要手动实现转换:
swift
struct Product: Codable {
let id: Int
let price: Double
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
// 处理 ID:可能是 String,也可能是 Int
if let idInt = try? container.decode(Int.self, forKey: .id) {
self.id = idInt
} else {
let idString = try container.decode(String.self, forKey: .id)
self.id = Int(idString) ?? 0
}
// 处理价格:可能返回 "19.99"
if let priceDouble = try? container.decode(Double.self, forKey: .price) {
self.price = priceDouble
} else {
let priceString = try container.decode(String.self, forKey: .price)
self.price = Double(priceString) ?? 0.0
}
}
enum CodingKeys: String, CodingKey {
case id, price
}
}
八、继承与类(注意点)
当使用类继承 时,子类必须实现 required init(from:) 并在解码时调用父类方法:
swift
class Animal: Codable {
var name: String
enum CodingKeys: String, CodingKey { case name }
}
class Dog: Animal {
var breed: String
enum CodingKeys: String, CodingKey { case breed }
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.breed = try container.decode(String.self, forKey: .breed)
try super.init(from: decoder) // 关键:解码父类
}
}
建议 :在 Swift 开发中,优先使用
struct而非class来定义模型,能避免大量继承带来的编解码复杂度。
九、编码技巧(生成 JSON 请求体)
swift
struct LoginRequest: Codable {
let username: String
let password: String
}
let request = LoginRequest(username: "test", password: "123456")
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted // 美化输出(便于调试)
let data = try encoder.encode(request)
// 直接放入 URLRequest 的 httpBody
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
urlRequest.httpBody = data
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
十、常见陷阱与最佳实践
| 陷阱 | 解决方案 |
|---|---|
属性是 let 常量,且服务端字段缺失 |
改用 var 并赋默认值,或声明为可选 ? |
动态 Key(如 "data_123") |
实现 init(from:) 遍历 container.allKeys 手动解析 |
| 顶层 JSON 是数组而非字典 | 用 decode([Model].self, from: data) 即可 |
模型包含 @Published(SwiftUI) |
@Published 会破坏 Codable 自动合成,需手动实现 init(from:) 或使用 @propertyWrapper 的自定义解码 |
| 超大 JSON 解析性能 | 使用 JSONDecoder() 时设置 userInfo 上下文,或考虑增量解析(极少场景) |
十一、与 Combine 结合(黄金搭档)
Combine 的 decode 操作符天然支持 Codable:
swift
import Combine
func fetchUsers() -> AnyPublisher<[User], Error> {
let url = URL(string: "https://api.example.com/users")!
return URLSession.shared.dataTaskPublisher(for: url)
.map(\.data)
.decode(type: [User].self, decoder: JSONDecoder()) // 一行解析
.eraseToAnyPublisher()
}
总结
| 阶段 | 掌握内容 |
|---|---|
| 入门 | 结构体声明 Codable,用 JSONDecoder 一行解析,处理可选值 |
| 进阶 | CodingKeys 映射,日期策略,init(from:) 处理嵌套结构 |
| 精通 | 应对脏数据转换,动态 Key 解析,自定义编码器,与 Combine/SwiftUI 无缝集成 |
一句话心法 :Codable 的核心理念是"用 Swift 的类型系统描述 JSON 的结构"。只要模型写得准,解析就是一行代码的事。遇到复杂 JSON,优先用 QuickType 在线工具生成 Swift 模型代码,再微调映射逻辑,效率极高!