xml的解析比json复杂,最起码xml在元素的属性中也能蕴藏信息。下面是一个xml文件post1.xml:
你好,Golang
张三
表面上看,上述xml就是外层元素post里面有两个子元素content和author,但post和author都是带有id属性的,其实这意味着author有2项信息,不能用一个字段表达,所以,下面的代码为author定义了结构体。
package main
import (
"encoding/xml"
"fmt"
"io/ioutil"
"os"
)
type Post struct {
XMLName xml.Name `xml:"post"` // 元素名
Id string `xml:"id,attr"` // 元素的属性,模式标志 attr
Content string `xml:"content"` // “常规”子元素
Author Author `xml:"author"` // 复合型子元素
Xml string `xml:",innerxml"` // 元素内部的原始xml,模式标志 innerxml
}
type Author struct {
Id string `xml:"id,attr"`
Name string `xml:",chardata"` // 元素的字符数据,模式标志 chardata
}
func main() {
xmlFile, err := os.Open("post1.xml")
if err != nil {
fmt.Println("打开xml文件错误:", err)
return
}
defer xmlFile.Close()
xmlData, err := ioutil.ReadAll(xmlFile)
if err != nil {
fmt.Println("读取xml数据错误:", err)
return
}
var post Post
xml.Unmarshal(xmlData, &post)
fmt.Println(post)
}
运行后输出结构为:
sjg@sjg-PC:~/go/src/xml$ go run .
{{ post} 1 你好,Golang {2 张三}
你好,Golang
张三
}
我们修改一下post1.xml为以下post2.xml,设想返回该帖子时同时返回了对应的2条评论:
你好,Golang
张三
C++才是好语言
李四
Rust比C++好
王五
在修改解析代码前,我们先需要考虑:是否需要为comments元素定义一个结构体,然后再为comment元素定义一个结构体,前者是后者的列表?更广泛的,我们是否必须定义上级元素才能获取到下一级元素?答案是否定的。我们可以使用`xml:"a>b>c"`这样的结构标签实现不指定树状结构的情况下直接获取指定的XML元素(即跳过中间元素a和b,直接获取c)。代码添加或修改如下:
// ....................................................
type Post struct {
// ...................................
Xml string `xml:",innerxml"` // 元素内部的原始xml,模式标志 innerxml
Comments []Comment `xml:"comments>comment"`
}
type Author struct {
// ...................................
}
type Comment struct {
Id string `xml:"id,attr"`
Content string `xml:"content"`
Author Author `xml:"author"`
}
func main() {
xmlFile, err := os.Open("post2.xml")
// ......................................
}
注意上面对列表的处理。结果输出为:
sjg@sjg-PC:~/go/src/xml$ go run .
{{ post} 1 你好,Golang {2 张三}
你好,Golang
张三
C++才是好语言
李四
Rust比C++好
王五
[{1 C++才是好语言 {3 李四}} {2 Rust比C++好 {4 王五}}]}
unmarshal这种方式可以一次性获取所有信息,但这种方式适合体积不太大的xml。对于大个头xml,我们需要使用 Decoder 来手动解码:
// ..........................
defer xmlFile.Close()
decoder := xml.NewDecoder(xmlFile)
for {
t, err := decoder.Token()
if err == io.EOF {
break
}
if err != nil {
fmt.Println("解码 xml 到令牌出错:", err)
return
}
switch se := t.(type) { // 判断令牌类型
case xml.StartElement: // 是xml某个元素起始标签
if se.Name.Local == "comment" { // 元素名为 comment
var comment Comment
decoder.DecodeElement(&comment, &se)
fmt.Println(comment)
}
}
}
}
sjg@sjg-PC:~/go/src/xml$ go run .
{1 C++才是好语言 {3 李四}}
{2 Rust比C++好 {4 王五}}
要生成xml比解析要容易一些,并且也有2种方式:marshal方式或者encoder方式
// .............结构体与前面的代码相同....................
func main() {
post := Post{
Id: "1",
Content: "你好,Golang",
Author: Author{
Id: "2",
Name: "张三",
},
Comments: []Comment{
{
Id: "1",
Content: "C++才是好语言",
Author: Author{
Id: "3",
Name: "李四",
},
},
{
Id: "2",
Content: "Rust比C++好",
Author: Author{
Id: "4",
Name: "王五",
},
},
},
}
output, err := xml.MarshalIndent(&post, "", " ") // 用 Marshal 则不缩进
if err != nil {
fmt.Println("列集到xml出错:", err)
return
}
err = ioutil.WriteFile("post.xml", []byte(xml.Header+string(output)), 0644)
if err != nil {
fmt.Println("写入xml文件出错:", err)
return
}
}
生成的文件带了缩进,这里不再列出。
下面改写的代码,是用编码器方式生成xml文件:
post := Post{
// ..........................................
}
xmlFile, err := os.Create("post2.xml")
if err != nil {
fmt.Println("创建xml文件出错:", err)
return
}
xmlFile.WriteString(xml.Header)
encoder := xml.NewEncoder(xmlFile)
encoder.Indent("", "\t")
err = encoder.Encode(&post)
if err != nil {
fmt.Println("编码xml到文件出错:", err)
return
}
对于JSON,我们先来看如何创建。同样,我们可以用marshal方式或者encoder方式。
下面是marshal方式生成JSON,不过我们先需要适当改造前述代码中的结构体,使它们符合JSON标签的格式(JSON标签比xml简单些,也不需要头部声明,本身结构体赋值还和JSON数据很像):
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
)
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 Author `json:"author"`
}
func main() {
post := Post{
Id: 1,
Content: "你好,Golang",
Author: Author{
Id: 2,
Name: "张三",
},
Comments: []Comment{
{
Id: 1,
Content: "C++才是好语言",
Author: Author{
Id: 3,
Name: "李四",
},
},
{
Id: 2,
Content: "Rust比C++好",
Author: Author{
Id: 4,
Name: "王五",
},
},
},
}
output, err := json.MarshalIndent(&post, "", "\t")
if err != nil {
fmt.Println("列集到JSON时出错:", err)
return
}
err = ioutil.WriteFile("post.json", output, 0644)
if err != nil {
fmt.Println("写入JSON到文件出错:", err)
return
}
}
使用编码方式生成JSON的代码:
jsonFile, err := os.Create("post.json")
if err != nil {
fmt.Println("创建JSON文件出错:", err)
return
}
encoder := json.NewEncoder(jsonFile)
encoder.SetIndent("", "\t")
err = encoder.Encode(&post)
if err != nil {
fmt.Println("编码JSON到文件出错:", err)
return
}
解析JSON也是两种方式:unmarshal或decoder。一般地Body,对于流式输入(如http.Request的),用解码方式较好;对于字符串或者内存的JSON数据,用unmarshal更好。
// .................................................
func main() {
jsonFile, err := os.Open("post.json")
if err != nil {
fmt.Println("打开JSON文件出错:", err)
return
}
defer jsonFile.Close()
jsonData, err := ioutil.ReadAll(jsonFile)
if err != nil {
fmt.Println("读取JSON数据出错:", err)
return
}
var post Post
json.Unmarshal(jsonData, &post)
fmt.Println(post)
}
// ..............................................
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("解码JSON出错:", err)
return
}
fmt.Println(post)
}
}
下面我们仅就posts表来编写web服务:需要准备好mariadb 10.3数据库,另外,基本的表操作代码基本上是拷贝 golang web学习随便记4-内存、文件、数据库_sjg20010414的博客-CSDN博客 一文中的,只是 Post 结构体添加JSON标签。下面是main包中的data.go文件:
package main
import (
"database/sql"
"log"
_ "github.com/go-sql-driver/mysql"
)
type Post struct {
Id int `json:"id"`
Content string `json:"content"`
Author string `json:"author"`
}
var Db *sql.DB
func init() { // 此 init 不显式调用,自动隐式调用,实现初始化全局变量 Db
var err error
Db, err = sql.Open("mysql", "gwp:dbpassword@tcp(172.17.0.1:3306)/gwp?charset=utf8mb4,utf8")
if err != nil {
panic(err)
}
}
func Posts(limit int) (posts []Post, err error) {
rows, err := Db.Query("SELECT id, content, author FROM posts LIMIT ?", limit) // Query(..) 预期返回多行结果集
if err != nil {
return
}
for rows.Next() { // 用循环遍历多行结果集
post := Post{}
err = rows.Scan(&post.Id, &post.Content, &post.Author) // Scan(..) 将结果集当前列值绑定到变量
if err != nil {
return
}
posts = append(posts, post) // 结果依次放入切片 posts
}
rows.Close() // 关闭结果集,清理内存
return
}
func GetPost(id int) (post Post, err error) {
post = Post{}
err = Db.QueryRow("SELECT id, content, author FROM posts WHERE id = ?", id). // QueryRow(..) 预期返回单行结果集
Scan(&post.Id, &post.Content, &post.Author) // pgsql 可以 RETURNING id
return
}
func (post *Post) Create() (err error) {
sql := "INSERT INTO posts (content, author) VALUES (?, ?)"
stmt, err := Db.Prepare(sql) // 对于插入使用准备语句
if err != nil {
log.Fatal(err)
return
}
defer stmt.Close()
// err = stmt.QueryRow(post.Content, post.Author).Scan(&post.Id) // pgsql 可以一步设置 post.id
if result, err := stmt.Exec(post.Content, post.Author); err != nil { // Exec(..)返回执行结果
log.Fatal(err)
} else {
if last_insert_id, err := result.LastInsertId(); err != nil {
log.Fatal(err)
} else {
post.Id = int(last_insert_id) // Mariadb/MySQL应该支持执行结果的 LastInsertId()
}
}
return
}
func (post *Post) Update() (err error) {
_, err = Db.Exec("UPDATE posts SET content = ?, author = ? WHERE id = ?",
post.Content, post.Author, post.Id)
return
}
func (post *Post) Delete() (err error) {
_, err = Db.Exec("DELETE FROM posts WHERE id = ?", post.Id)
return
}
下面是main包中的main.go文件:
package main
import (
"encoding/json"
"net/http"
"path"
"strconv"
)
func main() {
server := http.Server{
Addr: "127.0.0.1:8088",
}
http.HandleFunc("/post/", handleRequest)
http.HandleFunc("/posts/", handleList)
server.ListenAndServe()
}
func handleRequest(w http.ResponseWriter, r *http.Request) {
var err error
switch r.Method {
case "GET":
err = handleGet(w, r)
case "POST":
err = handlePost(w, r)
case "PUT":
err = handlePut(w, r)
case "DELETE":
err = handleDelete(w, r)
}
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func handleGet(w http.ResponseWriter, r *http.Request) (err error) {
id, err := strconv.Atoi(path.Base(r.URL.Path)) // 路径中最后一个元素转换成整数
if err != nil {
return
}
post, err := GetPost(id)
if err != nil {
return
}
output, err := json.MarshalIndent(&post, "", "\t")
if err != nil {
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(output)
return
}
func handlePost(w http.ResponseWriter, r *http.Request) (err error) {
len := r.ContentLength
body := make([]byte, len)
r.Body.Read(body)
var post Post
json.Unmarshal(body, &post)
err = post.Create()
if err != nil {
return
}
w.WriteHeader(200)
return
}
func handlePut(w http.ResponseWriter, r *http.Request) (err error) {
id, err := strconv.Atoi(path.Base(r.URL.Path)) // 路径中最后一个元素转换成整数
if err != nil {
return
}
post, err := GetPost(id)
if err != nil {
return
}
len := r.ContentLength
body := make([]byte, len)
r.Body.Read(body)
json.Unmarshal(body, &post)
err = post.Update()
if err != nil {
return
}
w.WriteHeader(200)
return
}
func handleDelete(w http.ResponseWriter, r *http.Request) (err error) {
id, err := strconv.Atoi(path.Base(r.URL.Path)) // 路径中最后一个元素转换成整数
if err != nil {
return
}
post, err := GetPost(id)
if err != nil {
return
}
err = post.Delete()
if err != nil {
return
}
w.WriteHeader(200)
return
}
func handleList(w http.ResponseWriter, r *http.Request) {
posts, err := Posts(10)
if err != nil {
return
}
output, err := json.MarshalIndent(&posts, "", "\t")
if err != nil {
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(output)
return
}
从 docker 启动 mariadb数据库,go get github.com/go-sql-driver/mysql 添加依赖包,然后 go run .运行服务。我们用 curl 来测试各项服务:
sjg@sjg-PC:~$ curl -i -X POST -H "Content-Type: application/json" -d '{"content":"我的第一条帖子","author":"赵六"}' http://127.0.0.1:8088/post/
HTTP/1.1 200 OK
Date: Thu, 18 May 2023 08:50:42 GMT
Content-Length: 0
sjg@sjg-PC:~$ curl -i -X GET http://127.0.0.1:8088/posts/
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 18 May 2023 08:52:35 GMT
Content-Length: 316
[
{
"id": 1,
"content": "你好, C++! 08:33:04",
"author": "李四"
},
{
"id": 2,
"content": "你好, C++! 08:46:48",
"author": "李四"
},
{
"id": 3,
"content": "你好, C++! 08:51:08",
"author": "李四"
},
{
"id": 4,
"content": "我的第一条帖子",
"author": "赵六"
}
]
sjg@sjg-PC:~$ curl -i -X GET http://127.0.0.1:8088/post/4
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 18 May 2023 08:53:28 GMT
Content-Length: 70
{
"id": 4,
"content": "我的第一条帖子",
"author": "赵六"
}
sjg@sjg-PC:~$ curl -i -X PUT -H "Content-Type: application/json" -d '{"content":"我的第一条帖子(修改版)","author":"赵六"}' http://127.0.0.1:8088/post/4
HTTP/1.1 200 OK
Date: Thu, 18 May 2023 08:55:08 GMT
Content-Length: 0
sjg@sjg-PC:~$ curl -i -X DELETE http://127.0.0.1:8088/post/3
HTTP/1.1 200 OK
Date: Thu, 18 May 2023 08:56:18 GMT
Content-Length: 0
最后的结果是:
sjg@sjg-PC:~$ curl -i -X GET http://127.0.0.1:8088/posts/HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 18 May 2023 08:56:59 GMT
Content-Length: 252
[
{
"id": 1,
"content": "你好, C++! 08:33:04",
"author": "李四"
},
{
"id": 2,
"content": "你好, C++! 08:46:48",
"author": "李四"
},
{
"id": 4,
"content": "我的第一条帖子(修改版)",
"author": "赵六"
}
]