golang学习随便记5-类型:JSON、文本与HTML模板

复合数据类型(构造类型)

JSON

golang 天生支持 JSON 和 HTML,意味着它天生为网络编程服务。

JSON使用的基本类型是数字(十进制或科学计数法)、布尔值(true或false)、字符串Unicode码点序列,用\为转义符,\uhhh得到的是UTF-16字符)。

JSON用基本类型可以组合出构造类型:数组对象。JSON的数组和golang数组slice对应,JSON的对象和golang map结构体对应。把golang的数组或结构体之类转成JSON的过程,称为Marshal(列集),而把JSON数据转换到golang数组或结构体变量,称为Unmarshal(反列集,解码列集)。因为golang结构体等用首字母大小写控制访问,而JSON字段名完全可能小写开头,而且,我们有时候并不想JSON字段名和结构体成员名一致,这时需要字段标签(field tag),它是编译期给编译器提示用的元信息。

package main

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

func main() {
	var movies = []Movie{
		{Title: "T1", Year: 1942, Color: false, Actor: []string{"AA", "BB"}},
		{Title: "T2", Year: 1967, Color: true, Actor: []string{"CC", "DD"}},
		{Title: "T3", Year: 1968, Color: true, Actor: []string{"EE", "FF"}},
	}
	data, err := json.MarshalIndent(movies, "", "    ")
	if err != nil {
		log.Fatalf("JSON marshaling failed: %s", err)
	}
	fmt.Printf("%s\n", data)
	var titles []struct{ Title string }
	if err := json.Unmarshal(data, &titles); err != nil {
		log.Fatalf("JSON unmarshaling failed: %s", err)
	}
	fmt.Println(titles)
}

type Movie struct {
	Title string
	Year  int  `json:"released"`
	Color bool `json:"color,omitempty"`
	Actor []string
}

输出

[
    {
        "Title": "T1",
        "released": 1942,
        "Actor": [
            "AA",
            "BB"
        ]
    },
    {
        "Title": "T2",
        "released": 1967,
        "color": true,
        "Actor": [
            "CC",
            "DD"
        ]
    },
    {
        "Title": "T3",
        "released": 1968,
        "color": true,
        "Actor": [
            "EE",
            "FF"
        ]
    }
]
PS C:\Users\zime\go\src\ch1\hello> go run .
[
    {
        "Title": "T1",
        "released": 1942,
        "Actor": [
            "AA",
            "BB"
        ]
    },
    {
        "Title": "T2",
        "released": 1967,
        "color": true,
        "Actor": [
            "CC",
            "DD"
        ]
    },
    {
        "Title": "T3",
        "released": 1968,
        "color": true,
        "Actor": [
            "EE",
            "FF"
        ]
    }
]
[{T1} {T2} {T3}]

字段标签(总是反引号包围) json:"color,omitempty" ,表示对应JSON字段为color,并且该字段为零值(对bool型false就是零值)时从JSON忽略它,所以,对于Color为false的条目,JSON中没有color项。

下面的例子是向GitHub发送Get请求获取JSON格式的issue有关信息。创建项目目录github,go mod init github 添加依赖跟踪文件go.mod,然后在项目目录github下创建data.go和func.go 。

data.go 文件定义了一些结构体,对应返回的JSON格式的数据(只对应需要提取的信息,因为原始返回数据字段很多)

package github

import "time"

const IssuesUrl = "https://api.github.com/search/issues"

type IssuesSearchResult struct {
	TotalCount int `json:"total_count"`
	Items      []*Issue
}

type Issue struct {
	Number    int
	HtmlUrl   string `json:"html_url"`
	Title     string
	State     string
	User      *User
	CreatedAt time.Time `json:"created_at"`
	Body      string    // in markdown format
}

type User struct {
	Login   string
	HtmlUrl string `json:"html_url"`
}

func.go 包含了发起请求进行查询的函数(请求类似 https://api.github.com/search/issues?q=repo%3Agolang%2Fgo+is%3Aopen+json+decoder

package github

import (
	"fmt"
	// "io/ioutil"
	"encoding/json"
	"net/http"
	"net/url"
	"strings"
)

func SearchIssues(terms []string) (*IssuesSearchResult, error) {
	q := url.QueryEscape(strings.Join(terms, " ")) // 注意,一个空格连接各部分
	println(IssuesUrl + "?q=" + q)
	resp, err := http.Get(IssuesUrl + "?q=" + q)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		resp.Body.Close()
		return nil, fmt.Errorf("查询失败: %s", resp.Status)
	}

	var result IssuesSearchResult
	if err = json.NewDecoder(resp.Body).Decode(&result); err != nil {
		resp.Body.Close()
		return nil, err
	}

	// info, _ := ioutil.ReadAll(resp.Body)
	// fmt.Println(string(info)) // 查看原始返回信息

	resp.Body.Close()
	return &result, nil
}

另外创建一个项目,目录 src/issues,该项目将调用前述github项目中的函数。go mod init  issues 创建依赖跟踪文件 go.mod。用 go mod edit -replace  github=../github 对依赖项 github 进行重定向(参考另一贴 golang学习随便记1-准备工作、程序结构_sjg20010414的博客-CSDN博客),然后 go mod tidy 添加依赖项。该项目下文件 issues.go 如下

package main

import (
	"fmt"
	"github"
	"log"
	"os"
)

// test like: go run . repo:golang/go is:open json decoder

func main() {
	result, err := github.SearchIssues(os.Args[1:])
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%d 个问题:\n", result.TotalCount)
	for _, item := range result.Items {
		fmt.Printf("#%-5d %9.9s %.55s\n", item.Number, item.User.Login, item.Title)
	}
}

