Go 语言是静态类型语言,虽然它也可以表现出动态类型,但是使用一个嵌套的 `map[string]interface{}` 在那里乱叫会让代码变得特别丑。通过掌握语言的静态特性,我们可以做的更好。
通过同一通道交换多种信息的时候,我们经常需要 JSON 具有动态的,或者更合适的参数内容。首先,让我们来讨论一下消息封装(message envelopes),JSON 在这里看起来就像这样:
```json
{
"type": "this part tells you how to interpret the message",
"msg": ...the actual message is here, in some kind of json...
}
```
## 通过不同的消息类型生成 JSON
通过 `interface{}`,我们可以很容易的将数据结构编码成为独立封装的,具有多种类型的消息体的 JSON 数据。为了生成下面的 JSON :
```json
{
"type": "sound",
"msg": {
"description": "dynamite",
"authority": "the Bruce Dickinson"
}
}
```
```json
{
"type": "cowbell",
"msg": {
"more": true
}
}
```
我们可以使用这些 Go 类型:
```go
package main
import (
"encoding/json"
"fmt"
"log"
)
type Envelope struct {
Type string
Msg interface{}
}
type Sound struct {
Description string
Authority string
}
type Cowbell struct {
More bool
}
func main() {
s := Envelope{
Type: "sound",
Msg: Sound{
Description: "dynamite",
Authority: "the Bruce Dickinson",
},
}
buf, err := json.Marshal(s)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", buf)
c := Envelope{
Type: "cowbell",
Msg: Cowbell{
More: true,
},
}
buf, err = json.Marshal(c)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", buf)
}
```
输出的结果是:
```json
{"Type":"sound","Msg":{"Description":"dynamite","Authority":"the Bruce Dickinson"}}
{"Type":"cowbell","Msg":{"More":true}}
```
这些并没有什么特殊的。
## 解析 JSON 到动态类型
如果你想将上面的 JSON 对象解析成为一个 `Envelope` 类型的对象,最终你会将 `Msg` 字段解析成为一个 `map[string]interface{}`。 这种方式不是很好用,会使你后悔你的选择。
```go
package main
import (
"encoding/json"
"fmt"
"log"
)
const input = `
{
"type": "sound",
"msg": {
"description": "dynamite",
"authority": "the Bruce Dickinson"
}
}
`
type Envelope struct {
Type string
Msg interface{}
}
func main() {
var env Envelope
if err := json.Unmarshal([]byte(input), &env); err != nil {
log.Fatal(err)
}
// for the love of Gopher DO NOT DO THIS
var desc string = env.Msg.(map[string]interface{})["description"].(string)
fmt.Println(desc)
}
```
输出:
```
dynamite
```
## 明确的解析方式
就像前面说的,我推荐修改 `Envelope` 类型,就像这样:
```go
type Envelope {
Type string
Msg *json.RawMessage
}
```
[`json.RawMessage`](http://golang.org/pkg/encoding/json/#RawMessage) 非常有用,它可以让你延迟解析相应的 JSON 数据。它会将未处理的数据存储为 `[]byte`。
这种方式可以让你显式控制 `Msg` 的解析。从而延迟到获取到 `Type` 的值之后,依据 `Type` 的值进行解析。这种方式不好的地方在于你需要先明确解析 `Msg`,或者你需要单独分为 `EnvelopeIn` 和 `EnvelopeOut` 两种类型,其中 `EnvelopeOut` 仍然有 `Msg interface{}`。
## 结合 `*json.RawMessage` 和 `interface{}` 的优点
那么如何将上述两者好的一面结合起来呢?通过在 `interface{}` 字段中放入 `*json.RawMessage`!
```go
package main
import (
"encoding/json"
"fmt"
"log"
)
const input = `
{
"type": "sound",
"msg": {
"description": "dynamite",
"authority": "the Bruce Dickinson"
}
}
`
type Envelope struct {
Type string
Msg interface{}
}
type Sound struct {
Description string
Authority string
}
func main() {
var msg json.RawMessage
env := Envelope{
Msg: &msg,
}
if err := json.Unmarshal([]byte(input), &env); err != nil {
log.Fatal(err)
}
switch env.Type {
case "sound":
var s Sound
if err := json.Unmarshal(msg, &s); err != nil {
log.Fatal(err)
}
var desc string = s.Description
fmt.Println(desc)
default:
log.Fatalf("unknown message type: %q", env.Type)
}
}
```
输出:
```
dynamite
```
## 如何把所有数据都放在最外层(顶层)
虽然我极其推荐你将动态可变的部分放在一个单独的 key 下面,但是有时你可能需要处理一些预先存在的数据,它们并没有用这样的方式进行格式化。
如果可以的话,请使用文章前面提到的风格。
```json
{
"type": "this part tells you how to interpret the message",
...the actual message is here, as multiple keys...
}
```
我们可以通过解析两次数据的方式来解决。
```go
package main
import (
"encoding/json"
"fmt"
"log"
)
const input = `
{
"type": "sound",
"description": "dynamite",
"authority": "the Bruce Dickinson"
}
`
type Envelope struct {
Type string
}
type Sound struct {
Description string
Authority string
}
func main() {
var env Envelope
buf := []byte(input)
if err := json.Unmarshal(buf, &env); err != nil {
log.Fatal(err)
}
switch env.Type {
case "sound":
var s struct {
Envelope
Sound
}
if err := json.Unmarshal(buf, &s); err != nil {
log.Fatal(err)
}
var desc string = s.Description
fmt.Println(desc)
default:
log.Fatalf("unknown message type: %q", env.Type)
}
}
```
```
dynamite
```
本文由 GCTT 原创编译,Go语言中文网 荣誉推出
有疑问加站长微信联系(非本文作者))