Last active
August 24, 2024 07:17
-
-
Save dimohamdy/13e9b4ec0432c8096f476bb6143eaf1d to your computer and use it in GitHub Desktop.
Accessing Dictionaries with Key Paths
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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