博客 / 詳情

返回

「Go工具箱」GoCSV包:一個能將結構體和csv內容互轉的工具

大家好,我是漁夫子。本號新推出「Go工具箱」系列,意在給大家分享使用go語言編寫的、實用的、好玩的工具。同時瞭解其底層的實現原理,以便更深入地瞭解Go語言。

大家在開發中一定遇到過將數據導出成csv格式文件的需求。go標準庫中的csv包是隻能寫入字符串類型的切片。而在go中一般都是將內容寫入到結構體中。所以,若使用標準的csv包,就需要將結構體先轉換成對應的字符串類型,再寫入文件。那可不可以將結構體對象直接輸出成csv格式內容呢?

今天給大家推薦的就是一個能將結構體和csv內容進行快速互轉的工具包:gocsv

gocsv小檔案

gocsv 小檔案
star 1.5 k used by 1.6k
contributors 80 作者 gocarina
功能簡介 提供一個簡單、高效地將csv內容和結構體進行互轉的功能
項目地址 https://github.com/gocarina/gocsv
相關知識 reflect、結構體tag

gocsv的基本功能

gocsv包的最基本的作用就是能夠方便的將csv內容轉換到對應的結構體上,或者將結構體的內容快速的轉換成csv格式(包括寫入文件)。

image.png

gocsv.UnmarshalFile函數:csv內容轉成結構體

假設文件中的內容如下:

client_id,client_name,client_age
1,Jose,42
2,Daniel,26
3,Vincent,32

然後從文件中讀取出內容,並直接轉換到結構體Client上,如下:

package main

import (
    "fmt"
    "os"

    "github.com/gocarina/gocsv"
)

type NotUsed struct {
    Name string
}

type Client struct { // Our example struct, you can use "-" to ignore a field
    Id            string `csv:"client_id"`
    Name          string `csv:"client_name"`
    Age           string `csv:"client_age"`
    NotUsedString string `csv:"-"`
    NotUsedStruct NotUsed `csv:"-"` 
}

func main() {
    clientsFile, err := os.OpenFile("clients.csv", os.O_RDWR|os.O_CREATE, os.ModePerm)
    if err != nil {
        panic(err)
    }
    defer clientsFile.Close()

    clients := []*Client{}

    if err := gocsv.UnmarshalFile(clientsFile, &clients); err != nil { // Load clients from file
        panic(err)
    }
    for _, client := range clients {
        fmt.Println("Hello", client.Name)
    }
}

gocsv.MarshalFile函數:結構體轉成csv文件

package main

import (
    "fmt"
    "os"

    "github.com/gocarina/gocsv"
)

type NotUsed struct {
    Name string
}

type Client struct { // Our example struct, you can use "-" to ignore a field
    Id            string `csv:"client_id"`
    Name          string `csv:"client_name"`
    Age           string `csv:"client_age"`
    NotUsedString string `csv:"-"`
    NotUsedStruct NotUsed `csv:"-"` 
}

func main() {
    clientsFile, err := os.OpenFile("clients.csv", os.O_RDWR|os.O_CREATE, os.ModePerm)
    if err != nil {
        panic(err)
    }
    defer clientsFile.Close()

    clients := []*Client{}

    clients = append(clients, &Client{Id: "12", Name: "John", Age: "21"}) // Add clients
    clients = append(clients, &Client{Id: "13", Name: "Fred"})
    clients = append(clients, &Client{Id: "14", Name: "James", Age: "32"})
    clients = append(clients, &Client{Id: "15", Name: "Danny"})
    
    err = gocsv.MarshalFile(&clients, clientsFile) // Use this to save the CSV back to the file
    if err != nil {
        panic(err)
    }

}

自定義類型轉換器

gocsv包還可以給自定義的結構體類型定義csv和結構體的互轉函數。只要自定義的類型實現如下接口即可:

type TypeMarshaller interface {
    MarshalCSV() (string, error)
}

// TypeUnmarshaller is implemented by any value that has an UnmarshalCSV method
// This converter is used to convert a string to your value representation of that string
type TypeUnmarshaller interface {
    UnmarshalCSV(string) error
}

或者將結構體轉換成csv字符串時,需要實現如下接口:

// MarshalText encodes the receiver into UTF-8-encoded text and returns the result.
type TextMarshaler interface {
    MarshalText() (text []byte, err error)
}

