11

I've been trying to convert the document retrieved from the Firebase's Cloud Firestore to a custom object in Swift 5. I'm following the documentation:

However, Xcode shows me the error Value of type 'NSObject' has no member 'data' for the line try $0.data(as: JStoreUser.self). I've defined the struct as Codable.

The code:

func getJStoreUserFromDB() {
    db = Firestore.firestore()
    let user = Auth.auth().currentUser
    db.collection("users").document((user?.email)!).getDocument() { 
        (document, error) in
        let result = Result {
            try document.flatMap {
                try $0.data(as: JStoreUser.self)
            }
        }
    }
}

The user struct:

public struct JStoreUser: Codable {
    let fullName: String
    let whatsApp: Bool
    let phoneNumber: String
    let email: String
    let creationDate: Date?
}

The screenshot:

The screenshot

Does anyone know how to resolve this?

Christopher Moore
  • 15,626
  • 10
  • 42
  • 52
  • Just init the custom object with the dictionary returned by Firestore – trndjc Dec 31 '19 at 23:58
  • I am not sure what you are doing in the `.getDocument()` completion handler, but I think you need to convert the `Data` returned into a dictionary via `guard let json = let json = try JSONSerialization.jsonObject(with: document.data(), options: []) as? [String:[String:Any]]`, or something along those lines (your data may not be structured as `[String:[String:Any]]`). From there, you can grab the individual values and initialize an object form those values. As bsod mentioned, you could also pass a dictionary in the initializer and initialize an object that way as well. – David Chopin Jan 01 '20 at 00:14
  • Wait, I'm seeing which part of the documentation you are replicating. I use realtime database, and have no issues converting the `Data` returned from the request into a dictionary via `JSONSerialization.jsonObject`. So long as the request is returning JSON data, you should be able to convert it into an object via this method. – David Chopin Jan 01 '20 at 00:18
  • It would help to understand what you're trying to do here - what does the Firestore structure you're reading look like and what is JStoreUser class? Are you attempting to take data from a Firestore Document and make a Swift Struct from it? Note this `$0.data` is a dictionary – Jay Jan 01 '20 at 17:20

4 Answers4

23

After contacting the firebase team, I found the solution I was looking for. It turns out I have to do import FirebaseFirestoreSwift explicitly instead of just doing import Firebase. The error will disappear after this. (And of course you'll need to add the pod to your podfile first:D)

1

You can do it as shown below:-

First create model class:-

import FirebaseFirestore
import Firebase

//#Mark:- Users model
struct CommentResponseModel {

    var createdAt : Date?
    var commentDescription : String?
    var documentId : String?

    var dictionary : [String:Any] {
        return [
                "createdAt": createdAt  ?? "",
                "commentDescription": commentDescription  ?? ""
        ]
    }

   init(snapshot: QueryDocumentSnapshot) {
        documentId = snapshot.documentID
        var snapshotValue = snapshot.data()
        createdAt = snapshotValue["createdAt"] as? Date
        commentDescription = snapshotValue["commentDescription"] as? String
    }
}

Then you can convert firestore document into custom object as shown below:-

func getJStoreUserFromDB() {
    db = Firestore.firestore()
    let user = Auth.auth().currentUser
    db.collection("users").document((user?.email)!).getDocument() { (document, error) in
        //        Convert firestore document your custom object
        let commentItem = CommentResponseModel(snapshot: document)
    }
}
Nexus
  • 560
  • 1
  • 4
  • 9
  • 2
    Thanks for the suggestion. However, I was looking for a way to use the `Codable` protocol and convert the document to an object directly (like in Java, one can use `document.toObject(User.class)`.) The Firebase documentation shows something similar, but it's not working for some reason. – Tianyao 'Till' Chen Jan 01 '20 at 18:37
1

You need to initialize your struct and then you can extend the QueryDocumentSnapshot and QuerySnapshot like:

extension QueryDocumentSnapshot {
    func toObject<T: Decodable>() throws -> T {
        let jsonData = try JSONSerialization.data(withJSONObject: data(), options: [])
        let object = try JSONDecoder().decode(T.self, from: jsonData)
        
        return object
    }
}

extension QuerySnapshot {
    
    func toObject<T: Decodable>() throws -> [T] {
        let objects: [T] = try documents.map({ try $0.toObject() })
        return objects
    }
}

Then, try to call the Firestore db by:

db.collection("users").document((user?.email)!).getDocument() { (document, error) in
    guard error == nil else { return }
    guard let commentItem: [CommentResponseModel] = try? document.toObject() else { return }
     // then continue with your code
}
sangak
  • 11
  • 2
0

In the past, I had some issues though importing FirebaseFirestore with the package manager in my project. So I explain about the access to FirebaseFirestore in swift.

  1. SnapshotListener

     import Foundation
     import FirebaseFirestore
    
     class BooksViewModel: ObservableObject {
       @Published var books = [Book]()
    
       private var db = Firestore.firestore()
    
       func fetchData() {
         db.collection("books").addSnapshotListener { (querySnapshot, error) in
           guard let documents = querySnapshot?.documents else {
             print("No documents")
             return
           }
    
       self.books = documents.map { queryDocumentSnapshot -> Book in
         let data = queryDocumentSnapshot.data()
         let title = data["title"] as? String ?? ""
         let author = data["author"] as? String ?? ""
         let numberOfPages = data["pages"] as? Int ?? 0
    
         return Book(id: .init(), title: title, author: author, numberOfPages: numberOfPages)
          }
        }
      }
    }
    
  2. using uid and getDocument function

     Firestore.firestore().collection("users").document(uid).getDocument { snapshot, error in
                     if let error = error {
                         self.errorMessage = "Failed to fetch current user: \(error)"
                         print("Failed to fetch current user:", error)
                         return
                     }
    
                     guard let data = snapshot?.data() else {
                         self.errorMessage = "No data found"
                         return
    
                     }
             let uid = data["uid"] as? String ?? ""
             let email = data["email"] as? String ?? ""
             let profileImageUrl = data["profileImageUrl"] as? String ?? ""
             self.chatUser = ChatUser(uid: uid, email: email, profileImageUrl: profileImageUrl)
     }
    
Dev Solution
  • 144
  • 2
  • 10