From c010d6338a4ccc9ead707b387b395ce4bff4807d Mon Sep 17 00:00:00 2001 From: yxy <645455385@qq.com> Date: Sat, 12 Jun 2021 01:10:38 +0800 Subject: [PATCH 1/4] part 1 --- chap-25-json-and-xml/chap-25-json-and-xml.md | 40 ++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 chap-25-json-and-xml/chap-25-json-and-xml.md diff --git a/chap-25-json-and-xml/chap-25-json-and-xml.md b/chap-25-json-and-xml/chap-25-json-and-xml.md new file mode 100644 index 0000000..4aa8a2f --- /dev/null +++ b/chap-25-json-and-xml/chap-25-json-and-xml.md @@ -0,0 +1,40 @@ +# **Chapter 25: JSON 与 XML** +![](chap-25-json-and-xml/encoding-decoding.5c1de086.jpg) +- - - - +## 1、你将在本章学到什么? + + 什么是编码/解码? + + 什么是 JSON,XML? + + 什么是序列化和反序列化? + + 如何将变量转换为 JSON 或者 XML? + + 如何将 JSON 或 XML 字符串(或文件)转化成变量 + +## 2、涵盖的技术概念 + + 编码/解码 + + 序列化/反序列化 + + 数据流 + + JSON + + XML + + 接口实现 + +## 3、简介 +JSON 和 XML 是现代应用中的两种常见格式,相较于 protocol 和 buffer,它的便利之处体现在其自身天然的可读性以及易于被人们所理解的特性。 + +## 4、编码/解码 +大多数时候,应用程序将数据传输给其他应用程序,或者传输给人。在 Go 程序中,我们操作的数据通常以类型结构的形式出现: +``` +type Cart struct { + ID string + Paid bool +} +cart := Cart{ + ID: "121514", + Paid: false, +} +``` +如果我们想将 ::cart::变量中的数据传输到另一个程序,该怎么办?如果这个程序不是用Go写的呢?这就是为什么我们需要编码。 + + * **编码是将一段数据转换为“编码格式”的过程** + +当通过通信通道传输信息时,我们希望确保接收者能够理解该信息。因此,发送方和接收方将决定使用哪种编码技术。编码技术这个术语可能听起来不太熟悉,但你每天都在使用编码的过程……当我们与另一个人交谈时,我们将我们的思想编码成一种编码形式:语言(英语、法语、法裔加拿大人、普通话,……)。当你说话时,你将你的思想编码。(当然,如果你在说话之前想过的话)。我们使用的语言有一系列的规则,我们必须尊重这些规则来让我们的信息更容易理解(包括词法,语法……)。这对于所有的“编码形式”都是一样的。 + * **通过解码,“编码形式”可以被转换回原始信息。** +在本节中,我们将研究两种“编码形式”: JSON 和 XML。 \ No newline at end of file -- Gitee From d90965caf39f076ea17315359cb54dcc49d6d87c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E4=BF=A1=E4=B8=80=E7=82=B9=E9=83=BD=E4=B8=8D?= =?UTF-8?q?=E4=BA=8C?= <645455385@qq.com> Date: Sat, 12 Jun 2021 01:28:07 +0800 Subject: [PATCH 2/4] update chap-25-json-and-xml/chap-25-json-and-xml.md. --- chap-25-json-and-xml/chap-25-json-and-xml.md | 40 ++++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/chap-25-json-and-xml/chap-25-json-and-xml.md b/chap-25-json-and-xml/chap-25-json-and-xml.md index 4aa8a2f..8489d33 100644 --- a/chap-25-json-and-xml/chap-25-json-and-xml.md +++ b/chap-25-json-and-xml/chap-25-json-and-xml.md @@ -1,27 +1,26 @@ # **Chapter 25: JSON 与 XML** -![](chap-25-json-and-xml/encoding-decoding.5c1de086.jpg) +![Image](https://www.practical-go-lessons.com/img/encoding-decoding.5c1de086.jpg) - - - - ## 1、你将在本章学到什么? - + 什么是编码/解码? - + 什么是 JSON,XML? - + 什么是序列化和反序列化? - + 如何将变量转换为 JSON 或者 XML? - + 如何将 JSON 或 XML 字符串(或文件)转化成变量 ++ 什么是编码/解码? ++ 什么是序列化和反序列化? ++ 如何将变量转换为 `JSON` 或者 `XML`? ++ 如何将 `JSON` 或 `XML` 字符串(或文件)转化成变量 ## 2、涵盖的技术概念 - + 编码/解码 - + 序列化/反序列化 - + 数据流 - + JSON - + XML - + 接口实现 ++ 编码/解码 ++ 序列化/反序列化 ++ 数据流 ++ JSON ++ XML ++ 接口实现 ## 3、简介 -JSON 和 XML 是现代应用中的两种常见格式,相较于 protocol 和 buffer,它的便利之处体现在其自身天然的可读性以及易于被人们所理解的特性。 +`JSON` 和 `XML` 是现代应用中的两种常见格式,相较于 `protocol` 和 `buffer`,它的便利之处体现在其自身天然的可读性以及易于被人们所理解的特性。 ## 4、编码/解码 -大多数时候,应用程序将数据传输给其他应用程序,或者传输给人。在 Go 程序中,我们操作的数据通常以类型结构的形式出现: -``` +大多数时候,应用程序的功能是将数据传输给其他应用程序,或者传输给人。在 `Go` 程序中,我们操作的数据通常以类型结构的形式出现: +```go type Cart struct { ID string Paid bool @@ -31,10 +30,11 @@ cart := Cart{ Paid: false, } ``` -如果我们想将 ::cart::变量中的数据传输到另一个程序,该怎么办?如果这个程序不是用Go写的呢?这就是为什么我们需要编码。 +如果我们想将 `cart` 变量中的数据传输到另一个程序,该怎么办?如果这个程序不是用 `Go` 写的呢? 这就是为什么我们需要编码。 - * **编码是将一段数据转换为“编码格式”的过程** +> 编码是将一段数据转换为“编码格式”的过程 -当通过通信通道传输信息时,我们希望确保接收者能够理解该信息。因此,发送方和接收方将决定使用哪种编码技术。编码技术这个术语可能听起来不太熟悉,但你每天都在使用编码的过程……当我们与另一个人交谈时,我们将我们的思想编码成一种编码形式:语言(英语、法语、法裔加拿大人、普通话,……)。当你说话时,你将你的思想编码。(当然,如果你在说话之前想过的话)。我们使用的语言有一系列的规则,我们必须尊重这些规则来让我们的信息更容易理解(包括词法,语法……)。这对于所有的“编码形式”都是一样的。 - * **通过解码,“编码形式”可以被转换回原始信息。** -在本节中,我们将研究两种“编码形式”: JSON 和 XML。 \ No newline at end of file +当通过通信通道传输信息时,我们希望确保接收者能够理解该信息。因此,发送方和接收方将决定使用哪种编码技术。编码技术这个术语可能听起来不太熟悉,但你每天都在使用编码的过程…… 当我们与另一个人交谈时,我们将我们的思想编码成一种编码形式:语言(英语、法语、加拿大法语、普通话…… )。当你说话时,你将你的思想编码。(当然,如果你在说话之前想过的话)。我们使用的语言有一系列的规则,我们必须尊重这些规则来让我们的信息更容易理解(包括词法,语法…… )。这对于所有的“编码形式”都是一样的。 +> 通过解码,“编码形式”可以被转换回原始信息。 + +在本节中,我们将研究两种“编码形式”: `JSON` 和 `XML`。 \ No newline at end of file -- Gitee From a7758f5c3868680026f2c15d485e1062f49f753c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E4=BF=A1=E4=B8=80=E7=82=B9=E9=83=BD=E4=B8=8D?= =?UTF-8?q?=E4=BA=8C?= <645455385@qq.com> Date: Sat, 12 Jun 2021 09:28:36 +0800 Subject: [PATCH 3/4] update chap-25-json-and-xml/chap-25-json-and-xml.md. --- chap-25-json-and-xml/chap-25-json-and-xml.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/chap-25-json-and-xml/chap-25-json-and-xml.md b/chap-25-json-and-xml/chap-25-json-and-xml.md index 8489d33..cd53d79 100644 --- a/chap-25-json-and-xml/chap-25-json-and-xml.md +++ b/chap-25-json-and-xml/chap-25-json-and-xml.md @@ -37,4 +37,15 @@ cart := Cart{ 当通过通信通道传输信息时,我们希望确保接收者能够理解该信息。因此,发送方和接收方将决定使用哪种编码技术。编码技术这个术语可能听起来不太熟悉,但你每天都在使用编码的过程…… 当我们与另一个人交谈时,我们将我们的思想编码成一种编码形式:语言(英语、法语、加拿大法语、普通话…… )。当你说话时,你将你的思想编码。(当然,如果你在说话之前想过的话)。我们使用的语言有一系列的规则,我们必须尊重这些规则来让我们的信息更容易理解(包括词法,语法…… )。这对于所有的“编码形式”都是一样的。 > 通过解码,“编码形式”可以被转换回原始信息。 -在本节中,我们将研究两种“编码形式”: `JSON` 和 `XML`。 \ No newline at end of file +在本节中,我们将研究两种“编码形式”: `JSON` 和 `XML`。 + +## 5、序列化/反序列化 “ `Marshal` / `Unmarshal` ” +在接下来的章节,你将会看到序列化和反序列化这一具体过程。 ++ 序列化是一个将 `object` 转化为某种`可存储`的表现形式的一个过程 + + 例如,我们可以将 `struct` 类型的变量序列化为 `JSON` 格式 ++ 而反序列化则与这一过程相反 + +"序列化"从某种意义上来说和“编码”类似。然而,当你在使用序列化时,你处理的是有明确定义的对象(例如:一个 `map`,一个 `slice`,一个 `struct` 类型的变量…)。`encoding` 过程操作**数据流**,而 `marshale` 过程操作数据结构,即对象。 + +## 6、什么是 `JSON` +`JSON` 是 `JavaScript Object Notation` (JavaScript对象表示法)的简称。 \ No newline at end of file -- Gitee From 72c3dc9305e3f6f9ba3b250e06918799a554b495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E4=BF=A1=E4=B8=80=E7=82=B9=E9=83=BD=E4=B8=8D?= =?UTF-8?q?=E4=BA=8C?= <645455385@qq.com> Date: Sat, 12 Jun 2021 16:54:57 +0800 Subject: [PATCH 4/4] update chap-25-json-and-xml/chap-25-json-and-xml.md. finish --- chap-25-json-and-xml/chap-25-json-and-xml.md | 820 ++++++++++++++++++- 1 file changed, 819 insertions(+), 1 deletion(-) diff --git a/chap-25-json-and-xml/chap-25-json-and-xml.md b/chap-25-json-and-xml/chap-25-json-and-xml.md index cd53d79..9a18a12 100644 --- a/chap-25-json-and-xml/chap-25-json-and-xml.md +++ b/chap-25-json-and-xml/chap-25-json-and-xml.md @@ -48,4 +48,822 @@ cart := Cart{ "序列化"从某种意义上来说和“编码”类似。然而,当你在使用序列化时,你处理的是有明确定义的对象(例如:一个 `map`,一个 `slice`,一个 `struct` 类型的变量…)。`encoding` 过程操作**数据流**,而 `marshale` 过程操作数据结构,即对象。 ## 6、什么是 `JSON` -`JSON` 是 `JavaScript Object Notation` (JavaScript对象表示法)的简称。 \ No newline at end of file +`JSON` 是 `JavaScript Object Notation` (JavaScript对象表示法)的简称。这是一种文本格式,用于对结构化数据进行 `encode` / `decode`。首先由道格拉斯·克罗克福德提出。 +下面是一个例子 +```go +[ + { + "id": 1, + "first_name": "Annalise", + "last_name": "Marlin", + "city": "Bang Bon" + }, + { + "id": 2, + "first_name": "Dacy", + "last_name": "Biffin", + "city": "Cuilco" + }, + { + "id": 3, + "first_name": "Wendye", + "last_name": "Taillard", + "city": "Preobrazhenka" + } +] +``` ++ 上面这段代码表示拥有三个 `object` 的 `array` ++ `array` 从 `[` 开始,到 `]` 结束 ++ 每个 `object` 由一对花括号包裹起来(`{}`) ++ 每个 `user` 的属性都是一对 `key-value` 格式的键值对 + +> "id":3 + +## 7、反序列化 `JSON` +要解码包含 `JSON` 的字符串,你需要创建一个结构体类型。让我们举个例子。 +假设你想解码以下 `JSON` 字符串: +```go +{ + "cat": { + "name": "Joey", + "age": 8 + } +} +``` +我们有一个属性为 `cat` 的对象,它是一个具有两个属性的对象: `name` 和 `age`。当您想要解码时,首先必须创建一个保存数据的结构体。 +我们将首先创建一个包含 `cat` 结构体的结构体: +```go +type MyJson struct { + Cat +} +``` +接下来我们定义 `Cat` 结构体 +```go +type Cat struct { + Name string + Age uint +} +``` +我们有两个字段,分别是 `Name` 和 `Age`,和我们想要解码的 `JSON` 结构相同。现在我们来试试解码函数 +```go +// json-xml/unmarshall-json/main.go + +func main() { + myJson := []byte(`{"cat":{ "name":"Joey", "age":8}}`) + c := MyJson{} + err := json.Unmarshal(myJson, &c) + if err != nil { + panic(err) + } + fmt.Println(c.Cat.Name) + fmt.Println(c.Cat.Age) +} +``` +首先,我们将 `JSON` 字符串转化为 `byte` 切片,接着我们创建一个空的 `MyJson` 结构体 `c`,然后我们可以调用 `json.Unmarshal` 方法来给我们创建的变量 `c` 赋值。请注意 `json.Unmarshal` 的参数为: ++ 需要解码的 `JSON` 格式的 `byte` 切片(`myJson`) ++ 承接解码结果的变量指针c(`&c`) + +为什么我们需要传入一个指针呢?因为 `Unmarshal` 方法会修改变量 `c`,=。 +添加上打印信息 +```go +fmt.Println(c.Cat.Name) +fmt.Println(c.Cat.Age +``` +我们希望看到的输出如下 +```go +Joey +8 +``` +如果你运行示例的代码,你会发现没有任何输出!这是为什么呢? +这是因为 `json.Unmarshal` 方法没有匹配到正确的字段!**`Unmarshal` 方法将获取我们的结构中字段的名称,并尝试在 `JSON` 字符串中找到相应的属性**。如果你仔细观察我们的结构,就会发现结构体中导出字段的首字母大写了。 +### 7.0.0.1 解决方案 +1. 我们可以使我们的字段不被导出(首字母小写)。但这不并是一个好主意。应用程序的外部格式不应干扰应用程序类型! +2. 我们需要能够定义其他与 `JSON` 字符串中的字段名不相关的字段名。 + +第二个解决方案看起来很复杂,其实不然,我们可以使用 `tag` + +### 7.0.0.2 结构体标签 `Struct Tags` +`Tags` 是一段写在字段声明后的字符串。在解码过程中,`Tag` 能够被解码器获并使用,它往往用于正确地解码 `JSON` 字符串 +```go +// json-xml/unmarshall-json-tags/main.go + +type MyJson struct { + Cat `json:"cat"` +} + +type Cat struct { + Name string `json:"name"` + Age uint `json:"age"` +} +``` +你可以看到,我们向类型结构中的字段分别添加了三个 `Tag`。所有的结构体 `Tag` 都具有相同的结构: +```go +`json:"nameOfTheFieldInJson"` +``` +`Unmarshaler` 会找到 `JSON` 中的字段。它们可以通过 `reflect` 包访问(更准确地说,标签是结构体 `reflect. structfield` 的一个字段) +![Type Struct to JSON with struct tags](https://www.practical-go-lessons.com/img/type_struct_to_json.4a2ee4f3.png) + +## 8、序列化 `JSON` +要在 `go` 语言中创建 `JSON` 字符串,你必须定义一个 `struct`来定义你的数据结构。我们将以一个引用产品的电子商务网站为例。我们的目标是利用产品切片创建一个 `JSON` 字符串。 +我们首先定义 `Product` 结构体和 `Category` 结构体(一个商品只属于一个唯一的 `Category`): +```go +type Product struct { + ID uint64 + Name string + SKU string + Cat Category +} +type Category struct { + ID uint64 + Name string +} + +``` +接下来我们创建一个产品,比如说,一个茶壶。 +```go +p := Product{ID: 42, Name: "Tea Pot", SKU: "TP12", Category: Category{ID: 2, Name: "Tea"}} +``` +当我们准备好变量以后,让我们来试一试 `json.Marshal` 方法 +```go +// json-xml/marshall-json/main.go + +b, err := json.Marshal(p) +if err != nil { + panic(err) +} +``` +变量 `b` 是一个 `byte` 切片,请注意 `encoding` 操作可能会产生 `err`.当 `err` 发生时我们需要将其捕获并 `panic`.接下来我们将看到如下输出 +```go +{"ID":42,"Name":"Tea Pot","SKU":"TP12","Price":30.5,"Category":{"ID":2,"Name":"Tea"}} +``` +这种格式并不是最理想的 `JSON` 字符串格式。我们可以使用另一个方法 `json.MarshalIndent`.它将在序列化后的结果中带上缩进。 +```go +// json-xml/marshall-json/main.go + +bI, err := json.MarshalIndent(p,""," ") +if err != nil { + panic(err) +} +fmt.Println(string(bI)) +``` +`json.MarshalIndent` 的参数如下: ++ 需要序列化的 `data` ++ `prefix` 前缀字符串(输出的每一行结果将由这段字符串开始),在刚才的例子中,前缀被一段空字符串代替。 ++ `indent` 缩进,在刚在的例子中我们将缩进设置为了 `tab` + +输出的结果如下 +```json +{ + "ID": 42, + "Name": "Tea Pot", + "SKU": "TP12", + "Category": { + "ID": 2, + "Name": "Tea" + } +} +``` +通常,在 `JSON中`,属性的名称不会以大写字母开头。这里,`json` 包简单地使用了 `Product` 结构字段的名称,没有做任何修改。 +我们可以在 `Product` 结构体中添加 `tag` 来控制打印结果: +```go +// json-xml/marshall-json/main.go + +type Product struct { + ID uint64 `json:"id"` + Name string `json:"name"` + SKU string `json:"sku"` + Category Category `json:"category"` +} +type Category struct { + ID uint64 `json:"id"` + Name string `json:"name"` +} +``` +现在输出变成了如下: +```json +{ + "id": 42, + "name": "Tea Pot", + "sku": "TP12", + "category": { + "id": 2, + "name": "Tea" + } +} +``` + +## 9、 `Struct tags` +在前两节中,我们已经看到可以用 `tag` 控制 `JSON` 的结构。我们还学习了如何解码 `JSON` 字符串,并使结构的字段名与`JSON` 属性的名称匹配。 +### 9.1 如何忽略空字段 +结构体初始化时没有被赋值的字段都将被赋值为其类型的零值,在 `json.Marshal ` 和 `json.MarshalIndent` 方法中同样也会被序列化 +``` +// json-xml/tags/main.go + +type Product struct { + ID uint64 `json:"id"` + Name string `json:"name"` +} + +func main() { + p := Product{ID: 42} + bI, err := json.MarshalIndent(p, "", " ") + if err != nil { + panic(err) + } + fmt.Println(string(bI)) +} +``` +结果如下 +``` +{ + "id": 42, + "name": "" +} +``` +结构体中的 `name` 字段是一个空字符串,我们可以在 `name` 字段的声明后面添加 `omitempty` 指令让该字段在序列化的过程中被 `JSON` 字符串忽略: +```go +type Product struct { + ID uint64 `json:"id"` + Name string `json:"name,omitempty"` +} +``` +结果如下: +```json +{ + "id": 42 +} +``` +可以发现由于 `name` 字段是空的,所以在序列化后的结果中被忽略了。 +### 9.2 跳过某个字段 `Skip a field` +这个例子很简单,我们有一个包含十个字段的结构体,因为某些原因我不想将其中某些字段出现在编码后的 `JSON` 字符串中(例如,该字段不再被客户端使用)。有时候我们可以将它从结构中删除。但如果在代码的其余部分仍然使用这个字段,那么这个解决方案就不太合理了。 + +我们可以用 `-` 指令实现这一需求: +```go +// json-xml/tags/main.go + +type Product struct { + ID uint64 `json:"id"` + Name string `json:"name,omitempty"` + Description string `json:"-"` +} +``` +这个指令其实告诉 `json` 包不要在编码的字符串中输出 `Description` 字段 +### 9.3 如何解析结构体中的 tag `How to parse the tags of your struct` +你可以通过使用 `reflect` 包获取 `struct` 中的 `tag` 的值: +```go +// json-xml/access-tags/main.go + +type Product struct { + ID uint64 `json:"id"` + Name string `json:"name,omitempty"` + Description string `json:"-"` +} + +func main() { + p:= Product{ID:32} + t := reflect.TypeOf(p) + for i := 0; i < t.NumField(); i++ { + fmt.Printf("field Name : %s\n",t.Field(i).Name) + fmt.Printf("field Tag : %s\n",t.Field(i).Tag) + } +} +``` +这里,我们首先定义 `Product` 类型,该类型包含三个字段,每个字段上都有 `tag`。然后在 `main` 函数中,我们创建一个名为 `p` 的新变量(类型为 `Product`)。通过标准包反射的 `TypeOf` 方法检索出类型数据。 + +`TyoeOf()` 方法返回一个类型为 `Type` 的元素。 +`NumField()` 方法返回结构体中字段的数量,在这个例子中,返回值是3. +我们通过 `Field()` 方法遍历结构体中的所有字段,他将接收字段的**下标**作为参数,返回一个类型为 `StructField` 的值: +```go +// package reflect +// file: type.go +type StructField struct { + Name string + PkgPath string + Type Type + Tag StructTag + Offset uintptr + Index []int + Anonymous bool +} +``` +`StructTag ` 类型封装了一个简单的字符串,使你可以像操作常见的方法一样去操作 `tag` : `Get()` +下面是使用 `Get()` 方法的例子 +```go +//... +// for loop + fmt.Println(t.Field(i).Tag.Get("json")) +//... +``` +结合上一个代码片段,它的输出如下: +```go +id +name,omitempty +- +``` +`LookUp()` 方法 +下面是一个使用 `LookUp()` 方法的例子,该方法的目的是为了确认某个 `tag` 是否存在: +```go +// json-xml/access-tags/main.go + +if tagValue, ok := t.Field(i).Tag.Lookup("test"); ok { + fmt.Println(tagValue) +} else { + fmt.Println("no tag 'test'") +} +``` +### 9.4 如何创建自己的 `tags` `How to create your tags` +`JSON` 和 `XML` 创建标记很方便;我们可以依照特定的语法创建自己的 `struct tag`. +![](https://www.practical-go-lessons.com/img/struct_tag.ce137ff6.png) +`tag` 的实质是包含 `key-value` 的列表。在上图中,你可以看到这个 `tag` 包含两个元素。第一个元素有 `json` 键和值 `name omitempty`。第二个元素有键 `xml`,它的值是 `Name`。注意,两个元素之间用空格分隔。 +遵循这个模型,我们可以创建以下带有三个键的标记: +``myKey1:"test,test2" myKey2:"yo" myKey3:"yoyooo"`` + +## 10 序列化/反序列化 `XML` `Marshal / Unmarshal XML ` + +`XML` 是 `Xtensible Markup Language` “可扩展标记语言”的简称。它是一种用于以结构化方式存储信息的文件格式。今天它仍然在某些领域被使用,但它的使用率正在下降;主要是因为它很冗长(例如,用 `XML` 编码的消息比 `JSON` 需要更多的存储空间。 +例如,这是 `XML`编码的产品对象: +``` + + 42 + Tea Pot + TP12 + + 2 + Tea + + +``` +这段内容用 `JSON` 表示如下: +```json +{ + "id": 42, + "name": "Tea Pot", + "sku": "TP12", + "category": { + "id": 2, + "name": "Tea" + } +} +``` ++ `XML` 格式编码需要111个字节 ++ `JSON` 格式编码同样的内容只需要95个字节 + +解码 `XML` 比解码 `JSON` 要容易一些 +首先,定义一个 `struct` 来保存数据: +```go +// json-xml/xml/decode/main.go + +type MyJson struct { + Cat `xml:"cat"` +} + +type Cat struct { + Name string `xml:"name"` + Age uint `xml:"age"` +} +``` +我们将 `XML struct tag` 添加到结构中(它允许解码器知道在哪里找到字段的值)。 +接下来我们调用 `xml.Unmarshal` 方法来解码: +```go +// json-xml/xml/decode/main.go + +func main() { + myXML := []byte(` + Ti + 23 +`) + c := MyXML{} + err := xml.Unmarshal(myXML, &c) + if err != nil { + panic(err) + } + fmt.Println(c.Cat.Name) + fmt.Println(c.Cat.Age) +} +``` +编码 `XML` 也非常简单; 你可以使用 `xml.Marshal`,或者像下面的例子里一样使用`xml.MarshalIndent` 显示一个优雅的 `xml`字符串: +```go +// json-xml/xml/encode/main.go + +func main() { + p := Product{ID: 42, Name: "Tea Pot", SKU: "TP12", Price: 30.5, Category: Category{ID: 2, Name: "Tea"}} + bI, err := xml.MarshalIndent(p, "", " ") + if err != nil { + panic(err) + } + xmlWithHeader := xml.Header + string(bI) + fmt.Println(xmlWithHeader) +} +``` +记住,你必须在你的 `XML` 中添加一个头文件: +```xml + +``` +这个头文件将为使用 `XML` 文档的系统提供重要信息。它提供了有关所使用的 `XML` 版本(在我们的例子中是“1.0”,可以追溯到1998年)和文档编码的信息。解析器需要这些信息来正确地解码文档。 +`xml` 包定义了一个可以直接使用的常量: +```go +xmlWithHeader := xml.Header + string(bI) +``` +下面就是编码后的产品信息: +```xml + + + 42 + Tea Pot + TP12 + 30.5 + + 2 + Tea + + +``` +## 11.`xml.Name` 类型 `xml.Name type` + +如果您创建了以下类型的变量: +```go +type MyXmlElement struct { + Name string `xml:"name"` +} +``` +在序列化以后,结果将会变成下面的样子: +```xml + + Testing + +``` +我们可以控制属性 `name` 的序列化处理过程,但不能控制元素 `MyXmlElement` 的方式。现在,如果我们想改变这个属性的名字,我们必须改变类型的名字…这不是很方便。 +你可以在你的结构中添加一个特殊字段来控制 `XML` 元素的名称: +```go +type MyXmlElement struct { + XMLName xml.Name `xml:"myOwnName"` + Name string `xml:"name"` +} +``` +该字段必须命名为 `XMLName`,类型必须为 `xml.Name`。在标记中,你可以赋值你想要的名字(它将在序列化的处理过程中注入)。 +让我们来看看这个例子: +```go +// json-xml/xml/xmlNameType/main.go +package main + +import ( + "encoding/xml" + "fmt" +) + +type MyXmlElement struct { + XMLName xml.Name `xml:"myOwnName"` + Name string `xml:"name"` +} + +func main() { + elem := MyXmlElement{Name: "Testing"} + m, err := xml.MarshalIndent(elem, "", " ") + if err != nil { + panic(err) + } + fmt.Println(string(m)) + +} +``` +程序输出的结果如下: +```xml + + Testing + +``` +## 12.一些特殊的 `XML tag` `Specificity of XML tags` +除了省略空值 (`omitempty`)和 `ignore` 指令 "-",标准库还提供了大量的 `tag` 值: +### 12.1 Attributes +你可以给 `XML` 标签添加一些特殊的值,例如: +```xml +123 + +``` +这里的字段名为 `price`,还有一个名为 `currency` 的属性。你可以使用下面的 `struct` 来编码/解码它: +```go +type Price struct { + Text string `xml:",chardata"` + Currency string `xml:"currency,attr"` +} + +type Product struct { + Price Price `xml:"price"` + Name string `xml:"name"` +} +``` +我们定义了一个名为 `Price` 的结构体,它有两个字段:`Text` 用来接收价格(例子里的“123”) +包含 `Currency ` 属性值的 `Currency `(在本例中是“EUR”) +注意使用的标记。要想得到价格,标签是: +```go +`xml:",chardata"` +``` +它告诉 `Go` 获取开始标签 `` 和结束标签 `` 之间包含的数据。要获取属性,必须使用关键字 `attr` 和属性名。该字段被写成“字符数据”,没有 `xml` 元素。 +```go +`xml:"currency,attr"` +``` +一般总是以属性的名称开始: +![](https://www.practical-go-lessons.com/img/xml_attr.442fb0bb.png) +### 12.2 CDATA +如果想通过 `XML` 传输 `XML`,可以使用 `CDATA` 字段。`CDATA` 字段允许在 `XML` 消息中注入像 `>` 和 `<` 这样的字符。如果在没有 `CDATA` 字段的情况下注入这种数据,`XML` 可能会失效。 +让我们举个例子。我们在 `XML` 中添加了一个字段 `myXml`,其中填充了字符数据。注意字符数据是由`![CDATA[*和\lstinline!] ]>!\textbf{。因此,该字段的值为\lstinline!yo>12!` +```xml + + 123 + 12]]> + Kit + + +``` +下面是一个有效的结构体,它允许你检索 `myXml` 的值: +```go +// json-xml/xml/CDATA/main.go + +type Product struct { + Price Price `xml:"price"` + Name string `xml:"name"` + MyXml MyXml `xml:"myXml"` +} + +type MyXml struct { + Text string `xml:",cdata"` +} +``` +注意,我们创建了类型结构 `MyXml`,它只有一个字段 `Text`。这个字段有以下标记: +```go +`xml:",cdata"` +``` +### 12.3 Comments +和 `JSON` 不同的是,你可以在 `XML` 添加 `comment` +```go +// json-xml/xml/comments/main.go + +type Price struct { + Text string `xml:",chardata"` + Currency string `xml:"currency,attr"` +} + +type Product struct { + Comment string `xml:",comment"` + Price Price `xml:"price"` + Name string `xml:"name"` +} +``` +这里,我们向类型结构 `Product` 添加了一个名为 `Comment` 的字段。特殊的注释标签被添加到字段中。 +让我们创建一个 `Product` 并转化为 `XML` 格式: +```go +// json-xml/xml/comments/main.go + +c := Product{} +c.Comment = "this is my comment" +c.Price = Price{Text : "123",Currency:"EUR"} +c.Name = "testing" + +b, err := xml.MarshalIndent(c,""," ") +if err != nil { + panic(err) +} +fmt.Println(string(b)) +``` +这段脚本将会有如下输出 +```xml + + + 123 + testing + +``` +### 12.4 嵌套字段 `Nested fields` +一个简单的字段可以嵌套到多个 `XML` 元素中: +```go +// json-xml/xml/nesting/main.go +package main + +import ( + "encoding/xml" + "fmt" +) + +type Product struct { + Name string `xml:"first>second>third"` +} + +func main() { + c := Product{} + c.Name = "testing" + b, err := xml.MarshalIndent(c, "", " ") + if err != nil { + panic(err) + } + fmt.Println(string(b)) +} +``` +输出结果如下: +```xml + + + + testing + + + +``` +您可以看到字段 `Name ("testing")` 的值被包含在元素 `"third"` 中,`"third" ` 元素也同样被 `"second"` 和 `"first"` 元素包含。 +这是因为我们在 `struct` 标签中指定了它: +```go +`xml:"first>second>third"` +``` +## 13.结构体生成器 `Struct type generators` +构建一个结构可能非常耗时。特别是当您必须处理复杂的 `JSON/XML`结构时。我可以建议你使用这两个工具: ++ JSON: + + https://github.com/mholt/json-to-go + + 由马特·霍尔特建造 这是一个在线生成器。 只需复制你的 `JSON`,粘贴它,并让工具生成相应的结构。 ++ XML + + https://github.com/miku/zek + + 由 `Martin Czygan` 提供。 这是一个 `CLI` 工具,允许您基于示例 `XML` 生成结构 +## 14 自定义 `JSON` 序列化/反序列化(高级) `Custom JSON Marshaler / Unmarshaler (advanced)` +如果要使用特定的逻辑将类型序列化成 `JSON`,你可以创建自定义的序列化和反序列化工具。 +### 14.1 自定义反序列化工具 +要创建自定义 `JSON` 反序列化程序,必须实现 `json.Unmarshaler` 接口。 +下面是定义接口的例子: +```go +type Unmarshaler interface { + UnmarshalJSON([]byte) error +} +``` +它有一个指定的唯一方法: `UnmarshalJSON([]byte) error` +#### 14.1.1 示例:`time.Time` +让我们以一个实现为例 `time.Time` 类型。`JSON` 字符串可能包含时间字符串。这些时间字符串可以转换为 `time.Time` 类型: +```json +[ + { + "id": 1, + "first_name": "Hedvig", + "last_name": "Largen", + "created_at": "2020-07-02T08:41:35Z" + }, + { + "id": 2, + "first_name": "Win", + "last_name": "Alloisi", + "created_at": "2020-02-21T12:36:41Z" + } +] +``` ++ 在这个 `JSON` 中,我们有一个 `user` 集合。每个元素都有一个 `created_date`。我们希望将这些数据反序列化到变量 `users`(一个 `user` 的切片): +```go +type User struct { + ID int `json:"id"` + Firstname string `json:"first_name"` + Lastname string `json:"last_name"` + CreatedDate time.Time `json:"created_at"` +} +users := make([]User,2) +``` +对于每种类型,`Go` 将检查它是否有自定义的反序列化方法 ++ `time.Time` 类型定义了一个i定义的 `JSON unmarshaller` ++ 换句话说,`time.Time` 类型拥有一个签名为 `([]byte,error)` 的 `UnmarshalJSON` 方法 ++ 换句话说,`time.Time` 实现了 `json.Unmarshaler` 接口 +```go +// UnmarshalJSON implements the json.Unmarshaler interface. +// The time is expected to be a quoted string in RFC 3339 format. +func (t *Time) UnmarshalJSON(data []byte) error { + // Ignore null, like in the main JSON package. + if string(data) == "null" { + return nil + } + // Fractional seconds are handled implicitly by Parse. + var err error + *t, err = Parse(`"`+RFC3339+`"`, string(data)) + return err +} +``` ++ 在这个方法里,空值被忽略了不会报错。 + + 如果在 `JSON` 字符串的 `”created_at"` 属性是个空值,在反序列化的过程中,这个值将被赋值为 `time.Time` 的零值 `January 1, year 1, 00:00:00.000000000 UTC`. ++ 接下来这个方法将会尝试解析 `data` 里的数据 +```go +*t, err = Parse(`"`+RFC3339+`"`, string(data)) +``` ++ 这行代码将会按照 `"2006-01-02T15:04:05Z07:00"`的格式解析时间 ++ 注意,包含的双引号包含了解析格式。 ++ 这是因为 `JSON` 中的值的格式是 `“2020-07-02T08:41:35Z”`,而不是`2020-07-02T08:41:35Z` ! +`time.Parse` 方法将会返回解析的结果或者一个错误 ++ 解析的值被分配给 `*t` + + 这儿使用解引用操作符`*`设置 `t` 地址处的值(`t` 是指针类型) + +### 14.2 自定义序列化 `Custom Marshaller` +下面是需要在你的类型上实现的接口: +```go +type Marshaler interface { + MarshalJSON() ([]byte, error) +} +``` +它由一个签名为 `([]byte, error)` 和方法名为 `MarshalJSON` 的方法组成。 ++ 自定义序列化方法将输出字节切片,里面的内容就是序列化以后的 `JSON` ++ 自定义序列化方法同时还会输出一个 `error` +#### 14.2.1 Example: time.Time +```go +// MarshalJSON implements the json.Marshaler interface. +// The time is a quoted string in RFC 3339 format, with sub-second precision added if present. +func (t Time) MarshalJSON() ([]byte, error) { + if y := t.Year(); y < 0 || y >= 10000 { + // RFC 3339 is clear that years are four digits exactly. + // See golang.org/issue/4556#c15 for more discussion. + return nil, errors.New("Time.MarshalJSON: year outside of range [0,9999]") + } + + b := make([]byte, 0, len(RFC3339Nano)+2) + b = append(b, '"') + b = t.AppendFormat(b, RFC3339Nano) + b = append(b, '"') + return b, nil +} +``` +这里是 `json.Marshaller` 的实现。`Time` 类型的序列化程序接口。其思想是将 `Time` 类型的值转换为其 `JSON` 表示形式。 ++ 第一步是对年份的检查,必须在0到9999之间(不超过4位) ++ 创建一个 `byte` 切片 `b` + + 切片的长度是0,容量是 `len(RFC3339Nano)+2` + + 时间将按照 `constantRFC3339Nano` 中描述的格式输出 + + 在格式的长度上添加2,用于处理两个双引号(2字节) ++ 向切片中添加一个开头的双引号 ++ 然后使用 `AppendFormat ` 方法将格式化的时间附加到切片中 ++ 最后向切片中添加一个结尾的双引号 +## 15 自测模块 +### 15.1 问题 +1. 你可以使用哪种方法将变量转换为 `JSON`? +2. 你可以使用哪种方法将 `JSON` 字符串转换为特定类型的变量? +3. 在 `JSON` 序列化过程中如何改变结构体字段的名称? +4. 在 `XML` 序列化过程中如何改变结构体字段的名称? +5. `encoding` 和 `marshaling` 的区别是什么? +6. 如何自定义特定类型的反序列化方法? +7. 如何自定义特定类型的序列化方法? +### 15.2 答案 +1. 你可以使用哪种方法将变量转换为 `JSON`? +```go +json.Marshal() +``` +2. 你可以使用哪种方法将 `JSON` 字符串转换为特定类型的变量? +```go +json.Unmarshal() +``` +3. 在 `JSON` 序列化过程中如何改变结构体字段的名称? + 1. 用 `struct tag` + 2. EX: + ```go + type User struct { + ID int `json:"id"` + } + ``` +4. 在 `XML` 序列化过程中如何改变结构体字段的名称? + 1. 用 `struct tag` + 2. EX: + ```go + type User struct { + ID int `xml:"id"` + } + ``` +5. `encoding` 和 `marshaling` 的区别是什么? + 1. `encoding` 和 `marshaling` 指向相同的过程 + 2. 当你要操作数据流时需要用到 `encoding` + 3. 当你想将变量转换为编码形式时,将使用`marshaling` +6. 如何自定义特定类型的反序列化方法? + 1. 让你的类型实现 `json.Marshaller` 接口 +7. 如何自定义特定类型的序列化方法? + 1. 让你的类型实现 `json.Unmarshaller` 接口 +## 16.关键信息释义 ++ `JSON` `(JavaScript 对象表示法)`是一种流行的格式,用于表示人类或机器易于阅读的结构化数据。 ++ "将变量转换为编码形式" = " **marshaling** " ++ "将编码形式转换为变量" = " **unmarshaling**" ++ "将数据流转换为编码形式" = " **encoding** " ++ "将编码形式转换为数据流" = " **decoding** " ++ 要将变量转换为 `JSON/XML` 格式,我们可以使用 `json.Marshal/xml.Marshal ` ++ 要将`JSON/XML` 格式转换为变量 ,我们可以使用 `json.Unmarshal/xml.Unmarshal ` ++ `Struct tag` 允许您修改 `JSON/XML` 中字段的输出方式 +```go +type Product struct { + Price Price `xml:"price"` + Name string `xml:"name"` + MyXml MyXml `xml:"myXml"` +} +``` ++ 要获得 `struct tag` 的值,您必须使用 `reflect` 标准包 +```go +p:= Product{ID:32} +t := reflect.TypeOf(p) +for i := 0; i < t.NumField(); i++ { + fmt.Printf("field Name : %s\n",t.Field(i).Name) + fmt.Printf("field Tag : %s\n",t.Field(i).Tag) +} +``` ++ 一些特殊的 `struct tag` + + 跳过字段:`json:"-"` (`JSON/XML` 中不会输出) + + 如果字段为空则忽略:`json:"first_name,omitempty"` ++ 您可以将用于 `JSON` 的 `struct tag` 与其他 `struct tag` 组合起来 + + 例如和 `xml tag` 同时使用 + ```go + type User struct { + ID int `json:"id" xml:"id"` + } + ``` +---- +在实际使用中不建议使用 `panic`;你应该处理这个错误并使用一个正确的退出逻辑(作者系︎) \ No newline at end of file -- Gitee