如何在Go中使用JSON
作为 Write for DOnations 计划的一部分,作者选择了 Diversity in Tech Fund 来接受捐赠。
介绍
在现代程序中,一个程序和另一个程序之间的通信很重要。 无论是 Go 程序检查用户是否可以访问另一个程序,JavaScript 程序获取过去订单列表以显示在网站上,还是 Rust[X183X ] 程序从文件中读取测试结果,程序需要一种方法来为其他程序提供数据。 然而,许多编程语言都有自己的内部存储数据的方式,这是其他语言无法理解的。 为了允许这些语言进行交互,需要将数据转换为他们都能理解的通用格式。 其中一种格式 JSON 是通过 Internet 以及在同一系统中的程序之间传输数据的流行方式。
许多现代编程语言在其标准库中包含一种将数据与 JSON 相互转换的方法,Go 也是如此。 通过使用 Go 提供的 encoding/json 包,您的 Go 程序还可以与任何其他可以使用 JSON 进行通信的系统进行交互。
在本教程中,您将首先创建一个程序,该程序使用 encoding/json
包将 map
中的数据编码为 JSON 数据,然后更新您的程序以使用 struct
类型而是对数据进行编码。 之后,您将更新程序以将 JSON 数据解码为 map
,然后最终将 JSON 数据解码为 struct
类型。
先决条件
要遵循本教程,您将需要:
- Go version 1.16 or greater installed. To set this up, follow the How To Install Go tutorial for your operating system.
- 熟悉 JSON,可以在 An Introduction to JSON 中找到。
- 使用 Go 结构标签自定义
struct
类型字段的能力。 更多信息可以在 How To Use Struct Tags in Go 中找到。 - (可选)了解如何在 Go 中创建日期和时间值。 您可以在 如何在 Go 中使用日期和时间中阅读更多内容。
使用地图生成 JSON
Go 对 JSON 编码和解码的支持由标准库的 encoding/json
包提供。 您将从该包中使用的第一个函数是 json.Marshal 函数。 Marshalling,有时也称为序列化,是将内存中的程序数据转换为可以传输或保存在其他地方的格式的过程。 json.Marshal
函数用于将 Go 数据转换为 JSON 数据。 json.Marshal
函数接受 interface{}
类型作为编组为 JSON 的值,因此任何值都可以作为参数传入,并将返回 JSON 数据作为结果。 在本节中,您将创建一个程序,使用 json.Marshal
函数从 Go map
值生成包含各种类型数据的 JSON,然后将这些值打印到输出。
大多数 JSON 表示为一个对象,具有 string
键和各种其他类型作为值。 因此,在 Go 中生成 JSON 数据最灵活的方法是使用 string
键和 interface{}
值将数据放入 map
。 string
键可以直接翻译成JSON对象键,interface{}
值允许该值是任何其他值,无论是string
,int
,甚至另一个 map[string]interface{}
。
要开始在程序中使用 encoding/json
包,您需要有该程序的目录。 在本教程中,您将使用一个名为 projects
的目录。
首先,创建 projects
目录并导航到它:
mkdir projects cd projects
接下来,为您的项目创建目录。 在这种情况下,使用目录 jsondata
:
mkdir jsondata cd jsondata
在 jsondata
目录中使用 nano
或您喜欢的编辑器打开 main.go
文件:
nano main.go
在 main.go
文件中,您将添加一个 main
函数来运行您的程序。 接下来,您将添加具有各种键和数据类型的 map[string]interface{}
值。 然后,您将使用 json.Marshal
函数将 map
数据编组为 JSON 数据。
将以下行添加到 main.go
:
main.go
package main import ( "encoding/json" "fmt" ) func main() { data := map[string]interface{}{ "intValue": 1234, "boolValue": true, "stringValue": "hello!", "objectValue": map[string]interface{}{ "arrayValue": []int{1, 2, 3, 4}, }, } jsonData, err := json.Marshal(data) if err != nil { fmt.Printf("could not marshal json: %s\n", err) return } fmt.Printf("json data: %s\n", jsonData) }
您将在 data
变量中看到每个值都有一个 string
作为键,但这些键的值各不相同。 一个是 int
值,另一个是 bool
值,甚至是另一个 map[string]interface{}
值,其中包含 []int
值。
当您将 data
变量传递给 json.Marshal
时,该函数将查看您提供的所有值并确定它们的类型以及如何在 JSON 中表示它们。 如果翻译中有任何问题,json.Marshal
函数将返回描述问题的 error
。 但是,如果转换成功,jsonData
变量将包含编组 JSON 数据的 []byte
。 由于可以使用 myString := string(jsonData)
或格式字符串中的 %s
verb 将 []byte
值转换为 string
值,因此您然后可以使用 fmt.Printf
将 JSON 数据打印到屏幕上。
保存并关闭文件。
要查看程序的输出,请使用 go run
命令并提供 main.go
文件:
go run main.go
您的输出将类似于以下内容:
Outputjson data: {"boolValue":true,"intValue":1234,"objectValue":{"arrayValue":[1,2,3,4]},"stringValue":"hello!"}
在输出中,您将看到顶级 JSON 值是一个由围绕它的花括号 ({}
) 表示的对象。 您在 data
中包含的所有值都存在。 您还将看到 objectValue
的 map[string]interface{}
被转换为另一个由 {}
包围的 JSON 对象,并且其中还包含 arrayValue
和数组值[1,2,3,4]
。
JSON 中的编码时间
但是,encoding/json
包不仅支持 string
和 int
值等类型。 它还可以编码更复杂的类型。 它支持的更复杂的类型之一是 time 包中的 time.Time
类型。
注意: 关于 Go 的 time
包的更多信息,请查看教程,如何在 Go 中使用日期和时间。
要查看实际情况,请再次打开 main.go
文件并使用 time.Date
函数将 time.Time
值添加到数据中:
main.go
package main import ( "encoding/json" "fmt" "time" ) func main() { data := map[string]interface{}{ "intValue": 1234, "boolValue": true, "stringValue": "hello!", "dateValue": time.Date(2022, 3, 2, 9, 10, 0, 0, time.UTC), "objectValue": map[string]interface{}{ "arrayValue": []int{1, 2, 3, 4}, }, } ... }
此更新会将 UTC
时区中的日期 March 2, 2022,
和时间 9:10:00 AM
分配给 dateValue
键。
保存更改后,使用与以前相同的 go run
命令再次运行程序:
go run main.go
您的输出将类似于以下内容:
Outputjson data: {"boolValue":true,"dateValue":"2022-03-02T09:10:00Z","intValue":1234,"objectValue":{"arrayValue":[1,2,3,4]},"stringValue":"hello!"}
这次在输出中,您将在 JSON 数据中看到 dateValue
字段,其时间使用 RFC 3339 格式进行格式化,这是一种用于将日期和时间表示为 string
值。
在 JSON 中编码 null
值
根据您的程序与之交互的系统,您可能需要在 JSON 数据中发送 null
值,Go 的 encoding/json
包也可以为您处理。 使用 map
,只需添加具有 nil
值的新 string
键。
要将几个 null
值添加到 JSON 输出,请再次打开 main.go
文件并添加以下行:
main.go
... func main() { data := map[string]interface{}{ "intValue": 1234, "boolValue": true, "stringValue": "hello!", "dateValue": time.Date(2022, 3, 2, 9, 10, 0, 0, time.UTC), "objectValue": map[string]interface{}{ "arrayValue": []int{1, 2, 3, 4}, }, "nullStringValue": nil, "nullIntValue": nil, } ... }
您添加到数据中的值具有表示它是 string
值或 int
值的键,但实际上代码中没有任何内容可以使其成为其中任何一个值。 由于 map
具有 interface{}
值,所有代码都知道 interface{}
的值是 nil
。 由于您仅使用此 map
将 Go 数据转换为 JSON 数据,因此此时的区别并没有什么区别。
将更改保存到 main.go
后,使用 go run
运行程序:
go run main.go
您的输出将类似于以下内容:
Outputjson data: {"boolValue":true,"dateValue":"2022-03-02T09:10:00Z","intValue":1234,"nullIntValue":null,"nullStringValue":null,"objectValue":{"arrayValue":[1,2,3,4]},"stringValue":"hello!"}
现在在输出中,您将看到 nullIntValue
和 nullStringValue
字段包含在 JSON null
值中。 这样,您仍然可以使用 map[string]interface{}
值将 Go 数据转换为具有预期字段的 JSON 数据。
在本节中,您创建了一个可以将 map[string]interface{}
值编组为 JSON 数据的程序。 然后,您在数据中添加了一个 time.Time
字段,还包括了一对 null
值字段。
虽然使用 map[string]interface{}
来编组 JSON 数据可能非常灵活,但如果您需要在多个地方发送相同的数据,它也会变得很麻烦。 如果将此数据复制到代码中的多个位置,则很容易意外键入字段名称,或将不正确的数据分配给字段。 在这种情况下,使用 struct
类型来表示您要转换为 JSON 的数据会很有好处。
使用结构生成 JSON
使用像 Go 这样的 静态类型 语言的好处之一是,您可以使用这些类型让编译器检查或强制执行程序的一致性。 Go 的 encoding/json
包允许您通过定义 struct
类型来表示 JSON 数据来利用这一点。 您可以使用 struct tags 控制如何转换 struct
中包含的数据。 在本节中,您将更新程序以使用 struct
类型而不是 map
类型来生成 JSON 数据。
当您使用 struct
定义 JSON 数据时,您希望翻译的字段名称(不是 struct
类型名称本身)必须导出,这意味着它们必须以大写字母开头,例如作为 IntValue
或 encoding/json
包将无法访问字段以将其转换为 JSON。 如果不使用结构标签来控制这些字段的命名,则字段名称将直接翻译为它们在 struct
上的样子。 使用默认名称可能是您希望在 JSON 数据中使用的名称,具体取决于您希望数据的形成方式。 如果是这种情况,您不需要添加任何结构标签。 但是,许多 JSON 使用者使用 intValue
或 int_value
等名称格式作为其字段名称,因此添加这些结构标签将允许您控制转换的发生方式。
例如,假设您有一个 struct
和一个名为 IntValue
的字段,您将其编组为 JSON:
type myInt struct { IntValue int } data := &myInt{IntValue: 1234}
如果您使用 json.Marshal
函数将 data
变量编组为 JSON,您最终会得到以下值:
{"IntValue":1234}
但是,如果您的 JSON 使用者希望该字段被命名为 intValue
而不是 IntValue
,那么您需要一种方法来告诉 encoding/json
。 由于 json.Marshal
不知道您希望该字段在 JSON 数据中命名的内容,因此您可以通过向该字段添加一个结构标记来告诉它。 通过将 json
结构标记添加到值为 intValue
的 IntValue
字段,您告诉 json.Marshal
它应该使用名称 intValue
生成 JSON 数据时:
type myInt struct { IntValue int `json:"intValue"` } data := &myInt{IntValue: 1234}
这一次,如果你将 data
变量编组为 JSON,json.Marshal
函数将看到 json
结构标记并且知道将字段命名为 intValue
,所以你'会得到你预期的结果:
{"intValue":1234}
现在,您将更新您的程序以对 JSON 数据使用 struct
值。 您将添加一个 myJSON
struct
类型来定义您的顶级 JSON 对象,以及一个 myObject
struct
来定义您的内部 JSON 对象ObjectValue
字段。 您还将向每个字段添加一个 json
结构标记,以告诉 json.Marshal
如何在 JSON 数据中命名它们。 您还需要更新 data
变量分配以使用 myJSON
结构,声明它类似于您对任何其他 Go struct
的声明。
打开您的 main.go
文件并进行以下更改:
main.go
... type myJSON struct { IntValue int `json:"intValue"` BoolValue bool `json:"boolValue"` StringValue string `json:"stringValue"` DateValue time.Time `json:"dateValue"` ObjectValue *myObject `json:"objectValue"` NullStringValue *string `json:"nullStringValue"` NullIntValue *int `json:"nullIntValue"` } type myObject struct { ArrayValue []int `json:"arrayValue"` } func main() { otherInt := 4321 data := &myJSON{ IntValue: 1234, BoolValue: true, StringValue: "hello!", DateValue: time.Date(2022, 3, 2, 9, 10, 0, 0, time.UTC), ObjectValue: &myObject{ ArrayValue: []int{1, 2, 3, 4}, }, NullStringValue: nil, NullIntValue: &otherInt, } ... }
其中许多更改与之前的 IntValue
字段名称示例类似,但其中一些更改值得特别指出。 其中之一,ObjectValue
字段,使用 *myObject
的引用类型来告诉 JSON 编组器期望对 myObject
值或 [ 的引用X160X] 值。 这就是您可以定义一个深度为多层自定义对象的 JSON 对象的方式。 如果您的 JSON 数据需要它,您还可以在 myObject
类型中引用另一个 struct
类型,依此类推。 使用这种模式,您可以使用 Go struct
类型描述非常复杂的 JSON 对象。
上述代码中要查看的另一对字段是 NullStringValue
和 NullIntValue
。 与 StringValue
和 IntValue
不同,这些值的类型是引用类型 *string
和 *int
。 默认情况下,string
和 int
类型不能有 nil
的值,因为它们的“空”值是 ""
和 0
。 因此,如果要表示可以是一种类型或 nil
的字段,则需要将其设为引用。 例如,假设您有一份用户问卷,并且您希望能够表示用户是否选择不回答问题(null
值),或者用户没有问题的答案( ""
值)。
此代码还更新了 NullIntValue
字段,将 4321
值分配给它,以显示如何将值分配给引用类型,例如 *int
。 在 Go 中,您只能使用变量创建对 原始类型 的引用,例如 int
和 string
。 因此,为了给 NullIntValue
字段赋值,首先将值赋值给另一个变量 otherInt
,然后使用 &otherInt
获取对它的引用(而不是直接做&4321
)。
保存更新后,使用 go run
运行程序:
go run main.go
您的输出将类似于以下内容:
Outputjson data: {"intValue":1234,"boolValue":true,"stringValue":"hello!","dateValue":"2022-03-02T09:10:00Z","objectValue":{"arrayValue":[1,2,3,4]},"nullStringValue":null,"nullIntValue":4321}
您将看到此输出与使用 map[string]interface{}
值时相同,除了这次 nullIntValue
的值为 4321
因为那是 [ X155X]。
最初,设置 struct
值可能需要一些额外的时间,但是一旦定义了它们,就可以在代码中反复使用它们,无论在哪里使用,结果都是一样的他们。 您也可以在一个地方更新它们,而不是试图找到每个可以使用 map
的地方。
Go 的 JSON 编组器还允许您根据值是否为空来控制是否应将字段包含在 JSON 输出中。 有时您可能有一个大型 JSON 对象或您不想一直包含的可选字段,因此省略这些字段可能很有用。 通过 json
结构标签中的 omitempty
选项控制字段是否为空时是否省略。
现在,更新您的程序以使 NullStringValue
字段 omitempty
并添加一个名为 EmptyString
的新字段,具有相同的选项:
main.go
... type myJSON struct { ... NullStringValue *string `json:"nullStringValue,omitempty"` NullIntValue *int `json:"nullIntValue"` EmptyString string `json:"emptyString,omitempty"` } ...
现在,当 myJSON
被编组时,如果 EmptyString
和 NullStringValue
字段的值为空,则它们都将从输出中排除。
保存更改后,使用 go run
运行程序:
go run main.go
您的输出将类似于以下内容:
Outputjson data: {"intValue":1234,"boolValue":true,"stringValue":"hello!","dateValue":"2022-03-02T09:10:00Z","objectValue":{"arrayValue":[1,2,3,4]},"nullIntValue":4321}
这次在输出中,您将看到 nullStringValue
字段不再出现。 由于具有 nil
值将其视为空,因此 omitempty
选项将其从输出中排除。 您还会看到新的 emptyString
字段也不包括在内。 即使 emptyString
值不是 nil
,字符串的默认 ""
值也被认为是空的,因此它也被排除在外。
在本节中,您更新了您的程序以使用 struct
类型来生成具有 json.Marshal
而不是 map
类型的 JSON 数据。 您还更新了程序以从 JSON 输出中省略空字段。
但是,为了让您的程序很好地适应 JSON 生态系统,您需要做的不仅仅是生成 JSON 数据。 您还需要能够读取响应您的请求而发送的 JSON 数据,或其他向您发送请求的系统。 encoding/json
包还提供了一种将 JSON 数据解码为各种 Go 类型的方法。 在下一部分中,您将更新程序以将 JSON 字符串解码为 Go map
类型。
使用地图解析 JSON
与本教程的第一部分类似,您使用 map[string]interface{}
作为一种灵活的方式来生成 JSON 数据,您也可以使用它作为一种灵活的方式来读取 JSON 数据。 json.Unmarshal
函数,本质上与 json.Marshal
函数相反,它将获取 JSON 数据并将其转换回 Go 数据。 您向 json.Unmarshal
提供 JSON 数据以及 Go 变量以将未编组的数据放入其中,如果无法执行此操作,它将返回 error
值,或者返回 [ X181X] 成功时的错误值。 在本节中,您将更新您的程序以使用 json.Unmarshal
函数将 JSON 数据从预定义的 string
值读取到 map
变量中。 您还将更新程序以将 Go 数据打印到输出。
现在,更新您的程序以使用 json.Unmarshal
将 JSON 数据解组为 map[string]interface{}
。 您将首先将原始 data
变量替换为包含 JSON 字符串的 jsonData
变量。 然后,您将声明一个新的 data
变量作为 map[string]interface{}
以接收 JSON 数据。 最后,您将使用 json.Unmarshal
和这些变量来访问 JSON 数据。
打开 main.go
文件并将 main
函数中的行替换为以下内容:
main.go
... func main() { jsonData := ` { "intValue":1234, "boolValue":true, "stringValue":"hello!", "dateValue":"2022-03-02T09:10:00Z", "objectValue":{ "arrayValue":[1,2,3,4] }, "nullStringValue":null, "nullIntValue":null } ` var data map[string]interface{} err := json.Unmarshal([]byte(jsonData), &data) if err != nil { fmt.Printf("could not unmarshal json: %s\n", err) return } fmt.Printf("json map: %v\n", data) }
在此更新中,jsonData
变量是使用 原始字符串文字 设置的,以允许声明跨越多行以便于阅读。 将 data
声明为 map[string]interface{}
后,将 jsonData
和 data
传递给 json.Unmarshal
以将 JSON 数据解组到 [ X128X] 变量。
jsonData
变量作为 []byte
传递给 json.Unmarshal
因为该函数需要 []byte
类型并且 jsonData
最初定义为string
类型。 这是因为 Go 中的 string
可以转换为 []byte
,反之亦然。 data
变量作为引用传递,因为为了使 json.Unmarshal
将数据放入变量中,它需要引用变量在内存中的存储位置。
最后,一旦 JSON 数据被解组到 data
变量中,就可以使用 fmt.Printf
将其打印到屏幕上。
要运行更新后的程序,请保存更改并使用 go run
运行程序:
go run main.go
输出将类似于以下内容:
Outputjson map: map[boolValue:true dateValue:2022-03-02T09:10:00Z intValue:1234 nullIntValue:<nil> nullStringValue:<nil> objectValue:map[arrayValue:[1 2 3 4]] stringValue:hello!]
这一次,您的输出显示了 JSON 翻译的 Go 端。 您有一个 map
值,其中包含来自 JSON 数据的各种字段。 您会看到,即使是 JSON 数据中的 null
字段也会显示在地图中。
现在,由于您的 Go 数据位于 map[string]interface{}
中,因此需要进行一些工作来使用这些数据。 您需要使用所需的 string
键值从 map
获取值,然后您需要确保收到的值是您期望的值,因为它作为 [ X199X] 值。
为此,请打开 main.go
文件并更新您的程序以使用以下代码读取 dateValue
字段:
main.go
... func main() { ... fmt.Printf("json map: %v\n", data) rawDateValue, ok := data["dateValue"] if !ok { fmt.Printf("dateValue does not exist\n") return } dateValue, ok := rawDateValue.(string) if !ok { fmt.Printf("dateValue is not a string\n") return } fmt.Printf("date value: %s\n", dateValue) }
在此更新中,您使用 data["dateValue"]
将 rawDateValue
作为 interface{}
类型,并使用 ok
变量确保 [X129X ] 字段位于 map
中。
然后,您使用 类型断言 断言 rawDateValue
的类型实际上是 string
值,并将其分配给变量 dateValue
。 之后,您再次使用 ok
变量来确保断言成功。
最后,您使用 fmt.Printf
打印 dateValue
。
要再次运行更新后的程序,请保存更改并使用 go run
运行它:
go run main.go
您的输出将类似于以下内容:
Outputjson map: map[boolValue:true dateValue:2022-03-02T09:10:00Z intValue:1234 nullIntValue:<nil> nullStringValue:<nil> objectValue:map[arrayValue:[1 2 3 4]] stringValue:hello!] date value: 2022-03-02T09:10:00Z
您可以看到 date value
行显示从 map
提取并转换为 string
值的 dateValue
字段。
在本节中,您更新了程序以使用带有 map[string]interface{}
变量的 json.Unmarshal
函数将 JSON 数据解组为 Go 数据。 然后,您更新了程序以从围棋数据中提取 dateValue
的值并将其打印到屏幕上。
但是,此更新确实显示了使用 map[string]interface{}
在 Go 中解组 JSON 的缺点之一。 由于 Go 不知道每个字段是哪种类型的数据(它唯一知道的是它是一个 interface{}
),它可以做的最好的解组数据就是做出最好的猜测。 这意味着无法为您解组 dateValue
字段的 time.Time
等复杂值,只能作为 string
访问。 如果您尝试以这种方式访问 map
中的任何数值,则会发生类似的问题。 由于 json.Unmarshal
不知道该数字是否应该是 int
、float
、int64
等等,所以它可以做出的最佳猜测是将其放入可用的最灵活的数字类型中,即 float64
。
虽然使用 map
来解码 JSON 数据可能很灵活,但在解释您拥有的数据时也会为您留下更多的工作。 类似于 json.Marshal
函数可以使用 struct
值来生成 JSON 数据,json.Unmarshal
函数可以使用 struct
值来读取 JSON 数据。 这可以帮助消除使用 map
的类型断言复杂性,方法是使用 struct
字段上的类型定义来确定 JSON 数据应解释为哪些类型。 在下一节中,您将更新您的程序以使用 struct
类型来消除这些复杂性。
使用结构解析 JSON
当您读取 JSON 数据时,您很有可能已经知道所接收数据的结构; 否则,将难以解释。 您可以使用这些结构知识为 Go 提供一些关于您的数据是什么样的以及您期望的数据类型的提示。
在上一节中,您定义了 myJSON
和 myObject
struct
值并添加了 json
结构标签,让 Go 在生成 JSON 时知道如何命名字段. 现在您可以使用相同的 struct
值来解码您一直在使用的 JSON 字符串,如果您正在编组和解组相同的 JSON 数据,这有助于减少程序中的重复代码。 使用 struct
解组 JSON 数据的另一个好处是,您可以告诉 Go 每个字段预期的数据类型。 最后,您还可以使用 Go 的编译器检查您在字段上使用的名称是否正确,而不是在您将与 map
值一起使用的 string
值中可能会丢失拼写错误.
现在,打开您的 main.go
文件并更新 data
变量声明以使用对 myJSON
struct
的引用,并添加一些 [X141X ] 行显示 myJSON
上各个字段的数据:
main.go
... func main() { ... var data *myJSON err := json.Unmarshal([]byte(jsonData), &data) if err != nil { fmt.Printf("could not unmarshal json: %s\n", err) return } fmt.Printf("json struct: %#v\n", data) fmt.Printf("dateValue: %#v\n", data.DateValue) fmt.Printf("objectValue: %#v\n", data.ObjectValue) }
由于您之前定义了 struct
类型,因此您只需更新 data
字段的类型以支持解组为 struct
。 其余的更新显示了 struct
本身的一些数据。
现在,保存更新并使用 go run
运行程序:
go run main.go
您的输出将类似于以下内容:
Outputjson struct: &main.myJSON{IntValue:1234, BoolValue:true, StringValue:"hello!", DateValue:time.Date(2022, time.March, 2, 9, 10, 0, 0, time.UTC), ObjectValue:(*main.myObject)(0x1400011c180), NullStringValue:(*string)(nil), NullIntValue:(*int)(nil), EmptyString:""} dateValue: time.Date(2022, time.March, 2, 9, 10, 0, 0, time.UTC) objectValue: &main.myObject{ArrayValue:[]int{1, 2, 3, 4}}
这次在输出中有几件事需要注意。 您将在 json struct
行和 dateValue
行中看到 JSON 数据中的日期值现已转换为 time.Time
值([X155X ] 格式是 %#v
用作格式动词时显示的内容)。 由于 Go 能够在 myJSON
的 DateValue
字段上看到 time.Time
类型,因此它也能够为您解析 string
值。
需要注意的另一件事是 EmptyString
显示在 json struct
行上,即使它没有包含在原始 JSON 数据中。 如果某个字段包含在用于 JSON 解组的 struct
中,并且未包含在被解组的 JSON 数据中,则该字段仅设置为其类型的默认值并被忽略。 通过这种方式,您可以安全地定义 JSON 数据可能具有的所有可能字段,而不必担心如果流程的任一侧都不存在字段时会出现错误。 NullStringValue
和 NullIntValue
也都设置为默认值 nil
,因为 JSON 数据表明它们的值为 null
,但它们也将设置为nil
如果这些字段已从 JSON 数据中排除。
类似于当 JSON 数据中缺少 emptyString
字段时 json.Unmarshal
忽略 struct
上的 EmptyString
字段,反之亦然。 如果一个字段包含在 JSON 数据中,但在 Go struct
上没有相应的字段,则该 JSON 字段将被忽略并继续解析下一个 JSON 字段。 这样,如果您正在读取的 JSON 数据非常大,而您的程序只关心其中的一小部分字段,您可以选择创建一个仅包含您关心的字段的 struct
。 JSON 数据中包含的任何未在 struct
上定义的字段都将被忽略,Go 的 JSON 解析器将继续处理下一个字段。
要查看实际情况,请最后一次打开 main.go
文件并更新 jsonData
以包含 myJSON
中未包含的字段:
main.go
... func main() { jsonData := ` { "intValue":1234, "boolValue":true, "stringValue":"hello!", "dateValue":"2022-03-02T09:10:00Z", "objectValue":{ "arrayValue":[1,2,3,4] }, "nullStringValue":null, "nullIntValue":null, "extraValue":4321 } ` ... }
添加 JSON 数据后,保存文件并使用 go run
运行它:
go run main.go
您的输出将类似于以下内容:
Outputjson struct: &main.myJSON{IntValue:1234, BoolValue:true, StringValue:"hello!", DateValue:time.Date(2022, time.March, 2, 9, 10, 0, 0, time.UTC), ObjectValue:(*main.myObject)(0x14000126180), NullStringValue:(*string)(nil), NullIntValue:(*int)(nil), EmptyString:""} dateValue: time.Date(2022, time.March, 2, 9, 10, 0, 0, time.UTC) objectValue: &main.myObject{ArrayValue:[]int{1, 2, 3, 4}}
您不应该看到此输出与之前的输出有任何区别,因为 Go 将忽略 JSON 数据中的 extraValue
字段并继续。
在本节中,您更新了程序以使用您之前定义的 struct
类型来解组 JSON 数据。 您看到了 Go 如何为您解析 time.Time
值并忽略在 struct
类型上定义的 EmptyString
字段,但不在 JSON 数据中。 您还向 JSON 数据添加了一个附加字段,以查看 Go 将安全地继续解析数据,即使您仅在 JSON 数据中定义了字段的子集。
结论
在本教程中,您创建了一个新程序来使用 Go 标准库中的 encoding/json
包。 首先,您使用具有 map[string]interface{}
类型的 json.Marshal
函数以灵活的方式创建 JSON 数据。 然后,您更新了您的程序以使用带有 json
结构标记的 struct
类型,以使用 json.Marshal
以一致且可靠的方式生成 JSON 数据。 之后,您使用带有 map[string]interface{}
类型的 json.Unmarshal
函数将 JSON 字符串解码为 Go 数据。 最后,您使用了之前使用 json.Unmarshal
函数定义的 struct
类型,让 Go 根据这些 struct
字段为您进行解析和类型转换。
使用 encoding/json
包,您将能够与 Internet 上提供的许多 API 进行交互,以创建您自己与流行网站的集成。 您还可以将自己程序中的 Go 数据转换为可以保存的格式,然后稍后加载以从程序停止的地方继续。
除了您在本教程中使用的函数之外,encoding/json 包还包括其他可用于与 JSON 交互的有用函数和类型。 例如,json.MarshalIndent 函数可用于 pretty print JSON 数据进行故障排除。
本教程也是 DigitalOcean How to Code in Go 系列的一部分。 该系列涵盖了许多 Go 主题,从第一次安装 Go 到如何使用语言本身。