如何在Swift中访问深层嵌套的词典

问题描述 投票:30回答:8

我的应用程序中有一个非常复杂的数据结构,我需要操作它。我试图跟踪玩家在他们的花园中有多少种类型的错误。有十种类型的错误,每种错误有十种模式,每种模式有十种颜色。因此,可能有1000个独特的错误,我想跟踪玩家拥有的这些类型中的每一个。嵌套字典看起来像:

var colorsDict: [String : Int]
var patternsDict: [String : Any] // [String : colorsDict]
var bugsDict: [String : Any] // [String : patternsDict]

我没有用这种语法得到任何错误或抱怨。

当我想增加玩家的bug收集时,这样做:

bugs["ladybug"]["spotted"]["red"]++

我收到此错误:字符串不能转换为'DictionaryIndex <String,Any>',错误的胡萝卜在第一个字符串下。

另一篇类似的帖子建议使用“as Any?”在代码中,但该帖子的OP只有一个深度字典,所以可以很容易地做到:dict [“string”]为Any? ...

我不知道如何用多级字典做到这一点。任何帮助,将不胜感激。

dictionary swift nested
8个回答
43
投票

使用字典时,您必须记住字典中可能不存在密钥。因此,词典总是返回选项。因此,每次按键访问字典时,都必须按如下方式打开每个级别:

bugsDict["ladybug"]!["spotted"]!["red"]!++

我认为你知道关于选项,但是要清楚,如果你100%确定字典中存在键,请使用感叹号,否则最好使用问号:

bugsDict["ladybug"]?["spotted"]?["red"]?++

附录:这是我在游乐场测试时使用的代码:

var colorsDict = [String : Int]()
var patternsDict =  [String : [String : Int]] ()
var bugsDict = [String : [String : [String : Int]]] ()

colorsDict["red"] = 1
patternsDict["spotted"] = colorsDict
bugsDict["ladybug"] = patternsDict


bugsDict["ladybug"]!["spotted"]!["red"]!++ // Prints 1
bugsDict["ladybug"]!["spotted"]!["red"]!++ // Prints 2
bugsDict["ladybug"]!["spotted"]!["red"]!++ // Prints 3
bugsDict["ladybug"]!["spotted"]!["red"]! // Prints 4

36
投票

另一种选择:你可以尝试调用dict.value( forKeyPath: "ladybug.spotted.red" )!


所以我只是尝试使用Swift 5:

import Foundation

var d = [ "ladybug" : [ "spotted" : [ "red" : 123 ] ] ] as [String:Any]

(d as NSDictionary).value(forKeyPath: "ladybug.spotted.red")

它有效,但这可能是最好的方法:

d["ladybug"]?["spotted"]?["red"]

8
投票

我的主要用例是从深层词典中读取ad-hoc值。在我的Swift 3.1项目中,没有给出的答案对我有用,所以我去寻找并发现Ole Begemann对Swift词典的优秀扩展,以及它如何工作的detailed explanation

我用我制作的Swift文件制作了一个Github gist,我欢迎反馈。

要使用它,您可以将Keypath.swift添加到项目中,然后您可以在任何[String:Any]字典上使用keyPath下标语法,如下所示。

考虑到你有一个像这样的JSON对象:

{
    "name":"John",
    "age":30,
    "cars": {
        "car1":"Ford",
        "car2":"BMW",
        "car3":"Fiat"
    }
}

存储在字典var dict:[String:Any]中。您可以使用以下语法来获取对象的各种深度。

if let name = data[keyPath:"name"] as? String{
    // name has "John"
}
if let age = data[keyPath:"age"] as? Int{
    // age has 30
}
if let car1 = data[keyPath:"cars.car1"] as? String{
    // car1 has "Ford"
}

请注意,扩展程序也支持写入嵌套字典,但我还没有使用它。

我仍然没有找到一种方法来使用它来访问字典对象中的数组,但这是一个开始!我正在寻找一个针对Swift的JSON Pointer实现,但尚未找到一个。


5
投票

我有同样的问题,我想让boolValue嵌套在字典中。

{
  "Level1": {
    "leve2": {
      "code": 0,
      "boolValue": 1
    }
  }
}

我尝试了很多解决方案,但那些对我不起作用,因为我缺少类型转换。所以我使用以下代码从json获取boolValue,其中json是[String:Any]类型的嵌套字典。

let boolValue = ((json["level1"]
    as? [String: Any])?["level2"]
    as? [String: Any])?["boolValue"] as? Bool

2
投票

如果它只是关于检索(不是操作)那么这里是Swift 3的字典扩展(代码准备好粘贴到Xcode游乐场):

//extension
extension Dictionary where Key: Hashable, Value: Any {
    func getValue(forKeyPath components : Array<Any>) -> Any? {
        var comps = components;
        let key = comps.remove(at: 0)
        if let k = key as? Key {
            if(comps.count == 0) {
                return self[k]
            }
            if let v = self[k] as? Dictionary<AnyHashable,Any> {
                return v.getValue(forKeyPath : comps)
            }
        }
        return nil
    }
}

//read json
let json = "{\"a\":{\"b\":\"bla\"},\"val\":10}" //
if let parsed = try JSONSerialization.jsonObject(with: json.data(using: .utf8)!, options: JSONSerialization.ReadingOptions.mutableContainers) as? Dictionary<AnyHashable,Any>
{
    parsed.getValue(forKeyPath: ["a","b"]) //-> "bla"
    parsed.getValue(forKeyPath: ["val"]) //-> 10
}

//dictionary with different key types
let test : Dictionary<AnyHashable,Any> = ["a" : ["b" : ["c" : "bla"]], 0 : [ 1 : [ 2 : "bla"]], "four" : [ 5 : "bla"]]
test.getValue(forKeyPath: ["a","b","c"]) //-> "bla"
test.getValue(forKeyPath: ["a","b"]) //-> ["c": "bla"]
test.getValue(forKeyPath: [0,1,2]) //-> "bla"
test.getValue(forKeyPath: ["four",5]) //-> "bla"
test.getValue(forKeyPath: ["a","b","d"]) //-> nil

//dictionary with strings as keys
let test2 = ["one" : [ "two" : "three"]]
test2.getValue(forKeyPath: ["one","two"]) //-> "three"

1
投票

您可以在Swift 3/4上使用以下语法:

if let name = data["name"] as? String {
    // name has "John"
}

if let age = data["age"] as? Int {
    // age has 30
}

if let car = data["cars"] as? [String:AnyObject],
    let car1 = car["car1"] as? String {
    // car1 has "Ford"
}

0
投票

不幸的是,这些方法都不适合我,所以我自己构建了一个简单的字符串路径,如“element0.element1.element256.element1”等。希望这可以节省其他人的时间。 (只需在字符串中的元素名称之间使用点)

Json的例子:

{
    "control": {
        "type": "Button",
        "name": "Save",
        "ui": {
            "scale": 0.5,
            "padding": {
                "top": 24,
                "bottom": 32
            }
        }
    }
}

步骤1,将json String转换为Dictionary

static func convertToDictionary(text: String) -> [String: Any]? {
        if let data = text.data(using: .utf8) {
            do {
                return try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
            } catch {
                print(error.localizedDescription)
            }
        }
        return nil
    }

第2步,帮助获取嵌套对象

//path example: "control.ui.scale"
    static func getDictValue(dict:[String: Any], path:String)->Any?{
        let arr = path.components(separatedBy: ".")
        if(arr.count == 1){
            return dict[String(arr[0])]
        }
        else if (arr.count > 1){
            let p = arr[1...arr.count-1].joined(separator: ".")
            let d = dict[String(arr[0])] as? [String: Any]
            if (d != nil){
                return getDictValue(dict:d!, path:p)
            }
        }
        return nil
    }

第3步,使用帮助器

let controlScale = getDictValue(dict:dict, path: "control.ui.scale") as! Double?
print(controlScale)

let controlName = getDictValue(dict:dict, path: "control.name") as! String?
print(controlName)

返回

0.5
Save

0
投票

字典的Swift 4 default:下标使得嵌套字典中的更新值更加简洁。

获取并设置默认值而不是处理选项:

var dict = [String : [String : String]]()
dict["deep", default: [:]]["nested"] = "dictionary"

print(dict)
// ["deep": ["nested": "dictionary"]]

https://swift.org/blog/dictionary-and-set-improvements/

© www.soinside.com 2019 - 2024. All rights reserved.