Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save Amzd/e908d4bf3cbad2c9d766586baa619566 to your computer and use it in GitHub Desktop.
Save Amzd/e908d4bf3cbad2c9d766586baa619566 to your computer and use it in GitHub Desktop.
Reading and writing to (possible) nested dictionaries for a path of keys, using a recursive approach
extension Dictionary {
public subscript(path string: Key, separator separator: String) -> Value? where Key == String {
get { self[path: string.split(separator: separator).map(String.init)] }
set { self[path: string.split(separator: separator).map(String.init)] = newValue }
}
public subscript(path path: Key...) -> Value? {
get { self[path: path] }
set { self[path: path] = newValue }
}
public subscript(path path: [Key]) -> Value? {
get { getValue(forPath: path) }
set {
setValue(newValue, forPath: path)
if newValue == nil {
cleanUp(forPath: path)
}
}
}
/// recursively (attempt to) access queried subdictionaries
private func getValue(forPath path: [Key]) -> Value? {
if path.count == 1, let key = path.first {
return self[key]
} else if let key = path.first {
let next = self[key] as? Self
return next?.getValue(forPath: Array(path.dropFirst()))
} else {
return nil
}
}
/// recursively access, create or overwrite the queried subdictionaries to finally set the "inner value"
private mutating func setValue(_ value: Value?, forPath path: [Key]) {
if path.count == 1, let key = path.first {
self[key] = value
} else if let key = path.first {
var subDict = self[key] as? Self ?? Self()
subDict.setValue(value, forPath: Array(path.dropFirst()))
self[key] = subDict as? Value
}
}
/// (attempt to) remove left over empty subdictionaries
private mutating func cleanUp(forPath path: [Key]) {
var path = path
while !path.isEmpty {
if let value = getValue(forPath: path) {
// Stop cleanUp unless an empty dict is found
guard let dict = value as? [Key: Any], dict.isEmpty else { break }
setValue(nil, forPath: path)
}
path = path.dropLast()
}
}
}
var dict: [String: Any] = [
"some": [
"nested": [
"data": "Often found in json"
]
]
]
let a = dict[keyPath: "some/nested/data", separator: "/"] // "Often found in json"
let b = dict[keyPath: "some", "nested", "data"] // "Often found in json"
let c = dict[keyPath: ["some", "nested", "data"]] // "Often found in json"
dict[keyPath: "some/nested/data", separator: "/"] = "Replacing data"
dict[keyPath: "another/nested/path", separator: "/"] = "Creating new subdirectories"
print(dict as AnyObject)
/*
{
another = {
nested = {
path = "Creating new subdirectories";
};
};
some = {
nested = {
data = "Replacing data";
};
};
}
*/
// Clean up removes all empty subdirectories
dict[keyPath: "some/nested/data", separator: "/"] = nil
print(dict as AnyObject)
/*
{
another = {
nested = {
path = "Creating new subdirectories";
};
};
}
*/
// Warning: I opted to allow overwriting of data
dict[keyPath: "another/nested/path/new/path", separator: "/"] = "Overwrote path node"
print(dict as AnyObject)
/*
{
another = {
nested = {
path = {
new = {
path = "Overwrote path node";
};
};
};
};
}
*/
// This also means if we set a deeper node to nil
// cleanup will delete higher empty levels since
// those are overwritten
dict[keyPath: "another/nested/path/new/path/even/deeper/path", separator: "/"] = nil
print(dict as AnyObject)
/*
{
}
*/
@Amzd
Copy link
Author

Amzd commented Jun 14, 2021

Updated from casperzandbergenyaacomm/DictionaryKeyPath.swift

The separator is now explicit so you can use this when your keys contain "/"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment