如何在golang中解析JSON而不进行两次封送处理

I've got a web socket connection that sends different types of messages in a JSON object, and I want to unmarshal the contents into some known structs. To do this, I figure I should do the following:

Step 1) Unmarshal the JSON into a generic map[string]interface{}

Step 2) Find the key I'm looking for

Step 3) Try to cast the value into one of my types (this fails)

Step 3 alternate) json marshal this value and unmarshal it to my known struct

If I try to use myStruct, ok := value.(myType) it will fail, but if I json.marshal(value) and then json.unmarshal to myStruct, it works just fine. Is that how I'm supposed to do this? Going json-> map[string]interface{} -> json -> myStruct seems redundant to me.

Sample code:

https://play.golang.org/p/to_0Id_ja9

package main

import (
    "encoding/json"
    "fmt"
)

type Ping struct {
    Ping string `json:"ping"`
}

type Ack struct {
    Messages []Message `json:"messages"`
}

type Message string

func main() {
    testJSON := []byte(`{"ack":{"messages":["Hi there","Hi again"]}}`)
    var myAck = Ack{}
    var myMap map[string]interface{}
    err := json.Unmarshal(testJSON, &myMap)
    if err != nil {
        fmt.Println("error unmarshalling: ", err)
    }
    for k, v := range myMap {
        fmt.Printf("key: %s, value: %s 
", k, v)

        switch k {
        case "ping":
            fmt.Println(k, " is a ping", v)
        case "ack":
            fmt.Println(k, " is an ack containing a message list")
            ackjson, err := json.Marshal(v)
            if err != nil {
                fmt.Println("marshal error: ", err)
            }
            err = json.Unmarshal(ackjson, &myAck)
            if err != nil {
                fmt.Println("unmarshal error", err)
            } else {
                fmt.Println("New ack object: ", myAck)
            }
        default:
            fmt.Printf("%s is of a type (%T) I don't know how to handle", k, v)
        }
    }
}

You will want to create a struct which contains all keys you might receive, then unmarshal once, finally based on which keys are nil you can decide what kind of packet you got.

The example below displays this behavior: https://play.golang.org/p/aFG6M0SPJs

package main

import (
  "encoding/json"
  "fmt"
  "strings"
)


type Ack struct {
  Messages []string `json:"messages"`
}

type Packet struct {
  Ack  * Ack    `json:"ack"`
  Ping * string `json:"ping"`
}

func runTest(testJSON []byte) {
  var p = Packet{}
  err := json.Unmarshal(testJSON, &p)
  if err != nil {
    fmt.Println("error unmarshalling: ", err)
  }
  if (p.Ack != nil) {
    fmt.Println("Got ACK: ", strings.Join(p.Ack.Messages, ", "));
  } else if (p.Ping != nil){
          fmt.Println("Got PING");
  }
}

func main() {
  tests := [][]byte{
    []byte(`{"ack":{"messages":["Hi there","Hi again"]}}`),
    []byte(`{"ping": "Are you there?"}`),
  }
  for _, test := range tests {
    runTest(test)
  }

}

Another solution is to use a common key between all messages (eg: type) which indicates what type of message it is. After this, if the messages aren't sufficiently complicated, you can build the raw structs from the other map keys / type assert map values:

From some related websocket code I wrote that does this: https://github.com/blaskovicz/cut-me-some-slack/blob/master/chat/message.go#L66

func DecodeClientMessage(c *ClientMessage) (typedMessage interface{}, err error) {
    buff := map[string]string{}
    err = json.NewDecoder(bytes.NewReader(c.Raw)).Decode(&buff)
    if err != nil {
        return
    }

    switch t := buff["type"]; t {
    case "message":
        channelID := buff["channel_id"]
        if channelID == "" {
            err = fmt.Errorf("invalid client message received: missing channel_id")
            return
        }
        cms := &ClientMessageSend{ChannelID: channelID, Text: buff["text"]}
        if cms.Text == "" {
            err = fmt.Errorf("invalid client message received: missing text")
        } else {
            typedMessage = cms
        }

... and then the caller code does a result.(type) assertion and switch: https://github.com/blaskovicz/cut-me-some-slack/blob/master/chat/hub.go#L150

switch m := raw.(type) {
case *ClientMessageHistory:
    channelID := h.resolveSlackChannel(m.ChannelID)
    if channelID == "" {
        log.Printf("error: no channel found matching %s
", m.ChannelID)
        return
    }
    var username string
    if c.Client.User != nil {
        username = c.Client.User.Username
    } else {
        username = "<anonymous>"
    }
    log.Printf("sending previous messages for channel %s to client %s
", channelID, username)
    for _, prevMessage := range h.previousMessages(channelID, m.Limit) {
        c.Client.send <- prevMessage
    }

One solution is to partially unmarshal the data by unmarshalling the values into a json.RawMessage instead of an interface{}

var myMap map[string]json.RawMessage

Later in the switch, which still is required, you do not need to marshal. Just do:

err = json.Unmarshal(v, &myAck)

Playground: https://play.golang.org/p/NHd3uH5e7z