4

I have a httpsCallable cloud function which is being called from the app. Technically speaking it is working fine but as you can see, the code is very nested and I have the feeling that this can be handled somehow better. I also get a warning.

The response result?.data is of type Any so I can not use it directly in try decoder.decode(PaymentIntent.self, from: data) since this requires Data type. And for this I'm using try? JSONSerialization.data(withJSONObject: result?.data)

Any idea how to decode everything to PaymentIntent model?

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase

functions.httpsCallable("createPaymentIntent").call(data) { (result, error) in
    if let error = error as NSError? {
        completion(nil, error)
    } else {
        let data = try? JSONSerialization.data(withJSONObject: result?.data)
        
        if let data = data {
            do {
                let paymentIntent = try decoder.decode(PaymentIntent.self, from: data) // Expression implicitly coerced from 'Any?' to 'Any'
                completion(paymentIntent.clientSecret, nil)
            } catch {
                completion(nil, error);
            }
        }

        // Update

        if let data = result?.data as? NSDictionary {
            print(data)
            
            do {
                // Cannot convert value of type 'NSDictionary' to expected argument type 'Data'
                let paymentIntent = try decoder.decode(PaymentIntent.self, from: data)
                completion(paymentIntent.clientSecret, nil)
            } catch {
                completion(nil, error);
            }
        }
    
    }
}
Joris Mans
  • 6,024
  • 6
  • 42
  • 69
M1X
  • 4,971
  • 10
  • 61
  • 123

3 Answers3

3

Firebase added decoders meanwhile:

import FirebaseSharedSwift
import FirebaseFunctions

    ...
func retrieve<T: Decodable>(_ strategy: JSONDecoder.DateDecodingStrategy? = nil) async throws -> T {
    let decoder = FirebaseDataDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    
    let result = try await functions
        .httpsCallable(endpoint, responseAs: T.self, decoder: decoder)
        .call(params)
    return result
}

Replace T with your class if your method is not generic and params should be Encodable.

Calin Drule
  • 2,899
  • 1
  • 15
  • 12
1

The swift SDK for Firebase's callable functions does not return a JSON string. It returns a data type that's already been deserialized from JSON. It's going to be either a dictionary, array, or something that directly corresponds to whatever the cloud function generates. You should do some step-through debugging to examine what result.data actually is, then write some code to verify that it is what you expect, and cast it in order to use it safely.

Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
  • When I do this `let data = result?.data as? NSDictionary` and print it I get the correct response. so I'm assuming it is a Dictionary. The problem is that when i try to use this `let paymentIntent = try decoder.decode(PaymentIntent.self, from: data)` like this I get an error: `Cannot convert value of type 'NSDictionary' to expected argument type 'Data'` So Do i need to cast the dictionary to Data? (I updated my question) – M1X Mar 15 '21 at 23:00
  • That sounds like a whole new question, actually. I suggest revering your update and ask it separately given the new information you have. You will have to be clear about what you final destination is and what you've tried already to get there. Given that you know you have a dictionary in hand, the question is now only really about how to handle that dictionary. Firebase is no longer really in play. – Doug Stevenson Mar 15 '21 at 23:10
0

You can't do this right now. You'll always get a Dictionary back and you'll need to instantiate the objects yourself using the contents of that Dictionary.

However they are working on it:

https://github.com/firebase/firebase-ios-sdk/pull/8854

Joris Mans
  • 6,024
  • 6
  • 42
  • 69