如何有效,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。 这里唯一的限制是对于嵌套字典中的所有字典, Keytypes都是相同的。

履行

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:)方法启发)。


性能?

上面的方法将使用一些(子)字典复制,但除非你使用大型字典,这不应该是一个问题。 总之,不用担心性能,直到一个分析器告诉你有一个瓶颈。