Home > OS >  Use of flatMap on a generic Publisher results in a compile error
Use of flatMap on a generic Publisher results in a compile error

Time:01-22

I'm writing a transform function that would take network request results and try to parse them automatically using a dict to Model transformer(not Decodable due to several backend reasons). So the chain should look like this:

func getModel -> Single<Model> {
   return networkRequest(requestParameters).parse(modelTranslator)
}

The translator is a generic protocol:

public protocol Translator {
    associatedtype Model
    func translateFrom(dictionary json: [String: Any]) throws -> Model
}

Single is a wrapper around Deferred and Future:

public typealias Single<T> = Deferred<Future<T, Error>>

The problematic parse extension method here is:

public extension Publisher {
    func parse<T: Translator, M>(translator: T) -> Single<M> where T.Model == M {
        return self.flatMap { (data: Data) -> Single<M> in
            return Deferred {
                return Future<M, any Error> { promise in
                    guard
                        let json = try? JSONSerialization.jsonObject(with: data, options: []),
                        let dict = json as? [String : Any]
                    else {
                        let error: any Error = TranslatorError.invalidJSONObject
                        return promise(Result.failure(error))
                    }
                    do {
                        let translatedModel: M = translator.translateFrom(dictionary: dict)
                        return promise(Result.success(translatedModel))
                    } catch let error {
                        return promise(Result.failure(error))
                    }
                }
            }
        }
    }
}

It won't compile. It shows 2 errors on the .flatmap row:

  1. No 'flatMap' candidates produce the expected contextual result type 'Single' (aka 'Deferred<Future<M, any Error>>')
  2. No exact matches in call to instance method 'flatMap'

I believe that it has something to do with a type mismatch? Could you please help me see the problem?

Thank you in advance!

CodePudding user response:

You are trying too hard. A simple tryMap is all you need to parse your [String: Any] into the appropriate model type. Here is a complete example:

func getFoo(_ requestParameters: RequestParameters) -> AnyPublisher<Foo, Error> {
    getModel(requestParameters, modelTranslator: FooTranslator())
}

func getModel<T>(_ requestParameters: RequestParameters, modelTranslator: T) -> AnyPublisher<T.Model, Error> where T: Translator {
    networkRequest(requestParameters)
        .tryMap { try modelTranslator.translateFrom(dictionary: $0) }
        .eraseToAnyPublisher()
}

The above assumes the following declarations:

func networkRequest(_ params: RequestParameters) -> Single<[String: Any]> ...

struct FooTranslator: Translator {
    func translateFrom(dictionary json: [String : Any]) throws -> Foo ...
}

  • Related