在Go中转换JSON数据为CSV文件的方法

1,320 阅读3分钟

要在Go中把JSON数据转换为CSV文件,需要为JSON数据创建一个新的结构,然后把JSON文件解码为这些结构的数组,最后把这个数组的数据保存为CSV文件的后续行。做到这一点所需的两个主要包是 encoding/json对JSON数据进行解码,用 json.Decoderencoding/csv写入输出的CSV数据,使用 csv.Writer.

在下面的例子中,我们使用data.json 文件。

[
{
"vegetable": "carrot",
"fruit": "banana",
"rank": 1
},
{
"vegetable": "potato",
"fruit": "strawberry",
"rank": 2
}
]

代码

package main

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

type FruitAndVegetableRank struct {
    // 1. Create a new struct for storing read JSON objects
    Vegetable string `json:"vegetable"`
    Fruit     string `json:"fruit"`
    Rank      int64  `json:"rank"`
}

func convertJSONToCSV(source, destination string) error {
    // 2. Read the JSON file into the struct array
    sourceFile, err := os.Open(source)
    if err != nil {
        return err
    }
    // remember to close the file at the end of the function
    defer sourceFile.Close()

    var ranking []FruitAndVegetableRank
    if err := json.NewDecoder(sourceFile).Decode(&ranking); err != nil {
        return err
    }

    // 3. Create a new file to store CSV data
    outputFile, err := os.Create(destination)
    if err != nil {
        return err
    }
    defer outputFile.Close()

    // 4. Write the header of the CSV file and the successive rows by iterating through the JSON struct array
    writer := csv.NewWriter(outputFile)
    defer writer.Flush()

    header := []string{"vegetable", "fruit", "rank"}
    if err := writer.Write(header); err != nil {
        return err
    }

    for _, r := range ranking {
        var csvRow []string
        csvRow = append(csvRow, r.Vegetable, r.Fruit, fmt.Sprint(r.Rank))
        if err := writer.Write(csvRow); err != nil {
            return err
        }
    }
    return nil
}

func main() {
    if err := convertJSONToCSV("data.json", "data.csv"); err != nil {
        log.Fatal(err)
    }
}

输出的data.csv 文件的内容:

vegetable,fruit,rank
carrot,banana,1
potato,strawberry,2

它是如何工作的

  1. 创建一个新的结构来存储读取的JSON对象
    type FruitAndVegetableRank struct {
    // 1. Create a new struct for storing read JSON objects
    Vegetable string `json:"vegetable"`
    Fruit     string `json:"fruit"`
    Rank      int64  `json:"rank"`
}

JSON到CSV转换的第一步是将JSON数据加载到Go结构中。因此,我们定义一个适当的类型,其字段与文件中的数据相匹配,并使用JSON结构字段标签对其进行注释,以便将JSON解码到该结构。

  1. 将JSON文件读入结构数组
// 2. Read the JSON file into the struct array
sourceFile, err := os.Open(source)
if err != nil {
    return err
}
// remember to close the file at the end of the function
defer sourceFile.Close()

var ranking []FruitAndVegetableRank
if err := json.NewDecoder(sourceFile).Decode(&ranking); err != nil {
    return err
}

我们可以开始处理我们的JSON文件。我们打开该文件(记得关闭该文件以将资源释放回系统,例如,使用 [`defer`](https://go.dev/blog/defer-panic-and-recover)关键字),然后创建一个新的 [`json.Decoder`](https://pkg.go.dev/encoding/json#Decoder)文件作为参数。由于 [`json.NewDecoder(r io.Reader)`](https://pkg.go.dev/encoding/json#NewDecoder)要求 [`io.Reader`](https://pkg.go.dev/io#Reader),我们不需要事先读取文件的内容。如果我们要使用 [`json.Unmarshal()`](https://pkg.go.dev/encoding/json#Unmarshal)函数,这将是必要的。用 [`Decode()`](https://pkg.go.dev/encoding/json#Decoder.Decode)方法,我们读取JSON文件并将其转换为`FruitAndVegetableRank` 对象的片断。

3. 创建一个新的文件来存储CSV数据

// 3. Create a new file to store CSV data
outputFile, err := os.Create(destination)
if err != nil {
    return err
}
defer outputFile.Close()

CSV数据将被保存到一个文件中,因此在这一步中,我们以一种非常标准的方式创建一个新的destination 文件。

  1. 通过迭代JSON结构数组写入CSV文件的标题和连续的行
// 4. Write the header of the CSV file and the successive rows by iterating through the JSON struct array
writer := csv.NewWriter(outputFile)
defer writer.Flush()

header := []string{"vegetable", "fruit", "rank"}
if err := writer.Write(header); err != nil {
    return err
}

for _, r := range ranking {
    var csvRow []string
    csvRow = append(csvRow, r.Vegetable, r.Fruit, fmt.Sprint(r.Rank))
    if err := writer.Write(csvRow); err != nil {
        return err
    }
}

作为最后一步,我们创建一个新的 csv.Writer将CSV格式的数据写到输出文件中。记住要调用 writer.Flush以确保所有的缓冲内容在函数结束前被写入。写入过程包括遍历FruitAndVegetableRank 对象的数组,并为每个对象做一个CSV行。然后,这个行被保存在 writer.Write()方法保存。在这个例子中,我们也把标题行写在文件的第一行。