我最近回答了一个想要删除(而不是更新)嵌套字典字典中的嵌套值的问答。
我们可以在这里使用类似的方法:使用递归方法,通过反复尝试将(子)字典值转换为 [Key: Any] 字典本身来访问给定的键路径。这里唯一的限制是Key 类型对于嵌套字典中的所有字典都基本相同。
实施
递归“核心”函数updateValue(_:, inDict:, forKeyPath:) 和公共updateValue(_:, forKeyPath:) 方法为[Key] 形成关键路径(例如,["a", "b"] 应用于您的示例):
/* general "key path" extension */
public extension Dictionary {
public mutating func updateValue(_ value: Value, forKeyPath keyPath: [Key])
-> Value? {
let (valInd, newDict) = updateValue(value, inDict: self,
forKeyPath: Array(keyPath.reversed()))
if let dict = newDict as? [Key: Value] { self = dict }
return valInd
}
fileprivate func updateValue(_ value: Value, inDict dict: [Key: Any],
forKeyPath keyPath: [Key]) -> (Value?, [Key: Any]) {
guard let key = keyPath.last else { return (value, dict) }
var dict = dict
if keyPath.count > 1, let subDict = dict[key] as? [Key: Any] {
let (val, newSubDict) = updateValue(value, inDict: subDict,
forKeyPath: Array(keyPath.dropLast()))
dict[key] = newSubDict
return (val, dict)
}
let val = dict.updateValue(value, forKey: key) as? Value
return (val, dict)
}
}
对于符合ExpressibleByStringLiteral 的密钥,较少公开的updateValue(_:, forKeyPath:) 方法(使用上面的核心函数); my.key.path 表单上的关键路径(例如,"a.b" 应用于您的示例):
/* String literal specific "key path" extension */
public extension Dictionary where Key: ExpressibleByStringLiteral {
public mutating func updateValue(_ value: Value, forKeyPath keyPath: String)
-> Value? {
let keyPathArr = keyPath.components(separatedBy: ".")
.reversed().flatMap { $0 as? Key }
if keyPathArr.isEmpty { return self.updateValue(value, forKey: "") }
let (valInd, newDict) = updateValue(value,
inDict: self, forKeyPath:keyPathArr)
if let dict = newDict as? [Key: Value] { self = dict }
return valInd
}
}
示例用法
我们会将上述方法应用于链接线程中的示例。
var dict: [String: Any] = [
"countries": [
"japan": [
"capital": [
"name": "tokyo",
"lat": "35.6895",
"lon": "139.6917"
],
"language": "japanese"
]
],
"airports": [
"germany": ["FRA", "MUC", "HAM", "TXL"]
]
]
使用ExpressibleByStringLiteral键路径方法更新现有键值对的值:
if let oldValue = dict.updateValue("nihongo",
forKeyPath: "countries.japan.language") {
print("Removed old value: ", oldValue)
}
else {
print("Added new key-value pair")
}
print(dict)
/* Removed old value: japanese
[
"countries": [
"japan": [
"capital": [
"name": "tokyo",
"lon": "139.6917"
],
"language": "nihongo"
]
],
"airports": [
"germany": ["FRA", "MUC", "HAM", "TXL"]
]
] */
与在给定键路径字典中添加新键值对的方法相同:
if let oldValue = dict.updateValue("asia",
forKeyPath: "countries.japan.continent") {
print("Removed old value: ", oldValue)
}
else {
print("Added new key-value pair")
}
print(dict)
/* Added new key-value pair
[
"countries": [
"japan": [
"capital": [
"name": "tokyo",
"lon": "139.6917"
],
"language": "nihongo",
"continent": "asia"
]
],
"airports": [
"germany": ["FRA", "MUC", "HAM", "TXL"]
]
] */
如果我们使用通用的[Key] 作为关键路径方法而不是上面使用的ExpressibleByStringLiteral,我们将得到与上述示例相同的结果。使用前者,调用将变为:
... = dict.updateValue("nihongo",
forKeyPath: ["countries", "japan", "language"]
... = dict.updateValue("asia",
forKeyPath: ["countries", "japan", "continent"]
最后请注意,使用[Key] 作为键路径方法调用updateValue 将返回nil,即使是空数组作为参数传递([])。这可能会更改为抛出情况,因为上面的 nil 返回应该告诉我们添加了一个新的键值对(受 stdlib 中的 the updateValue(_:, forKey:) method 启发)。
性能?
上述方法将使用一些(子)字典复制,但除非您使用庞大的字典,否则这应该不是问题。无论如何,在分析器告诉您存在瓶颈之前,无需担心性能。