如何處理動態JSON in Go

假如要設計一個統計的json解析模塊,json格式爲git

{
    "type": "用來識別不一樣的json數據",
    "msg": "嵌套的實際數據"
}

代碼github

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中的字段golang

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字段延遲解析json

type Envelope {
    Type string
    Msg  *json.RawMessage
}

結合interface{}和*json.RawMessage的完整例子app

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)
    }
}

第一部分結束了,接下來還有來個地方能夠提高.net

  1. 將定義的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裏的序列化和反序列化都作好了code

  1. 針對不一樣的json type字段,能夠定義一個方法來返回不一樣的msg struct
var kindHandlers = map[Kind]func() interface{}{
    sound:   func() interface{} { return &SoundMsg{} },
    cowbell: func() interface{} { return &CowbellMsg{} },
}
  1. 結合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字段ip

{
    "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)
    }
}

本文是下述博客的翻譯和整理,僅供參考

  1. Dynamic JSON in Go
  2. Go JSON unmarshaling based on an enumerated field value
相關文章
相關標籤/搜索