输出如下:(省略后面类似的行)

sjg@sjg-PC:~/go/src/issues$ go run . repo:golang/go is:open json decoder
https://api.github.com/search/issues?q=repo%3Agolang%2Fgo+is%3Aopen+json+decoder
79 个问题:
#56733 rolandsho encoding/json: add (*Decoder).SetLimit
#48298     dsnet encoding/json: add Decoder.DisallowDuplicateFields
#59053   joerdav proposal: encoding/json: add a generic Decode function
#29035    jaswdr proposal: encoding/json: add error var to compare  the 
#36225     dsnet encoding/json: the Decoder.Decode API lends itself to m
#42571     dsnet encoding/json: clarify Decoder.InputOffset semantics

文本和HTML模板

接着上述 json 例子,我们把请求返回的json,用指定的文本模板输出。

新建项目目录 src/issuesreport,go mod init issuesreport,因为我们要用到 github 项目包,所以,go mod  edit  -replace  github=../github 添加重定向,编写以下代码issuesreport.go并用go mod  tidy 添加依赖:

package main

import (
	"github"
	"log"
	"os"
	"text/template"
	"time"
)

const tpl = `{{.TotalCount}} 个问题:
{{range .Items}}-------------------
编号: {{.Number}}
用户: {{.User.Login}}
标题: {{.Title | printf "%.64s"}}
时长: {{.CreatedAt | daysAgo}} 天
{{end}}`

func daysAgo(t time.Time) int {
	return int(time.Since(t).Hours() / 24)
}

var report = template.Must(template.New("issuelist").
	Funcs(template.FuncMap{"daysAgo": daysAgo}).
	Parse(tpl))

func main() {
	result, err := github.SearchIssues(os.Args[1:])
	if err != nil {
		log.Fatal(err)
	}
	if err := report.Execute(os.Stdout, result); err != nil {
		log.Fatal(err)
	}
}

template.New 创建指定名称的模板对象,模板对象的方法Funcs添加模板中的函数键名到实际函数的映射关系,模板对象的方法Parse将解析模板(Funcs必须在Parse前面),准备调用模板对象的Execute方法“执行模板”。所谓执行模板,就是将数据合并到模板,然后输出。

输出结果类似如下:(省略后续类似的项)

sjg@sjg-PC:~/go/src/issuesreport$ go run . repo:golang/go is:open json decoder
https://api.github.com/search/issues?q=repo%3Agolang%2Fgo+is%3Aopen+json+decoder
79 个问题:
-------------------
编号: 56733
用户: rolandshoemaker
标题: encoding/json: add (*Decoder).SetLimit
时长: 163 天
-------------------
编号: 48298
用户: dsnet
标题: encoding/json: add Decoder.DisallowDuplicateFields
时长: 594 天
-------------------

Html模板的使用和文本模板并无多少差别:mkdir  issueshtml 创建项目目录,cd  issueshtml,go  mod init  issueshtml 初始化,同样,用 go mod edit -replace github=../github 使用本地包,将项目目录添加到VS Code的工作区,项目目录下新建 issueshtml.go,添加如下代码 (注意:前面 github项目中 data.go 中,我们定义的关于HtmlURL的字段名都是HtmlUrl,模板中使用时大小写务必与此一致)

package main

import (
	"github"
	"html/template"
	"log"
	"os"
)

var issueList = template.Must(template.New("issuelist").Parse(`
<h1>{{.TotalCount}} 个问题</h1>
<table>
<tr style='text-align: left'>
  <th>#</th>
  <th>状态</th>
  <th>用户</th>
  <th>主题</th>
</tr>
{{range .Items}}
<tr>
  <td><a href='{{.HtmlUrl}}'>{{.Number}}</a></td>
  <td>{{.State}}</td>
  <td><a href='{{.User.HtmlUrl}}'>{{.User.Login}}</a></td>
  <td><a href='{{.HtmlUrl}}'>{{.Title}}</a></td>
</tr>
{{end}}
</table>
`))

func main() {
	result, err := github.SearchIssues(os.Args[1:])
	if err != nil {
		log.Fatal(err)
	}
	if err := issueList.Execute(os.Stdout, result); err != nil {
		log.Fatal(err)
	}
}

然后, go  mod  tidy  在 go.mod 中添加上代码中的依赖 github包。运行 go run . repo:golang/go commenter:gopherbot json encoder >issues.html  将输出结果重定向到html文件。打开该html文件,结果类似如下图

表面上看,html/template 和  text/template 没有差别,但区别是,html/template 会自动对特殊字符(如 < 、>符号)转义,而 text/template 不转义。与此同时,存在一种需求,即 html/template 模板中需要对某些字符串按原始样子输出(不转义)。我们可以使用类型 template.HTML 来标记可信HTML字符串的类型。下面的例子可以直观展示这一点: autoescaple.go 代码如下

package main

import (
	"html/template"
	"log"
	"os"
)

func main() {
	const tpl = `<p>A: {{.A}}</p><p>B: {{.B}}</p>`
	t := template.Must(template.New("escape").Parse(tpl))
	var data struct {
		A string
		B template.HTML // 可信 HTML
	}
	data.A = "<b>Hello!</b>"
	data.B = "<b>Hello!</b>"
	if err := t.Execute(os.Stdout, data); err != nil {
		log.Fatal(err)
	}
}

go run . 输出结果为:<p>A: &lt;b&gt;Hello!&lt;/b&gt;</p><p>B: <b>Hello!</b></p>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值