XML、JSON 与 CSV 文件处理


XML 文件


可扩展标记语言 (Extensible Markup Language ,XML) ,标准通用标记语言的子集,可以用来标记数据、定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言。


解码 XML 文件


一次性读取 XML 文件

Go 语言中 xml 包提供了 Unmarshal() 函数来解析 xml 文件,其函数声明如下:

func Unmarshal(data []byte, v interface{}) error

第一个是 data 接收的是 xml 数据流,第二个是存储的对应类型,目前支持 struct 、slice 和 string ,xml 包内部采用了反射来进行数据的映射,所以 v 中的字段必须是导出的,定义为 interface 形式可以把 xml 转换为任意的格式。

xml 本质上是一种树形的数据格式,可以定义与之匹配的 Go 语言的 struct 类型,然后通过 Unmarshal() 函数将 xml 中的数据解析成对应的 struct 对象,编写解析 xml 文件程序的过程如下:

  • 创建存储 xml 数据的结构。

  • 通过 Unmarshal() 函数解码 xml 数据到结构体。

例如创建一个 servers.xml 文件,该文件的具体内容如下:

<?xml version="1.0" encoding="UTF-8"?>
  <servers version="1">
     <server>
        <serverName>Shanghai_VPN</serverName>
        <serverIP>127.0.0.1</serverIP>
     </server>
     <server>
        <serverName>Beijing_VPN</serverName>
        <serverIP>127.0.0.2</serverIP>
     </server>
  </servers>

编写一个解码该 xml 文件的程序,该程序的具体内容如下:

package main
import (
		"encoding/xml"
        "fmt"
        "io/ioutil"
        "os"
)
type Recurlyservers struct {
      	XMLName    xml.Name `xml:"servers"`
        Version    string   `xml:"version,attr"`
        Svs        []server `xml:"server"`
        Description string   `xml:",innerxml"`
}

type server struct {
     	XMLName   xml.Name `xml:"server"`
        ServerName string   `xml:"serverName"`
        ServerIP   string   `xml:"serverIP"`
}
       
func main() {
      	file, err := os.Open("servers.xml") 
        if err != nil {
                fmt.Printf("Opening the file is failed: %v", err)
                return
        }
        defer file.Close()
        data, err := ioutil.ReadAll(file)
        if err != nil {
                fmt.Printf("Reading the file is failed: %v", err)
                return
        }
        v := Recurlyservers{}
        err = xml.Unmarshal(data, &v)
        if err != nil {
                fmt.Printf("Unmarshaling xml is failed: %v", err)
                return
        }
        fmt.Println(v)
}

执行程序输出的结果如下:

{{ servers} 1 [{{ server} Shanghai_VPN 127.0.0.1} {{ server} Beijing_VPN 127.0.0.2}] 
     <server>
        <serverName>Shanghai_VPN</serverName>
        <serverIP>127.0.0.1</serverIP>
     </server>
     <server>
        <serverName>Beijing_VPN</serverName>
        <serverIP>127.0.0.2</serverIP>
     </server>
  }

Unmarshal() 函数将 xml 文件解析成对应的 struct 对象的过程:

  • struct 定义后面添加一些类似于 xml:"serverName" 这样的内容,这个是 struct 的一个特性,它们被称为 strcut tag 用来辅助反射。
  • Unmarshal() 函数解析时,xml 元素与字段对应。这有一个优先级读取流程,首先会读取 struct tag ,如果没有,那么就会对应字段名,在解析时 tag 、字段名、xml 元素都是大小写敏感的,必须一一对应字段,Go 语言具有反射机制可以利用这些 tag 信息将来自 xml 文件中的数据反射成对应的 struct 对象。

解析 xml 文件到 struct 对象时遵循如下规则:

  • 如果 struct 的一个字段是 string 或者 []byte 类型且它的 tag 含有 ",innerxml"Unmarshal 函数会将此字段所对应的元素内所有内嵌的原始 xml 累加到此字段上,如上面例子中的 Description 定义,最后输出 Shanghai_VPN127.0.0.1Beijing_VPN127.0.0.2

  • 如果 struct 中有一个叫做 XMLName 且类型为 xml.Name 的字段,那么在解析时就会保存这个 element 的名字到该字段,如上面例子中的 servers 。

  • 如果某个 struct 字段的 tag 定义中含有 xml 结构中 element 的名称,那么解析时就会把相应的 element 值赋值给该字段,如上 servername 和 serverip 的定义。

  • 如果某个 struct 字段的 tag 定义中含有 ",attr" ,那么解析时就会将该结构所对应的 element 与字段同名的属性的值赋值给该字段,如上 version 的定义。

  • 如果某个 struct 字段的 tag 定义形如 "a>b>c" ,那么解析时会将 xml 结构 a 中的子结构 b 中的 c 元素的值赋值给该字段。

  • 如果某个 struct 字段的 tag 定义了 "-",那么不会为该字段解析匹配任何 xml 数据。

  • 如果 struct 字段后面的 tag 定义了 ",any" ,它的子元素在不满足其他规则的时候就会匹配到这个字段。

  • 如果某个 xml 元素包含一条或者多条注释,那么这些注释将被累加到第一个 tag 含有 ",comments" 的字段上,这个字段的类型可能是 []bytestring,如果没有这样的字段存在,那么注释将会被抛弃。

