Remove nested key from dictionary
collections, dictionary, ios, nsdictionary, swift
Solution
When working with a subscript, if the subscript is get/set and the variable is mutable, then the entire expression is mutable. However, due to the type cast the expression "loses" the mutability. (It's not an l-value anymore).
The shortest way to solve this is by creating a subscript that is get/set and does the conversion for you.
extension Dictionary {
subscript(jsonDict key: Key) -> [String:Any]? {
get {
return self[key] as? [String:Any]
}
set {
self[key] = newValue as? Value
}
}
}
Now you can write the following:
dict[jsonDict: "countries"]?[jsonDict: "japan"]?[jsonDict: "capital"]?["name"] = "berlin"
We liked this question so much that we decided to make a (public) Swift Talk episode about it: mutating untyped dictionaries
Problem
Let's say I have a rather complex dictionary, like this one: ``` let dict: [String: Any] = [ "countries": [ "japan": [ "capital": [ "name": "tokyo", "lat": "35.6895", "lon": "139.6917" ], "language": "japanese" ] ], "airports": [ "germany": ["FRA", "MUC", "HAM", "TXL"] ] ] ``` I can access all fields with `if let ..` blocks optionally casting to something that I can work with, when reading. However, I am currently writing unit tests where I need to selectively break dictionaries in multiple ways. But I don't know how to elegantly remove keys from the dictionary. For example I want to remove the key `"japan"` in one test, in the next `"lat"` should be nil. Here's my current implementation for removing `"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 } ``` Surely there must be a more elegant way? Ideally I'd write a test helper that takes a KVC string and has a signature like: ``` func dictWithoutKeyPath(_ path: String) -> [String: Any] ``` In the `"lat"` case I'd call it with `dictWithoutKeyPath("countries.japan.capital.lat")`.