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
MappingDynamic RowsEdge Cases
Mapper

Dynamic Rows

Work with dynamic row maps, typed row helpers, and manual struct mapping.

Mapper is most commonly used to scan database rows into structs.

For dynamic use cases, it can also scan rows into map[string]any, fill structs from maps, or provide typed access to row values.

This is useful when:

  • the result shape is dynamic
  • you do not want to define a struct for every query
  • you are working with joins, reports, exports, or admin tooling
  • you need custom mapping logic
  • you want to inspect raw database values before assigning them

Scan Rows Into Maps

Use ScanMapRows when you want each database row as a map[string]any.

rows, err := db.Query(`
	SELECT *
	FROM users
`)

if err != nil {
	return err
}

defer rows.Close()

err = mapper.ScanMapRows(rows, func(row map[string]any) error {
	fmt.Println(row["id"], row["name"])
	return nil
})

if err != nil {
	return err
}

Each returned row is represented as a map where:

  • the key is the database column name
  • the value is the scanned database value

Example result:

map[string]any{
	"id":     "u_123",
	"name":   "Alice",
	"email":  "alice@example.com",
	"active": true,
}

When to Use Maps

Maps are useful when the result does not have a stable struct shape.

For example:

rows, err := db.Query(`
	SELECT status, COUNT(*) AS total
	FROM users
	GROUP BY status
`)

You can process the result dynamically:

err = mapper.ScanMapRows(rows, func(row map[string]any) error {
	status := row["status"]
	total := row["total"]

	fmt.Println(status, total)
	return nil
})

For stable application models, structs are usually preferred because they provide stronger type safety.

The Row Type

Row is a convenience type for working with map[string]any.

type Row map[string]any

It provides typed helper methods for common values.

row := mapper.Row{
	"id":     int64(123),
	"name":   "Alice",
	"active": true,
}

You can read values using typed accessors:

id, ok := row.Int64("id")
if !ok {
	return errors.New("invalid id")
}

name, ok := row.String("name")
if !ok {
	return errors.New("invalid name")
}

active, ok := row.Bool("active")
if !ok {
	return errors.New("invalid active value")
}

Row Helpers

The Row type provides these helper methods:

MethodPurpose
IntRead a value as int
Int64Read a value as int64
StringRead a value as string
BoolRead a value as bool
TimeRead a value as time.Time

Example:

createdAt, ok := row.Time("created_at")
if !ok {
	return errors.New("invalid created_at value")
}

Each helper returns two values:

value, ok := row.String("name")

If the value can be converted, ok is true.

If the value is missing or cannot be converted, ok is false.

Standalone Converters

The same conversions are also available as standalone functions.

name, ok := mapper.AsString(row["name"])
active, ok := mapper.AsBool(row["active"])
createdAt, ok := mapper.AsTime(row["created_at"])

Available converters:

FunctionPurpose
AsIntConvert a value to int
AsInt64Convert a value to int64
AsStringConvert a value to string
AsBoolConvert a value to bool
AsTimeConvert a value to time.Time

These helpers are useful when working with raw row maps or custom mapping logic.

Fill a Struct From a Map

Use FillFromMap when you already have a map[string]any and want to fill a struct.

type User struct {
	ID     int64  `db:"id"`
	Name   string `db:"name"`
	Email  string `db:"email"`
	Active bool   `db:"active"`
}

row := map[string]any{
	"id":     int64(1),
	"name":   "Alice",
	"email":  "alice@example.com",
	"active": true,
}

var user User

err := mapper.FillFromMap(&user, row)
if err != nil {
	return err
}

The same mapping rules are used as with row scanning:

  1. db tag
  2. json tag
  3. Go field name
  4. snake_case fallback

Maps and Column Names

When scanning rows into maps, mapper uses the column names returned by the database driver.

This means SQL aliases become map keys.

rows, err := db.Query(`
	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_id
`)

The row map will contain keys such as:

map[string]any{
	"user_id":   "u_123",
	"user_name": "Alice",
	"role_name": "Admin",
}

Aliases are recommended when joining tables that contain columns with the same name.

Custom Mapping With ScanMapper

For advanced cases, a struct can implement the ScanMapper interface.

type User struct {
	ID   string
	Name string
}

func (u *User) ScanMap(row map[string]any) error {
	id, ok := mapper.AsString(row["id"])
	if !ok {
		return errors.New("invalid id")
	}

	name, ok := mapper.AsString(row["name"])
	if !ok {
		return errors.New("invalid name")
	}

	u.ID = id
	u.Name = name

	return nil
}

This gives you full control over how a row map is converted into a struct.

Custom mapping is useful when:

  • column names do not match struct fields
  • values need custom parsing
  • multiple columns should be combined into one field
  • fallback values are needed
  • validation should happen during mapping

Structs vs Maps

Use structs when the result shape is known.

users, err := mapper.ScanStructSlice[User](rows)

Use maps when the result shape is dynamic.

err = mapper.ScanMapRows(rows, func(row map[string]any) error {
	fmt.Println(row)
	return nil
})

In most application code, structs are preferred.

Maps are better for generic tooling, reports, exports, debugging, and custom data processing.

Recommended Usage

For regular application queries:

users, err := mapper.ScanStructSlice[User](rows)

For dynamic query results:

err = mapper.ScanMapRows(rows, func(row map[string]any) error {
	fmt.Println(row)
	return nil
})

For manual mapping:

var user User

err := mapper.FillFromMap(&user, row)
if err != nil {
	return err
}

For custom parsing and validation, implement ScanMapper.

Mapping

Learn how mapper matches database columns to Go struct fields.

Edge Cases

Understand how mapper handles null values, missing columns, extra columns, type conversions, pointers, JSON fields, and row count errors.

On this page

Scan Rows Into MapsWhen to Use MapsThe Row TypeRow HelpersStandalone ConvertersFill a Struct From a MapMaps and Column NamesCustom Mapping With ScanMapperStructs vs MapsRecommended Usage