注:为了正确解析,Go 语言的 xml 包要求 struct 定义中的所有字段必须是可导出的(即首字母大写)。

上面详细讲述了如何定义 structtagtag 和 xml 的 element 是一一对应的关系,还可以通过 slice 来表示多个同级元素。

例如创建一个 email_config.xml 文件,该文件的具体内容如下:

<!--email_config.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<config>
    <smtpServer>smtp.163.com</smtpServer>
    <smtpPort>25</smtpPort>
    <sender>test@163.com</sender>
    <senderPassword>123456</senderPassword>
    <receivers flag="true">
        <user>cqupthao@gmail.com</user>
        <user>2733443175@qq.com</user>
    </receivers>
</config>

编写一个解码该 xml 文件的程序,该程序的具体内容如下:

package main

import (
        "encoding/xml"
        "fmt"
        "io/ioutil"
        "os"
)

type EmailConfig struct {
        XMLName  xml.Name `xml:"config"`
        SmtpServer string `xml:"smtpServer"`
        SmtpPort int `xml:"smtpPort"`
        Sender string `xml:"sender"`
        SenderPassword string `xml:"senderPassword"`
        Receivers EmailReceivers `xml:"receivers"`
}

type EmailReceivers struct {
        Flag string `xml:"flag,attr"`
        User []string `xml:"user"`
}

func main() {
        file, err := os.Open("email_config.xml")
        if err != nil {
                fmt.Printf("Opening the file is failed: %v", err)
                return
        }
        defer file.Close()
        data, err := ioutil.ReadAll(file)
        if err != nil {
                fmt.Printf("Reading the file is failed: %v", err)
                return
        }
        v := EmailConfig{}
        err = xml.Unmarshal(data, &v)
        if err != nil {
                fmt.Printf("Unmarshaling xml is failed: %v", err)
                return
        }

        fmt.Println(v)
        fmt.Println("SmtpServer is : ",v.SmtpServer)
        fmt.Println("SmtpPort is : ",v.SmtpPort)
        fmt.Println("Sender is : ",v.Sender)
        fmt.Println("SenderPasswd is : ",v.SenderPassword)
        fmt.Println("Receivers.Flag is : ",v.Receivers.Flag)
        for i,element := range v.Receivers.User {
                fmt.Println(i,element)
        }
}

执行程序输出的结果如下:

{{ config} smtp.163.com 25 test@163.com 123456 {true [cqupthao@gmail.com 2733443175@qq.com]}}
SmtpServer is :  smtp.163.com
SmtpPort is :  25
Sender is :  test@163.com
SenderPasswd is :  123456
Receivers.Flag is :  true
0 cqupthao@gmail.com
1 2733443175@qq.com

流方式读取 XML 文件

高效地处理以流(stream)方式传输的 xml 文件以及体积较大的 xml 文件,使用 Decoder 结构代替 Unmarshal() 函数,通过手动解码 xml 元素的方式来解码 xml 数据,编写程序过程如下:

  • 创建存储 xml 数据的结构。

  • 创建解码 xml 的解码器。

  • 遍历 xml 文件并将数据解码至结构。

例如创建一个 post.xml 文件,该文件的具体内容如下:

<?xml version="1.0" encoding="utf-8"?>
<post id="1">
  <content>Hello CQUPT!</content>
  <author id="2">cqupthao</author>
  <comments>
    <comment id="1">
      <content>Happy Day!</content>
      <author id="3">TIM</author>
    </comment>
    <comment id="2">
      <content>Nice!</content>
      <author id="4">WINE</author>
    </comment>
  </comments>
</post>

编写一个解码该 xml 文件的程序,该程序的具体内容如下:

package main

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

type Post struct {
		XMLName  xml.Name  `xml:"post"`
		Id       string    `xml:"id,attr"`
		Content  string    `xml:"content"`
		Author   Author    `xml:"author"`
		Xml      string    `xml:",innerxml"`
		Comments []Comment `xml:"comments>comment"`
}

type Author struct {
		Id   string `xml:"id,attr"`
		Name string `xml:",chardata"`
}

type Comment struct {
		Id      string `xml:"id,attr"`
		Content string `xml:"content"`
		Author  Author `xml:"author"`
}

func main() {
		xmlFile, err := os.Open("post.xml")
		if err != nil {
				fmt.Println("Openning the file is failed: %v", err)
				return
		}
		defer xmlFile.Close()

		decoder := xml.NewDecoder(xmlFile)
		for {
				t, err := decoder.Token()
				if err == io.EOF {
						break
				}
				if err != nil {
						fmt.Println("Decoding xml is failed: %v", err)
					return
				}

				switch se := t.(type) {
				case xml.StartElement:
						if se.Name.Local == "comment" {
								var comment Comment
								decoder.DecodeElement(&comment, &se)
								fmt.Println(comment)
						}
				}
		}
}

执行程序输出的结果如下:

{1 Happy Day! {3 TIM}}
{2 Nice! {4 WINE}}

编码 XML 文件


结构体生成 XML 文件

Go 语言中 xml 包提供了 Marshal()MarshalIndent() 两个函数来输出 xml 文件,两个函数中的第一个参数用来生成 xml 的结构定义类型数据,都是返回生成的 xml 数据流,这两个函数主要的区别是第二个函数会增加前缀和缩进,函数的声明如下:

func Marshal(v interface{}) ([]byte, error)
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)

例如编写一个编码生成 xml 文件的程序,该程序的具体内容如下:

package main

import (
       	"encoding/xml"
        "fmt"
        "os"
)
type Servers struct {
       	XMLName xml.Name `xml:"servers"`
		Version string   `xml:"version,attr"`
        Svs    []server `xml:"server"`
}
type server struct {
      	ServerName string `xml:"serverName"`
        ServerIP   string `xml:"serverIP"`
}
        
func main() {
      	 v := &Servers{Version: "1"}
         v.Svs = append(v.Svs, server{"Shanghai_VPN", "127.0.0.1"})
         v.Svs = append(v.Svs, server{"Beijing_VPN", "127.0.0.2"})
         output, err := xml.MarshalIndent(v, "  ", "   ")
         if err != nil {
                fmt.Printf("MarshalIndenting xml is failed: %v", err)
         }
         os.Stdout.Write([]byte(xml.Header))
         os.Stdout.Write(output)
 }

执行程序输出的结果如下:

<?xml version="1.0" encoding="UTF-8"?>
  <servers version="1">
     <server>
        <serverName>Shanghai_VPN</serverName>
        <serverIP>127.0.0.1</serverIP>
     </server>
     <server>
        <serverName>Beijing_VPN</serverName>
        <serverIP>127.0.0.2</serverIP>
     </server>
  </servers>

输出的结果与之前定义的文件格式一模一样,之所以会有 os.Stdout.Write([]byte(xml.Header)) 这句代码的出现是因为 xml.MarshalIndent() 或者 xml.Marshal() 函数输出的信息都是不带 xml 头,为了生成正确的 xml 文件,使用了 xml 包预定义的 Header 变量, Marshal() 函数接收的参数 vinterface{} 类型的,可以接受任意类型的参数。

xml 包根据如下的规则来生成相应的 xml 文件。

  • 如果 v 是 arrayslice ,那么输出每一个元素,类似 value

  • 如果 v 是指针,那么会 Marshal 指针指向的内容,如果指针为空,什么都不输出。

  • 如果 v 是 interface ,那么就处理 interface 所包含的数据。

  • 如果 v 是其他数据类型,就会输出这个数据类型所拥有的字段信息。

生成的 xml 文件中的 element 的名字是根据元素名按照如下优先级从 struct 对象中获取:

  • 如果 v 是 structtag 中定义为 XMLName 的字段。

  • 通过 struct 中字段的 tag

  • 通过 struct 的字段名。

  • 通过 Marshall 的类型名称。

设置 struct 中字段的 tag 信息以控制最终 xml 文件的生成的方式:

  • XMLName 不会被输出。

  • tag 中含有 "-" 的字段不会输出。

  • tag 中含有 "name,attr" ,会以 name 作为属性名,字段值作为值输出为这个 xml 元素的属性,如上 version 字段所描述。

  • tag 中含有 ",attr" ,会以这个 struct 的字段名作为属性名输出为 xml 元素的属性,类似上一条,只是这个 name 默认是字段名。

  • tag 中含有 ",chardata" ,会输出为 xml 的 character data ,而非 element 。

  • tag 中含有 ",innerxml" ,会被原样输出,而不会进行常规的编码过程。

  • tag 中含有 ",comment" ,会被当作 xml 注释来输出,而不会进行常规的编码过程,字段值中不能含有 "--" 字符串。

  • tag 中含有 "omitempty" ,如果该字段的值为空值,那么该字段就不会被输出到 xml ,空值包括:false 、0 、nil 指针或 nil 接口,任何长度为 0 的 array 、slice 、map 或者 string 。

  • tag 中含有 "a>b>c" ,会循环输出三个元素 a 包含 b 、b 包含 c。

包含关系,例如如下的程序代码:

FirstName string   `xml:"name>first"`
LastName  string   `xml:"name>last"`
<name>
   <first>Xiong</first>
   <last>Hao</last>
</name>

上面介绍了如何使用 Go 语言的 xml 包进行编/解码 xml 文件,对 xml 的所有操作都是通过 struct tag 来实现的,在文章中简要列举了定义 tag 的方法 。

例如编写一个编码生成 xml 文件的程序,该程序的具体内容如下:

package main

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

type Languages struct {
        XMLName xml.Name `xml:"languages"`
        Version string `xml:"version,attr`
        Lang []Language `xml:"language"`
}

type Language struct {
        Name string `xml:"name"`
        Site string `xml:"site`
}

func main() {
        v := &Languages{Version: "2"}
        v.Lang = append(v.Lang, Language{"JAVA", "https://www.java.com/"})
        v.Lang = append(v.Lang, Language{"Go", "https://golang.org/"})
        output, err := xml.MarshalIndent(v, " ", " ")
        if err != nil {
                fmt.Printf("MarshalIndenting xml is failed: %v", err)
                return
        }
        os.Stdout.Write([]byte(xml.Header)) //生成 xml 头
        os.Stdout.Write(output)
        file, _ := os.Create("languages.xml")
        defer file.Close()
        file.Write([]byte(xml.Header))  
        file.Write(output)
}

执行程序后会生成一个 languages.xml 文件,该文件的具体内容如下:

<?xml version="1.0" encoding="UTF-8"?>
 <languages>
  <Version>2</Version>
  <language>
   <name>JAVA</name>
   <Site>https://www.java.com/</Site>
  </language>
  <language>
   <name>Go</name>
   <Site>https://golang.org/</Site>
  </language>
 </languages>

编码器生成 XML 文件

编写通过编码器生成 xml 文件程序的过程如下:

  • 创建结构并填充数据。

  • 创建 xml 文件。

  • 创建用于编码结构的编码器。

  • 通过编码器将结构编码至 xml 文件。

  • 将结构编码(encode)至 xml 形式。

例如编写一个编码生成 xml 文件的程序,该程序的具体内容如下:

package main

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

type Post struct {
		XMLName xml.Name `xml:"post"`
		Id      string   `xml:"id,attr"`
		Content string   `xml:"content"`
		Author  Author   `xml:"author"`
}

type Author struct {
		Id   string `xml:"id,attr"`
		Name string `xml:",chardata"`
}

func main() {
		post := Post{
				Id:      "1",
				Content: "Hello CQUPT!",
				Author: Author{
						Id:   "2",
						Name: "cqupthao",
				},
		}

		xmlFile, err := os.Create("post.xml")
		if err != nil {
				fmt.Println("Creating the file is failed: %v", err)
			return
		}
		defer xmlFile.Close()

		// 添加 xml 声明
		xmlFile.Write([]byte(xml.Header))

		encoder := xml.NewEncoder(xmlFile)
		// 指定前缀和缩进
		encoder.Indent("", "\t")
		err = encoder.Encode(&post)
		if err != nil {
				fmt.Println("Encoding xml is failed: %v", err)
				return
		}
}

执行程序后会生成一个 post.xml 文件,该文件的具体内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<post id="1">
        <content>Hello CQUPT!</content>
        <author id="2">cqupthao</author>
</post>

JSON 文件


JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,它是基于 ECMAScript 规范的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。


解码 JSON 文件


一次性读取 JSON 文件

Go 语言中 json 包提供了 Unmarshal() 函数来解析 json 文件,其函数的声明如下:

func Unmarshal(data []byte, v interface{}) error

编写一次性读取 json 文件程序的过程如下:

  • 创建用于包含 json 数据的结构。

  • 通过 json.Unmarshal() 函数将 json 数据解封到结构体或 interface{} 。


解析到结构体

例如创建一个 json_parse.json 文件,该文件的具体内容如下:

{
        "port": "27017",
        "mongo": {
                "mongoAddr": "127.0.0.1",
                "mongoPoolLimit": 500,
                "mongoDb": "my_db",
                "mongoCollection": "table1"
        }
}

编写一个解码该 json 文件的程序,该程序具体内容如下:

package main

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

// 定义配置文件解析后的结构
type MongoConfig struct {
        MongoAddr       string
        MongoPoolLimit  int
        MongoDb         string
        MongoCollection string
}

type Config struct {
        Port  string
        Mongo MongoConfig
}

func main() {
        v := Config{}
        err := Load("json_parse.json", &v)
        if err != nil {
                return
        }
		fmt.Println(v)
        fmt.Println("v.Port: " , v.Port)
        fmt.Println("v.Mongo.MongoAddr: " , v.Mongo.MongoAddr)
        fmt.Println("v.Mongo.MongoPoolLimit: " , v.Mongo.MongoPoolLimit)
        fmt.Println("v.Mongo.MongoDb: " , v.Mongo.MongoDb)
        fmt.Println("v.Mongo.MongoCollection: ", v.Mongo.MongoCollection)
}

func Load(filename string, v interface{}) error {
        // ReadFile 函数会读取文件的全部内容,并将结果以[]byte类型返回
        data, err := ioutil.ReadFile(filename)
        if err != nil {
                return err
        }

        //读取的数据为 json 格式,需要进行解码
        err = json.Unmarshal(data, v)
        if err != nil {
                return err
        }
        return nil
}

执行程序输出的结果如下:

{27017 {127.0.0.1 500 my_db table1}}
v.Port:  27017
v.Mongo.MongoAddr:  127.0.0.1
v.Mongo.MongoPoolLimit:  500
v.Mongo.MongoDb:  my_db
v.Mongo.MongoCollection:  table1

通过上面的示例代码中定义了与 json 数据对应的结构体(数组对应 slice ,字段名对应 json 里面的 key )。

例如 json 文件中的 key 是 Foo ,在解析时将 json 数据与 struct 字段相匹配的过程如下:

  • 首先查找 tag 含有 Foo 的可导出的 struct 字段(首字母大写)。

  • 其次查找字段名是 Foo 的导出字段。

  • 最后查找类似 FOO 或者 FoO,这类除首字母之外,其他大小写不敏感的导出字段。

能够被赋值的字段必须是可导出字段(即首字母大写),同时 json 解析时只会解析能找到的字段,如果找不到的字段,就会被忽略。这样的一个好处是当接收到一个很大的 json 数据结构却只想获取其中的部分数据的时候,只需将想要的数据对应的字段名大写即可轻松解决这个问题。

json 转 Go 数据结构工具 quicktype 可以轻松地将 json 文件转换成各种语言对应的数据结构。

上面的解析方式是在知道被解析的 json 数据结构的前提下采取的方案,如果不知道被解析的数据格式,应该采用如下的方式来解析。


解析到 Interface{}

interface{} 可以用来存储任意数据类型的对象,这种数据结构正好用于存储解析的未知结构的 json 数据的结果,在 json 包中采用 map[string]interface{}[]interface{} 结构来存储任意的 json 对象和数组,Go 语言类型和 json 类型的对应关系如下:

Go 类型JSON 类型
map[string]interface{}objects
[]interface{}arrays
boolbooleans
float64numbers
stringstrings
nilnull

假设有如下 json 数据:

b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)

如果在不知道结构的情况下,可以通过下面的方式把它解析到 interface{}

var f interface{}
err := json.Unmarshal(b, &f)

这时 f 里面存储了一个 map 类型,它们的 key 是 string ,值存储到空的 interface{} 。

f = map[string]interface{}{
     	"Name": "Wednesday",
        "Age":  6,
        "Parents": []interface{}{
        "Gomez",
        "Morticia",
      	},
}

访问这些数据可通过断言的方式,通过断言之后就可以通过如下方式来访问里面的数据。

m := f.(map[string]interface{})

for k, v := range m {
		switch vv := v.(type) {
            case string:
                fmt.Println(k, "is string", vv)
            case int:
                fmt.Println(k, "is int", vv)
            case []interface{}:
                fmt.Println(k, "is an array:")
                for i, u := range vv {
                    fmt.Println(i, u)
                }
            default:
                fmt.Println(k, "is of a type I don't know how to handle!")
      	}
}

通过 interface{}type assert 的配合就可以解析未知结构的 json 数据,上述解决方案是官方提供的,其实很多时候如果通过类型断言,操作起来不是很方便,目前 Bitly 公司开源了一个 simplejson 的包,在处理未知结构体的 json 文件时很方便,详细例子:

js, err := NewJson([]byte(`{
     	"test": {
                "array": [1, "2", 3],
                "int": 10,
                "float": 5.150,
                "bignum": 9223372036854775807,
                "string": "simplejson",
                "bool": true
        		}
        }`))
        arr, _ := js.Get("test").Get("array").Array()
        i, _ := js.Get("test").Get("int").Int()
        ms := js.Get("test").Get("string").MustString()   

与官方包相比,使用这个库操作 json 更简单,详细内容请参考 GitHub


流式读取 JSON 文件

编写流式读取 json 文件程序的过程如下:

  • 创建存储 json 的结构。

  • 创建解码 json 的解码器。

  • 遍历 json 文件并用解码器将数据解码至结构。

  • 使用 Decoder 手动地将 json 数据解码到结构里面,处理流式 json 数据。

例如创建一个 post.json 文件,该文件具体内容如下:

{
  "id" : 1,
  "content" : "Hello CQUPT!",
  "author" : {
    "id" : 2,
    "name" : "cqupthao"
  },
  "comments" : [
    { 
      "id" : 1, 
      "content" : "Happy Day!", 
      "author" : "TIM"
    },
    {
      "id" : 2, 
      "content" : "Greate!", 
      "author" : "WINNE"
    }
  ]
}

编写一个解码该 json 文件的程序,该程序具体内容如下:

package main

import (
		"encoding/json"
		"fmt"
		"io"
		"os"
)

type Post struct {
		Id       int       `json:"id"`
		Content  string    `json:"content"`
		Author   Author    `json:"author"`
		Comments []Comment `json:"comments"`
}

type Author struct {
		Id   int    `json:"id"`
		Name string `json:"name"`
}

type Comment struct {
		Id      int    `json:"id"`
		Content string `json:"content"`
		Author  string `json:"author"`
}

func main() {
		jsonFile, err := os.Open("post.json")
		if err != nil {
				fmt.Println("Opening the file is failed: %v", err)
				return
		}
		defer jsonFile.Close()

		decoder := json.NewDecoder(jsonFile)
		for {
				var post Post
				err := decoder.Decode(&post)
				if err == io.EOF {
						break
				}
				if err != nil {
						fmt.Println("Decoding json is failed: %v", err)
						return
				}
				fmt.Println(post)
		}
}

执行程序输出的结果如下:

{1 Hello CQUPT! {2 cqupthao} [{1 Happy Day! TIM} {2 Greate! WINNE}]}

反序列化示例


反序列化为结构体

package main

import (
        "encoding/json"
        "fmt"
)
type Student struct{
		Name string
		Age int
		Birthday string
		Address string
}
func DeserializeStruct() {
     	str := "{\"Name\":\"cqupthao\",\"Age\":24,\"Birthday\":\"1999-01-01\",\"Address\":\"重庆市渝北区\"}"

        // 使用 Unmarshal 函数反序列化
        var student Student
        err := json.Unmarshal([]byte(str), &student)
        if err != nil {
                fmt.Println("Unmarshaling json is failed: %v", err)
        }

        fmt.Println("反序列化后:", student) // 反序列化后: {cqupthao 24 1999-01-01 重庆市渝北区}
}

func main(){
		DeserializeStruct()
}

执行程序输出的结果如下:

反序列化后: {cqupthao 24 1999-01-01 重庆市渝北区}

反序列化为 map

package main

import (
        "encoding/json"
        "fmt"
)

func DeserializeMap() {
        str := " {\"address\":\"重庆\",\"age\":23,\"name\":\"cqupthao\"}"

        // 反序列化的时候不需要 make,被封装到 Unmarshal 中了
        var m map[string]interface{}

        err := json.Unmarshal([]byte(str), &m)
        if err != nil {
                fmt.Println("Unmarshaling json is failed: %v", err)
        }

        fmt.Println("反序列化后:", m) // 反序列化后: map[address:重庆 age:23 name:cqupthao]
}
func main(){
		DeserializeMap()
}

执行程序输出的结果如下:

反序列化后: map[address:重庆 age:23 name:cqupthao]

反序列化为切片

package main

import (
        "encoding/json"
        "fmt"      
)
func DeserializeSlice() {
        str := " [{\"address\":\"重庆\",\"age\":23,\"name\":\"cqupthao\"}]"

        var slice []map[string]interface{}

        err := json.Unmarshal([]byte(str), &slice)
        if err != nil {
                fmt.Println("Unmarshaling json is failed: %v", err)
        }

        fmt.Println("反序列化后:", slice) // 反序列化后: [map[address:重庆 age:23 name:cqupthao]]
}

func main(){
		DeserializeSlice()
}

执行程序输出的结果如下:

反序列化后: [map[address:重庆 age:23 name:cqupthao]]

编码 JSON 文件


结构体生成 JSON 文件

Go 语言中 json 包提供了 Marshal() 函数和 MarshalIndent() 函数实现对一组数据进行 json 格式编码,函数的声明如下:

func Marshal(v interface{}) ([]byte, error)
// 用缩进对输出进行格式化
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)

编写一次性生成 json 文件的程序过程如下:

  • 创建结构并向其填充数据。
  • 结构封装为 json 数据。

MarshalIndent() 函数可自动添加前缀(前缀字符串一般设置为空)和缩进,在封装成 json 的时候可以进行"美化"。

例如编写一个编码通过 MarshalIndent() 函数生成 json 文件的程序,该程序内容如下:

package main

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

type User struct {
        UserName string
        NickName string `json:"nickname"`
        Email    string
}

func main() {
        user := User{
                UserName: "cqupthao",
                NickName: "Tao",
                Email:    "2733443175@qq.com",
        }

        //data, err := json.Marshal(&user)
        data, err := json.MarshalIndent(&user, "", "\t")
        if err != nil {
                fmt.Printf("Marshaling json is failed: %v", err)
                return
        }
        fmt.Printf("%s\n", string(data))

        file, err := os.Create("json_write.json")
        if err != nil {
                fmt.Printf("Creating the file is failed: %v", err)
                return
        }
        defer file.Close()

        file.Write(data)
}

执行程序会生成一个 json_write.json 文件,该文件具体内容如下:

{
        "UserName": "cqupthao",
        "nickname": "Tao",
        "Email": "2733443175@qq.com"
}

例如编写一个通过 Marshal() 函数编码生成 json 形式的服务器列表信息的程序,该程序的内容如下:

package main
  
import (
      	"encoding/json"
        "fmt"
)
type Server struct {
      	ServerName string
		ServerIP   string
}
type Serverslice struct {
        Servers []Server
}
     
func main() {
       	var s Serverslice
        s.Servers  =  append(s.Servers,  Server{ServerName:  "Shanghai_VPN", ServerIP: "127.0.0.1"})
        s.Servers  =  append(s.Servers,  Server{ServerName:  "Beijing_VPN", ServerIP: "127.0.0.2"})
        b, err := json.Marshal(s)
        if err != nil {
                fmt.Println("Encoding json is failed: %v", err)
            }
        fmt.Println(string(b))
}

执行程序输出的结果如下:

{"Servers":[{"ServerName":"Shanghai_VPN","ServerIP":"127.0.0.1"},{"ServerName":"Beijing_VPN","ServerIP":"127.0.0.2"}]}

上面的输出字段名都是大写的,json 输出的时候必须注意只有导出的字段才会被输出,如果修改字段名,那么就会发现什么都不会输出,所以必须通过如下方式的 struct tag 定义来实现。

