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: 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