Swift Decodable解析含数字的蛇形命名JSON键失败问题求助
看起来你遇到了Swift JSON解码里一个很容易踩的坑——默认的蛇形转驼峰策略在处理包含数字的键时,会把数字后的字母大写,导致和你的模型属性名不匹配。让我们一步步拆解问题,然后给出可行的解决方案。
问题根源
先看错误信息:当你使用convertFromSnakeCase时,解码器尝试寻找v_price_24h对应的模型属性,但默认策略会把v_price_24h转换成**vPrice24H**(注意最后一个H是大写的),而你的模型里属性是vPrice24h(h小写),两者不匹配,所以抛出keyNotFound错误。
另外,你在InsideModel里定义了CodingKeys,同时又设置了全局keyDecodingStrategy,这会导致冲突——CodingKeys的优先级高于全局解码策略,解码器会直接用你指定的vPrice24h = "v_price_24h",但因为开启了convertFromSnakeCase,它又会把模型属性名vPrice24h反向转换成蛇形键v_price_24_h,自然找不到对应的值。
解决方案
这里有三个可行的方案,你可以根据自己的需求选择:
方案1:调整模型属性名匹配默认策略的转换结果
最简单的办法是把模型里的vPrice24h改成vPrice24H,这样默认的convertFromSnakeCase就能完美匹配v_price_24h:
import Foundation var json = """ { "result": [ { "symbol": "A", "v_price_24h": "39652.50" } ], "time_now": "1645931312.379455" } """ struct TopModel: Decodable { let timeNow: String let result: [InsideModel] } struct InsideModel: Decodable { let symbol: String let vPrice24H: String // 注意这里H大写 } let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase do { let model = try decoder.decode(TopModel.self, from: json.data(using: .utf8)!) print(model.result[0].vPrice24H) // 输出39652.50 } catch let thrownError { print("🚀", thrownError) }
方案2:自定义key解码策略,精准处理数字后的字母
如果你不想修改模型属性名,可以自定义一个解码策略,确保数字后的字母保持小写。下面是修正后的自定义策略:
import Foundation var json = """ { "result": [ { "symbol": "A", "v_price_24h": "39652.50" } ], "time_now": "1645931312.379455" } """ struct TopModel: Decodable { let timeNow: String let result: [InsideModel] } struct InsideModel: Decodable { let symbol: String let vPrice24h: String // 保持原属性名 } let decoderCustom = JSONDecoder() decoderCustom.keyDecodingStrategy = .custom { keys in let snakeCaseKey = keys.last!.stringValue // 分割蛇形键为组件:["v", "price", "24", "h"] let components = snakeCaseKey.components(separatedBy: "_") // 处理每个组件:第一个组件小写,数字组件保持原样,其他组件首字母大写其余小写 let camelCaseComponents = components.enumerated().map { index, component in if index == 0 { return component.lowercased() } if Int(component) != nil { return component } return component.prefix(1).uppercased() + component.dropFirst().lowercased() } // 拼接成驼峰键:v + Price + 24 + h → vPrice24h let camelCaseKey = camelCaseComponents.joined() return CodingKeys(stringValue: camelCaseKey)! } do { let model = try decoderCustom.decode(TopModel.self, from: json.data(using: .utf8)!) print(model.result[0].vPrice24h) // 输出39652.50 } catch let thrownError { print("✅", thrownError) }
方案3:手动指定CodingKeys,绕开自动转换
如果你的键数量不多,也可以直接在模型里定义CodingKeys,同时不要设置全局keyDecodingStrategy(或者保持默认的.useDefaultKeys),这样就能精准映射:
import Foundation var json = """ { "result": [ { "symbol": "A", "v_price_24h": "39652.50" } ], "time_now": "1645931312.379455" } """ struct TopModel: Decodable { enum CodingKeys: String, CodingKey { case timeNow = "time_now" case result } let timeNow: String let result: [InsideModel] } struct InsideModel: Decodable { enum CodingKeys: String, CodingKey { case symbol case vPrice24h = "v_price_24h" } let symbol: String let vPrice24h: String } let decoder = JSONDecoder() do { let model = try decoder.decode(TopModel.self, from: json.data(using: .utf8)!) print(model.result[0].vPrice24h) // 输出39652.50 } catch let thrownError { print("🛑", thrownError) }
对你现有代码的修正说明
你之前的自定义策略里用了capitalized,它会把每个单词的首字母大写,包括数字后面的"h"变成"H",导致和模型属性vPrice24h不匹配。另外,同时使用CodingKeys和全局keyDecodingStrategy会造成冲突,所以要么去掉CodingKeys,要么不要设置全局策略。
内容的提问来源于stack exchange,提问作者Shial

