为带嵌套关联值的类型实现自定义Decodable初始化报错求助
自定义Decodable协议init方法时遇到解码错误
错误信息
DecodingError.typeMismatch(Swift.Dictionary<Swift.String, Foundation.JSONValue>)
Expected to decode Dictionary<String, JSONValue> but found a string instead.
问题代码
struct Model: Decodable { let title: String let gotoAction: GotoAction // String work fine } enum GotoAction: Decodable { case noAction case websiteLink(String) case sheet(OpenSheet) private enum CodingKeys: String, CodingKey { case noAction case websiteLink case sheet } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) if let link = try container.decodeIfPresent(String.self, forKey: .websiteLink) { self = .websiteLink(link) } if let sheet = try container.decodeIfPresent(OpenSheet.self, forKey: .sheet) { self = .sheet(sheet) } // let noAction = try container.decodeIfPresent(String.self, forKey: .noAction) // self = noAction throw DecodingError.dataCorruptedError(forKey: .websiteLink, in: container, debugDescription: "No match") } } enum OpenSheet: Decodable { case firstBanner case secondBanner(String) } let json = """ { "title": "Hello", "goto_action": "no_action" } """ let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase let result = try decoder.decode(Model.self, from: json.data(using: .utf8)!) print(result)
问题分析与修复
核心问题有两个:
GotoAction的解码逻辑错误假设goto_action是键值对结构,但实际JSON中它是单个字符串,调用decoder.container(keyedBy: CodingKeys.self)会触发类型不匹配错误。- 未正确处理
noAction的解码逻辑,且无论匹配结果如何都会抛出错误。
修改步骤:
1. 调整GotoAction解码逻辑适配字符串类型
因为goto_action是单个字符串值,直接用单值容器解码后匹配枚举case:
enum GotoAction: Decodable { case noAction case websiteLink(String) case sheet(OpenSheet) init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() // 先解析字符串类型的case let rawValue = try container.decode(String.self) switch rawValue { case "no_action": self = .noAction case let link where link.hasPrefix("http"): // 根据实际规则判断网站链接 self = .websiteLink(link) default: // 尝试解析OpenSheet类型(需根据实际JSON格式调整) do { let sheet = try container.decode(OpenSheet.self) self = .sheet(sheet) } catch { throw DecodingError.dataCorruptedError(in: container, debugDescription: "Unsupported goto action: \(rawValue)") } } } }
2. 完善OpenSheet的解码逻辑
如果OpenSheet对应JSON是字符串格式,自定义解码逻辑:
enum OpenSheet: Decodable { case firstBanner case secondBanner(String) init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let rawValue = try container.decode(String.self) if rawValue == "first_banner" { self = .firstBanner } else if rawValue.starts(with: "second_banner:") { let content = String(rawValue.dropFirst("second_banner:".count)) self = .secondBanner(content) } else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "Unsupported sheet type: \(rawValue)") } } }
3. 兼容混合格式(可选)
如果goto_action有时是字符串、有时是对象,可先判断容器类型:
init(from decoder: Decoder) throws { if let singleContainer = try? decoder.singleValueContainer() { // 处理字符串类型 let rawValue = try singleContainer.decode(String.self) switch rawValue { case "no_action": self = .noAction case let link where link.hasPrefix("http"): self = .websiteLink(link) default: throw DecodingError.dataCorruptedError(in: singleContainer, debugDescription: "Unsupported action string: \(rawValue)") } } else if let keyedContainer = try? decoder.container(keyedBy: CodingKeys.self) { // 处理键值对结构(比如{"sheet": "first_banner"}) if let sheet = try keyedContainer.decodeIfPresent(OpenSheet.self, forKey: .sheet) { self = .sheet(sheet) } else if let link = try keyedContainer.decodeIfPresent(String.self, forKey: .websiteLink) { self = .websiteLink(link) } else { throw DecodingError.dataCorruptedError(forKey: .websiteLink, in: keyedContainer, debugDescription: "No valid action found") } } else { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Unsupported JSON structure for GotoAction")) } }
内容的提问来源于stack exchange,提问作者Nick Rossik
相关产品推荐
相关产品推荐

