如何有效,dynamic地改变字典字典的价值
如果我有这样的字典“dic”:
{ "a": { "b": Any } "c": { "d": Any } }
如果我想改变键“b”的值,我知道我可以这样做:
dic["a"]["b"] = somethingNew
但是如果关键path是可变的,我怎么能根据关键path来改变这个值呢? 有没有像这样的API:
dic.updateValueByKeyPath(path: ["a", "b"], updateValue: somethingNew)
或者想达到这个目的,谢谢〜
我最近回答了一个Q&A,想要删除 (而不是更新)嵌套字典字典中的嵌套值。
- 从字典中删除嵌套的密钥
我们可以在这里使用类似的方法:使用recursion方法通过反复尝试将(子)字典值转换为[Key: Any]
字典本身来访问给定的键path。 这里唯一的限制是对于嵌套字典中的所有字典, Key
types都是相同的。
履行
recursion“核心”函数updateValue(_:, inDict:, forKeyPath:)
和public 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
键,使用较less的一般public updateValue(_:, forKeyPath:)
方法(使用上面的核心函数); 表单my.key.path
上的关键path(例如应用于你的例子的"ab"
):
/* 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
键path方法来更新现有键值对的值:
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"] ] ] */
用于在给定键path字典中添加新键值对的相同方法:
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]
作为关键path方法,而不是上面使用的ExpressibleByStringLiteral
我们会得到和上面例子相同的结果。 使用前者,调用将被改为:
... = dict.updateValue("nihongo", forKeyPath: ["countries", "japan", "language"] ... = dict.updateValue("asia", forKeyPath: ["countries", "japan", "continent"]
最后注意,如果一个空数组作为参数( []
)传递, updateValue
使用[Key]
作为关键path方法的updateValue
调用将返回nil
。 这可能会改变为抛出的情况,因为上面的nil
返回应该告诉我们一个新的键值对被添加(由stdlib中的updateValue(_:, forKey:)
方法启发)。
性能?
上面的方法将使用一些(子)字典复制,但除非你使用大型字典,这不应该是一个问题。 总之,不用担心性能,直到一个分析器告诉你有一个瓶颈。