我的应用程序中有一个非常复杂的数据结构,我需要操作它。我试图跟踪玩家在他们的花园中有多少种类型的错误。有十种类型的错误,每种错误有十种模式,每种模式有十种颜色。因此,可能有1000个独特的错误,我想跟踪玩家拥有的这些类型中的每一个。嵌套字典看起来像:
var colorsDict: [String : Int]
var patternsDict: [String : Any] // [String : colorsDict]
var bugsDict: [String : Any] // [String : patternsDict]
我没有用这种语法得到任何错误或抱怨。
当我想增加玩家的bug收集时,执行此操作:
bugs["ladybug"]["spotted"]["red"]++
我收到此错误:字符串无法转换为' DictionaryIndex<字符串,任意>' ,第一个字符串下包含错误胡萝卜。
另一篇类似的帖子建议使用"作为Any?"在代码中,但该帖子的OP只有一个字典深,所以可以很容易地用:dict [" string"]为Any? ...
我不知道如何使用多级字典来完成此操作。任何帮助将不胜感激。
答案 0 :(得分:42)
使用词典时,您必须记住词典中可能不存在某个键。因此,词典总是返回选项。因此,每次按键访问字典时,都必须按如下方式打开每个级别:
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
答案 1 :(得分: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"]
答案 2 :(得分:6)
我的主要用例是从深层词典中读取 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实现但尚未找到一个。
答案 3 :(得分:4)
我有同样的问题,我希望将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
答案 4 :(得分: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"
答案 5 :(得分: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"
}
答案 6 :(得分:1)
您可以使用此扩展程序:
extension Dictionary {
/// - Description
/// - The function will return a value on given keypath
/// - if Dictionary is ["team": ["name": "KNR"]] the to fetch team name pass keypath: team.name
/// - If you will pass "team" in keypath it will return team object
/// - Parameter keyPath: keys joined using '.' such as "key1.key2.key3"
func valueForKeyPath <T> (_ keyPath: String) -> T? {
let array = keyPath.components(separatedBy: ".")
return value(array, self) as? T
}
/// - Description:"
/// - The function will return a value on given keypath. It keep calling recursively until reach to the keypath. Here are few sample:
/// - if Dictionary is ["team": ["name": "KNR"]] the to fetch team name pass keypath: team.name
/// - If you will pass "team" in keypath it will return team object
/// - Parameters:
/// - keys: array of keys in a keypath
/// - dictionary: The dictionary in which value need to find
private func value(_ keys: [String], _ dictionary: Any?) -> Any? {
guard let dictionary = dictionary as? [String: Any], !keys.isEmpty else {
return nil
}
if keys.count == 1 {
return dictionary[keys[0]]
}
return value(Array(keys.suffix(keys.count - 1)), dictionary[keys[0]])
}
}
用法:
let dictionary = ["values" : ["intValue": 3]]
let value: Int = dictionary.valueForKeyPath("values.intValue")
答案 7 :(得分:0)
不幸的是,这些方法都不适合我,因此我建立了自己的方法来使用简单的字符串路径,例如“ element0.element1.element256.element1”,等等。希望这可以为其他人节省时间。 (仅在字符串中的元素名称之间使用点)
Json示例:
{
"control": {
"type": "Button",
"name": "Save",
"ui": {
"scale": 0.5,
"padding": {
"top": 24,
"bottom": 32
}
}
}
}
步骤1,将json字符串转换为字典
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
答案 8 :(得分:0)
字典的Swift 4 default:
下标使嵌套字典中的值更新更加简洁。
获取并设置默认值,而不是处理可选值:
var dict = [String : [String : String]]()
dict["deep", default: [:]]["nested"] = "dictionary"
print(dict)
// ["deep": ["nested": "dictionary"]]
答案 9 :(得分:0)
另一种使用各种重载Dictionary下标实现的方法:
let dict = makeDictionary(fromJSONString:
"""
{
"control": {
"type": "Button",
"name": "Save",
"ui": {
"scale": 0.5,
"padding": {
"top": 24,
"bottom": 32
}
}
}
}
""")!
dict[Int.self, ["control", "ui", "padding", "top"]] // 1
dict[Int.self, "control", "ui", "padding", "top"] // 2
dict[Int.self, "control.ui.padding.top"] // 3
以及实际的实现方式:
extension Dictionary {
// 1
subscript<T>(_ type: T.Type, _ pathKeys: [Key]) -> T? {
precondition(pathKeys.count > 0)
if pathKeys.count == 1 {
return self[pathKeys[0]] as? T
}
// Drill down to the innermost dictionary accessible through next-to-last key
var dict: [Key: Value]? = self
for currentKey in pathKeys.dropLast() {
dict = dict?[currentKey] as? [Key: Value]
if dict == nil {
return nil
}
}
return dict?[pathKeys.last!] as? T
}
// 2. Calls 1
subscript<T>(_ type: T.Type, _ pathKeys: Key...) -> T? {
return self[type, pathKeys]
}
}
extension Dictionary where Key == String {
// 3. Calls 1
subscript<T>(_ type: T.Type, _ keyPath: String) -> T? {
return self[type, keyPath.components(separatedBy: ".")]
}
}
func makeDictionary(fromJSONString jsonString: String) -> [String: Any]? {
guard let data = jsonString.data(using: .utf8)
else { return nil}
let ret = try? JSONSerialization.jsonObject(with: data, options: [])
return ret as? [String: Any]
}