type TextUnmarshaler interface {
    UnmarshalText(text []byte) error
}

例如,我們定義了一個結構體DateTime,裏面有一個time.Time類型的屬性。並且DateTime類型實現了TypeMarshaller接口的MarshalCSV函數和TypeUnmarshaller接口的UnmarshalCSV函數。如下:

type DateTime struct {
    time.Time
}

// Convert the internal date as CSV string
func (date *DateTime) MarshalCSV() (string, error) {
    return date.Time.Format("20060201"), nil
}

// You could also use the standard Stringer interface 
func (date *DateTime) String() (string) {
    return date.String() // Redundant, just for example
}

// Convert the CSV string as internal date
func (date *DateTime) UnmarshalCSV(csv string) (err error) {
    date.Time, err = time.Parse("20060201", csv)
    return err
}

type Client struct { // Our example struct with a custom type (DateTime)
    Id       string   `csv:"id"`
    Name     string   `csv:"name"`
    Employed DateTime `csv:"employed"`
}

func main() {

    client := []Client{
        {
            Id: "001",
            Name: "Go學堂",
            Employed: DateTime{time.Now()},
        },
    }

    csvContent, _ := gocsv.MarshalString(client)
    fmt.Println("csv:", csvContent) //輸出內容是 001,Go學堂,20231003
}

當我們運行上述代碼,最終的輸出內容是:

001,Go學堂,20231003

最後的日期就是按DateTime的MarshalCSV函數格式輸出的。

自定義CSV的Reader/Writer

在開頭處我們提到,csv文件中的分隔符默認是逗號。但也可以是其他字符。這就要求我們在讀取或寫入之前指定好內容的分隔號。那麼就可以通過自定義的Reader/Writer來覆蓋默認的Reader/Writer的選項。如下:

  • 指定讀取內容的分割符是 "|"

    gocsv.SetCSVReader(func(in io.Reader) gocsv.CSVReader {
      r := csv.NewReader(in)
      r.Comma = '|'
      return r // Allows use pipe as delimiter
    })
  • 指定寫入的內容是用 分割符 "|" 進行分割的

    gocsv.SetCSVWriter(func(out io.Writer) *gocsv.SafeCSVWriter {
      writer := csv.NewWriter(out)
      writer.Comma = '|'
      return gocsv.NewSafeCSVWriter(writer)
    })

gocsv包的特點總結

1、結構體切片和csv內容互轉。能夠將結構體切片(或數組)直接輸出成csv內容或輸出到文件。反之亦然。

2、csv標籤。其轉換過程是通過結構體上的“csv”標籤進行關聯的。

3、csv標籤對應csv內容表頭。當結構體和csv格式互轉時,結構體中的csv標籤對應的就是csv表格的表頭,結構體中的字段順序對應的就是csv文件列的順序。

4、底層依然是使用標準庫中的csv。在寫入csv文件時,底層實際上用的還是go標準庫中的encoding/csv/Writer結構體的Write(row []string)方法。

5、自動將結構體字段的類型轉換成字符串:大家看到標準csv包中的Write方法的入參是string類型的切片,而在要轉換的結構體上的字段可以是各種類型。這裏就是gocsv包中的一個特點:可以將字段中的非string類型轉換成string類型,最終寫入到csv文件中。

6、可自定義類型轉換器。可以通過實現TypeMarshaller接口或TypeUnMarshaller接口對自定義類型的內容按對應的格式輸出成csv內容。

7、可自定義CSV的Reader/Writer來覆蓋默認參數。比如csv格式的內容默認使用逗號分隔內容。通過該功能我們可以指定使用其他分隔符的csv內容。比如使用"|"或";"等。

這裏需要注意的是 將csv文件的內容一定是解析到結構體類型的切片數組中。同樣,也只有是結構體類型的切片數組才能直接寫入到csv文件中。

以上,就是今天我們要分享的工具包。如需瞭解更多內容,請關注「Go學堂」。

---特別推薦---

特別推薦:一個專注go項目實戰、項目中踩坑經驗及避坑指南、各種好玩的go工具的公眾號,「Go學堂」,專注實用性,非常值得大家關注。點擊下方公眾號卡片,直接關注。關注送《100個go常見的錯誤》pdf文檔。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.