Skip to content

Instantly share code, notes, and snippets.

@IanKeen
Created September 8, 2020 23:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save IanKeen/4d4f4aca3dbb686ffd3442fc5d1e7102 to your computer and use it in GitHub Desktop.
Save IanKeen/4d4f4aca3dbb686ffd3442fc5d1e7102 to your computer and use it in GitHub Desktop.
Extract nested values from a Dictionary using key paths
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