IO, Json & XML

IO - Read & Write

Read from user input

var (
    firstName, lastName, s string
    i                      int
    f                      float32
    input                  = "56.12 / 5212 / Go"
    format                 = "%f / %d / %s"
)

func main() {
    fmt.Println("Please enter your full name: ")
    fmt.Scanln(&firstName, &lastName)
    // fmt.Scanf("%s %s", &firstName, &lastName)
    fmt.Printf("Hi %s %s!\n", firstName, lastName) // Hi Chris Naegels
    fmt.Sscanf(input, format, &f, &i, &s)
    fmt.Println("From the string we read: ", f, i, s)
    // ouwtput: From the string we read: 56.12 5212 Go
}

Read command-line argument

func main() {
    who := "Harry "
    if len(os.Args) > 1 {
        who += strings.Join(os.Args[1:], " ")
    }
    fmt.Println("Good Morning", who)
}

Read from file

  • Read by lines
func main() {
    inputFile, inputError := os.Open("hello.go")
    if inputError != nil {
        fmt.Printf("An error occurred on opening the inputfile\n" +
            "Does the file exist?\n" +
            "Have you got acces to it?\n")
        return // exit the function on error
    }
    defer inputFile.Close()  // Close file before exits the main func
    inputReader := bufio.NewReader(inputFile)
    for {
        inputString, readerError := inputReader.ReadString('\n')
        if readerError == io.EOF {
            return
        }
        fmt.Printf("The input was: %s", inputString)
    }
}

NOTE: The End-of-line characters of text-files in Unix end on \n, but in Windows this is \r\n. By using the method ReadString or ReadBytes with \n as a delimiter you don’t have to worry about this.

  • Read entire file into a string
func main() {
    inputFile := "hello.go"
    outputFile := "hello.go.txt"
    buf, err := ioutil.ReadFile(inputFile)
    if err != nil {
        fmt.Fprintf(os.Stderr, "File Error: %s\n", err)
        // panic(err.Error())
    }
    fmt.Printf("%s\n", string(buf))
    err = ioutil.WriteFile(outputFile, buf, 0x644)
    if err != nil {
        panic(err.Error())
    }
}
  • Read into buffer
for {
    n, err := inputReader.Read(buf)
    if (n == 0) { break}
}
  • Read columns of data, such as csv
