swift中Codable编码与解码源码解读

swift中编码与解码有两个协议,Encodable和Decodable。

下面是编码与解码的示例

swift 复制代码
import Foundation

// 编码
struct Person: Encodable {
    let name: String
    let age: Int
}

let person = Person(name: "lihua", age: 18)

let encodedData = try JSONEncoder().encode(person)

if let jsonsString = String(data: encodedData, encoding: .utf8){
    print(jsonsString)

}

需要解释的是 编码通过JSONEncoder()编码器将Swift对象转为json格式,encode(person)将对象编码,但只是一种抽象,具体编码成什么,还需要看是什么编码器。

这里的二进制json数据不方便查看,所以需要转为字符串。

swift 复制代码
//解码

struct Education: Decodable {
    let classname: String
    let school: String
}

let jsonString = """
{
    "classname": "one",
    "school": "first"
}
"""

if let jsonData = jsonString.data(using: .utf8){
    let education = try JSONDecoder().decode(Education.self, from: jsonData)
    print(education.classname)
    
} else{
    print("解码失败")
}

这里我们将字符串的jsonString转为json,由于data方法返回的是一个可选型,所以需要可选绑定。这里使用if let

解码把data数据转换为Education的实例,这里decode方法的第一个参数是一个类型,返回一个实例对象,所以写成decode(Education.self, from: jsonData),Education.self表示类型本身,而Education表示对象

codable

这个协议是解码和编码的组合

swift 复制代码
//codable
import Foundation

// ✅ 定义模型
struct Person2: Codable {
    let name: String
    let age: Int
}

// ✅ 模拟后端返回的 JSON 字符串
let jsonString2 = """
{
    "name": "Tom",
    "age": 25
}
"""

// ✅ 模拟网络返回的二进制 Data
if let jsonData = jsonString2.data(using: .utf8) {
    do {
        // ✅ JSONDecoder 解码 Data → Person 对象
        let person = try JSONDecoder().decode(Person2.self, from: jsonData)
        
        // ✅ 使用解析出的对象
        print("名字:\(person.name)")
        print("年龄:\(person.age)")
        
        // ✅ 编码示例(Swift 对象 → JSON)
        let encodedData = try JSONEncoder().encode(person)
        if let jsonString = String(data: encodedData, encoding: .utf8) {
            print("编码后的 JSON 字符串:\(jsonString)")
        }
        
    } catch {
        print("解析或编码失败:\(error)")
    }
}

解码和编码

我们来看一看解码内部,decode遵循Decodable协议,返回一个实例对象

Decodable和Encodable本身没有什么好说的,就是把对象编码成其他格式和把其他格式解码并初始化一个实例对象的能力,里面有方法encode和decode,重要的是其中的方法遵循的协议。Eocoder和Decoder。解码时,会生成一个实例对象,对象的参数必须初始化,所以使用init()。 下面是Encoder协议

swift 复制代码
public protocol Encoder {

    /// The path of coding keys taken to get to this point in encoding.
    var codingPath: [any CodingKey] { get }

    /// Any contextual information set by the user for encoding.
    var userInfo: [CodingUserInfoKey : Any] { get }

    /// Returns an encoding container appropriate for holding multiple values
    /// keyed by the given key type.
    ///
    /// You must use only one kind of top-level encoding container. This method
    /// must not be called after a call to `unkeyedContainer()` or after
    /// encoding a value through a call to `singleValueContainer()`
    ///
    /// - parameter type: The key type to use for the container.
    /// - returns: A new keyed encoding container.
    func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key : CodingKey

    /// Returns an encoding container appropriate for holding multiple unkeyed
    /// values.
    ///
    /// You must use only one kind of top-level encoding container. This method
    /// must not be called after a call to `container(keyedBy:)` or after
    /// encoding a value through a call to `singleValueContainer()`
    ///
    /// - returns: A new empty unkeyed container.
    func unkeyedContainer() -> any UnkeyedEncodingContainer

    /// Returns an encoding container appropriate for holding a single primitive
    /// value.
    ///
    /// You must use only one kind of top-level encoding container. This method
    /// must not be called after a call to `unkeyedContainer()` or
    /// `container(keyedBy:)`, or after encoding a value through a call to
    /// `singleValueContainer()`
    ///
    /// - returns: A new empty single value container.
    func singleValueContainer() -> any SingleValueEncodingContainer
}

协议有三种方法,定义了三种容器,即按照编码的类型不同,放入不同的容器中,container适合键值对的数据,unkeyedContainer适合无键的数据,即数组,singleValueContainer适合单个的数据。键值对数据要遵守CodingKey协议,这是什么?

由源码可以知道,它从对象属性得到key,映射对象属性和最终外部数据之间的对应关系。

事情还没有完,得到容器类型后呢?看看第一种,

它遵循了一个协议,协议下面有编码方法encode,就得到一个想要的类型,

一个完整的编码步骤应该是这样:

swift 复制代码
import Foundation

struct User: Encodable {
    var fullName: String
    var age: Int
    var email: String

    // 定义自定义 CodingKey
    enum CodingKeys: String, CodingKey {
        case name    // fullName 映射到 "name"
        case age
        case email
    }

    func encode(to encoder: Encoder) throws {
        // 获取 Keyed container
        var container = encoder.container(keyedBy: CodingKeys.self)
        
        // 手动映射属性到 key,并进行自定义逻辑
        try container.encode(fullName, forKey: .name)
        
        // 自定义处理,比如把年龄「加密」后再存
        let encryptedAge = age + 100
        try container.encode(encryptedAge, forKey: .age)
        
        try container.encode(email, forKey: .email)//常规
    }
}

你已经看到,我在编码时手动实现了一些东西,这是它的优势,虽然代码量大了 我可以修改:

  • 1 属性名映射(外部 key 与内部属性不同)
swift 复制代码
struct User: Encodable {
    var fullName: String
    
    enum CodingKeys: String, CodingKey {
        case name // JSON 需要的 key
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(fullName, forKey: .name)
    }
}
  • 加密或脱敏后再编码

比如你想对涉密字段加密

swift 复制代码
try container.encode(phoneNumber.masked(), forKey: .phone)
  • 只编码部分字段、合并字段后编码、给编码的字段加条件
swift 复制代码
// 不编码某些属性(不写就不会被编码)
try container.encode(name, forKey: .name)
// 不调用 encode(password, forKey: .password),密码就不会被写入
swift 复制代码
let fullName = "\(firstName) \(lastName)"
try container.encode(fullName, forKey: .fullName)
swift 复制代码
if isPremiumUser {
    try container.encode(premiumInfo, forKey: .premiumInfo)
}
只有会员才编码写入json

解码也类似:

swift 复制代码
struct User: Decodable {
    var fullName: String
    var age: Int
    var email: String
    
    // 定义自定义 CodingKey
    enum CodingKeys: String, CodingKey {
        case name
        case age
        case email
    }
    
    // ✅ 自定义解码逻辑
    init(from decoder: Decoder) throws {
        // 获取 Keyed container
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        // 这里可以进行复杂映射或校验
        // 例如:把 JSON 里的 name 映射到 fullName
        self.fullName = try container.decode(String.self, forKey: .name)
        
        // 自定义处理:从「加密」后的 age 解密
        let encryptedAge = try container.decode(Int.self, forKey: .age)
        self.age = encryptedAge - 100
        
        // 常规解码 email
        self.email = try container.decode(String.self, forKey: .email)
    }
}
相关推荐
10年前端老司机2 小时前
React无限级菜单:一个项目带你突破技术瓶颈
前端·javascript·react.js
阿芯爱编程7 小时前
2025前端面试题
前端·面试
FreeBuf_7 小时前
黄金旋律IAB组织利用暴露的ASP.NET机器密钥实施未授权访问
网络·后端·asp.net
前端小趴菜058 小时前
React - createPortal
前端·vue.js·react.js
晓13138 小时前
JavaScript加强篇——第四章 日期对象与DOM节点(基础)
开发语言·前端·javascript
张小洛8 小时前
Spring AOP 是如何生效的(入口源码级解析)?
java·后端·spring
菜包eo8 小时前
如何设置直播间的观看门槛,让直播间安全有效地运行?
前端·安全·音视频
烛阴9 小时前
JavaScript函数参数完全指南:从基础到高级技巧,一网打尽!
前端·javascript
why技术9 小时前
也是出息了,业务代码里面也用上算法了。
java·后端·算法
chao_78910 小时前
frame 与新窗口切换操作【selenium 】
前端·javascript·css·selenium·测试工具·自动化·html