Lab 10: File I/O & JSON

Objective

Read and write files using os, bufio, and io; encode and decode JSON with encoding/json; use struct tags to control serialization.

Time

30 minutes

Prerequisites

  • Lab 09 (Packages & Stdlib)

Tools

  • Docker image: zchencow/innozverse-go:latest


Lab Instructions

Step 1: Reading & Writing Files

docker run --rm zchencow/innozverse-go:latest go run - << 'EOF'
package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"
)

func main() {
    path := "/tmp/golab-io.txt"

    // Write entire file at once
    content := "Line 1: Hello, Go!\nLine 2: File I/O\nLine 3: innoZverse\n"
    if err := os.WriteFile(path, []byte(content), 0644); err != nil {
        fmt.Println("Write error:", err)
        return
    }
    fmt.Println("Wrote", len(content), "bytes")

    // Read entire file
    data, err := os.ReadFile(path)
    if err != nil { fmt.Println("Read error:", err); return }
    fmt.Printf("Read %d bytes\n", len(data))

    // Read line by line with bufio.Scanner
    f, _ := os.Open(path)
    defer f.Close()

    scanner := bufio.NewScanner(f)
    lineNum := 0
    for scanner.Scan() {
        lineNum++
        fmt.Printf("  [%d] %s\n", lineNum, scanner.Text())
    }

    // Append to file
    f2, _ := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0644)
    defer f2.Close()
    writer := bufio.NewWriter(f2)
    fmt.Fprintln(writer, "Line 4: Appended!")
    writer.Flush()

    // Verify append
    final, _ := os.ReadFile(path)
    lines := strings.Split(strings.TrimSpace(string(final)), "\n")
    fmt.Printf("Total lines: %d\n", len(lines))
}
EOF

💡 bufio.Scanner reads line by line without loading the whole file into memory — essential for large files. bufio.Writer batches small writes into larger I/O operations, dramatically improving performance. Always call writer.Flush() at the end or writes may be lost.

📸 Verified Output:


Step 2: JSON Encoding & Decoding

💡 JSON struct tags control serialization: json:"name" sets the field name, omitempty skips zero-value fields, json:"-" excludes the field entirely. Without tags, Go uses the field name as-is (case-sensitive). The encoding/json package uses reflection to read these tags at runtime.

📸 Verified Output:


Step 3: JSON Streaming with Encoder/Decoder

💡 json.Encoder/Decoder stream JSON directly to/from io.Writer/io.Reader. This avoids loading the entire JSON into memory — critical for large API responses or log files. dec.More() returns true while there's more to decode from the stream.

📸 Verified Output:


Steps 4–8: Custom Marshaler, CSV, Config File, Directory Walker, Capstone

📸 Verified Output:


Summary

Task
Go approach

Read whole file

os.ReadFile(path)

Write whole file

os.WriteFile(path, data, 0644)

Read line by line

bufio.NewScanner(f) + scanner.Scan()

Buffered writes

bufio.NewWriter(f) + w.Flush()

JSON encode

json.Marshal(v) or json.NewEncoder(w).Encode(v)

JSON decode

json.Unmarshal(data, &v) or json.NewDecoder(r).Decode(&v)

CSV read

csv.NewReader(f).ReadAll()

CSV write

csv.NewWriter(f).Write(row) + Flush()

Walk directory

filepath.WalkDir(root, fn)

Further Reading

Last updated