NetLife Guru

Open source Go packages for fast, maintainable web systems. Built with a documentation-first approach.

Product
OverviewGolang packagesNews
Documentation
DocumentationGo LoggerGo RouterGo DB Form
Company
OverviewContactNewsGitHub
Community / Support
Supportinfo@netlife.guru
© 2026 NetLife Guru. All rights reserved.
GitHubinfo@netlife.guru
NetLife GuruNetLife GuruNetLife Guru
NetLife GuruNetLife GuruNetLife Guru
OverviewDocumentationNewsSupportContact

Golang packages

About
ScanStructRowsScanStructSliceScanStructOneScanMapRowsFillFromMap
MappingDynamic RowsEdge Cases
MapperReal Usage

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, nil

The 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:

ScanStructRows example

Quick Start

Scan database rows into a typed Go slice.

ScanStructSlice

Use ScanStructSlice to load database rows into a typed Go slice.

On this page

When to Use ItDefine a StructQuery RowsScan Rows With a CallbackComplete Query ExampleComplete Usage ExampleCallback BehaviorDifference From ScanStructSliceRows Are ConsumedRelated Example