文章目录
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"
的字段上,这个字段的类型可能是[]byte
或string
,如果没有这样的字段存在,那么注释将会被抛弃。
注:为了正确解析,Go 语言的
xml
包要求 struct 定义中的所有字段必须是可导出的(即首字母大写)。
上面详细讲述了如何定义 struct 的 tag
,tag
和 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()
函数接收的参数 v 是interface{} 类型的,可以接受任意类型的参数。
xml
包根据如下的规则来生成相应的xml
文件。
-
如果 v 是
array
或slice
,那么输出每一个元素,类似value
。 -
如果 v 是指针,那么会
Marshal
指针指向的内容,如果指针为空,什么都不输出。 -
如果 v 是
interface
,那么就处理interface
所包含的数据。 -
如果 v 是其他数据类型,就会输出这个数据类型所拥有的字段信息。
生成的 xml 文件中的
element
的名字是根据元素名按照如下优先级从 struct 对象中获取:
-
如果 v 是 struct ,
tag
中定义为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 |
bool | booleans |
float64 | numbers |
string | strings |
nil | null |
假设有如下 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语言中任意的类型)。 - channel 、complex 和 function 不能被编码成 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]
-
参考链接 Go语言实现JSON解析的方法详解
-
参考链接 Go语言通过结构体生成JSON示例解析
-
参考书籍:《Go Web 编程》(谢孟军 著)
-
参考书籍:《Go Web 编程从入门到精通》(廖显东 著)