简体   繁体   English

解析JSON时,Nil Coalescing返回nil而不是默认值

[英]Nil Coalescing returning nil instead of default value when parsing JSON

Im parsing JSON and I'm having an issue when i use nil coalescing to try and return a default value for a key / value set that sometimes doesn't exist. 我正在解析JSON,当我使用nil合并尝试为有时不存在的键/值集返回默认值时遇到问题。 The website I'm parsing json from is http://heroesjson.com . 我从中解析json的网站是http://heroesjson.com The creator has even given me a map of how to parse it. 创作者甚至给了我一张如何解析它的地图。 Where I run into problems is when I try to parse the talents key. 当我尝试解析人才关键时,就会遇到问题。 Not every talent has a "cooldown" or "prerequisite" key. 并非每个人才都有“冷却时间”或“必备条件”键。 SO I tried to use nil coalescing to assign a default value if the key/value set doesn't exist BUT when I try to assign the value to iCooldown or sPrerequisite, i get the error unexpectedly found nil when trying to unwrap optional. 因此,当我尝试将值分配给iCooldown或sPrerequisite时,如果键/值集不存在,则我尝试使用nil合并来分配默认值,但尝试拆开可选包装时我意外发现nil错误。 How can that be since I gave it a default value? 自从我给它一个默认值以来怎么办?

//Parse Talent Class
                        if let talentsArray = dict[x]["talents"] as? Dictionary<String, AnyObject> {

                            for y in 0 ..< dict.count {
                                if let allHeroTalents = talentsArray["\(y)"]{
                                    for z in 0 ..< allHeroTalents.count {


                                            let id = allHeroTalents[z]["id"]
                                            let name = allHeroTalents[z]["name"]
                                            let description = allHeroTalents[z]["description"]
                                            let cooldown = allHeroTalents[z]["cooldown"] ?? 0.0
                                            let prerequisite = allHeroTalents[z]["prerequisite"] ?? ""
                                            let icon = allHeroTalents[z]["icon"]


                                            let sId = id as? String
                                            let sName = name as? String
                                            let sDescription = description as? String
                                            //let iCooldown = cooldown as! Double
                                            //let sPrerequisite = prerequisite as! String
                                            let sIcon = icon as? String
                                            //let talent = Talent(id: sId!, name: sName!, description: sDescription!, cooldown: iCooldown, prerequisite: sPrerequisite, icon: sIcon!)
                                        print("\(hero.name) has talent \(cooldown)")


                                    }
                                }
                            }

                        }