type Server struct {
     	ServerName string `json:"serverName"`
        ServerIP   string `json:"serverIP"`
}
type Serverslice struct {
       	Servers []Server `json:"servers"`
}

通过修改上面的结构体定义输出的 json 串就和最开始定义的 json 串保持一致了,对 json 的输出,在定义 struct tag 时需要注意以下几点:

  • 如果字段的 tag"-" ,那么这个字段不会输出到 json 。
  • 如果 tag 中带有自定义名称,那么这个自定义名称会出现在 json 的字段名中,例如上面例子中的 serverName 。
  • 如果 tag 中带有 "omitempty" 选项,那么如果该字段值为空,就不会输出到 json 串中。
  • 如果字段类型是 bool 、string 、int 、 int64 等,而 tag 中带有 ",string" 选项,那么这个字段在输出到 json 时会把该字段对应的值转换成 json 字符串。

Marshal() 函数只有在转换成功的时候才会返回数据,在转换的过程中需要注意以下几点:

  • json 对象只支持 string 作为 key,所以要编码一个 map 必须是 map[string]T 这种类型(T 是 Go语言中任意的类型)。
  • channelcomplexfunction 不能被编码成 json 。
  • 嵌套的数据不能编码,不然会让 json 编码进入死循环。
  • 指针在编码的时候会输出指针指向的内容,而空指针会输出 null 。

编码器生成 JSON 文件

编写编码器生成 json 文件程序的过程:

  • 创建结构并向其填充数据。
  • 创建解码存储 json 的 json 文件。
  • 创建编码 json 的编码器。
  • 遍历 json 文件并用编码器将数据编码至结构体。

例如编写一个编码生成 json 文件的程序,该程序的具体内容如下:

package main

import (
		"encoding/json"
		"fmt"
		"io"
		"os"
)

type Post struct {
		Id       int       `json:"id"`
		Content  string    `json:"content"`
		Author   Author    `json:"author"`
		Comments []Comment `json:"comments"`
}

type Author struct {
		Id   int    `json:"id"`
		Name string `json:"name"`
}

type Comment struct {
		Id      int    `json:"id"`
		Content string `json:"content"`
		Author  string `json:"author"`
}

func main() {
		post := Post{
				Id:      1,
				Content: "Hello CQUPT!",
				Author: Author{
				Id:   2,
				Name: "cqupthao",
				},
			Comments: []Comment{
				Comment{
						Id:      1,
						Content: "Happy Day!",
						Author:  "TIM",
			},
				Comment{
						Id:      2,
						Content: "Greate!",
						Author:  "WINE",
				},
			},
		}

		jsonFile, err := os.Create("post.json")
		if err != nil {
				fmt.Println("Creating json file is failed: %v", err)
				return
		}
		defer jsonFile.Close()

		jsonWriter := io.Writer(jsonFile)
		encoder := json.NewEncoder(jsonWriter)

		// 指定前缀和缩进
		encoder.SetIndent("", "\t")

		err = encoder.Encode(&post)
		if err != nil {
				fmt.Println("Encoding json is failed: %v", err)
			return
		}
}

执行程序会生成一个 post.json 文件,该文件具体内容如下:

{
        "id": 1,
        "content": "Hello CQUPT!",
        "author": {
                "id": 2,
                "name": "cqupthao"
        },
        "comments": [
                {
                        "id": 1,
                        "content": "Happy Day!",
                        "author": "TIM"
                },
                {
                        "id": 2,
                        "content": "Greate!",
                        "author": "WINE"
                }
        ]
}

序列化示例


基本数据类型序列化

package main

import (
        "encoding/json"
        "fmt"
)
func SerializeBasic() {
        num := 1.24
        marshal, err := json.Marshal(num)
        if err != nil {
                fmt.Println("Marshaling json is failed: %v", err)
        }
        fmt.Println("序列化后:", string(marshal)) // 序列化后: 1.24
}

func main(){
		SerializeBasic()
}

执行程序输出的结果如下:

序列化后: 1.24

结构体序列化

package main

import (
        "encoding/json"
        "fmt"
)

/**
type Student struct {
        Name     string
        Age      int
        Birthday string
        Address  string
}
*/
// 如果加上`json:"student_name"`,序列化以后的数据字段是返回指定格式的,可以小写,json固定,后面的随意
type Student struct {
        // 变量首字母大写才能被解析
        Name     string `json:"student_name"`
        Age      int    `json:"student_age"`
        Birthday string `json:"student_birthday"`
        Address  string `json:"student_address"`
}


func SerializeStruct() {
        student := Student{
                Name:     "cqupthao",
                Age:      23,
                Birthday: "1999-01-01",
                Address:  "CQUPT",
        }
        marshal, err := json.Marshal(&student)
        if err != nil {
                fmt.Println("Marshaling json is failed: %v", err)
        }
        fmt.Println("序列化后:", string(marshal)) // 序列化后: {"student_name":"cqupthao","student_age":23,"student_birthday":"1999-01-01","student_address":"CQUPT"}

}

func main() {
        SerializeStruct()
}

执行程序输出的结果如下:

