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)
    }
}
相关推荐
2301_793086878 分钟前
Springboot 04 starter
java·spring boot·后端
太阳伞下的阿呆3 小时前
本地环境vue与springboot联调
前端·vue.js·spring boot
无限大63 小时前
只出现一次的数字:从暴力美学到位运算神技的进化之路
后端·面试
宇寒风暖3 小时前
Flask 框架全面详解
笔记·后端·python·学习·flask·知识
你的人类朋友3 小时前
❤️‍🔥为了省内存选择sqlite,代价是什么
数据库·后端·sqlite
阳光是sunny3 小时前
走进微前端(1)手写single-spa核心原理
前端·javascript·vue.js
还是鼠鼠3 小时前
tlias智能学习辅助系统--SpringAOP-进阶-通知顺序
java·后端·mysql·spring·mybatis·springboot
Pitayafruit3 小时前
Spring AI 进阶之路01:三步将 AI 整合进 Spring Boot
spring boot·后端·ai编程
烛阴4 小时前
Ceil -- 从平滑到阶梯
前端·webgl
90后的晨仔4 小时前
🔍Vue 模板引用(Template Refs)全解析:当你必须操作 DOM 时
前端·vue.js