英文:
Swift not converting specific values to a float
问题
你的代码似乎在尝试将 JSON 中的 1.1 转换为浮点数时出现问题,但可以正常处理其他值。这可能是由于 JSON 中的数字表示形式或浮点数的精度问题引起的。你可以尝试使用以下方法来解决问题:
-
确保 JSON 中的 1.1 是合法的数字表示形式,不包含额外的空格或字符。
-
尝试使用
Float
的初始化方法来将 JSON 中的值转换为浮点数,如下所示:
if let val = dict["atkdefval"] as? Float {
// 此处 val 应该包含 1.1
// 进行处理
} else {
// 处理无法转换的情况
}
如果你尝试了这些方法仍然无法正常工作,可能需要检查 JSON 数据是否符合预期,以确保没有其他问题导致无法正确解析 1.1。
英文:
My code is not working to convert 1.1 to a float. It works to convert 1.0, 1.25, and 1.5 to floats but doesn't convert 1.1. I do not understand why this would happen as they are all decimals with very few digits of precision required. This is when decoding from a JSON.
The code in question:
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)
}
The 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
}
]
The class code (in case it helps):
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
}
}
I tried to have it convert from a JSON dict as a float but it converted as nil
instead of the expected 1.1. I tried converting it to a string from the JSON and to a float from there and it worked but it is confusing as to why the first way doesn't work.
答案1
得分: 2
你应该按照 McKinley 描述的方式使用 JSONDecoder,但 1.1 失败的原因是默认的内部类型是 Double,1.1 的 (四舍五入的) Double 值不精确地适应于 Float。考虑:
import Foundation
NSNumber(1.1) as? Double // 1.1
NSNumber(1.1) as? Float // nil
NSNumber(1.5) as? Double // 1.5
NSNumber(1.5) as? Float // 1.5
1.5 可以在 Float 和 Double 中精确表示,因此可以转换。
JSONSerialization 将所有内容打包到 NSNumber 中,如果需要四舍五入,则无法将其 as?
桥接到 Float。你可以这样写(但我不建议):
if let actionName = dict["name"] as? String,
let type = dict["type"] as? String,
let amt = dict["amtlost"] as? Int,
let val = (dict["atkdefval"] as? NSNumber)?.floatValue, // <==
let dtype = dict["element"] as? String,
let target = dict["target"] as? Bool,
let steal = (dict["stealAmt"] as? NSNumber)?.floatValue // <==
你也可以通过在所有地方使用 Double 而不是 Float 来解决此问题,你应该这样做。如果有疑问,在 Swift 中使用 Double 来处理浮点数。
此外,更好的解决方案是使用 JSONDecoder,避免这些微妙的问题。由于所有类型都已经是 Decodable,你的整个解码逻辑可以被替换为:
self.moves = try JSONDecoder().decode([Actions].self, from: data)
英文:
You should use JSONDecoder as McKinley describes, but the reason this fails for 1.1 is because the default internal type is Double and the (rounded) Double value 1.1 doesn't fit in Float exactly. Consider:
import Foundation
NSNumber(1.1) as? Double // 1.1
NSNumber(1.1) as? Float // nil
NSNumber(1.5) as? Double // 1.5
NSNumber(1.5) as? Float // 1.5
1.5 can be expressed precisely in both Float and Double, so it converts.
JSONSerialization packs everything into an NSNumber, and that won't as?
bridge to Float if it requires rounding. You could write it this way (but I don't recommend it):
if let actionName = dict["name"] as? String,
let type = dict["type"] as? String,
let amt = dict["amtlost"] as? Int,
let val = (dict["atkdefval"] as? NSNumber)?.floatValue, // <==
let dtype = dict["element"] as? String,
let target = dict["target"] as? Bool,
let steal = (dict["stealAmt"] as? NSNumber)?.floatValue // <==
You can also fix this by using Double everywhere rather than Float, and you should do that anyway. When in doubt, use Double in Swift for floating point.
But in addition, the better solution is to use JSONDecoder which avoids these subtle problems. Since all the types are already Decodable, your entire decoding logic can be replaced by:
self.moves = try JSONDecoder().decode([Actions].self, from: data)
答案2
得分: 1
由于您的Actions
类符合Codable
,您实际上可以使用更简单的JSONDecoder
而不是JSONSerialization
:
let listOfMoves = try JSONDecoder().decode([Actions].self, from: data)
self.moves.append(contentsOf: listOfMoves)
这样,您就不必担心手动将字典条目强制转换为Float
和Bool
等,然后手动创建Actions
。相反,JSONDecoder
将为您执行所有操作,并生成一个漂亮的Actions
数组——或者如果JSON格式不正确,则抛出错误。
英文:
Since your Actions
class conforms to Codable
, you can actually use the simpler JSONDecoder
instead of JSONSerialization
:
let listOfMoves = try JSONDecoder().decode([Actions].self, from: data)
self.moves.append(contentsOf: listOfMoves)
Then you don't have to worry about manually casting the entries of the dictionary to Float
s and Bool
s, etc., and then manually creating Actions
. Instead, JSONDecoder
will perform all that for you and spit out a nice array of Actions
-- or throw an error if the JSON is improperly formatted.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论