I have included the whole file below for reference. 我在下面提供了整个文件,以供参考。 My main file just calls func parseData(). 我的主文件只是调用func parseData()。

 import Foundation class Hero { var id: String? var attributeid: String? var name: String? var title: String? var description: String? var role: String? var type: String? var gender: String? var franchise: String? var difficulty: String? var icon: String? var ratings: Ratings? var stats: Stats? var talents: [Talent]? init(id: String, attributeid: String, name: String, title: String, description: String, role: String, type: String, gender: String, franchise: String, difficulty: String, icon: String){ self.id = id self.attributeid = attributeid self.name = name self.title = title self.description = description self.role = role self.type = type self.gender = gender self.franchise = franchise self.difficulty = difficulty self.icon = icon } } class Ratings { var damage: Int? var utility: Int? var survivability: Int? var complexity: Int? init(damage: Int, utility: Int, survivability: Int, complexity: Int) { self.damage = damage self.utility = utility self.survivability = survivability self.complexity = complexity } } class Stats { var hp: Int? var hpPerLevel: Int? var hpRegen: Double? var hpRegenPerLevel: Double? var mana: Int? var manaPerLevel: Int? var manaRegen: Double? var manaRegenPerLevel: Double? init(hp: Int, hpPerLevel: Int, hpRegen: Double, hpRegenPerLevel: Double, mana: Int, manaPerLevel: Int, manaRegen: Double, manaRegenPerLevel: Double) { self.hp = hp self.hpPerLevel = hpPerLevel self.hpRegen = hpRegen self.hpRegenPerLevel = hpRegenPerLevel self.mana = mana self.manaPerLevel = manaPerLevel self.manaRegen = manaRegen self.manaRegenPerLevel = manaRegenPerLevel } } class Talent { var id: String? var name: String? var description: String? var cooldown: Double? var prerequisite: String? var icon: String? init(id: String, name: String, description: String, cooldown: Double, prerequisite: String, icon: String) { self.id = id self.name = name self.description = description self.cooldown = cooldown self.prerequisite = prerequisite self.icon = icon } } class Ability { var id: String? var name: String? var description: String? var shortcut: String? var cooldown: Double? var manaCost: Double? var manaCostPerSecond: Double? var aimType: String? var heroic: Bool? var trait: Bool? var mount: Bool? var icon: String? init(id: String, name: String, description: String, shortcut: String, cooldown: Double, manaCost: Double, manaCostPerSecond: Double, aimType: String, heroic: Bool, trait: Bool, mount: Bool, icon: String){ self.id = id self.name = name self.description = description self.shortcut = shortcut self.cooldown = cooldown self.manaCost = manaCost self.manaCostPerSecond = manaCostPerSecond self.aimType = aimType self.heroic = heroic self.trait = trait self.mount = mount self.icon = icon } } func parseData(){ let urlString = "http://heroesjson.com/heroes.json" let session = NSURLSession.sharedSession() let url = NSURL(string: urlString)! session.dataTaskWithURL(url) { (data: NSData?, response:NSURLResponse?, error: NSError?) -> Void in if let responseData = data { do { let json = try NSJSONSerialization.JSONObjectWithData(responseData, options: NSJSONReadingOptions.AllowFragments) if let dict = json as? [Dictionary<String, AnyObject>] { for x in 0 ..< dict.count { if let id = dict[x]["id"], let attributeid = dict[x]["attributeid"], let name = dict[x]["name"], let title = dict[x]["title"], let description = dict[x]["description"], let role = dict[x]["role"], let type = dict[x]["type"], let gender = dict[x]["gender"], let franchise = dict[x]["franchise"], let difficulty = dict[x]["difficulty"], let icon = dict[x]["icon"] { let hero = Hero(id: id as! String, attributeid: attributeid as! String, name: name as! String, title: title as! String, description: description as! String, role: role as! String, type: type as! String, gender: gender as! String, franchise: franchise as! String, difficulty: difficulty as! String, icon: icon as! String) // Parse Ratings Class if let dataArray = dict[x]["ratings"] as? Dictionary<String, Int> { if let damage = dataArray["damage"], let utility = dataArray["utility"], let survivability = dataArray["damage"], let complexity = dataArray["complexity"] { let rating = Ratings(damage: damage , utility: utility , survivability: survivability , complexity: complexity ) hero.ratings = rating //print("\\(hero.name) has a damage rating of \\(hero.ratings!.damage)") } } //Parse Stats Class if let statsArray = dict[x]["stats"] as? Dictionary<String, AnyObject> { if let dummy = statsArray[hero.id!]{//error handleing for vikings if let hp = statsArray[hero.id!]!["hp"], let hpPerLevel = statsArray[hero.id!]!["hpPerLevel"], let hpRegen = statsArray[hero.id!]!["hpRegen"], let hpRegenPerLevel = statsArray[hero.id!]!["hpRegenPerLevel"], let mana = statsArray[hero.id!]!["mana"], let manaPerLevel = statsArray[hero.id!]!["manaPerLevel"], let manaRegen = statsArray[hero.id!]!["manaRegen"], let manaRegenPerLevel = statsArray[hero.id!]!["manaRegenPerLevel"] { let stats = Stats(hp: hp as! Int, hpPerLevel: hpPerLevel as! Int, hpRegen: hpRegen as! Double, hpRegenPerLevel: hpRegenPerLevel as! Double, mana: mana as! Int, manaPerLevel: manaPerLevel as! Int, manaRegen: manaRegen as! Double, manaRegenPerLevel: manaRegenPerLevel as! Double) hero.stats = stats } }//closes let dummy } //Parse Talent Class if let talentsArray = dict[x]["talents"] as? Dictionary<String, AnyObject> { for y in 0 ..< dict.count { if let allHeroTalents = talentsArray["\\(y)"]{ for z in 0 ..< allHeroTalents.count { let id = allHeroTalents[z]["id"] let name = allHeroTalents[z]["name"] let description = allHeroTalents[z]["description"] let cooldown = allHeroTalents[z]["cooldown"] ?? 0.0 let prerequisite = allHeroTalents[z]["prerequisite"] ?? "" let icon = allHeroTalents[z]["icon"] let sId = id as? String let sName = name as? String let sDescription = description as? String //let iCooldown = cooldown as! Double //let sPrerequisite = prerequisite as! String let sIcon = icon as? String //let talent = Talent(id: sId!, name: sName!, description: sDescription!, cooldown: iCooldown, prerequisite: sPrerequisite, icon: sIcon!) print("\\(hero.name) has talent \\(cooldown)") } } } } // Parse Ability Class if let abilitiesArray = dict[x]["abilities"] as? Dictionary<String, AnyObject> { for c in 0 ..< abilitiesArray.count { for d in 0 ..< abilitiesArray.count { if let dummy = abilitiesArray[hero.id!]{ let id = abilitiesArray[hero.id!]!["id"] let name = abilitiesArray[hero.id!]!["name"] let description = abilitiesArray[hero.id!]!["description"] let shortcut = abilitiesArray[hero.id!]!["shortcut"] let cooldown = abilitiesArray[hero.id!]!["cooldown"] let manaCost = abilitiesArray[hero.id!]!["manaCost"] let manaCostPerSecond = abilitiesArray[hero.id!]!["manaCostPerSecond"] let aimType = abilitiesArray[hero.id!]!["aimType"] let heroic = abilitiesArray[hero.id!]!["heroic"] let trait = abilitiesArray[hero.id!]!["trait"] let mount = abilitiesArray[hero.id!]!["mount"] let icon = abilitiesArray[hero.id!]!["icon"] let sId = id as? String let sName = name as? String let sDescription = description as? String let sShortcut = shortcut as? String let sCooldown = cooldown as? Double let sManaCost = manaCost as? Double let sManaCostPerSecond = manaCostPerSecond as? Double let sAimType = aimType as? String let sHeroic = heroic as? Bool let sTrait = trait as? Bool let sMount = mount as? Bool let sIcon = icon as? String // let abilities = Ability(id: sId!, name: sName!, description: sDescription!, shortcut: sShortcut!, cooldown: sCooldown!, manaCost: sManaCost!, manaCostPerSecond: sManaCostPerSecond!, aimType: sAimType!, heroic: sHeroic!, trait: sTrait!, mount: sMount!, icon: sIcon!) } } } } heroes.append(hero) } } } } catch { print("Could not serialize") } } }.resume() } 

I might be wrong, but it seems that you are using your Dictionary as an Array. 我可能是错的,但似乎您正在将Dictionary用作数组。 which causes you to unwrap optional values that are not there. 这会使您解开不存在的可选值。

From your code: 从您的代码:

//- talentsArray is a dictionary! 
if let talentsArray = dict[x]["talents"] as? Dictionary<String, AnyObject> {
    for y in 0 ..< dict.count {
        //- talentsArray["\(y)"] y is a value from 0 to the count, not necesarilly a key.
        if let allHeroTalents = talentsArray["\(y)"]{
            for z in 0 ..< allHeroTalents.count {
              // ...
            }
        }
    }
}

Dictionaries do not maintain order, if you need to iterate through them you can Enumerate your dictionary, you have to use the for (key, value) in dictionary syntax. 字典不维护顺序,如果需要遍历它们,可以枚举字典,则必须for (key, value) in dictionary语法中使用for (key, value) in dictionary Something along the lines of: 类似于以下内容:

if let talents = dict[x]["talents"] as? Dictionary<String, AnyObject> {
    for (index, talent) in talents {
        for attribute in talent {
           // Do your thing.
        }
    }
}

Your problem as I understand it, is not the coalescing, but the incorrect use of the dictionary DS. 据我了解,您的问题不是合并,而是字典DS的错误使用。 I replicated the API call and can see that even if you where to iterate through it while treating the index as the key of the dictionary, you'll run into some trouble as the keys are in no way sequential: 我复制了API调用,并且可以看到,即使您在将索引作为字典的键时将其遍历的位置,也将遇到一些麻烦,因为键绝不是顺序的:

在此处输入图片说明

UPDATE 更新

As I mentioned in the comments, while your solution works let cooldown = item[cooldown]! 正如我在评论中提到的那样,在您的解决方案有效的同时, let cooldown = item[cooldown]! will force unwrap your optional, which is not the best approach when dealing with optional values fetched from an API (Specially if they are not always included in the data as you mention in the original question). 将强制解开您的可选值,这不是处理从API提取的可选值时的最佳方法(特别是如果它们不总是如您在原始问题中提到的那样始终包含在数据中)。 Assuming that your 'cooldown' is a double and 'prerequisite' is a string, I would implement it like this: 假设您的“ cooldown”是一个双精度字符,而“ prerequisite”是一个字符串,我将这样实现它:

let cooldown = item["cooldown"] as? Double ?? 0.00
let prerequisite = item["prerequisite"] as? String ?? ""

This approach will unwrap your optional while casting to the correct expected type, and then coalesce the value of the key value pair. 此方法将在强制转换为正确的预期类型时解开可选项,然后合并键值对的值。 This approach is much safer as it does not assume that the API Call will contain these keys. 这种方法更加安全,因为它不假定API调用将包含这些密钥。

Hope this helps! 希望这可以帮助!

I stumbled across the answer by messing with my code. 我弄乱了我的代码,偶然发现了答案。 Apparently all I needed to do was unwrap (use !) the variable going into the nil coalescing operator. 显然,我需要做的只是解包(使用!)进入nil合并运算符的变量。

let cooldown = item["cooldown"]! ?? 0.00
let prerequisite = item["prerequisite"]! ?? ""

Entire Working Code 整个工作守则

 import Foundation class Hero { var id: String? var attributeid: String? var name: String? var title: String? var description: String? var role: String? var type: String? var gender: String? var franchise: String? var difficulty: String? var icon: String? var ratings: Ratings? var stats: Stats? var talents: [String: [Talent]]? var abilities: [String: [Ability]]? init(id: String, attributeid: String, name: String, title: String, description: String, role: String, type: String, gender: String, franchise: String, difficulty: String, icon: String){ self.id = id self.attributeid = attributeid self.name = name self.title = title self.description = description self.role = role self.type = type self.gender = gender self.franchise = franchise self.difficulty = difficulty self.icon = icon } } class Ratings { var damage: Int? var utility: Int? var survivability: Int? var complexity: Int? init(damage: Int, utility: Int, survivability: Int, complexity: Int) { self.damage = damage self.utility = utility self.survivability = survivability self.complexity = complexity } } class Stats { var hp: Int? var hpPerLevel: Int? var hpRegen: Double? var hpRegenPerLevel: Double? var mana: Int? var manaPerLevel: Int? var manaRegen: Double? var manaRegenPerLevel: Double? init(hp: Int, hpPerLevel: Int, hpRegen: Double, hpRegenPerLevel: Double, mana: Int, manaPerLevel: Int, manaRegen: Double, manaRegenPerLevel: Double) { self.hp = hp self.hpPerLevel = hpPerLevel self.hpRegen = hpRegen self.hpRegenPerLevel = hpRegenPerLevel self.mana = mana self.manaPerLevel = manaPerLevel self.manaRegen = manaRegen self.manaRegenPerLevel = manaRegenPerLevel } } class Talent { var id: String? var name: String? var description: String? var cooldown: Double? var prerequisite: String? var icon: String? init(id: String, name: String, description: String, cooldown: Double, prerequisite: String, icon: String) { self.id = id self.name = name self.description = description self.cooldown = cooldown self.prerequisite = prerequisite self.icon = icon } } class Ability { var id: String? var name: String? var description: String? var shortcut: String? var cooldown: Double? var manaCost: Double? var manaCostPerSecond: Double? var aimType: String? var heroic: Bool? var trait: Bool? var mount: Bool? var icon: String? init(id: String, name: String, description: String, shortcut: String, cooldown: Double, manaCost: Double, manaCostPerSecond: Double, aimType: String, heroic: Bool, trait: Bool, mount: Bool, icon: String){ self.id = id self.name = name self.description = description self.shortcut = shortcut self.cooldown = cooldown self.manaCost = manaCost self.manaCostPerSecond = manaCostPerSecond self.aimType = aimType self.heroic = heroic self.trait = trait self.mount = mount self.icon = icon } } func parseData() { let urlString = "http://heroesjson.com/heroes.json" let session = NSURLSession.sharedSession() let url = NSURL(string: urlString)! session.dataTaskWithURL(url) { (data: NSData?, response:NSURLResponse?, error: NSError?) -> Void in if let responseData = data { do { let json = try NSJSONSerialization.JSONObjectWithData(responseData, options: NSJSONReadingOptions.AllowFragments) if let dict = json as? [Dictionary<String, AnyObject>] { for x in 0 ..< dict.count { if let id = dict[x]["id"], let attributeid = dict[x]["attributeid"], let name = dict[x]["name"], let title = dict[x]["title"], let description = dict[x]["description"], let role = dict[x]["role"], let type = dict[x]["type"], let gender = dict[x]["gender"], let franchise = dict[x]["franchise"], let difficulty = dict[x]["difficulty"], let icon = dict[x]["icon"] { let hero = Hero(id: id as! String, attributeid: attributeid as! String, name: name as! String, title: title as! String, description: description as! String, role: role as! String, type: type as! String, gender: gender as! String, franchise: franchise as! String, difficulty: difficulty as! String, icon: icon as! String) // Parse Ratings Class if let dataArray = dict[x]["ratings"] as? Dictionary<String, Int> { if let damage = dataArray["damage"], let utility = dataArray["utility"], let survivability = dataArray["damage"], let complexity = dataArray["complexity"] { let rating = Ratings(damage: damage , utility: utility , survivability: survivability , complexity: complexity ) hero.ratings = rating //print("\\(hero.name) has a damage rating of \\(hero.ratings!.damage)") } } //Parse Stats Class if let statsArray = dict[x]["stats"] as? Dictionary<String, AnyObject> { if let dummy = statsArray[hero.id!]{//error handleing for vikings - parsing without this causes errors if let hp = statsArray[hero.id!]!["hp"], let hpPerLevel = statsArray[hero.id!]!["hpPerLevel"], let hpRegen = statsArray[hero.id!]!["hpRegen"], let hpRegenPerLevel = statsArray[hero.id!]!["hpRegenPerLevel"], let mana = statsArray[hero.id!]!["mana"], let manaPerLevel = statsArray[hero.id!]!["manaPerLevel"], let manaRegen = statsArray[hero.id!]!["manaRegen"], let manaRegenPerLevel = statsArray[hero.id!]!["manaRegenPerLevel"] { let stats = Stats(hp: hp as! Int, hpPerLevel: hpPerLevel as! Int, hpRegen: hpRegen as! Double, hpRegenPerLevel: hpRegenPerLevel as! Double, mana: mana as! Int, manaPerLevel: manaPerLevel as! Int, manaRegen: manaRegen as! Double, manaRegenPerLevel: manaRegenPerLevel as! Double) hero.stats = stats } }//closes let dummy } //Parse Talent Class if let talentsDict = dict[x]["talents"] as? Dictionary<String, AnyObject> { for (index, talentsAtLevel) in talentsDict { var talents = [Talent]() for item in talentsAtLevel as! [AnyObject] { let id = item["id"] let name = item["name"] let description = item["description"] let cooldown = item["cooldown"]! ?? 0.00 let prerequisite = item["prerequisite"]! ?? "" let icon = item["icon"] let sId = id as? String let sName = name as? String let sDescription = description as? String let iCooldown = cooldown as! Double let sPrerequisite = prerequisite as! String let sIcon = icon as? String let talent = Talent(id: sId!, name: sName!, description: sDescription!, cooldown: iCooldown, prerequisite: sPrerequisite, icon: sIcon!) talents.append(talent) } hero.talents = ["\\(index)": talents] } } //Parse Ability Class if let abilitiesDict = dict[x]["abilities"] as? Dictionary<String, AnyObject> { for (index, abilitiesAtLevel) in abilitiesDict { //index = heroid var abilities = [Ability]() for item in abilitiesAtLevel as! [AnyObject] { //item = ability let id = item["id"]! ?? "" let name = item["name"]! ?? "" let description = item["description"]! ?? "" let shortcut = item["shortcut"]! ?? "" let cooldown = item["cooldown"]! ?? 0.0 let manaCost = item["manaCost"]! ?? 0.0 let manaCostPerSecond = item["manaCostPerSecond"]! ?? 0.0 let aimType = item["aimType"]! ?? "" let heroic = item["heroic"]! ?? false let trait = item["trait"]! ?? false let mount = item["mount"]! ?? false let icon = item["icon"]! ?? "" let sId = id as? String let sName = name as? String let sDescription = description as? String let sShortcut = shortcut as? String let sCooldown = cooldown as? Double let sManaCost = manaCost as? Double let sManaCostPerSecond = manaCostPerSecond as? Double let sAimType = aimType as? String let sHeroic = heroic as? Bool let sTrait = trait as? Bool let sMount = mount as? Bool let sIcon = icon as? String let ability = Ability(id: sId!, name: sName!, description: sDescription!, shortcut: sShortcut!, cooldown: sCooldown!, manaCost: sManaCost!, manaCostPerSecond: sManaCostPerSecond!, aimType: sAimType!, heroic: sHeroic!, trait: sTrait!, mount: sMount!, icon: sIcon!) abilities.append(ability) } hero.abilities = ["\\(index)": abilities] } } heroes.append(hero) } } } } catch { print("Could not serialize") } } }.resume() } 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM