Created
September 8, 2020 23:21
-
-
Save IanKeen/4d4f4aca3dbb686ffd3442fc5d1e7102 to your computer and use it in GitHub Desktop.
Extract nested values from a Dictionary using key paths
This file contains 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
public struct DictionaryKeyPathError: Error, CustomStringConvertible { | |
public enum FragmentError: Error { | |
case valueMissing | |
case valueMismatch(expected: Any.Type, received: Any.Type, value: Any) | |
} | |
public let path: [Any] | |
public let error: FragmentError | |
public var description: String { | |
switch error { | |
case .valueMissing: | |
return "No value found at \(path)" | |
case .valueMismatch(let expected, let received, let value): | |
return "Value mismatch at \(path). Found '\(value)' of type '\(received)', expected '\(expected)'" | |
} | |
} | |
} | |
public struct DictionaryKeyPath { | |
let path: [Any] | |
let extract: () throws -> Any | |
public func value<T>(_: T.Type = T.self, or fallback: T) -> T { | |
return (try? value(T.self)) ?? fallback | |
} | |
public func value<T>(_: T.Type = T.self) throws -> T { | |
do { | |
let extracted = try extract() | |
guard let value = extracted as? T else { | |
throw DictionaryKeyPathError.FragmentError.valueMismatch(expected: T.self, received: type(of: extracted), value: extracted) | |
} | |
return value | |
} catch let error as DictionaryKeyPathError.FragmentError { | |
throw DictionaryKeyPathError(path: path, error: error) | |
} | |
} | |
public func value() throws -> Any { | |
return try value(Any.self) | |
} | |
public subscript<K: Hashable>(key: K) -> DictionaryKeyPath { | |
return .init(path: path + [key]) { | |
let extracted = try self.value([K: Any].self) | |
guard let value = extracted[key] else { throw DictionaryKeyPathError.FragmentError.valueMissing } | |
return value | |
} | |
} | |
public subscript(index: Int) -> DictionaryKeyPath { | |
return .init(path: path + [index]) { | |
let extracted = try self.value([Any].self) | |
guard extracted.indices.contains(index) else { throw DictionaryKeyPathError.FragmentError.valueMissing } | |
return extracted[index] | |
} | |
} | |
} | |
extension DictionaryKeyPath { | |
public static func ==<T: Equatable>(lhs: DictionaryKeyPath, rhs: T) -> Bool { | |
return (try? lhs.value(T.self)) == rhs | |
} | |
public static func !=<T: Equatable>(lhs: DictionaryKeyPath, rhs: T) -> Bool { | |
return (try? lhs.value(T.self)) != rhs | |
} | |
} | |
extension Dictionary { | |
public subscript(path key: Key) -> DictionaryKeyPath { | |
return .init(path: [key]) { | |
guard let value = self[key] else { throw DictionaryKeyPathError.FragmentError.valueMissing } | |
return value | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment