I've been trying to parse this JSON message from PubNub without any luck:
type PubNubMessage struct {
body []string
}
[[{"text":"hey"}],"1231212412423235","channelName"]
json: cannot unmarshal array into Go value of type main.PubNubMessage
Does anyone have an idea on how to decode such complex types in golang?
The short answer is that you cannot directly unmarshal a JSON array of non-homogenous types (per your example) into a golang struct.
The long answer is that you should define an (m *PubNubMessage) UnmarshalJSON([]byte) error
method for your PubNubMessage type which unmarshals the JSON string into an interface{}
and then uses type assertions to ensure the expected format and populates the structure.
For example:
type TextMessage struct {
Text string
}
type PubNubMessage struct {
Messages []TextMessage
Id string
Channel string
}
func (pnm *PubNubMessage) UnmarshalJSON(bs []byte) error {
var arr []interface{}
err := json.Unmarshal(bs, &arr)
if err != nil {
return err
}
messages := arr[0].([]interface{}) // TODO: proper type check.
pnm.Messages = make([]TextMessage, len(messages))
for i, m := range messages {
pnm.Messages[i].Text = m.(map[string]interface{})["text"].(string) // TODO: proper type check.
}
pnm.Id = arr[1].(string) // TODO: proper type check.
pnm.Channel = arr[2].(string) // TODO: proper type check.
return nil
}
// ...
jsonStr := `[[{"text":"hey"},{"text":"ok"}],"1231212412423235","channelName"]`
message := PubNubMessage{}
err := json.Unmarshal([]byte(jsonStr), &message)
You can define UnmarshalJSON
on your PubNubMessage
to provide custom JSON deserialization. You probably should tweak this a little bit for your purposes, but the general idea is that you just unmarshal this json array into a slice, and then get all necessary parts from it.
Playground example here
Your json is a heterogeneous array. You can at least define PubNubMessage
as either
type PubNubMessage []interface{}
and then access data with type assertions text:= (message[0].([]interface {})[0].(map[string]interface {}))["text"].(string)
working example https://play.golang.org/p/xhwbE2ora1
or type PubNubMessage []json.RawMessage
and after json.Unmarshal(jsonBlob, &message)
do 'json.Unmarshal(message[0], structured.text)' for each peace separately
https://play.golang.org/p/TJ0DfiweGo
It is easier to use alternative to encoding/json
packages for parsing JSON arrays with values of distinct types. For instance, give a try to fastjson. It easily (and quickly) parses such arrays:
input := `[[{"text":"hey"}],"1231212412423235","channelName"]`
var p fastjson.Parser
v, err := p.Parse(input)
if err != nil {
log.Fatal(err)
}
a := v.GetArray()
for _, vv := range a {
switch vv.Type() {
case fastjson.TypeArray:
fmt.Printf("array %s\n", vv)
case fastjson.TypeString:
fmt.Printf("string %s\n", vv)
}
}
Additionally fastjson
provides handy functions for obtaining only the required fields from JSON:
// get v[0].text as Go byte slice
text := v.GetStringBytes("0", "text")
// get v[2]
channelName := v.Get("2") // this is the same as v.GetArray()[2]