序列化后: {"student_name":"cqupthao","student_age":23,"student_birthday":"1999-01-01","student_address":"CQUPT"}

map 序列化

package main

import (
        "encoding/json"
        "fmt"
)
func SerializeMap() {
        var m map[string]interface{}
        m = make(map[string]interface{})
        m["name"] = "cqupthao"
        m["age"] = 23
        m["address"] = "CQUPT"

        marshal, err := json.Marshal(m)
        if err != nil {
                fmt.Println("Marshaling json is failed: %v", err)
        }
        fmt.Println("序列化后:", string(marshal)) // 序列化后: {"address":"CQUPT","age":23,"name":"cqupthao"}
}

func main(){
		SerializeMap()
}

执行程序输出的结果如下:

序列化后: {"address":"CQUPT","age":23,"name":"cqupthao"}

切片序列化

package main

import (
        "encoding/json"
        "fmt"
)
func SerializeSlice() {
        var slice []map[string]interface{}
        var m map[string]interface{}
        m = make(map[string]interface{})
        m["name"] = "cqupthao"
        m["age"] = 23
        m["address"] = "CQUPT"
        slice = append(slice, m)
        marshal, err := json.Marshal(m)
        if err != nil {
                fmt.Println("Marshaling json is failed: %v", err)
        }
        fmt.Println("序列化后:", string(marshal)) // 序列化后: {"address":"CQUPT","age":23,"name":"cqupthao"}
}

func main(){
		SerializeSlice()
}

执行程序输出的结果如下:

序列化后: {"address":"CQUPT","age":23,"name":"cqupthao"}

CSV 文件


CSV 本质上是文本文件,该文件有以下要求:

  • 列之间用逗号分隔,行之间用换行分隔。

  • 单元格如果有逗号、引号之类的字符, 该单元格需要使用双引号括起来。

  • 如果内容包含中文,直接输出可能会乱码。

Go 语言中的 csv 包提供了 NewReader() 函数和 NewWriter() 函数实现 csv 文件数据的读写,函数的声明如下:

func NewReader(r io.Reader) *Reader
func NewWriter(w io.Writer) *Writer

函数返回的是结构体类型,其结构的定义分别如下:

type Reader struct {
        Comma rune
        Comment rune
        FieldsPerRecord int
        LazyQuotes bool
        TrimLeadingSpace bool
        ReuseRecord bool
        TrailingComma bool 
}
type Writer struct {
        Comma   rune // Field delimiter (set to ',' by NewWriter)
        UseCRLF bool // True to use \r\n as the line terminator
        // Has unexported fields.
}

常见的读写方法如下:

func (r *Reader) FieldPos(field int) (line, column int)
func (r *Reader) InputOffset() int64
func (r *Reader) Read() (record []string, err error)
func (r *Reader) ReadAll() (records [][]string, err error)

func (w *Writer) Error() error
func (w *Writer) Flush()
func (w *Writer) Write(record []string) error
func (w *Writer) WriteAll(records [][]string) error

写入 CSV 数据到文件中


package main

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

func WriteCsv() {
        f, err := os.Create("data.csv")
        if err != nil {
                fmt.Println(err)
                return
        }
        defer f.Close()

        var data = make([][]string, 3)
        data[0] = []string{"标题", "作者", "时间"}
        data[1] = []string{"羊皮卷", "鲁迅", "2008"}
        data[2] = []string{"易筋经", "唐生", "665"}

        f.WriteString("\xEF\xBB\xBF") //写入一个UTF-8 BOM

        w := csv.NewWriter(f) //创建一个新的写入文件流
        w.WriteAll(data)
        w.Flush()
}

func main() {
        WriteCsv()
}

执行程序会生成一个 data.csv 文件,该文件的具体内容如下:

标题,作者,时间
羊皮卷,鲁迅,2008
易筋经,唐生,665

从文件中导出 CSV 数据


package main

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

func ReadCsv() {
    //准备读取文件
    fileName := "data.csv"
    fs, err := os.Open(fileName)
    if err != nil {
        log.Fatalf("Opening the file is failed: %v", err)
    }
    defer fs.Close()
    r := csv.NewReader(fs)
    //针对大文件,一行一行的读取文件
    for {
        row, err := r.Read()
        if err != nil && err != io.EOF {
            log.Fatalf("Reading the file is failed: %v", err)
        }
        if err == io.EOF {
            break
        }
        fmt.Println(row)
    }
    fmt.Println("\n---------------------------\n")
    // 针对小文件,也可以一次性读取所有的文件。注意,r要重新赋值,因为readall是读取剩下的
    fs1, _ := os.Open(fileName)
    r1 := csv.NewReader(fs1)
    content, err := r1.ReadAll()
    if err != nil {
        log.Fatalf("Reading is failed, err is %+v", err)
    }
    for _, row := range content {
        fmt.Println(row)
    }
}

执行程序输出的结果如下:

[标题 作者 时间]
[羊皮卷 鲁迅 2008]
[易筋经 唐生 665]

---------------------------

[标题 作者 时间]
[羊皮卷 鲁迅 2008]
[易筋经 唐生 665]

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

물の韜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值