You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

从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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 10:03:34