Swift解码Firebase实时数据库数据失败及相关疑问
问题描述
在Firebase Realtime Database中根据lemma键搜索值时,能打印数据但无法完成解码,报错如下:
keyNotFound(CodingKeys(stringValue: "nouns", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "nouns", intValue: nil) ("nouns").", underlyingError: nil))
同时存在两个控制台显示疑问:
- 打印数据时
lemma和article的值为何没有引号? - 部分字符显示为
\U00e4是什么原因?
控制台输出
# Key: 1278, # Value: { accusativePlural = "Die H\U00e4mmer"; accusativeSingular = "Den Hammer"; // Why Der is not in String ? article = Der; dativePlural = "Den H\U00e4mmer"; dativeSingular = "Dem Hammer"; genitivePlural = "Der H\U00e4mmer"; genitiveSingular = "Des Hammers"; // Why Hammer is not in String ? lemma = Hammer; nominativePlural = "Die H\U00e4mmer"; nominativeSingular = "Der Hammer"; }
代码实现
RealtimeDatabaseManager
final class RealtimeDatabaseManager { static let shared = RealtimeDatabaseManager() private let ref = Database.database().reference() private init() { } func search(with text: String) { print("foo search: \(text)") ref.child("nouns") .queryOrdered(byChild: "lemma") .queryStarting(atValue: text) .queryEnding(atValue: text + "\u{f8ff}") .observe(.value) { snapshot in do { let decoded = try snapshot.data(as: GermanNounsResponse.self) print("# decoded: \(decoded.nouns)") } catch { print("# error \(error)") } if let snapDict = snapshot.value as? [String: Any] { for (key, value) in snapDict { print("# Key: \(key), # Value: \(value)") } } } } }
响应模型
struct GermanNounsResponse: Decodable { let nouns: [GermanResponse] enum CodingKeys: String, CodingKey { case nouns } } struct GermanResponse: Decodable { let lemma: String let pos: String let article: String let nominativSingular: String let nominativPlural: String let akkusativSingular: String let akkusativPlural: String let dativSingular: String let dativPlural: String let genitivSingular: String let genitivPlural: String enum CodingKeys: String, CodingKey { case lemma case pos case article case nominativSingular case nominativPlural case akkusativSingular case akkusativPlural case dativSingular case dativPlural case genitivSingular case genitivPlural } }
问题解答
1. 解码失败的原因及修复
核心原因
- 数据结构不匹配:查询返回的是
nouns节点下的直接子节点集合(键为节点ID,值为名词对象),但GermanNounsResponse模型期望根结构是包含nouns数组的对象,两者不兼容。 - 属性名映射错误:数据库使用英文拼写的键名(如
accusativeSingular),但模型用了德语拼写(如akkusativSingular),没有做对应映射。 - 缺失字段报错:模型中声明了
pos字段,但数据库返回的数据里没有该键,解码时因缺少必要字段失败。
修复步骤
步骤1:适配数据结构
因为查询返回的是[String: GermanResponse]格式,可直接解码为字典,或修改GermanNounsResponse的解码逻辑:
方法一:直接解码为字典
// 移除GermanNounsResponse,直接解码成字典 let decoded = try snapshot.data(as: [String: GermanResponse].self) // 转成数组使用 let nounsArray = Array(decoded.values)
方法二:修改GermanNounsResponse适配字典结构
struct GermanNounsResponse: Decodable { let nouns: [GermanResponse] init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let nounsDict = try container.decode([String: GermanResponse].self) nouns = Array(nounsDict.values) } }
步骤2:修正属性名映射
在GermanResponse的CodingKeys中添加数据库键名与模型属性的映射:
struct GermanResponse: Decodable { let lemma: String let article: String let nominativSingular: String let nominativPlural: String let akkusativSingular: String let akkusativPlural: String let dativSingular: String let dativPlural: String let genitivSingular: String let genitivPlural: String let pos: String? // 设为可选,因为数据库无此字段 enum CodingKeys: String, CodingKey { case lemma, article, pos case nominativSingular = "nominativeSingular" case nominativPlural = "nominativePlural" case akkusativSingular = "accusativeSingular" case akkusativPlural = "accusativePlural" case dativSingular = "dativeSingular" case dativPlural = "dativePlural" case genitivSingular = "genitiveSingular" case genitivPlural = "genitivePlural" } }
步骤3:处理缺失的pos字段
将模型中的pos设为可选类型(String?),避免因数据库无此字段导致解码失败。
2. 控制台显示疑问解答
疑问1:lemma和article的值没有引号
这是Swift打印[String: Any]的格式规则:纯字母数字的字符串(无空格、特殊字符)会省略引号,带空格的字符串(如"Den Hammer")会保留引号。仅为打印格式问题,实际数据类型仍是字符串,不影响使用。
疑问2:部分字符显示为\U00e4
\U00e4是Unicode转义序列,对应德语变音字符ä。控制台打印非ASCII字符时会自动转义,实际存储和使用时是正常的ä,直接访问字符串属性即可看到正确显示(如print(nominativePlural)会输出Die Hämmer)。
内容的提问来源于stack exchange,提问作者Ufuk Köşker

