Assume I have a JSON message that looks like this:
{
"type": string
"data": list of something that is based on above type
}
two examples might be
{
"type": "car"
"data": [{"color": "red", "mpg": 16.4}]
}
and
{
"type": "house"
"data": [{"color": "blue", "state": "CA"}]
}
I want to define a struct so I can basically only decode the type and then use that to properly unmarshal the data field. I tried the following
type message struct {
Type string `json:"type"`
Data []byte `json:"data"`
}
type car struct {
Color string
MPG float64
}
type house struct {
Color string
State string
}
erroneously thinking that it would just leave the Data field raw for me to unmarshal later into either the car or house struct. I know I can define Data as a []interface{} and do some other work to get what I want, but I was wondering if this is (currently) the best way in Go? In case it comes up, assume I cannot change the JSON definitions – I’m just the consumer of a service here.
>Solution :
This is a perfect use-case for json.RawMessage. Check out the Unmarshal example there.
For your example, this would look something like:
type message struct {
Type string `json:"type"`
Data []json.RawMessage `json:"data"`
}
type car struct {
Color string
MPG float64
}
type house struct {
Color string
State string
}
func main() {
if err := parseAndPrint(carJSON); err != nil {
panic(err)
}
if err := parseAndPrint(houseJSON); err != nil {
panic(err)
}
}
func parseAndPrint(b []byte) error {
msg := new(message)
if err := json.Unmarshal(b, msg); err != nil {
panic(err)
}
switch msg.Type {
case "car":
for _, data := range msg.Data {
c := new(car)
if err := json.Unmarshal(data, c); err != nil {
return err
}
fmt.Println(c)
}
case "house":
for _, data := range msg.Data {
h := new(house)
if err := json.Unmarshal(data, h); err != nil {
return err
}
fmt.Println(h)
}
}
return nil
}
// Tucked here to get out of the way of the example
var carJSON = []byte(`
{
"type": "car",
"data": [{"color": "red", "mpg": 16.4}]
}
`)
var houseJSON = []byte(`{
"type": "house",
"data": [{"color": "blue", "state": "CA"}]
}
`)
Now what you do with the parsed result is kinda up to you and your program’s needs. Parse at the edge and only pass down the fully-typed message, add a Parsed any field to the outer struct, etc.