Search code examples
firebasegogoogle-cloud-firestore

How to handle nested pointers in Firestore's DataTo method?


I have the following structs:

type Address struct {
    Line1 string `firestore:"line1"`
    City  string `firestore:"city"`
}
type User struct {
    Name     string   `firestore:"name,required"`
    Email    string   `firestore:"email,required"`
    Password string   `firestore:"password,required"`
    Address  *Address `firestore:"address"`
    Age      int      `firestore:"age,required"`
}

When I add a user doc to Firestore, everything works perfectly:

docRef, _, err := client.Collection("users").Add(ctx, User{
    Name: "John",
    Email: "jonh.doe@example.com",
    Password: "123456",
    Address: &Address{
        Line1: "1 Main Road",
        City: "NY",
    },
    Age: 18,
})
if err != nil {
    fmt.Println(err)
} else {
    fmt.Println(docRef.ID)
}

The problem is, when I try to fetch multiple users, using a query, and then converting them into User structs, using DataTo method, the address field seems to be the same for all users - i.e. the same pointer has been used for all users...

var user User
var users []Users
iter := query.Documents(ctx)

for {
    docSnap, err := iter.Next()
    if err == iterator.Done {
        break
    }
    if err != nil {
        fmt.Println(err)
    }

    err = docSnap.DataTo(&user)
    if err != nil {
        fmt.Println(err)
    }

    fmt.Println(user)
    users = append(users, user)
}

fmt.Println(docs)

Am I doing something wrong?

I know this isn't an issue if I don't use a pointer for Address, but since some users may have a nil address, I find that using a pointer in this case is beneficial. Is this a bug, or is this how it's supposed to work?


Solution

  • The first call to docSnap.DataTo(&user) sets user.Address to a newly allocated Address. Subsequent calls to DataTo reuse that Address. Fix by this and other potential issues by declaring user inside the scope of the for loop.

    var users []Users
    iter := query.Documents(ctx)
    
    for {
        docSnap, err := iter.Next()
        if err == iterator.Done {
            break
        }
        if err != nil {
            fmt.Println(err)
        }
    
        var user User // <-- declare user here
        err = docSnap.DataTo(&user)
        if err != nil {
            fmt.Println(err)
        }
    
        fmt.Println(user)
        users = append(users, user)
    }