Reading Files
ReadFile ยท Open ยท bufio.ScannerChoose the right read method: Small file (fits in memory) โ os.ReadFile โ one call, whole file as []byte Read line by line โ bufio.Scanner โ streams, low memory Random access / large file โ os.Open + manual Read / io.ReadAll Embed at compile time โ //go:embed directive
os.ReadFile โ whole file at once
ReadFile
// Reads entire file into []byte in one call data, err := os.ReadFile("config.json") if err != nil { return fmt.Errorf("read config: %w", err) } fmt.Println(string(data)) // os.ReadFile is equivalent to: // f, _ := os.Open(name) // defer f.Close() // io.ReadAll(f)
bufio.Scanner โ line by line
Scanner
f, err := os.Open("data.txt") if err != nil { return err } defer f.Close() scanner := bufio.NewScanner(f) for scanner.Scan() { line := scanner.Text() // one line, no trailing \n fmt.Println(line) } if err := scanner.Err(); err != nil { return err }
os.Open vs os.OpenFile โ open modes
OpenFile
// os.Open โ read-only shorthand f, err := os.Open("file.txt") // os.OpenFile โ full control over flags and permissions f, err = os.OpenFile("file.txt", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644, // permission bits: owner rw, group r, other r ) // Common flag combinations: // O_RDONLY โ read-only (default for os.Open) // O_WRONLY | O_CREATE | O_TRUNC โ create/overwrite for writing // O_WRONLY | O_CREATE | O_APPENDโ open or create, append only // O_RDWR โ read and write
Always
defer f.Close() immediately after a successful open โ before any other error-return path โ so the file is closed even if subsequent code returns early.Writing Files
WriteFile ยท bufio.Writer ยท Fprintf
os.WriteFile โ write all at once
WriteFile
// Creates or overwrites the file atomically data := []byte("hello, file\n") err := os.WriteFile("out.txt", data, 0644) if err != nil { return err } // Write a string directly err = os.WriteFile("out.txt", []byte("content"), 0644)
bufio.Writer โ buffered writes
bufio.Writer
f, err := os.Create("out.txt") // create or truncate if err != nil { return err } defer f.Close() w := bufio.NewWriter(f) fmt.Fprintln(w, "line 1") fmt.Fprintln(w, "line 2") // Must flush โ buffered writes are not sent until Flush() if err := w.Flush(); err != nil { return err }
When using
bufio.Writer, always call Flush() before closing the file. Data sitting in the buffer is silently discarded if you only call Close() โ the file ends up truncated with no error returned.
Append to an existing file
O_APPEND
f, err := os.OpenFile("log.txt", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644, ) if err != nil { return err } defer f.Close() fmt.Fprintf(f, "[%s] event happened\n", time.Now().Format(time.RFC3339))
io.Reader & io.Writer
interfaces ยท Copy ยท Pipe ยท MultiWriterio.Reader and io.Writer are the two most important interfaces in Go's standard library. Any type that implements Read([]byte) or Write([]byte) fits โ files, network connections, bytes.Buffer, strings.Reader all compose freely. io.Reader io.Writer
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Read(p []byte) (n int, err error) Write(p []byte) (n int, err error)
Implementors: *os.File, *bytes.Buffer, *strings.Reader,
*bufio.Reader, net.Conn, http.Request.Body, โฆ
io.Copy(dst Writer, src Reader) โ streams src โ dst without loading all in memory
io.Copy โ stream between any Reader/Writer
io.Copy
// Copy a file efficiently (32KB chunks internally) src, _ := os.Open("input.txt") defer src.Close() dst, _ := os.Create("output.txt") defer dst.Close() n, err := io.Copy(dst, src) fmt.Printf("copied %d bytes\n", n) // io.CopyN โ copy at most N bytes io.CopyN(dst, src, 1024)
bytes.Buffer & strings.Reader
In-memory
// bytes.Buffer โ in-memory Reader and Writer var buf bytes.Buffer fmt.Fprintf(&buf, "hello %s", "world") fmt.Println(buf.String()) // hello world // strings.NewReader โ turn a string into io.Reader r := strings.NewReader("some data") io.Copy(os.Stdout, r) // io.ReadAll โ drain a Reader into []byte data, err := io.ReadAll(r)
io.MultiWriter & io.TeeReader
Fan-out
// MultiWriter โ write to multiple destinations simultaneously logFile, _ := os.Create("app.log") defer logFile.Close() mw := io.MultiWriter(os.Stdout, logFile) fmt.Fprintln(mw, "written to stdout and file") // TeeReader โ read from src, simultaneously write to dst // Useful for logging/inspecting data passing through a pipeline var buf bytes.Buffer tee := io.TeeReader(src, &buf) // every read from tee also writes to buf json.NewDecoder(tee).Decode(&v) fmt.Println("raw payload:", buf.String())
JSON Encoding
Marshal ยท Encoder ยท json tags
json tags โ control serialization
struct tags
type User struct { ID int `json:"id"` Name string `json:"name"` Email string `json:"email,omitempty"` // omit if empty string Password string `json:"-"` // always omit CreatedAt time.Time `json:"created_at"` } // omitempty omits: false, 0, nil pointer, nil slice/map, "" // It does NOT omit a zero time.Time โ use *time.Time for that
json.Marshal โ to bytes
Marshal
u := User{ID: 1, Name: "Alice"} // Compact JSON data, err := json.Marshal(u) // {"id":1,"name":"Alice","created_at":"0001-..."} // Indented JSON for human readability data, err = json.MarshalIndent(u, "", " ") if err != nil { return err } fmt.Println(string(data))
json.Encoder โ stream to Writer
Encoder
// Prefer Encoder when writing to http.ResponseWriter, // os.File, or any io.Writer โ avoids allocating []byte enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") users := []User{{ID: 1, Name: "Alice"}} if err := enc.Encode(users); err != nil { return err } // Encode appends a trailing newline automatically
Use
json.Encoder when writing to an http.ResponseWriter or file. Use json.Marshal when you need a []byte to store, sign, or pass to another function. Encoder is more memory-efficient for large payloads.JSON Decoding
Unmarshal ยท Decoder ยท unknown fields
json.Unmarshal โ from bytes
Unmarshal
payload := []byte(`{"id":1,"name":"Alice"}`) var u User if err := json.Unmarshal(payload, &u); err != nil { return fmt.Errorf("decode user: %w", err) } fmt.Println(u.Name) // Alice // Unmarshal unknown structure into map var m map[string]any json.Unmarshal(payload, &m) fmt.Println(m["name"]) // Alice
json.Decoder โ stream from Reader
Decoder
// Decode from an HTTP response body or file resp, _ := http.Get("https://api.example.com/user") defer resp.Body.Close() var u User if err := json.NewDecoder(resp.Body).Decode(&u); err != nil { return err } // Reject unknown fields to catch typos in clients dec := json.NewDecoder(r.Body) dec.DisallowUnknownFields() dec.Decode(&u)
Custom MarshalJSON / UnmarshalJSON
Custom
type Duration struct{ time.Duration } // Encode as a human-readable string: "1h30m" func (d Duration) MarshalJSON() ([]byte, error) { return json.Marshal(d.Duration.String()) } // Decode from a human-readable string func (d *Duration) UnmarshalJSON(b []byte) error { var s string if err := json.Unmarshal(b, &s); err != nil { return err } dur, err := time.ParseDuration(s) d.Duration = dur return err }
JSON numbers decode to
float64 by default when unmarshalling into map[string]any. A large integer like a database ID can lose precision. Use json.Decoder with d.UseNumber() to get a json.Number instead, then call .Int64() on it.File Paths & Directories
filepath ยท os.MkdirAll ยท WalkDir
filepath โ portable path manipulation
filepath
// Join โ OS-correct separator (/ on Unix, \ on Windows) p := filepath.Join("data", "2024", "report.csv") // "data/2024/report.csv" filepath.Dir(p) // "data/2024" filepath.Base(p) // "report.csv" filepath.Ext(p) // ".csv" // Abs โ resolve relative path to absolute abs, err := filepath.Abs("./config.yaml") // Clean โ normalize ../ and // filepath.Clean("a/../b//c") // "b/c"
Creating & removing directories
MkdirAll
// Create a single directory os.Mkdir("output", 0755) // Create all intermediate directories os.MkdirAll("output/2024/reports", 0755) // Remove a file os.Remove("tmp.txt") // Remove directory and all contents os.RemoveAll("output") // Rename (also works as move) os.Rename("old.txt", "new.txt")
fs.WalkDir โ traverse a directory tree
WalkDir
filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error { if err != nil { return err // permission error accessing this entry } if d.IsDir() && d.Name() == ".git" { return filepath.SkipDir // skip entire subtree } if filepath.Ext(path) == ".go" { fmt.Println(path) } return nil }) // d.Type() โ FileMode (file, dir, symlink) // d.Info() โ fs.FileInfo (size, modtime, etc.) // d.IsDir() โ shorthand for d.Type().IsDir()
Embedding Files
//go:embed ยท embed.FS ยท string ยท []byteThe
//go:embed directive (Go 1.16+) bakes files into the binary at compile time. No runtime file reads, no deployment packaging โ the binary is self-contained.
Embed as string, []byte, or embed.FS
//go:embed
import _ "embed" // blank import required when embedding into var // Embed a single file as string //go:embed version.txt var version string // Embed a single file as []byte //go:embed default_config.json var defaultConfig []byte // Embed a whole directory (or multiple files) as embed.FS //go:embed templates/* var templates embed.FS func main() { fmt.Println(version) data, _ := templates.ReadFile("templates/index.html") fmt.Println(string(data)) }
embed.FS โ serve HTTP from binary
embed.FS
//go:embed static/* var staticFiles embed.FS // Serve embedded files over HTTP sub, _ := fs.Sub(staticFiles, "static") http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(sub)), ), )
Glob patterns in //go:embed
patterns
// Multiple files or patterns on separate lines //go:embed migrations/*.sql //go:embed config/defaults.yaml var assets embed.FS // List embedded files entries, _ := assets.ReadDir("migrations") for _, e := range entries { fmt.Println(e.Name()) } // Note: files beginning with . or _ are excluded // unless the pattern explicitly names them.
Quick Reference
Cheat-sheet| Concept | Call | Notes |
|---|---|---|
| Read whole file | os.ReadFile(path) | Returns []byte; small files only |
| Read line by line | bufio.NewScanner(f).Scan() | Check scanner.Err() after loop |
| Open read-only | os.Open(path) | Shorthand for O_RDONLY |
| Open with flags | os.OpenFile(path, flags, perm) | O_CREATE, O_TRUNC, O_APPENDโฆ |
| Write whole file | os.WriteFile(path, data, 0644) | Creates or overwrites |
| Create file | os.Create(path) | Equivalent to OpenFile O_WRONLY|O_CREATE|O_TRUNC |
| Buffered write | bufio.NewWriter(f) | Call Flush() before Close() |
| Stream copy | io.Copy(dst, src) | Any Reader โ any Writer; 32KB chunks |
| Drain reader | io.ReadAll(r) | Returns []byte |
| In-memory buffer | bytes.Buffer | Implements both Reader and Writer |
| String as Reader | strings.NewReader(s) | Lightweight, no allocation |
| Multi-destination | io.MultiWriter(w1, w2, โฆ) | Writes to all simultaneously |
| Tap a stream | io.TeeReader(r, w) | Reads from r and copies to w |
| Marshal to bytes | json.Marshal(v) | Returns []byte |
| Marshal to writer | json.NewEncoder(w).Encode(v) | More efficient for HTTP/file |
| Unmarshal bytes | json.Unmarshal(b, &v) | v must be a pointer |
| Decode from reader | json.NewDecoder(r).Decode(&v) | Use for HTTP bodies |
| Reject unknown fields | dec.DisallowUnknownFields() | Useful for strict APIs |
| Join path | filepath.Join(partsโฆ) | OS-correct separator |
| Dir / Base / Ext | filepath.Dir/Base/Ext(p) | Path components |
| Walk directory | filepath.WalkDir(root, fn) | Return filepath.SkipDir to skip subtree |
| Create dirs | os.MkdirAll(path, 0755) | No error if already exists |
| Embed file (string) | //go:embed file.txt + var s string | Baked in at compile time |
| Embed dir | //go:embed dir/* + var f embed.FS | Use f.ReadFile / f.ReadDir |