func main() {
    file, err := os.Open("csv_data.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    var col1, col2, col3 []string
    for {
        var v1, v2, v3 string
        _, err := fmt.Fscanln(file, &v1, &v2, &v3)
        // scans until newline
        if err != nil {
            break
        }
        col1 = append(col1, v1)
        col2 = append(col2, v2)
        col3 = append(col3, v3)
    }
    fmt.Println(" Col 1 ",col1)
    fmt.Println(" Col 3 ",col2)
    fmt.Println(" Col 3 ",col3)
}
  • Read from compressed file
func main() {
    fName := "public.zip"
    var r *bufio.Reader
    fi, err := os.Open(fName)
    if err != nil {
        fmt.Fprintf(os.Stderr, "%v, Can’t open %s: error: %s\n", os.Args[0],
            fName, err)
        os.Exit(1)
    }
    fz, err := gzip.NewReader(fi)
    if err != nil {
        r = bufio.NewReader(fi)
    } else {
        r = bufio.NewReader(fz)
    }
    for {
        line, err := r.ReadString('\n')
        if err != nil {

            fmt.Println("Done reading file")
            os.Exit(0)
        }
        fmt.Println(line)
    }
}

Read with flag package

  • The package flag has an extended functionality for parsing of command-line options.
  • flag.PrintDefaults() prints out the usage information of the defined flag(s)
var NewLine = flag.Bool("n", false, "print on newline")

// echo -n flag, of type *bool
const (
    Space   = " "
    Newline = "\n"
)

func main() {
    flag.PrintDefaults()
    flag.Parse()
    var s string = ""
    for i := 0; i < flag.NArg(); i++ {
        if i > 0 {
            s += Space
        }
        s += flag.Arg(i)
    }
    if *NewLine { // -n is parsed, flag becomes true
        s += Newline
    }
    os.Stdout.WriteString(s)
}
// :: Output
// -------------------------
// command: go run program.go -n abc
// out : 
//   -n    print on newline
// abc
//----------------------------
// command: go run program.go -a abc
// flag provided but not defined: -a
// Usage of /tmp/go-build701354435/b001/exe/program:
//   -n    print on newline
// exit status 2

Read with flag parsing & buffer

  • Assume there is a file named test.txt with some content
  • Run Command: go build program.go && ./program test
func cat(r *bufio.Reader) {
    for {
        buf, err := r.ReadBytes('\n')
        if err == io.EOF {
            break
        }
        fmt.Fprintf(os.Stdout, "%s", buf)
    }
    return
}
func main() {
    flag.Parse()
    if flag.NArg() == 0 {
        cat(bufio.NewReader(os.Stdin))
    }

    for i := 0; i < flag.NArg(); i++ {
        f, err := os.Open(flag.Arg(i))
        if err != nil {
            fmt.Fprintf(os.Stderr, "%s:error reading from %s: %s\n",
                os.Args[0], flag.Arg(i), err.Error())
            continue
        }
        cat(bufio.NewReader(f))
    }
}

Read with flag parsing & slice

  • Only different from previous sample is the cat function
func cat(f *os.File) {
    const NBUF = 512
    var buf [NBUF]byte
    for {
        switch nr, err := f.Read(buf[:]); true {
        case nr < 0:
            fmt.Fprintf(os.Stderr, "cat: error reading: %s\n", err.Error())
            os.Exit(1)
        case nr == 0: // EOF
            return
        case nr > 0:
            if nw, ew := os.Stdout.Write(buf[0:nr]); nw != nr {
                fmt.Fprintf(os.Stderr, "cat: error writing: %s\n",
                    ew)
            }
        }
    }
}

func main() {
    flag.Parse()
    if flag.NArg() == 0 {
        cat(os.Stdin)
    }

    for i := 0; i < flag.NArg(); i++ {
        f, err := os.Open(flag.Arg(i))
        if err != nil {
            fmt.Fprintf(os.Stderr, "%s:error reading from %s: %s\n",
                os.Args[0], flag.Arg(i), err.Error())
            os.Exit(1)
        }
        cat(f)
        f.Close()
    }
}

Write to a file

  • Flags for open output file

    • os.O_RDONLY: the read flag for read-only access
    • os.WRONLY: the write flag for write-only access
    • os.O_CREATE : the create flag: create the file if it doesn’t exist
    • os.O_TRUNC : the truncate flag: truncate to size 0 if the file already exists
  • Write with buffer

func main() {
    outputFile, outputError := os.OpenFile("output.txt",
        os.O_WRONLY|os.O_CREATE, 0666)
    if outputError != nil {
        fmt.Printf("An error occurred with file creation\n")
        return
    }
    defer outputFile.Close()
    outputWriter := bufio.NewWriter(outputFile)
    outputString := "hello world!\n"
    for i := 0; i < 10; i++ {
        outputWriter.WriteString(outputString)
    }
    outputWriter.Flush()
}
  • Write a file without buffer

    • Write the same content as previous sample
func main() {
    os.Stdout.WriteString("hello, world\n")
    f, _ := os.OpenFile("output.txt", os.O_CREATE|os.O_WRONLY, 0)
    defer f.Close()
    for i := 0; i < 10; i++ {
        f.WriteString("hello world!\n")
    }
}

Write to standard output: interface

  • The interface is fmt.Fprintf
  • The fmt.Fprintf writes to a variable of type io.Writer.
  • Any type that has a Write method, including os.Stdout, files (like os.File), pipes, network connections, channels, etc…, and also to write buffers from the bufio package.
func main() {
    // unbuffered: os.Stdout implements io.Writer
    fmt.Fprintf(os.Stdout, "%s\n", "hello world! - unbuffered")
    // buffered:
    buf := bufio.NewWriter(os.Stdout)
    // and now so does buf:
    fmt.Fprintf(buf, "%s\n", "hello world! - buffered")
    buf.Flush()
}

Copy

  • Simply use io.Copy method
func main() {
    CopyFile("target_hello.txt", "hello.go")
    fmt.Println("Copy done!")

}
func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }
    defer src.Close()
    dst, err := os.OpenFile(dstName, os.O_WRONLY|os.O_CREATE, 0644)
    if err != nil {
        return
    }
    defer dst.Close()
    return io.Copy(dst, src)
}

JSON

  • Mapping of Go type and Js type
    • Go: bool -> Js: boolean
    • Go: float64 -> Js: numbers
    • Go: string -> Js: strings
    • Go: nil -> Js: null
type Address struct {
    Type    string
    City    string
    Country string
}
type VCard struct {
    FirstName string
    LastName  string
    Addresses []*Address
    Remark    string
}

func main() {
    pa := &Address{"private", "Aartselaar", "Belgium"}
    wa := &Address{"work", "Boom", "Belgium"}
    vc := VCard{"Jan", "Kersschot", []*Address{pa, wa}, "none"}
    // fmt.Printf("%v: \n", vc)
    // {Jan Kersschot [0x126d2b80 0x126d2be0] none}:
    // JSON format:
    js, _ := json.Marshal(vc)
    fmt.Printf("JSON format: %s", js)
    // using an encoder:
    file, _ := os.OpenFile("vcard.json", os.O_CREATE|os.O_WRONLY, 0)
    defer file.Close()
    enc := json.NewEncoder(file)
    err := enc.Encode(vc)
    if err != nil {
        fmt.Println("Error in encoding json")
    }
}

