ScanStructRows
Use ScanStructRows to process database rows one by one with a callback.
Use ScanStructRows when you want to process database rows one by one.
The function scans each row into a Go struct and passes it to a callback.
This gives you control over what happens with every scanned row.
When to Use It
Use ScanStructRows when:
- you want callback-based row processing
- you want to build the result manually
- you want to validate each row while scanning
- you want to stop scanning by returning an error
- you need custom behavior for every scanned row
For simple list queries, ScanStructSlice is usually shorter.
ScanStructRows is useful when you want more control.
Define a Struct
Create a struct that represents one row from the query result.
type User struct {
ID int64 `db:"id"`
Name string `db:"name"`
Email string `db:"email"`
Active bool `db:"active"`
CreatedAt time.Time `db:"created_at"`
}The db tags tell mapper which database columns should be assigned to each field.
Query Rows
Query rows using your database driver.
rows, err := db.Query(`
SELECT *
FROM users
ORDER BY created_at DESC
`)
if err != nil {
return nil, err
}
defer rows.Close()The returned rows value is passed to mapper.
With Go’s standard database/sql, *sql.Rows already provides the methods required by mapper.
Scan Rows With a Callback
Use ScanStructRows to scan rows one by one.
var users []User
err = mapper.ScanStructRows[User](rows, func(user *User) error {
users = append(users, *user)
return nil
})
if err != nil {
return nil, err
}
return users, nilThe callback receives a pointer to the scanned struct.
In this example, each scanned User is appended to a slice manually.
Complete Query Example
package main
import (
"database/sql"
"time"
"github.com/netlifeguru/mapper"
)
type User struct {
ID int64 `db:"id"`
Name string `db:"name"`
Email string `db:"email"`
Active bool `db:"active"`
CreatedAt time.Time `db:"created_at"`
}
func getUsers(db *sql.DB) ([]User, error) {
rows, err := db.Query(`
SELECT *
FROM users
ORDER BY created_at DESC
`)
if err != nil {
return nil, err
}
defer rows.Close()
var users []User
err = mapper.ScanStructRows[User](rows, func(user *User) error {
users = append(users, *user)
return nil
})
if err != nil {
return nil, err
}
return users, nil
}Complete Usage Example
package main
import (
"fmt"
"log"
_ "github.com/go-sql-driver/mysql"
"github.com/joho/godotenv"
)
func main() {
err := godotenv.Load()
if err != nil {
log.Println(".env file not found, I'm using system env variables")
}
db, err := connectDB()
if err != nil {
log.Fatal(err)
}
defer db.Close()
users, err := getUsers(db)
if err != nil {
log.Fatal(err)
}
for _, user := range users {
fmt.Printf(
"ID: %d | Name: %s | Email: %s | Active: %t | Created: %s\n",
user.ID,
user.Name,
user.Email,
user.Active,
user.CreatedAt.Format("2006-01-02 15:04:05"),
)
}
}The connectDB function is part of the standalone example and is responsible for opening the MySQL database connection.
Callback Behavior
The callback is called once for every row.
err = mapper.ScanStructRows[User](rows, func(user *User) error {
users = append(users, *user)
return nil
})If the callback returns nil, scanning continues.
If the callback returns an error, scanning stops and that error is returned.
err = mapper.ScanStructRows[User](rows, func(user *User) error {
if user.ID == 0 {
return errors.New("missing user id")
}
users = append(users, *user)
return nil
})This is useful when you want to validate or reject rows during scanning.
Difference From ScanStructSlice
This example manually builds a slice:
var users []User
err = mapper.ScanStructRows[User](rows, func(user *User) error {
users = append(users, *user)
return nil
})For simple cases, ScanStructSlice can do this automatically:
users, err := mapper.ScanStructSlice[User](rows)Use ScanStructRows when you want callback control.
Use ScanStructSlice when you simply want all rows returned as []User.
Rows Are Consumed
Rows are consumed during scanning.
Do not scan the same rows value twice.
err = mapper.ScanStructRows[User](rows, func(user *User) error {
users = append(users, *user)
return nil
})
// Do not scan the same rows again.
// Run the query again if you need another scan.Related Example
A standalone example is available in the examples repository: