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 figur
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)
}
}
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
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\n", 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\n", channelID, username)
for _, prevMessage := range h.previousMessages(channelID, m.Limit) {
c.Client.send <- prevMessage
}