JSON
n和map/struct类型之间的转换。
直接上例子:
package jsonExample
import (
"encoding/json"
"fmt"
)
type Movie struct {
Name string
Year int `json:"released"`
Color bool `json:color, omitempty` //是否为彩色电影
Actors []string
score int
}
func Run() {
var movies []Movie // 定义一个电影列表,里面的类型是Movie结构体类型,相当于python中一个列表里面放多个字典
movies = []Movie{
Movie{
Name: "阿甘正传",
Year: 1994,
Color: true,
Actors: []string{"A", "B", "C"},
level: 9,
},
Movie{
Name: "霸王别姬",
Year: 1993,
Color: true,
Actors: []string{"D", "E"},
},
Movie{
Name: "乱世佳人",
Year: 1939,
Color: false,
Actors: []string{"F", "G"},
},
Movie{
Name: "罗马假日",
Year: 1953,
Actors: []string{"H", "I"},
},
}
res, err := json.Marshal(movies)
if err != nil {
fmt.Println(err)
}
resFormat, err := json.MarshalIndent(movies, "", " ")
if err != nil {
fmt.Println(err)
}
fmt.Printf("%s\n", res)
fmt.Printf("%s\n", resFormat)
lifetimes := Movie {
Name: "活着",
Year: 1994,
Actors: []string{"Z", "X"},
}
res, err = json.Marshal(lifetimes)
if err != nil {
fmt.Println(err)
}
fmt.Printf("%s", res)
}
json.Marshal函数可以将切片或者结构体转化为json字符串。
json.MarshalIndent函数则是返回格式化后的json字符串,该函数有两个额外的字符串参数用于表示每一行输出的前缀和每一个层级的缩进。
返回的结果res不要用Println打印,打印的是一堆数字。应该用Printf格式化打印出%s字符串
上例中json.Marshal的返回json为:
[{"Name":"阿甘正传","released":1994,"Color":true,"Actors":["A","B","C"]},{"Name":"霸王别姬","released":1993,"Color":true,"Actors":["D","E"]},{"Name":"乱世佳人","released":1939,"Color":false,"Actors":["F","G"]},{"Name":"罗马假日","released":1953,"Color":false,"Actors":["H","I"]}]
json.MarshalIndent的返回json为:
[
{
"Name": "阿甘正传",
"released": 1994,
"Color": true,
"Actors": [
"A",
"B",
"C"
]
},
{
"Name": "霸王别姬",
"released": 1993,
"Color": true,
"Actors": [
"D",
"E"
]
},
{
"Name": "乱世佳人",
"released": 1939,
"Color": false,
"Actors": [
"F",
"G"
]
},
{
"Name": "罗马假日",
"released": 1953,
"Color": false,
"Actors": [
"H",
"I"
]
}
]
注意,我在定义Movie结构体的时候
type Movie struct {
Name string
Year int `json:"released"`
Color bool `json:color, omitempty` //是否为彩色电影
Actors []string
score int
}
我在Year和Color这两个成员后面还定义了一串`json:"released"`,这其实是在struct成员中加了tag。意思是格式化为json字符串的时候,Year属性名用released代替。`json:color, omitempty`则是用color代替Color,omitempty表示如果给结构体赋值的时候没有给Color成员赋值,那么在json化的时候,也要自动生成一个零值的color属性(例如在定义“罗马假日”这部电影没有赋值color属性)。
如果使用`json:"-"`,表示这个成员不进行json化
`json:"color,string"` 表示json化的时候,将color成员从bool型转为字符串型
还要注意的是,如果Movie结构体有使用首字母小写的成员,那么无论结构体变量中是否给这个小写成员赋值,在json化的结果中都不会有这个小写成员的属性,因为它是非导出的(例如在定义“阿甘正传”这部电影赋值了level成员)。所以建议是,不能在要json化的结构体中使用首字母小写的成员。
下面这个例子介绍如何将json字符串变为slice和struct类型
package jsonExample
import (
"encoding/json"
"fmt"
)
type Title struct {
Name string
Year int `json:"released"`
}
func Run2() {
var movies_json = []byte(`[{"Name":"阿甘正传","released":1994,"Color":true,"Actors":["A","B","C"]},{"Name":"霸王别姬","released":1993,"Color":true,"Actors":["D","E"]},{"Name":"乱世佳人","released":1939,"Color":false,"Actors":["F","G"]},{"Name":"罗马假日","released":1953,"Color":false,"Actors":["H","I"]}]`)
var movies []Movie // 接收所有电影的所有成员字段
var titles []Title // 接收所有电影的仅Name和Year成员字段
if err := json.Unmarshal(movies_json, &movies); err != nil {
fmt.Println(err)
}
if err := json.Unmarshal(movies_json, &titles); err != nil {
fmt.Println(err)
}
fmt.Println(movies)
fmt.Println(titles)
}
这个例子中用了json.Unmarshal函数,可以将json字符串转为指定的结构体类型,第一参传入一个byte类型的内容(不能是string类型的,所以我在定义movies_json的时候特意用[]byte()将字符串转为byte切片类型),第二参传入一个指定的结构体类型的指针用于接收格式化为struct后的json数据。这个函数只返回错误信息。
第一个打印结果:
[{阿甘正传 1994 true [A B C] 0} {霸王别姬 1993 true [D E] 0} {乱世佳人 1939 false [F G] 0} {罗马假日 1953 false [H I] 0}]
每个struct后面的0是因为json字符串中没有level字段,但是Movie类型中却有level成员,所以会默认填入一个零值的level到movies中
第二个打印结果:
[{阿甘正传 1994} {霸王别姬 1993} {乱世佳人 1939} {罗马假日 1953}]
这里我使用了Title这个结构体去接收格式化后的结果,由于Title只有Name和Year两个成员,所以json字符串中Color/Actors等其他属性都被摒弃掉了。
这里也可以看出go中json的高度定制化功能。
其实json包还有更多的功能,这里只介绍最基础的json用法,有兴趣的朋友可以在网上搜索更多关于Go的json格式化的功能。
时间操作
time的基本用法
package main
import (
"fmt"
"time"
)
func main() {
// 获取当前时间对象,它本质是一个time.Time的自定义类型(底层是结构体类型)
now := time.Now()
fmt.Println(now)
// 获取时间戳
fmt.Println(now.Unix()) // 秒级 使用int64的整型
fmt.Println(now.UnixNano()) // 纳秒级 19位
// 年月日时分秒纳秒
fmt.Println(now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second(), now.Nanosecond())
// 你会发现 now.Month() 是一个英文月份,我们可以用Printf转一下
fmt.Printf("%d\n", now.Month())
// 格式化时间,这里的参数必须是2006-01-02 15:04:05才能格式化出当前的时间戳,是不是很奇怪哈哈哈
fmt.Println(now.Format("2006-01-02 15:04:05")) // 年月日时分秒
fmt.Println(now.Format("2006-01-02")) // 年月日
fmt.Println(now.Format("15:04:05")) // 时分秒
// 如果你不想写 2006-01-02 15:04:05 那么可以使用这种方式(getTimeStamp下面有定义)
fmt.Println(getTimeStamp())
// 将时间戳转为格式化时间(要先转为时间对象再格式化)
var timeStamp int64 = 1600000000 // 不能写为 timeStamp := 1600000000,因为这样默认推导为int型而不是int64类型,但是下面的Unix()方法必须传入一个int64类型
timeObj := time.Unix(timeStamp, 0) // 转为时间对象 第1参是秒 第2参是纳秒
fmt.Println(timeObj.Format("2006-01-02 15:04:05"))
// 格式化时间转为时间戳
timeStr := "2021-01-01 00:00:00"
timeObj2, _ := time.Parse("2006-01-02 15:04:05", timeStr) // 转为时间对象
fmt.Println(timeObj2.Unix())
// 知道年月日时分秒,转为时间戳或格式化为字符串
timeInfo := []int{2014, 9, 1, 0, 0, 0}
// 由于第二参必须传入 time.Month 类型(底层其实还是int类型)的月份,所以要用 time.Month()强制将int类型转为time,Month类型
timeObj3 := time.Date(timeInfo[0], time.Month(timeInfo[1]), timeInfo[2], timeInfo[3], timeInfo[4], timeInfo[5], 0, time.Local)
fmt.Println(timeObj3.Unix())
fmt.Println(timeObj3.Format("2006-01-02 15:04:05"))
// time.Date/time.Parse/time.Unix都是返回时间对象。 还有就是要区分一下 time.Unix() 和 时间对象.Unix()的区别,前者返回时间对象,后者返回时间戳
fmt.Println(time.Local) // time.Local本质是一个time包里面自定义的结构体类型的变量(是一个零值结构体变量)的指针
}
func getTimeStamp() string {
now := time.Now()
// 这里注意 %2d 是输出两位整数,不足的位用空格填充; %02d 是输出两位整数,不足的位用0填充
return fmt.Sprintf("%d-%02d-%02d %02d:%02d:%02d", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())
}
time.Parse() 将字符串转为时间对象
time.Date() 将时分秒月日年转为时间对象
时间对象.Format() 格式化时间
时间对象.Unix() 获取时间戳
时间计算:
func main() {
// 获取一天前的时间对象(获取了时间对象后,想获取格式化时间还是时间戳就都很容易了)
now := time.Now()
lastHour,_ := time.ParseDuration("-1h") // 返回的yesterday是一个Duration类型,本质是一个int64类型的纳数,这里的lastHour应该是一个负数
fmt.Println(lastHour, fmt.Sprintf("%d", lastHour))
timeObj := now.Add(lastHour) // Add需要传入一个Duration类型的纳秒,返回time.Time类型的时间对象
printTime(timeObj)
// ParseDuration支持的字符串有 “ns”, “us” (or “ns”), “ms”, “s”, “m”, “h”,也就是说最大支持小时这个维度,不支持天,如果像获取一天前的时间对象可以这样
yesterdayObj := now.Add(-time.Hour * 24) // time.Hour 是Duration类型的小时化为的纳秒数
printTime(yesterdayObj)
printf("%s", time.Since(now)) // 比对当前时间和now差了多少时间(纳秒),可以用来计算一个程序运行花费的时间
}
文本模板和html模板
直接上例子:
package main
import (
"log"
"os"
tt "text/template"
"time"
)
// 定义书架(有多本书)结构体类型
type Books struct {
TotalCount int
Items []Book // 书架有多本书,里面存放的元素都是Book类型的结构体
}
// 定义书的结构体类型
type Book struct {
Title string
Author *Writer // 结构体成员不能是结构体的类型,而只能是结构体的指针的类型,所以这里不能写为 Author Writer。用指针一样可以操控结构体
CreateAt time.Time // 书的创建时间是一个time.Time类型
}
// 定义作者的结构体类型
type Writer struct {
Name string
Country string
}
func main() {
// 第一步 定义模板内容 用··定义多行字符串
const templ = `{{.TotalCount}} issues:
{{range .Items}}
Author: {{.Author.Name}}
Title: {{.Title | printf "%.64s"}}
Age: {{.CreateAt | daysAgo}} days
{{end}}`
// 这个模板内容里面 {{}} 是变量, {{range xxx}}是循环, {{xxx | xxx}} 是使用函数,包括go已有的函数和自定义函数。如果是自定义函数则需要将这个函数注册进模板对象中
// 第二步 生成模板对象
myTemplateFuncs := tt.FuncMap{"daysAgo" : daysAgo} // FuncMap 是template包中自定义的哈希表类型
myTemplate, err := tt.New("myTemplate").Funcs(myTemplateFuncs).Parse(templ) // 这里创建了一个template对象,并注册了自定义函数,并指定了要使用的模板内容
if err != nil {
log.Fatal(err)
}
// 第三步 往模板中注入变量,使用模板
shengjingWriter := Writer{
Name: "Kernighan",
Country: "unknown",
}
shengjingCreateAt, _ := time.Parse("2006-01","2016-03")
shengjing := Book { // go语言圣经
Title:"The Go Programming Language",
Author:&shengjingWriter,
CreateAt:shengjingCreateAt,
}
var (
concurrenyInGo Book
concurrenyInGoWriter Writer
)
concurrenyInGoCreateAt, _ := time.Parse("2006-01", "2018-11")
concurrenyInGo = Book {
Title:"Go语言并发之道",
Author:&concurrenyInGoWriter,
CreateAt: concurrenyInGoCreateAt,
}
concurrenyInGoWriter = Writer {
Name: "Katherine Cox-Buday",
Country: "unknown",
}
books := Books {
TotalCount: 2,
Items: []Book{concurrenyInGo, shengjing},
}
// os.Stdout是标准输出,意思是将渲染好的模板内容直接打印到屏幕的意思
if err := myTemplate.Execute(os.Stdout, books); err != nil {
log.Fatal(err) // 该函数的作用是像正常报错一样抛出一个致命异常,会终止程序的执行
}
}
func daysAgo(createTime time.Time) int {
// time.Since(t) 返回当前时间的时间对象与t这个时间对象之间相差的纳秒的绝对值,返回的是Duration类型的纳秒
// time.Hour 是Duration类型的1个小时的纳秒,即 3600 * 10^9
return int(time.Since(createTime) / time.Hour / 24)
}
这玩意其实就和php里面的smarty模板一样的,用于web开发中创建html模板,但是不适合做日志,因为日志适合一条一条的追加,而这里的模板是一次性的输出到文件或者一次性打印出来。
传入Execute的模板变量的类型可以是任意类型,看看Execute()源代码的参数列表即可发现。
还有一点就是:模板变量中的结构体成员必须得是可导出的成员,首字母要大写,因为在模板中是无法渲染非导出成员的。
其实不只是在模板中要这样,在平时没事别定义非导出成员,尽量都在结构体中定义可导出成员。
因为模板通常在编译时就测试好了,如果模板解析失败将是一个致命的错误。template.Must辅助函数可以简化这个致命错误的处理:它接受一个模板和一个error类型的参数,检测error是否为nil(如果不是nil则发出panic异常),然后返回传入的模板。
简单的来说:
myTemplate:= tt.Must(tt.New("myTemplate").Funcs(myTemplateFuncs).Parse(templ))
与
myTemplate, err := tt.New("myTemplate").Funcs(myTemplateFuncs).Parse(templ)
if err != nil {
log.Fatal(err)
}
是等价的,用了tt.Must的化就不用再生成模板对象后检验是否有错误,它会自动帮你检验是否有错误。
上面介绍的是 text/template 包的使用, html/template 和 text/template 包几乎没有区别,唯一的区别是前者会对传入的模板变量中的字符串的特殊字符进行转义(将<变为<),防止 < 和 & 这样的特殊字符导致html文件引起歧义
下面我们再看一个例子:
package main
import (
"html/template"
"log"
"os"
)
type Tags struct {
ATag string
BTag template.HTML // 定义为 HTML 这个新类型, 这个类型的底层类型其实还是string
}
func main() {
templ := `<div>{{.ATag}}</div><div>{{.BTag}}</div>`
myTemplate := template.Must(template.New("myTemplate").Parse(templ))
data := Tags {
ATag: "<b>Hello</b>",
BTag: "<b>Hello</b>",
}
if err := myTemplate.Execute(os.Stdout, data); err != nil {
log.Fatal(err)
}
}
输出的结果如下:
<div><b>Hello</b></div><div><b>Hello</b></div>
所以如果输出到浏览器的话,结果就是第二个div看到了加粗效果,第一个div直接输出了<b>标签文本。