ScanMapRows
Use ScanMapRows to process database rows as dynamic map values.
Use ScanMapRows when you want each database row as a map[string]any.
This is useful when the result shape is dynamic, temporary, or not worth defining as a dedicated struct.
Typical use cases include:
- admin screens
- reports
- exports
- debugging queries
- dynamic dashboards
- generic tooling
- queries with computed columns
- queries where selected columns may change
Basic Idea
ScanMapRows scans each row into a map and passes it to a callback.
err := mapper.ScanMapRows(rows, func(row map[string]any) error {
fmt.Println(row["id"], row["type"])
return nil
})The callback is called once for every row.
Each map key is the column name returned by the database driver.
Complete Query Example
This example loads event rows as dynamic maps.
package main
import (
"database/sql"
"github.com/netlifeguru/mapper"
)
func getEvents(db *sql.DB) ([]map[string]any, error) {
rows, err := db.Query(`
SELECT id, type, payload, created_at
FROM events
ORDER BY created_at DESC
`)
if err != nil {
return nil, err
}
defer rows.Close()
var events []map[string]any
err = mapper.ScanMapRows(rows, func(row map[string]any) error {
events = append(events, row)
return nil
})
if err != nil {
return nil, err
}
return events, nil
}Complete Usage Example
This example calls getEvents and prints the returned map values.
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()
events, err := getEvents(db)
if err != nil {
log.Fatal(err)
}
for _, event := range events {
fmt.Printf(
"ID: %v | Type: %v | Payload: %v | Created: %v\n",
event["id"],
event["type"],
event["payload"],
event["created_at"],
)
}
}Returned Row Shape
Each row is represented as a map[string]any.
For the query above, one row may look like this:
map[string]any{
"id": int64(1),
"type": "user.created",
"payload": `{"name":"Alice"}`,
"created_at": time.Now(),
}The exact Go types depend on the database driver.
SQL Aliases
Map keys are based on returned column names.
Use SQL aliases when selecting computed values or joining tables.
SELECT
u.id AS user_id,
u.name AS user_name,
r.name AS role_name
FROM users u
JOIN roles r ON r.id = u.role_idThe scanned map will contain keys such as:
map[string]any{
"user_id": int64(1),
"user_name": "Alice",
"role_name": "Admin",
}Typed Access
For typed access, convert the map to mapper.Row.
row := mapper.Row(event)
eventType, ok := row.String("type")
if !ok {
return errors.New("invalid event type")
}mapper.Row provides helpers such as:
IntInt64StringBoolTime
For individual values, you can also use standalone converters:
eventType, ok := mapper.AsString(event["type"])When to Use ScanMapRows
Use ScanMapRows when:
- you do not have a stable struct shape
- you need dynamic columns
- you are building reports or exports
- you want to inspect raw row values
- you want to process data as maps
- you do not want to define a struct for the query
When Not to Use It
Do not use ScanMapRows when the result shape is known and stable.
Use struct scanning instead.
users, err := mapper.ScanStructSlice[User](rows)Structs make application code clearer and safer when the result has a known shape.
Notes
ScanMapRows consumes the rows.
Do not scan the same rows value again after calling it.
If the underlying rows object returns an error, ScanMapRows returns that error.
If the callback returns an error, scanning stops and returns that error.
Related Example
A standalone example is available in the examples repository: