0

I get a message like

type message struct {
    Type   string       `json:"type"`
    Data   interface{}  `json:"data"`
}

The Data type depends on the Type. Simply put, I want to get the Type from the message, swith over it, and depending on the result of json.Unmarshal(Data) into a specific structure. But it doesn’t work that way, because when I Unmarshal this message, Data immediately turns into a map[string]interface and then I can no longer turn it into a structure normally (well, either I have to Unmarshal 2 times). How can this problem be properly solved?

ERVIN228
  • 29
  • 2
  • @Fimzy, I'd say both the linked answers offer the solutions I'd qualify as suboptimal compared to simple elegant `json.RawMessage`. – kostix Oct 04 '22 at 09:46

1 Answers1

0

Then do two-staged decoding—exploiting the fact encoding/json has a special type RawMessage with the semantics "just save the sequence of bytes representing this value as is":

package main

import (
    "encoding/json"
    "fmt"
)

type message struct {
    Type string          `json:"type"`
    Data json.RawMessage `json:"data"`
}

type A struct {
    Foo string
    Bar string
}

type B struct {
    Quux  int
    Blorb []int
}

func decodeMessage(b []byte) (interface{}, error) {
    var m message
    err := json.Unmarshal(b, &m)
    if err != nil {
        return nil, err
    }

    switch m.Type {
    case "a":
        var a A
        err = json.Unmarshal(m.Data, &a)
        if err != nil {
            return nil, err
        }
        return a, nil
    case "b":
        var b B
        err = json.Unmarshal(m.Data, &b)
        if err != nil {
            return nil, err
        }
        return b, nil
    }
    return nil, fmt.Errorf("cannot handle type: %s", m.Type)
}

const msgA = `{
  "type": "a",
  "data": {
    "Foo": "xyzzy",
    "Bar": "r0xx0rz"
  }
}`

const msgB = `{
  "type": "b",
  "data": {
    "Quux": 42,
    "Blorb": [1, 2, 3, 4]
  }
}`

const msgX = `{
  "type": "x",
  "data": null
}`

func main() {
    for _, s := range []string{msgA, msgB, msgX} {
        d, err := decodeMessage([]byte(s))
        fmt.Println(d, err)
    }
}

Playground.

Another variant which is a bit different:

package main

import (
    "encoding/json"
    "fmt"
)

type message struct {
    Type string          `json:"type"`
    Data json.RawMessage `json:"data"`
}

type A struct {
    Foo string
    Bar string
}

type B struct {
    Quux  int
    Blorb []int
}

func decodeMessage(b []byte) (interface{}, error) {
    var m message
    err := json.Unmarshal(b, &m)
    if err != nil {
        return nil, err
    }

    var v interface{}
    switch m.Type {
    case "a":
        v = &A{}
    case "b":
        v = &B{}
    default:
        return nil, fmt.Errorf("cannot handle type: %s", m.Type)
    }

    err = json.Unmarshal(m.Data, v)
    if err != nil {
        return nil, err
    }
    return v, nil
}

const msgA = `{
  "type": "a",
  "data": {
    "Foo": "xyzzy",
    "Bar": "r0xx0rz"
  }
}`

const msgB = `{
  "type": "b",
  "data": {
    "Quux": 42,
    "Blorb": [1, 2, 3, 4]
  }
}`

const msgX = `{
  "type": "x",
  "data": null
}`

func main() {
    for _, s := range []string{msgA, msgB, msgX} {
        d, err := decodeMessage([]byte(s))
        fmt.Println(d, err)
    }
}

Playground.

kostix
  • 51,517
  • 14
  • 93
  • 176