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.
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)