如果我有这样的字典“dic”:
{
"a": {
"b": Any
}
"c": {
"d": Any
}
}
如果我想更改键“b”的值,我知道我可以这样做:
dic["a"]["b"] = somethingNew
但是如果密钥路径是可变的,我如何根据密钥路径更改值?有这样的api:
dic.updateValueByKeyPath(path: ["a", "b"], updateValue: somethingNew)
或者实现这个目的的想法,谢谢〜
答案 0 :(得分:2)
我最近回答了一个想要删除(而不是更新)嵌套字典中的嵌套值的问答。
我们可以在这里使用类似的方法:使用递归方法,通过反复尝试将(子)字典值转换为[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)
}
}
对于符合updateValue(_:, forKeyPath:)
的密钥,不太通用的公共ExpressibleByStringLiteral
方法(使用上面的核心函数);表单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"]
最后请注意,如果将空数组作为参数传递(updateValue
),则使用[Key]
作为键路径方法调用nil
也将返回[]
。这可能会更改为抛出案例,因为上面的nil
返回值应该告诉我们添加了一个新的键值对(受stdlib中的the updateValue(_:, forKey:)
method的启发)。
<强>性能吗
上述方法将使用一些(子)字典复制,但除非您使用大字典,否则这不应成为问题。无论如何,在探查器告诉你有瓶颈之前,无需担心性能。