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

Swift解析JSON时无法将1.1转换为Float的问题

Swift JSON解析问题:1.1无法直接转为Float,其他小数正常

问题描述

在Swift中解析JSON时遇到异常:JSON中的数值1.1无法直接通过as? Float转换成功,返回nil;但1.0、1.25、1.5这类小数却能正常转换。尝试先将数值转为字符串再转Float可以成功,需要明确直接转换失败的原因。

相关代码

JSON解析代码

if let path = Bundle.main.path(forResource: "moves", ofType: "json") {
    do {
        let data = try Data(contentsOf: URL(fileURLWithPath: path))
        let json = try JSONSerialization.jsonObject(with: data, options: [])
        if let array = json as? [[String:Any]] {
            for dict in array {
                if let actionName = dict["name"] as? String,
                   let type = dict["type"] as? String,
                   let amt = dict["amtlost"] as? Int,
                   let val = dict["atkdefval"] as? Float,
                   let dtype = dict["element"] as? String,
                   let target = dict["target"] as? Bool,
                   let steal = dict["stealAmt"] as? Float{
                    
                    let cmove = Actions(name: actionName, type: type, amtlost: amt, atkdefval: val, damageType: dtype, target: target, stealAmt: steal)
                    self.moves.append(cmove)
                } else {
                    print(dict)
                    let val = dict["atkdefval"] as? Float
                    print(val)
                }
            }
        }
    } catch {
        print("Error reading location JSON file: \(error)")
    }
} else {
    print("Could not read JSON")
}
print(self.moves.count)
for move in moves {
    print(move.name)
    print(move.atkdefval)
}

对应JSON数据

[
    {
        "name": "Slash",
        "type": "phys",
        "amtlost": 0,
        "atkdefval": 1.5,
        "element": "slashing",
        "target": false,
        "stealAmt": 0
    },
    {
        "name": "Punch",
        "type": "phys",
        "amtlost": 0,
        "atkdefval": 1.1,
        "element": "bludgeoning",
        "target": false,
        "stealAmt": 0
    },
    {
        "name": "Magic Missile",
        "type": "magic",
        "amtlost": 2,
        "atkdefval": 1.75,
        "element": "force",
        "target": false,
        "stealAmt": 0
    },
    {
        "name": "Block",
        "type": "defense",
        "amtlost": 0,
        "atkdefval": 1.5,
        "element": "bludgeoning",
        "target": false,
        "stealAmt": 0
    },
    {
        "name": "Healing Word",
        "type": "healing",
        "amtlost": 2,
        "atkdefval": 1.25,
        "element": "light",
        "target": false,
        "stealAmt": 0
    },
    {
        "name": "Shield",
        "type": "shield",
        "amtlost": 1,
        "atkdefval": 1.5,
        "element": "force",
        "target": false,
        "stealAmt": 0
    }
]

Actions类代码

enum ActionType:String, Codable{
    case phys = "phys"
    case magic = "magic"
    case defense = "defense"
    case healing = "healing"
    case shield = "shield"
}

enum DamageElement: String, Codable{
    case slash = "slashing"
    case pierce = "piercing"
    case bludgeon = "bludgeoning"
    case fire = "fire"
    case ice = "ice"
    case earth = "earth"
    case lightning = "lightning"
    case light = "light"
    case dark = "dark"
    case force = "force"
}

class Actions: Codable, Hashable, Equatable{
    var name: String
    var type: ActionType
    var amtlost: Int
    var atkdefval: Float
    var element: DamageElement
    var target: Bool // false is hp, true is mp
    var stealAmt: Float
    
    static func == (lhs: Actions, rhs: Actions) -> Bool {
        return lhs.name == rhs.name
    }
    
    func hash(into hasher: inout Hasher) { return hasher.combine(ObjectIdentifier(self))}
    
    init(){
        self.name = "punch"
        self.type = .phys
        self.amtlost = 0
        self.atkdefval = 4
        self.element = .bludgeon
        self.target = false
        self.stealAmt = 0
    }
    
    init(name: String, type: String, amtlost: Int, atkdefval: Float, damageType: String, target: Bool, stealAmt: Float) {
        self.name = name
        self.type = ActionType(rawValue: type.lowercased())!
        self.amtlost = amtlost
        self.atkdefval = atkdefval
        self.element = DamageElement(rawValue: damageType.lowercased())!
        self.target = target
        self.stealAmt = stealAmt
    }
    
}

原因分析

  1. JSONSerialization的数值存储类型:JSONSerialization解析JSON中的小数时,会统一将其转换为Double类型存储,而非Float。
  2. 二进制浮点数精度差异:
    • 像1.5、1.25这类数可以用二进制浮点数精确表示,对应的Double值转Float是无损的,因此as? Float能成功匹配类型并转换。
    • 1.1无法用二进制浮点数精确表示,存储为Double时是一个近似值。Swift的as?类型转换是严格的类型检查,不会自动执行数值类型的隐式转换,所以将Double实例直接强转为Float会返回nil。
  3. 字符串中转的原理:先转字符串再转Float,本质是通过字符串重新解析数值,这个过程会做近似处理,从而得到对应的Float值。

解决方案

方案1:显式转换Double为Float

将解析Float的代码改为先获取Double值,再显式转换为Float:

let val = (dict["atkdefval"] as? Double).map { Float($0) }
let steal = (dict["stealAmt"] as? Double).map { Float($0) }

方案2:使用Codable自动解析(推荐)

既然Actions类已经实现了Codable,可以直接用JSONDecoder完成解析,无需手动处理字典,代码更简洁且自动处理数值类型转换:

if let path = Bundle.main.path(forResource: "moves", ofType: "json") {
    do {
        let data = try Data(contentsOf: URL(fileURLWithPath: path))
        self.moves = try JSONDecoder().decode([Actions].self, from: data)
    } catch {
        print("Error parsing JSON: \(error)")
    }
} else {
    print("Could not read JSON")
}

注:确保JSON字段名与Actions类的属性名一致,Codable会自动完成映射;若字段名不一致,可通过CodingKeys枚举自定义映射关系。

内容的提问来源于stack exchange,提问作者Andrew Nazareth

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 05:37:50