CSV到结构建议

时间:2018-02-20 17:27:56

标签: json csv go struct

如果我有一个csv读入结构,我怎么能操纵输入来构建我想要的结构?我在各种教程后陷入困境。这是我最接近的。

我基本上想要打开一个csv,读取所选列,确保在引用该列时从同一行记录该值。然后以可以放入数据库的格式生成数据。

示例CSV:

Ignore,Customer,Fruit,Number
123,A,Apple,1
123,A,Apple,3
123,B,Orange,4
123,C,Melon,5

示例代码:

package main
import (
    "bufio"
    "encoding/csv"
    "encoding/json"
    "fmt"
    "io"
    "log"
    "os"
)

type Account struct {
    Customer string `json:"Customer"`
    LineItem *LineItem  `json:"LineItem"`
}

type LineItem struct {
    ProductName string `json:"ProductName"`
    Count string `json:"Count"`
}


func main() {
    csvFile, _ := os.Open("/home/frank/gocode/src/local/billing/fruit.csv")

    reader := csv.NewReader(bufio.NewReader(csvFile))
    var billData []Account
    for {
        line, error := reader.Read()
        if error == io.EOF {
            break
        } else if error != nil {
            log.Fatal(error)
        }
        billData = append(billData, Account{
            Customer: line[1],
            LineItem: &LineItem{
                ProductName:   line[2],
                Count: line[3],
            },
        })
    }

    billingJson, _ := json.Marshal(billData)
    fmt.Println(string(billingJson))
}

目前的输出是:

[{"Customer":"Customer","LineItem":{"ProductName":"Fruit","Count":"Number"}},{"Customer":"A","LineItem":{"ProductName":"Apple","Count":"1"}},{"Customer":"A","LineItem":{"ProductName":"Apple","Count":"3"}},{"Customer":"B","LineItem":{"ProductName":"Orange","Count":"4"}},{"Customer":"C","LineItem":{"ProductName":"Melon","Count":"5"}}]

我想摆脱第一条记录,所以不保留标题。 e.g。

[{"Customer":"A","LineItem":{"ProductName":"Apple","Count":"1"}},{"Customer":"A","LineItem":{"ProductName":"Apple","Count":"3"}},{"Customer":"B","LineItem":{"ProductName":"Orange","Count":"4"}},{"Customer":"C","LineItem":{"ProductName":"Melon","Count":"5"}}]

合并,因此客户A是包含LineItems的一条记录,例如

[{"Customer":"A","LineItem":{"ProductName":"Apple","Count":"1"},"LineItem":{"ProductName":"Apple","Count":"3"}},{"Customer":"B","LineItem":{"ProductName":"Orange","Count":"4"}},{"Customer":"C","LineItem":{"ProductName":"Melon","Count":"5"}}]

任何最佳做法 - 欢迎使用其他方法(不确定这里的地图是否更好)。希望有足够的信息来帮助我。

2 个答案:

答案 0 :(得分:3)

摆脱第一个条目就像#wrapper一样简单。那,或者做一个初始读取来拉取列名。

在第二部分中,您当前的数据结构不允许一对多关系(每个Account只有一个LineItem)。之后您需要在列表上进行一些处理。 CSV文件必须是1:1,因为每一行都被视为一个独立的记录。最简单的方法是通过使用地图来实现一对多,但您也可以简单地循环切片(保留更接近现有代码):

https://play.golang.org/p/3uevo0taKR5

img {
  max-width: 80px;
}

#wrapper {
  display: inline-block;
  padding: 1em;
  background-color: rgba(0, 0, 255, .2);
}

.image_off {
  display: block;
}
.image_on {
  display: none;
}

#wrapper:hover .image_off {
  display: none;
}
#wrapper:hover .image_on {
  display: block;
}

#wrapper .text {
  visibility: hidden;
}
#wrapper:hover .text {
  visibility: visible;
}

输出:

<div id="wrapper">
  <img class="image_off" src="//pbs.twimg.com/profile_images/723561620761391104/BQmg7aTz_400x400.jpg">
  <img class="image_on" src="//www.clickborde.com.br/image/data/produtos/prod_2109_815807521.jpg">
  <p class="text">TEXT TEXT TEXT TEXT</p>
</div>

最后,我建议您使用billData = billData[1:]或类似的错误变量。 package main import ( "bytes" "encoding/csv" "encoding/json" "fmt" "io" "log" ) var data = `Ignore,Customer,Fruit,Number 123,A,Apple,1 123,A,Apple,3 123,B,Orange,4 123,C,Melon,5` type Account struct { Customer string `json:"Customer"` LineItems []LineItem `json:"LineItems"` } type LineItem struct { ProductName string `json:"ProductName"` Count string `json:"Count"` } func main() { reader := csv.NewReader(bytes.NewBufferString(data)) // Read column label data and discard if _, err := reader.Read(); err != nil { log.Fatal(err) } var billData []Account for { line, err := reader.Read() if err == io.EOF { break } if err != nil { log.Fatal(err) } found := false for i := range billData { if billData[i].Customer == line[1] { found = true billData[i].LineItems = append(billData[i].LineItems, LineItem{ ProductName: line[2], Count: line[3], }) break } } if !found { billData = append(billData, Account{ Customer: line[1], LineItems: []LineItem{ { ProductName: line[2], Count: line[3], }, }, }) } } billingJson, err := json.MarshalIndent(billData, "", " ") if err != nil { log.Fatal(err) } fmt.Println(string(billingJson)) } 是内置错误类型的名称,因此通过命名您的变量,您将隐藏类型并使得无法在同一范围内声明该类型的变量。虽然这不会影响您当前的代码,但它仍然是非常糟糕的做法,最终可能会让您陷入困境。

答案 1 :(得分:0)

可以使用:

  • csvutil 将 csv 读入一个结构切片
  • 聚合相同客户线的地图
  • 转换为输出结构

这可能是:

package main

import (
    "encoding/json"
    "fmt"
    "io/ioutil"

    "github.com/jszwec/csvutil"
)

type Account struct {
    Customer  string     `json:"Customer"`
    LineItems []LineItem `json:"LineItems"`
}

type LineItem struct {
    ProductName string `json:"ProductName"`
    Count       string `json:"Count"`
}

type CsvEntry struct {
    Customer string
    Fruit    string
    Number   string
}

func main() {
    // read csv
    content, _ := ioutil.ReadFile("./fruits.csv")
    var entries []CsvEntry
    csvutil.Unmarshal(content, &entries)

    // aggregate by customer name
    customersMap := map[string][]LineItem{}
    for _, bill := range entries {
        customersMap[bill.Customer] = append(customersMap[bill.Customer], LineItem{bill.Fruit, bill.Number})
    }

    // build output structure
    accounts := []Account{}
    for customer, bill := range customersMap {
        accounts = append(accounts, Account{customer, bill})
    }

    // print json
    billingJson, _ := json.Marshal(accounts)
    fmt.Println(string(billingJson))
}

此支持列排列。