// cat vcard.json | jq  # jq is a tool for json file
// ----------------------------------------------------
// {
//   "FirstName": "Jan",
//   "LastName": "Kersschot",
//   "Addresses": [
//     {
//       "Type": "private",
//       "City": "Aartselaar",
//       "Country": "Belgium"
//     },
//     {
//       "Type": "work",
//       "City": "Boom",
//       "Country": "Belgium"
//     }
//   ],
//   "Remark": "none"
// }

XML

  • Sample xml
<Person>
    <FirstName>Laura</FirstName>
    <LastName>Lynn</LastName>
</Person>
  • The encoding/xml package also implements a simle xml parser (SAX) to read XML-data and parse it into its constituents.

var t, token xml.Token
var err error

func main() {
    input :=
        "<Person><FirstName>Laura</FirstName><LastName>Lynn</LastName></Person>"
    inputReader := strings.NewReader(input)
    p := xml.NewDecoder(inputReader)
    for t, err = p.Token(); err == nil; t, err = p.Token() {
        switch token := t.(type) {
        case xml.StartElement:
            name := token.Name.Local
            fmt.Printf("Token name: %s\n", name)
            for _, attr := range token.Attr {
                attrName := attr.Name.Local
                attrValue := attr.Value
                fmt.Printf("An attribute is: %s %s\n", attrName,
                    attrValue)
                // ...
            }
        case xml.EndElement:
            fmt.Println("End of token")
        case xml.CharData:
            content := string([]byte(token))
            fmt.Printf("This is the content: %v\n", content)
        // ...
        default:
            // ...
        }
    }
}

//----------------------
// Token name: Person
// Token name: FirstName
// This is the content: Laura
// End of token
// Token name: LastName
// This is the content: Lynn
// End of token
// End of token

Gob - Go binary format

  • Simulate network communication
type P struct {
    X, Y, Z int
    Name    string
}
type Q struct {
    X, Y *int32
    Name string
}

func main() {
    // Initialize the encoder and decoder. Normally enc and dec would
    // be bound to network connections and the encoder and decoder
    // would run in different processes.
    var network bytes.Buffer
    // Stand-in for a network connection
    enc := gob.NewEncoder(&network) // Will write to network.
    dec := gob.NewDecoder(&network)
    // Will read from network.
    // Encode (send) the value.
    err := enc.Encode(P{3, 4, 5, "Pythagoras"})
    if err != nil {
        log.Fatal("encode error:", err)
    }
    // Decode (receive) the value.
    var q Q
    err = dec.Decode(&q)
    if err != nil {
        log.Fatal("decode error:", err)
    }
    fmt.Printf("%q: {%d,%d}\n", q.Name, *q.X, *q.Y)
    // "Pythagoras": {3,4}
}
  • Simulate file operation
type Address struct {
    Type    string
    City    string
    Country string
}
type VCard struct {
    FirstName string
    LastName  string
    Addresses []*Address
    Remark    string
}

func main() {
    pa := &Address{"private", "Aartselaar", "Belgium"}
    wa := &Address{"work", "Boom", "Belgium"}
    vc := VCard{"Jan", "Kersschot", []*Address{pa, wa}, "none"}
    // fmt.Printf("%v: \n", vc)
    // {Jan Kersschot [0x126d2b80 0x126d2be0] none}:
    // using an encoder:
    file, _ := os.OpenFile("vcard.gob", os.O_CREATE|os.O_WRONLY, 0)
    defer file.Close()
    enc := gob.NewEncoder(file)
    err := enc.Encode(vc)
    if err != nil {
        log.Println("Error in encoding gob")
    }
}

Crypto

  • The hash package: implements the adler32, crc32, crc64 and fnv checksums;
  • The crypto package: implements other hashing algorithms like md4, md5, sha1, etc. and complete encryption implementations for aes, blowfish, rc4, rsa, xtea, etc.

func main() {
    hasher := sha1.New()
    io.WriteString(hasher, "test")
    b := []byte{}
    fmt.Printf("Result: %x\n", hasher.Sum(b))
    fmt.Printf("Result: %d\n", hasher.Sum(b))
    hasher.Reset()
    data := []byte("We shall overcome!")
    n, err := hasher.Write(data)
    if n != len(data) || err != nil {
        log.Printf("Hash write error: %v / %v", n, err)
    }
    checksum := hasher.Sum(b)
    fmt.Printf("Result: %x\n", checksum)
}

//----------------------------------
// Result: a94a8fe5ccb19ba61c4c0873d391e987982fbbd3
// Result: [169 74 143 229 204 177 155 166 28 76 8 115 211 145 233 135 152 47 187 211]
// Checksum: e2222bfc59850bbb00a722e764a555603bb59b2a