从JSON解析的字典中安全获取数值的更优方案探讨
我太懂你这个头疼的问题了——Swift里解析JSON的时候,数值类型的强转简直是个坑!服务器返回的整数、浮点数混着来,直接用as?转成你要的Double或者CGFloat经常翻车,只能用NSNumber绕一下,但写出来的代码又臭又长。
先帮你再明确下问题:当我们把JSON解析成[String: Any]字典后,各种数值类型(Int、Float、CGFloat、Double)只能转成自身类型,跨类型强转全返回nil,就像你举的例子那样:
let myDictionary: [String: Any] = [ "myNumber_int" : 1, "myNumber_float" : Float(1), "myNumber_cgFloat" : CGFloat(1), "myNumber_double" : 1.0 ] // Int转Float直接返回nil let numberInt2 = myDictionary["myNumber_int"] as? Float // nil
虽然用NSNumber能解决,但重复的(as? NSNumber)?.floatValue代码实在太冗余了。下面给你两个不依赖NSNumber的优雅方案:
方案一:基于Numeric协议的通用扩展
Swift的Numeric协议覆盖了所有数值类型,我们可以给Any写一个通用扩展,一键完成跨类型转换:
extension Any { // 可选值版本,转换失败返回nil func asNumeric<T: Numeric>() -> T? { switch self { case let num as T: return num case let int as Int: return T(exactly: int) case let float as Float: return T(exactly: float) case let double as Double: return T(exactly: double) case let cgFloat as CGFloat: return T(exactly: cgFloat) default: return nil } } // 带默认值的版本,直接拿到非可选结果,更实用 func asNumeric<T: Numeric>(default defaultValue: T) -> T { return asNumeric() ?? defaultValue } }
用起来超级简洁,不管原来是什么数值类型,都能转成你要的类型:
// 转成CGFloat数组,一行一个,干净利落 let myNumbers: [CGFloat] = [ myDictionary["myNumber_int"].asNumeric(default: 0), myDictionary["myNumber_float"].asNumeric(default: 0), myDictionary["myNumber_cgFloat"].asNumeric(default: 0), myDictionary["myNumber_double"].asNumeric(default: 0), ] // 结果:[1, 1, 1, 1] // 单独转成Double let doubleFromInt = myDictionary["myNumber_int"].asNumeric(default: 0.0) as Double // 1.0 // 单独转成Int let intFromDouble = myDictionary["myNumber_double"].asNumeric(default: 0) as Int // 1
如果经常从字典里取值,还可以给[String: Any]加个扩展,进一步简化:
extension Dictionary where Key == String, Value == Any { func numeric<T: Numeric>(for key: String, default defaultValue: T) -> T { return self[key].asNumeric(default: defaultValue) } }
用起来更爽:
let num = myDictionary.numeric(for: "myNumber_int", default: 0 as CGFloat)
这个方案的好处是:纯Swift原生实现,没有NSNumber的桥接开销,支持所有数值类型,代码复用性拉满。
方案二:用Codable彻底避免Any类型
如果你的项目经常处理JSON解析,更推荐直接用Codable协议,从根源上避免Any类型的麻烦。比如定义一个模型,统一处理数值转换:
struct NumberResponse: Codable { let myNumberInt: CGFloat let myNumberFloat: CGFloat let myNumberCgFloat: CGFloat let myNumberDouble: CGFloat // 映射JSON字段名 enum CodingKeys: String, CodingKey { case myNumberInt = "myNumber_int" case myNumberFloat = "myNumber_float" case myNumberCgFloat = "myNumber_cgFloat" case myNumberDouble = "myNumber_double" } // 自定义解码逻辑,统一转成CGFloat init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) // 先转成Double,再转CGFloat,兼容所有数值类型 myNumberInt = CGFloat(try container.decode(Double.self, forKey: .myNumberInt)) myNumberFloat = CGFloat(try container.decode(Double.self, forKey: .myNumberFloat)) myNumberCgFloat = CGFloat(try container.decode(Double.self, forKey: .myNumberCgFloat)) myNumberDouble = CGFloat(try container.decode(Double.self, forKey: .myNumberDouble)) } }
解析的时候直接用JSONDecoder,完全不用处理强转:
let jsonData = try JSONSerialization.data(withJSONObject: myDictionary) let response = try JSONDecoder().decode(NumberResponse.self, from: jsonData) // response里的所有属性都是CGFloat类型,值都是1
这个方案更适合大型项目,类型安全,代码可读性更高,后期维护也方便。
内容的提问来源于stack exchange,提问作者Matic Oblak
相关产品推荐
相关产品推荐

