Reputation: 21
I have a network request that returns Observable<Result<Data, APIError>>
which i map straight into array of Realm Object's so in the end i have Observable<Result<[Model], APIError>>
, where Model
conforms to Decodable
and subclasses Object
.
I would like to write an extension to Observable to filter out APIError
's and emit succesful results
So i tried to write something like this, but it wouln't work for me:
extension Observable where Element == Result<[Decodable], APIError> {
func toSuccesOrEmpty() -> Observable<[Decodable]> {
return flatMap { (result) -> Observable<[Decodable]> in
switch result {
case .success(let data):
return Observable<[Decodable]>.just(data)
case .failure:
return Observable<[Decodable]>.empty()
}
}
}
}
I'm new to RxSwift and i might miss smth. Also i don't want to use onError
when creating this observable since i want to use this error later on
Upvotes: 2
Views: 1145
Reputation: 33967
The way I would approach this problem is to first make a normal function. Since we are dealing with a Decodable, that means we want to work with generics... A normal function can't just ignore the error, it has to either make the return value Optional or throw the error. I chose to do the latter here:
func convertOrThrow<T>(input: Result<Data, APIError>) throws -> T where T: Decodable {
switch input {
case .success(let data):
return try JSONDecoder().decode(T.self, from: data)
case .failure(let error):
throw error
}
}
The above function is very easy to test. Make sure it works and then wrap it in an Observable extension like this:
extension ObservableType where Element == Result<Data, APIError> {
func toSuccesOrEmpty<T>() -> Infallible<T> where T: Decodable {
map(convertOrThrow(input:))
.asInfallible(onErrorRecover: { _ in Infallible.empty() })
}
}
By returning an Infallible
you are asserting that it will not emit an error.
However, creating a JSONDecoder
directly in the function feels wrong. We would like to be able to pass in a decoder. So lets turn that into a factory function:
func convertOrThrow<T>(decoder: JSONDecoder) -> (Result<Data, APIError>) throws -> T where T: Decodable {
{ input in
switch input {
case .success(let data):
return try JSONDecoder().decode(T.self, from: data)
case .failure(let error):
throw error
}
}
}
And then pass a decoder into it through the Observable extension...
extension ObservableType where Element == Result<Data, APIError> {
func toSuccesOrEmpty<T>(decoder: JSONDecoder) -> Infallible<T> where T: Decodable {
map(convertOrThrow(decoder: decoder))
.asInfallible(onErrorRecover: { _ in Infallible.empty() })
}
}
Lastly, it can be a real pain to have the generic type only in the return value. It means you have to manually set the type at the call site. Let's make that explicit the same way the decoder does it, by passing the type as a parameter:
extension ObservableType where Element == Result<Data, APIError> {
func toSuccesOrEmpty<T>(_ type: T.Type, decoder: JSONDecoder) -> Infallible<T> where T: Decodable {
map(convertOrThrow(type, decoder: decoder))
.asInfallible(onErrorRecover: { _ in Infallible.empty() })
}
}
func convertOrThrow<T>(_ type: T.Type, decoder: JSONDecoder) -> (Result<Data, APIError>) throws -> T where T: Decodable {
{ input in
switch input {
case .success(let data):
return try JSONDecoder().decode(T.self, from: data)
case .failure(let error):
throw error
}
}
}
You would call it like this:
let myModels = myRequest.toSuccesOrEmpty([Model].self, decoder: JSONDecoder())
Upvotes: 1