Swift 5.7中如何正确编解码[any Content]异构数组?
解决Swift异构Codable数组的编码/解码问题
核心思路
处理[any Content]这类异构Codable数组的关键,是通过ContentType枚举标记每个元素的类型:编码时为每个元素附加类型标识,解码时先读取类型再对应解析元素内容,从根本避免循环覆盖数组的问题。
完整代码实现
1. 定义基础协议与类型枚举
import Foundation // 遵循Codable的Content协议,要求实现类提供对应的ContentType protocol Content: Codable { static var contentType: ContentType { get } } // 标记内容类型的枚举,需遵循Codable enum ContentType: String, Codable { case text, image, video } // 示例Content实现类 struct TextContent: Content { static let contentType: ContentType = .text let text: String } struct ImageContent: Content { static let contentType: ContentType = .image let url: URL let width: Int let height: Int } struct VideoContent: Content { static let contentType: ContentType = .video let url: URL let duration: TimeInterval }
2. ContentCollection的编码/解码逻辑
class ContentCollection: Codable { var contents: [any Content] init(contents: [any Content]) { self.contents = contents } // 自定义编码:逐个元素添加到JSON数组 func encode(to encoder: Encoder) throws { var rootContainer = encoder.container(keyedBy: CodingKeys.self) var contentsArray = rootContainer.nestedUnkeyedContainer(forKey: .contents) for content in contents { // 为每个元素创建单独的键值容器,存储类型标识和内容 var elementContainer = contentsArray.nestedContainer(keyedBy: ElementCodingKeys.self) try elementContainer.encode(type(of: content).contentType, forKey: .type) try content.encode(to: elementContainer.superEncoder(forKey: .content)) } } // 自定义解码:根据类型标识解析对应元素 required init(from decoder: Decoder) throws { let rootContainer = try decoder.container(keyedBy: CodingKeys.self) var contentsArray = try rootContainer.nestedUnkeyedContainer(forKey: .contents) var decodedContents = [any Content]() while !contentsArray.isAtEnd { let elementContainer = try contentsArray.nestedContainer(keyedBy: ElementCodingKeys.self) let contentType = try elementContainer.decode(ContentType.self, forKey: .type) let contentDecoder = try elementContainer.superDecoder(forKey: .content) // 根据类型匹配对应的Content实现类 switch contentType { case .text: decodedContents.append(try TextContent(from: contentDecoder)) case .image: decodedContents.append(try ImageContent(from: contentDecoder)) case .video: decodedContents.append(try VideoContent(from: contentDecoder)) } } self.contents = decodedContents } // 根层级编码键 enum CodingKeys: String, CodingKey { case contents } // 单个元素的编码键 enum ElementCodingKeys: String, CodingKey { case type, content } }
3. 使用示例
// 创建异构Content数组 let sampleContents: [any Content] = [ TextContent(text: "Hello Swift"), ImageContent(url: URL(string: "https://example.com/photo.jpg")!, width: 1080, height: 1920), VideoContent(url: URL(string: "https://example.com/clips.mp4")!, duration: 90.5) ] let collection = ContentCollection(contents: sampleContents) // 编码测试 do { let jsonData = try JSONEncoder().encode(collection) if let jsonString = String(data: jsonData, encoding: .utf8) { print("编码结果:\n\(jsonString)") } } catch { print("编码失败: \(error.localizedDescription)") } // 解码测试 do { let jsonData = try JSONEncoder().encode(collection) let decodedCollection = try JSONDecoder().decode(ContentCollection.self, from: jsonData) print("\n解码成功,共\(decodedCollection.contents.count)个元素") } catch { print("解码失败: \(error.localizedDescription)") }
关键说明
- 编码时:通过
nestedUnkeyedContainer创建数组容器,每个元素单独用nestedContainer存储type和content字段,确保元素逐个添加不覆盖。 - 解码时:先读取每个元素的类型标识,再匹配对应的Content实现类解析内容,保证异构数组的正确还原。
- 扩展性:新增Content类型时,只需在
ContentType中添加对应的case,并在解码的switch分支中新增处理逻辑即可。
内容的提问来源于stack exchange,提问作者Jake
相关产品推荐
相关产品推荐

