假如要設計一個統計的json解析模塊,json格式為
{
"type": "用來識別不同的json數據",
"msg": "嵌套的實際數據"
}
代碼
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)
}
我們定義Msg類型為interface{},用來接受任意的類型。接下來試着解析msg中的字段
const input = `
{
"type": "sound",
"msg": {
"description": "dynamite",
"authority": "the Bruce Dickinson"
}
}
`
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)
有更好的寫法,使用*json.RawMessage, 將msg字段延遲解析
type Envelope {
Type string
Msg *json.RawMessage
}
結合interface{}和*json.RawMessage的完整例子
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)
}
}
第一部分結束了,接下來還有來個地方可以提升
- 將定義的json數據中的type字段抽出來,單獨定義成一個枚舉常量。需要使用github.com/campoy/jsonenums
//go:generate jsonenums -type=Kind
type Kind int
const (
sound Kind = iota
cowbell
)
定義完上述內容后,執行命令
jsonenums -type=Pill
這個模塊會自動生成一個*_jsonenums.go的文件,里面定義好了
func (t T) MarshalJSON() ([]byte, error)
func (t *T) UnmarshalJSON([]byte) error
這樣,就幫我們把自定義的Kind和json type里的序列化和反序列化都做好了
2. 針對不同的json type字段,可以定義一個方法來返回不同的msg struct
var kindHandlers = map[Kind]func() interface{}{
sound: func() interface{} { return &SoundMsg{} },
cowbell: func() interface{} { return &CowbellMsg{} },
}
- 結合1,2把之前代碼的switch塊去掉
完整代碼:
type App struct {
// whatever your application state is
}
// Action is something that can operate on the application.
type Action interface {
Run(app *App) error
}
type CowbellMsg struct {
// ...
}
func (m *CowbellMsg) Run(app *App) error {
// ...
}
type SoundMsg struct {
// ...
}
func (m *SoundMsg) Run(app *App) error {
// ...
}
var kindHandlers = map[Kind]func() Action{
sound: func() Action { return &SoundMsg{} },
cowbell: func() Action { return &CowbellMsg{} },
}
func main() {
app := &App{
// ...
}
// process an incoming message
var raw json.RawMessage
env := Envelope{
Msg: &raw,
}
if err := json.Unmarshal([]byte(input), &env); err != nil {
log.Fatal(err)
}
msg := kindHandlers[env.Type]()
if err := json.Unmarshal(raw, msg); err != nil {
log.Fatal(err)
}
if err := msg.Run(app); err != nil {
// ...
}
}
接下來是另外一種設想,加入定義的json字段都放在最外層,即沒有了嵌套的msg字段
{
"type": "用來識別不同的json數據",
...
}
那需要umarshal兩次json,第一次比對type字段,針對不同的type字段來unmarsh一次
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)
}
}
本文是下述博客的翻譯和整理,僅供參考