'How can I display my user custom data in String format?

I'm new to Swift UI and MongoDB Realms. I'm trying to display a user's email (from their respective custom user data) in a text box but I'm getting a weird result that I don't know how to fix. I've been semi-blindly trying all sorts of things hoping something would work but no luck so far. I'm using Email/Password authorization so I know that the email/password wouldn't be stored in the user custom data (I think) but its just an example for what I'm trying to do.

My current code so far below.

struct HomeView: View {
    let user = app.currentUser!
    @State var isLoggingOut = false
    
    var body: some View {
        let userEmail = user.customData["email"] ?? "no email"
        let userPassword = user.customData["password"] ?? "no password"
        let userDisplay = user.customData["display"] ?? "no display"
        let userName = user.customData["fullName"] ?? "no name"
        
        ZStack {
            Rectangle().foregroundColor(.yellow)

            VStack {

                Spacer()
                Text("Home")

                HStack {
                    Text("Email").frame(width: 100)
                    Spacer()
                    Text(String(reflecting: userEmail))
                }.padding(.vertical)

                HStack {
                    Text("Password").frame(width: 100)
                    Spacer()
                    Text(String(describing: userPassword))
                }.padding(.vertical)

                HStack {
                    Text("Display").frame(width: 100)
                    Spacer()
                    Text(String(describing: userDisplay))
                }.padding(.vertical)

                HStack {
                    Text("Full name").frame(width: 100)
                    Spacer()
                    Text(String(describing: userName))
                }.padding(.vertical)

                Spacer()
                Button("Log Out") {tryLogOut()}

            }.padding(40)
            
            if isLoggingOut {LoginView()}
        }
    }
    
    func tryLogOut() {
        app.currentUser?.logOut {error in}
        self.isLoggingOut = true
    }
}

After logging in with a test user, this is what I'm getting in the right HStack text boxes (for example, the top email text box):

Email      Optional(RealmSwift.AnyBSON.string("[email protected]"))

Obviously what I'm trying to end up with is:

Email      [email protected]

What am I doing wrong? Everything else works as intended but this problem is giving me a headache. Any help would be appreciated.


Also FYI - Everything I am trying to display in the text boxes is stored in the database as Strings according to Atlas so I don't see the problem. However in my NewUserRegistrationView, when I create the new user document, I use the following code, I'm not sure if there is anything conflicting with the AnyBSON types before inserting the document.

struct NewUserRegistrationView: View {

// Email, password, displayName, and fullName obtained from TextFields in the body ...
// createUserDocument() is called after registering and confirming the user

func createUserDocument() {
        let credentials = Credentials.emailPassword(
            email: self.email,
            password: self.password)
        app.login(credentials: credentials) {result in
            switch result {
            case .failure:
                self.statustext = "Document creation failed, try again"
            case .success(let user):
                let client = user.mongoClient("mongodb-atlas")
                let database = client.database(named: "AppDatabase")
                let collection = database.collection(withName: "userDocuments")

                collection.insertOne([
                    "userID": AnyBSON(user.id),
                    "email": AnyBSON(self.email),
                    "password": AnyBSON(self.password),
                    "display": AnyBSON(self.display),
                    "fullName": AnyBSON(self.fullName)
                ]) { result in
                    switch result {
                    case .failure:
                        self.statustext = "Could not add document"
                    case .success(let newObjectId):
                        self.statustext = "Inserted document with objID: \(newObjectId)"
                        self.isDone = true
                    }
                }
            }
        }
    }


Solution 1:[1]

It is because you are using String(...) change it to 'userEmail.description ?? ""`

Solution 2:[2]

The best way to display user data is to have a function in your class/struct that converts user data in whatever form to a string and then displays it. This would allow you to just use one function to convert data

Solution 3:[3]

I managed to figure it out but I have absolutely no idea why it works or what is happening. Lucky enough, I got it by brute force trial and error. Other answers didn't work unfortunately, but thanks to those for the suggestions.

I changed:

let userEmail = user.customData["email"] ?? "no email"
let userPassword = user.customData["password"] ?? "no password"
let userDisplay = user.customData["display"] ?? "no display"
let userName = user.customData["fullName"] ?? "no name"

To this:

let userEmail = ((user.customData["email"] ?? "email")?.stringValue)!
let userPassword = ((user.customData["password"] ?? "password")?.stringValue)!
let userDisplay = ((user.customData["display"] ?? "display")?.stringValue)!
let userName = ((user.customData["fullName"] ?? "fullName")?.stringValue)!

Text(userEmail)
Text(userPassword)
Text(userDisplay)
Text(userName)

Can anyone breakdown how this works? Like what do the question/exclamation marks do? And what is a simpler way to do this (if any)?

Solution 4:[4]

First of all conditionally downcast all dictionary values to AnyBSON to get the String value

let userEmail = (user.customData["email"] as? AnyBSON)?.stringValue ?? "no email"
let userPassword = (user.customData["password"] as? AnyBSON)?.stringValue ?? "no password"
let userDisplay = (user.customData["display"] as? AnyBSON)?.stringValue ?? "no display"
let userName = (user.customData["fullName"] as? AnyBSON)?.stringValue ?? "no name"

Without the cast you get Any which prints the weird output using String(reflecting


Then simply write

Text(userEmail)
...
Text(userPassword)
...
Text(userDisplay) 
...
Text(userName)

Solution 5:[5]

This solves the problem of decoding any AnyBSON. Not, user.customData...which in fact always returns nil!

I'm upset that it took so long to basically guess at the solution. And even this solution needs a refactor as I'm force unwrapping which means its a ticking bomb.

So I used the Realm call gist: collection.findOneDocument(filter: ["userId": AnyBSON(user.id)])

Using the result:

  case .success(let document): 

   let email = document?["email"] ?? "no email"
   print("Email: \(String(describing: email!.stringValue!))")

So this returns the email string by itself. The fact I had to double-unwrap is insane.

Naturally, you want to guard and or if let as necessary. The document in this case is a Realm Document which is: Dictionary <String : AnyBSON?>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 lorem ipsum
Solution 2 a1cd
Solution 3 cresendez744
Solution 4
Solution 5 kerryatkanjo