Skip to content

Instantly share code, notes, and snippets.

@dimohamdy
Last active December 11, 2023 14:58
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dimohamdy/13e9b4ec0432c8096f476bb6143eaf1d to your computer and use it in GitHub Desktop.
Save dimohamdy/13e9b4ec0432c8096f476bb6143eaf1d to your computer and use it in GitHub Desktop.
Accessing Dictionaries with Key Paths
/*
Thanks for @olebegemann
code of keypath from this link
https://oleb.net/blog/2017/01/dictionary-key-paths/
*/
struct KeyPath {
var segments: [String]
var isEmpty: Bool { return segments.isEmpty }
var path: String {
return segments.joined(separator: ".")
}
/// Strips off the first segment and returns a pair
/// consisting of the first segment and the remaining key path.
/// Returns nil if the key path has no segments.
func headAndTail() -> (head: String, tail: KeyPath)? {
guard !isEmpty else { return nil }
var tail = segments
let head = tail.removeFirst()
return (head, KeyPath(segments: tail))
}
}
/// Initializes a KeyPath with a string of the form "this.is.a.keypath"
extension KeyPath {
init(_ string: String) {
segments = string.components(separatedBy: ".")
}
}
/// Initializes a KeyPath with a string of the form "this.is.a.keypath"
extension KeyPath: ExpressibleByStringLiteral {
init(stringLiteral value: String) {
self.init(value)
}
init(unicodeScalarLiteral value: String) {
self.init(value)
}
init(extendedGraphemeClusterLiteral value: String) {
self.init(value)
}
}
// Needed because Swift 3.0 doesn't support extensions with concrete
// same-type requirements (extension Dictionary where Key == String).
protocol StringProtocol {
init(string s: String)
}
extension String: StringProtocol {
init(string s: String) {
self = s
}
}
extension Dictionary where Key: StringProtocol {
subscript(keyPath keyPath: KeyPath) -> Any? {
get {
switch keyPath.headAndTail() {
case nil:
// key path is empty.
return nil
case let (head, remainingKeyPath)? where remainingKeyPath.isEmpty:
// Reached the end of the key path.
let key = Key(string: head)
return self[key]
case let (head, remainingKeyPath)?:
// Key path has a tail we need to traverse.
let key = Key(string: head)
switch self[key] {
case let nestedDict as [Key: Any]:
// Next nest level is a dictionary.
// Start over with remaining key path.
return nestedDict[keyPath: remainingKeyPath]
default:
// Next nest level isn't a dictionary.
// Invalid key path, abort.
return nil
}
}
}
// ...
set {
switch keyPath.headAndTail() {
case nil:
// key path is empty.
return
case let (head, remainingKeyPath)? where remainingKeyPath.isEmpty:
// Reached the end of the key path.
let key = Key(string: head)
self[key] = newValue as? Value
case let (head, remainingKeyPath)?:
let key = Key(string: head)
let value = self[key]
switch value {
case var nestedDict as [Key: Any]:
// Key path has a tail we need to traverse
nestedDict[keyPath: remainingKeyPath] = newValue
self[key] = nestedDict as? Value
default:
// Invalid keyPath
return
}
}
}
}
}
extension Dictionary where Key: StringProtocol {
subscript(string keyPath: KeyPath) -> String? {
get { return self[keyPath: keyPath] as? String }
set { self[keyPath: keyPath] = newValue }
}
subscript(dict keyPath: KeyPath) -> [Key: Any]? {
get { return self[keyPath: keyPath] as? [Key: Any] }
set { self[keyPath: keyPath] = newValue }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment