Skip to main content
โšก Calmops

Working with XML and CSV in Go

Working with XML and CSV in Go

XML and CSV are common data formats used in many applications. Go provides excellent support for both through the standard library. This guide covers parsing, encoding, and practical techniques for working with these formats.

Working with CSV

Reading CSV Files

package main

import (
	"encoding/csv"
	"fmt"
	"log"
	"os"
)

func main() {
	// Open CSV file
	file, err := os.Open("data.csv")
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()

	// Create CSV reader
	reader := csv.NewReader(file)

	// Read all records
	records, err := reader.ReadAll()
	if err != nil {
		log.Fatal(err)
	}

	// Process records
	for i, record := range records {
		fmt.Printf("Row %d: %v\n", i, record)
	}
}

Reading CSV Line by Line

package main

import (
	"encoding/csv"
	"fmt"
	"log"
	"os"
)

func main() {
	file, err := os.Open("data.csv")
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()

	reader := csv.NewReader(file)

	// Read line by line
	for {
		record, err := reader.Read()
		if err != nil {
			break
		}

		fmt.Printf("Name: %s, Age: %s, Email: %s\n", record[0], record[1], record[2])
	}
}

Writing CSV Files

package main

import (
	"encoding/csv"
	"log"
	"os"
)

func main() {
	// Create CSV file
	file, err := os.Create("output.csv")
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()

	// Create CSV writer
	writer := csv.NewWriter(file)
	defer writer.Flush()

	// Write header
	writer.Write([]string{"Name", "Age", "Email"})

	// Write records
	writer.Write([]string{"Alice", "30", "[email protected]"})
	writer.Write([]string{"Bob", "25", "[email protected]"})
	writer.Write([]string{"Charlie", "35", "[email protected]"})
}

CSV with Custom Delimiter

package main

import (
	"encoding/csv"
	"fmt"
	"log"
	"os"
	"strings"
)

func main() {
	// CSV with semicolon delimiter
	data := "Name;Age;Email\nAlice;30;[email protected]\nBob;25;[email protected]"

	reader := csv.NewReader(strings.NewReader(data))
	reader.Comma = ';'

	records, err := reader.ReadAll()
	if err != nil {
		log.Fatal(err)
	}

	for _, record := range records {
		fmt.Println(record)
	}
}

Parsing CSV into Structs

package main

import (
	"encoding/csv"
	"fmt"
	"log"
	"os"
	"strconv"
)

type Person struct {
	Name  string
	Age   int
	Email string
}

func main() {
	file, err := os.Open("data.csv")
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()

	reader := csv.NewReader(file)
	records, err := reader.ReadAll()
	if err != nil {
		log.Fatal(err)
	}

	var people []Person

	// Skip header
	for _, record := range records[1:] {
		age, _ := strconv.Atoi(record[1])
		person := Person{
			Name:  record[0],
			Age:   age,
			Email: record[2],
		}
		people = append(people, person)
	}

	for _, p := range people {
		fmt.Printf("%+v\n", p)
	}
}

Working with XML

Parsing XML

package main

import (
	"encoding/xml"
	"fmt"
	"log"
)

type Book struct {
	Title  string `xml:"title"`
	Author string `xml:"author"`
	Year   int    `xml:"year"`
}

func main() {
	xmlData := `
	<book>
		<title>Go Programming</title>
		<author>John Doe</author>
		<year>2023</year>
	</book>
	`

	var book Book
	err := xml.Unmarshal([]byte(xmlData), &book)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("%+v\n", book)
}

Parsing XML with Attributes

package main

import (
	"encoding/xml"
	"fmt"
	"log"
)

type Person struct {
	Name string `xml:"name,attr"`
	Age  int    `xml:"age,attr"`
}

type Company struct {
	Name    string   `xml:"name"`
	Persons []Person `xml:"person"`
}

func main() {
	xmlData := `
	<company>
		<name>TechCorp</name>
		<person name="Alice" age="30"/>
		<person name="Bob" age="25"/>
	</company>
	`

	var company Company
	err := xml.Unmarshal([]byte(xmlData), &company)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("%+v\n", company)
	for _, p := range company.Persons {
		fmt.Printf("  %+v\n", p)
	}
}

Encoding to XML

package main

import (
	"encoding/xml"
	"fmt"
	"log"
)

type Book struct {
	Title  string `xml:"title"`
	Author string `xml:"author"`
	Year   int    `xml:"year"`
}

func main() {
	book := Book{
		Title:  "Go Programming",
		Author: "John Doe",
		Year:   2023,
	}

	// Marshal to XML
	data, err := xml.MarshalIndent(book, "", "  ")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(xml.Header + string(data))
}

Parsing XML from File

package main

import (
	"encoding/xml"
	"fmt"
	"log"
	"os"
)

type Library struct {
	Books []Book `xml:"book"`
}

type Book struct {
	Title  string `xml:"title"`
	Author string `xml:"author"`
}

func main() {
	file, err := os.Open("library.xml")
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()

	var library Library
	decoder := xml.NewDecoder(file)
	err = decoder.Decode(&library)
	if err != nil {
		log.Fatal(err)
	}

	for _, book := range library.Books {
		fmt.Printf("%s by %s\n", book.Title, book.Author)
	}
}

Practical Examples

CSV to JSON Conversion

package main

import (
	"encoding/csv"
	"encoding/json"
	"fmt"
	"log"
	"os"
)

func main() {
	// Read CSV
	file, err := os.Open("data.csv")
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()

	reader := csv.NewReader(file)
	records, err := reader.ReadAll()
	if err != nil {
		log.Fatal(err)
	}

	// Convert to JSON
	var data []map[string]string
	headers := records[0]

	for _, record := range records[1:] {
		row := make(map[string]string)
		for i, header := range headers {
			row[header] = record[i]
		}
		data = append(data, row)
	}

	// Output JSON
	jsonData, _ := json.MarshalIndent(data, "", "  ")
	fmt.Println(string(jsonData))
}

XML to CSV Conversion

package main

import (
	"encoding/csv"
	"encoding/xml"
	"log"
	"os"
)

type Person struct {
	Name  string `xml:"name"`
	Email string `xml:"email"`
}

type People struct {
	Persons []Person `xml:"person"`
}

func main() {
	// Read XML
	file, err := os.Open("people.xml")
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()

	var people People
	decoder := xml.NewDecoder(file)
	decoder.Decode(&people)

	// Write CSV
	outFile, _ := os.Create("output.csv")
	defer outFile.Close()

	writer := csv.NewWriter(outFile)
	defer writer.Flush()

	writer.Write([]string{"Name", "Email"})
	for _, p := range people.Persons {
		writer.Write([]string{p.Name, p.Email})
	}
}

Data Validation

package main

import (
	"encoding/csv"
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"
)

func validateRecord(record []string) error {
	if len(record) != 3 {
		return fmt.Errorf("invalid record length")
	}

	// Validate name
	if strings.TrimSpace(record[0]) == "" {
		return fmt.Errorf("name cannot be empty")
	}

	// Validate age
	age, err := strconv.Atoi(record[1])
	if err != nil || age < 0 || age > 150 {
		return fmt.Errorf("invalid age")
	}

	// Validate email
	if !strings.Contains(record[2], "@") {
		return fmt.Errorf("invalid email")
	}

	return nil
}

func main() {
	file, err := os.Open("data.csv")
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()

	reader := csv.NewReader(file)
	records, _ := reader.ReadAll()

	for i, record := range records[1:] {
		if err := validateRecord(record); err != nil {
			fmt.Printf("Row %d: %v\n", i+2, err)
		}
	}
}

Best Practices

โœ… Good Practices

// Always close files
file, _ := os.Open("data.csv")
defer file.Close()

// Handle errors
records, err := reader.ReadAll()
if err != nil {
	log.Fatal(err)
}

// Use struct tags for mapping
type Person struct {
	Name string `xml:"name" csv:"name"`
}

// Validate data
if err := validateRecord(record); err != nil {
	// Handle error
}

// Use appropriate delimiters
reader.Comma = ';'

โŒ Anti-Patterns

// Don't ignore errors
records, _ := reader.ReadAll()

// Don't forget to close files
file, _ := os.Open("data.csv")

// Don't assume data format
// Always validate

// Don't use hardcoded indices
// Use struct mapping instead

Resources

Summary

Go provides excellent support for CSV and XML:

  • Use encoding/csv for CSV data
  • Use encoding/xml for XML data
  • Map data to structs using tags
  • Always validate data
  • Handle errors properly
  • Close files with defer

With these tools, you can efficiently work with structured data formats in Go.

Comments