Swift解码ChatReceivedMessageModel遇keyNotFound("userFullName")错误求助
我尝试在listenDeleted()方法中获取并解码数据,能接收到数据,但解码时触发CodingKeys错误。
监听方法代码
func listenDeleted() { socket?.off("deletedmessage") socket?.on("deletedmessage") { data, ack in let messageDict = data.last as! NSDictionary let messageDataObject = try? JSONSerialization.data(withJSONObject: messageDict, options: []) guard let messageData = messageDataObject else { return } do { let jsonDecoder = JSONDecoder() let message = try jsonDecoder.decode(ChatReceivedMessageModel.self, from: messageData) print("success") let messageID = message.messageId ?? "" let usertoConnect = message.userToConnect ?? "" let room = message.room ?? "" let userInfo = ["messageId": messageID] NotificationCenter.default.post(name: Notification.Name("messageDeleted"), object: nil, userInfo: userInfo) } catch { print("Unexpected error: \(error).") } } }
模型代码
class ChatReceivedMessageModel: Serializable { var room: String? var message: String? var userToConnect: String? var userFullName: String? var senderId: String? var senderPhotoUrl: String? var creationTimestamp: Int64? var messageId: String? init(room: String, message: String, userToConnect: String, userFullName: String, senderId: String, senderPhotoUrl: String, creationTimestamp: Int64, messageId: String) { self.room = room self.message = message self.userToConnect = userToConnect self.userFullName = userFullName self.senderId = senderId self.senderPhotoUrl = senderPhotoUrl self.creationTimestamp = creationTimestamp self.messageId = messageId } enum CodingKeys: String, CodingKey { case room case message case userToConnect case userFullName case senderId case senderPhotoUrl case creationTimestamp case messageId } required init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) room = try values.decode(String.self, forKey: .room) message = try values.decodeIfPresent(String.self, forKey: .message) userToConnect = try values.decode(String.self, forKey: .userToConnect) userFullName = try values.decode(String.self, forKey: .userFullName) senderId = try values.decode(String.self, forKey: .senderId) senderPhotoUrl = try values.decode(String.self, forKey: .senderPhotoUrl) creationTimestamp = try values.decode(Int64.self, forKey: .creationTimestamp) messageId = try values.decode(String.self, forKey: .messageId) } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(room, forKey: .room) try container.encode(message, forKey: .message) try container.encode(userToConnect, forKey: .userToConnect) try container.encode(userFullName, forKey: .userFullName) try container.encode(senderId, forKey: .senderId) try container.encode(senderPhotoUrl, forKey: .senderPhotoUrl) try container.encode(creationTimestamp, forKey: .creationTimestamp) try container.encode(messageId, forKey: .messageId) } func getChatUserModelType() -> ChatUserModel { var model = ChatUserModel() model.communityUserId = self.senderId model.name = self.userFullName model.photoUrl = self.senderPhotoUrl return model } }
报错信息
Unexpected error: keyNotFound(CodingKeys(stringValue: "userFullName", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "userFullName", intValue: nil) ("userFullName").", underlyingError: nil)).
断点截图显示的数据

解决方案
问题出在模型的解码逻辑:返回的JSON中仅包含room、messageId、userToConnect三个字段,但解码方法里对userFullName、senderId等字段使用了强制解析的decode(_:forKey:),当JSON中没有对应键时就会抛出keyNotFound错误。
修改模型的init(from decoder:)方法,将所有非必填字段的解析替换为decodeIfPresent(_:forKey:),这样即使字段不存在也只会赋值为nil,不会触发错误:
required init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) room = try values.decodeIfPresent(String.self, forKey: .room) message = try values.decodeIfPresent(String.self, forKey: .message) userToConnect = try values.decodeIfPresent(String.self, forKey: .userToConnect) userFullName = try values.decodeIfPresent(String.self, forKey: .userFullName) senderId = try values.decodeIfPresent(String.self, forKey: .senderId) senderPhotoUrl = try values.decodeIfPresent(String.self, forKey: .senderPhotoUrl) creationTimestamp = try values.decodeIfPresent(Int64.self, forKey: .creationTimestamp) messageId = try values.decodeIfPresent(String.self, forKey: .messageId) }
另外,由于你的模型属性都是可选类型(String?/Int64?),使用decodeIfPresent完全匹配属性的可选语义,同时也符合删除消息通知仅携带必要字段的场景。
内容的提问来源于stack exchange,提问作者Mert Köksal

