Golang fmt.Scanner Interface
last modified May 8, 2025
This tutorial explains how to use the fmt.Scanner interface in Go.
We'll cover interface basics with practical examples of custom input scanning.
The fmt.Scanner interface allows types to define their own scanning
behavior. It's used by functions like fmt.Scan and fmt.Fscan
to parse input according to custom rules.
In Go, fmt.Scanner requires implementing a single Scan
method. This method defines how the type should read and interpret input data.
Basic Scanner Interface Definition
The fmt.Scanner interface is simple but powerful. It consists of
just one method that must be implemented.
Note: The method signature must match exactly for proper implementation.
package main
import "fmt"
type Scanner interface {
Scan(state fmt.ScanState, verb rune) error
}
The Scan method takes a fmt.ScanState and a verb rune.
It returns an error if scanning fails. The method defines custom scanning logic.
Implementing Scanner for a Custom Type
This example shows how to implement fmt.Scanner for a custom type.
We'll create a Coordinate type that scans "(x,y)" formatted input.
package main
import (
"fmt"
"io"
"strconv"
)
type Coordinate struct {
X, Y int
}
func (c *Coordinate) Scan(state fmt.ScanState, verb rune) error {
_, err := fmt.Fscanf(state, "(%d,%d)", &c.X, &c.Y)
return err
}
func main() {
var c Coordinate
_, err := fmt.Sscan("(10,20)", &c)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Scanned coordinate: %+v\n", c)
}
The Coordinate type implements Scan to parse "(x,y)"
format. fmt.Fscanf helps with the actual parsing from the state.
Scanning CSV Data with Scanner
We can use fmt.Scanner to parse CSV-formatted input. This example
shows a CSVRecord type that scans comma-separated values.
package main
import (
"fmt"
"io"
"strings"
)
type CSVRecord struct {
Fields []string
}
func (r *CSVRecord) Scan(state fmt.ScanState, verb rune) error {
data, err := state.Token(true, func(r rune) bool {
return r != '\n' && r != '\r'
})
if err != nil {
return err
}
r.Fields = strings.Split(string(data), ",")
return nil
}
func main() {
var record CSVRecord
fmt.Sscan("John,Doe,42,New York", &record)
fmt.Printf("Scanned record: %v\n", record.Fields)
}
The Scan method uses state.Token to read until newline.
It then splits the input by commas to create the record fields.
Scanning Key-Value Pairs
This example demonstrates scanning key-value pairs with custom formatting.
We'll implement a KeyValue type that parses "key=value" input.
package main
import (
"fmt"
"strings"
)
type KeyValue struct {
Key string
Value string
}
func (kv *KeyValue) Scan(state fmt.ScanState, verb rune) error {
data, err := state.Token(false, func(r rune) bool {
return r != '='
})
if err != nil {
return err
}
kv.Key = string(data)
// Read the '=' separator
_, _, err = state.ReadRune()
if err != nil {
return err
}
// Read the rest as value
value, err := state.Token(false, nil)
kv.Value = string(value)
return err
}
func main() {
var kv KeyValue
fmt.Sscan("name=John", &kv)
fmt.Printf("Scanned: %+v\n", kv)
}
The method first reads until '=' for the key, then reads the remainder as value.
state.Token and ReadRune provide precise control.
Scanning Hexadecimal Numbers
This example shows how to scan hexadecimal numbers with a custom type.
We'll create a HexNumber that parses "0x" prefixed values.
package main
import (
"fmt"
"strconv"
)
type HexNumber int
func (h *HexNumber) Scan(state fmt.ScanState, verb rune) error {
// Check for 0x prefix
prefix := make([]rune, 2)
for i := 0; i < 2; i++ {
r, _, err := state.ReadRune()
if err != nil {
return err
}
prefix[i] = r
}
if prefix[0] != '0' || (prefix[1] != 'x' && prefix[1] != 'X') {
return fmt.Errorf("missing 0x prefix")
}
// Read the hex digits
digits, err := state.Token(false, nil)
if err != nil {
return err
}
value, err := strconv.ParseInt(string(digits), 16, 64)
if err != nil {
return err
}
*h = HexNumber(value)
return nil
}
func main() {
var num HexNumber
fmt.Sscan("0xFF", &num)
fmt.Printf("Scanned hex: %d (0x%X)\n", num, num)
}
The Scan method verifies the "0x" prefix before parsing the hex
digits. strconv.ParseInt converts the string to an integer.
Scanning Custom Date Format
This example implements a Date type that scans dates in "YYYY-MM-DD"
format. It demonstrates more complex parsing logic.
package main
import (
"fmt"
"strconv"
"time"
)
type Date struct {
time.Time
}
func (d *Date) Scan(state fmt.ScanState, verb rune) error {
// Read exactly 10 characters (YYYY-MM-DD)
data := make([]rune, 10)
for i := 0; i < 10; i++ {
r, _, err := state.ReadRune()
if err != nil {
return err
}
data[i] = r
}
// Parse the components
year, err := strconv.Atoi(string(data[0:4]))
if err != nil {
return err
}
month, err := strconv.Atoi(string(data[5:7]))
if err != nil {
return err
}
day, err := strconv.Atoi(string(data[8:10]))
if err != nil {
return err
}
// Validate separators
if data[4] != '-' || data[7] != '-' {
return fmt.Errorf("invalid date format")
}
d.Time = time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC)
return nil
}
func main() {
var date Date
fmt.Sscan("2025-05-08", &date)
fmt.Printf("Scanned date: %s\n", date.Format("January 02, 2006"))
}
The method reads exactly 10 characters and validates the format. It parses year, month, and day components separately before constructing the final date.
Scanning with Multiple Values
This example shows how to scan multiple values with a single Scan
call. We'll create a Person type that scans name and age together.
package main
import (
"fmt"
"strings"
)
type Person struct {
Name string
Age int
}
func (p *Person) Scan(state fmt.ScanState, verb rune) error {
// Read name (until whitespace)
name, err := state.Token(true, func(r rune) bool {
return !(r == ' ' || r == '\t' || r == '\n')
})
if err != nil {
return err
}
p.Name = string(name)
// Skip whitespace
_, _, err = state.ReadRune()
if err != nil {
return err
}
// Read age
ageStr, err := state.Token(false, nil)
if err != nil {
return err
}
// Simple age parsing (in real code, use strconv)
p.Age = len(strings.TrimSpace(string(ageStr)))
return nil
}
func main() {
var person Person
fmt.Sscan("John 42", &person)
fmt.Printf("Scanned person: %+v\n", person)
}
The method first reads the name until whitespace, then skips whitespace before reading the age. This shows how to handle multi-value scanning in one call.
Source
This tutorial covered the fmt.Scanner interface in Go with practical
examples of custom input scanning implementations.
Author
List all Golang tutorials.