scr*_*rrr 8 collections dictionary nsdictionary ios swift
假设我有一个相当复杂的字典,就像这个:
let dict: [String: Any] = [
"countries": [
"japan": [
"capital": [
"name": "tokyo",
"lat": "35.6895",
"lon": "139.6917"
],
"language": "japanese"
]
],
"airports": [
"germany": ["FRA", "MUC", "HAM", "TXL"]
]
]
Run Code Online (Sandbox Code Playgroud)
我可以使用if let ..块访问所有字段,可选择在阅读时投射到我可以使用的内容.
但是,我目前正在编写单元测试,我需要以多种方式选择性地中断字典.
但我不知道如何优雅地从字典中删除键.
例如,我想"japan"在一个测试中删除密钥,在下一个"lat"应该是零.
这是我当前删除的实现"lat":
if var countries = dict["countries"] as? [String: Any],
var japan = countries["japan"] as? [String: Any],
var capital = japan["capital"] as? [String: Any]
{
capital.removeValue(forKey: "lat")
japan["capital"] = capital
countries["japan"] = japan
dictWithoutLat["countries"] = countries
}
Run Code Online (Sandbox Code Playgroud)
当然必须有一个更优雅的方式?
理想情况下,我会编写一个测试助手,它接受一个KVC字符串并具有如下签名:
func dictWithoutKeyPath(_ path: String) -> [String: Any]
Run Code Online (Sandbox Code Playgroud)
在"lat"我打电话的情况下 dictWithoutKeyPath("countries.japan.capital.lat").
小智 6
使用下标时,如果下标是get/set且变量是可变的,则整个表达式是可变的.但是,由于类型转换,表达"失去"可变性.(它不再是l值).
解决此问题的最短方法是创建一个get/set下标并为您进行转换.
extension Dictionary {
subscript(jsonDict key: Key) -> [String:Any]? {
get {
return self[key] as? [String:Any]
}
set {
self[key] = newValue as? Value
}
}
}
Run Code Online (Sandbox Code Playgroud)
现在您可以编写以下内容:
dict[jsonDict: "countries"]?[jsonDict: "japan"]?[jsonDict: "capital"]?["name"] = "berlin"
Run Code Online (Sandbox Code Playgroud)
我们非常喜欢这个问题,所以我们决定制作一个关于它的(公共)Swift Talk剧集:改变无类字典
您可以构造递归方法(读/写),通过重复尝试将(子)字典值转换为字典本身来访问给定的关键路径[Key: Any]。此外,允许公众通过新的subscript.
请注意,您可能必须显式导入Foundation才能访问(桥接)components(separatedBy:)的方法String。
extension Dictionary {
subscript(keyPath keyPath: String) -> Any? {
get {
guard let keyPath = Dictionary.keyPathKeys(forKeyPath: keyPath)
else { return nil }
return getValue(forKeyPath: keyPath)
}
set {
guard let keyPath = Dictionary.keyPathKeys(forKeyPath: keyPath),
let newValue = newValue else { return }
self.setValue(newValue, forKeyPath: keyPath)
}
}
static private func keyPathKeys(forKeyPath: String) -> [Key]? {
let keys = forKeyPath.components(separatedBy: ".")
.reversed().flatMap({ $0 as? Key })
return keys.isEmpty ? nil : keys
}
// recursively (attempt to) access queried subdictionaries
// (keyPath will never be empty here; the explicit unwrapping is safe)
private func getValue(forKeyPath keyPath: [Key]) -> Any? {
guard let value = self[keyPath.last!] else { return nil }
return keyPath.count == 1 ? value : (value as? [Key: Any])
.flatMap { $0.getValue(forKeyPath: Array(keyPath.dropLast())) }
}
// recursively (attempt to) access the queried subdictionaries to
// finally replace the "inner value", given that the key path is valid
private mutating func setValue(_ value: Any, forKeyPath keyPath: [Key]) {
guard self[keyPath.last!] != nil else { return }
if keyPath.count == 1 {
(value as? Value).map { self[keyPath.last!] = $0 }
}
else if var subDict = self[keyPath.last!] as? [Key: Value] {
subDict.setValue(value, forKeyPath: Array(keyPath.dropLast()))
(subDict as? Value).map { self[keyPath.last!] = $0 }
}
}
}
Run Code Online (Sandbox Code Playgroud)
设置示例
// your example dictionary
var dict: [String: Any] = [
"countries": [
"japan": [
"capital": [
"name": "tokyo",
"lat": "35.6895",
"lon": "139.6917"
],
"language": "japanese"
]
],
"airports": [
"germany": ["FRA", "MUC", "HAM", "TXL"]
]
]
Run Code Online (Sandbox Code Playgroud)
用法示例:
// read value for a given key path
let isNil: Any = "nil"
print(dict[keyPath: "countries.japan.capital.name"] ?? isNil) // tokyo
print(dict[keyPath: "airports"] ?? isNil) // ["germany": ["FRA", "MUC", "HAM", "TXL"]]
print(dict[keyPath: "this.is.not.a.valid.key.path"] ?? isNil) // nil
// write value for a given key path
dict[keyPath: "countries.japan.language"] = "nihongo"
print(dict[keyPath: "countries.japan.language"] ?? isNil) // nihongo
dict[keyPath: "airports.germany"] =
(dict[keyPath: "airports.germany"] as? [Any] ?? []) + ["FOO"]
dict[keyPath: "this.is.not.a.valid.key.path"] = "notAdded"
print(dict)
/* [
"countries": [
"japan": [
"capital": [
"name": "tokyo",
"lon": "139.6917",
"lat": "35.6895"
],
"language": "nihongo"
]
],
"airports": [
"germany": ["FRA", "MUC", "HAM", "TXL", "FOO"]
]
] */
Run Code Online (Sandbox Code Playgroud)
请注意,如果为分配(使用 setter)提供的键路径不存在,则这不会导致等效嵌套字典的构造,而只是导致字典不发生变化。
| 归档时间: |
|
| 查看次数: |
1075 次 |
| 最近记录: |