• Swift中枚举转JSON你还在手写序列化吗?
  • 发布于 4小时前
  • 16 热度
    0 评论
  • 弄潮儿
  • 2 粉丝 39 篇博客
  •   
前言
昨天写代码的时候遇到个枚举要转 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 吗?有没有踩过坑?评论区聊聊!
用户评论