在解组json时处理不同的类型[重复]

This question already has an answer here:

I'm consuming an endpoint (which I don't own and I cannot fix) and this endpoint returns JSON.

The problem is this JSON can come in different formats:

Format 1:

{
  "message": "Message"
}

or

{
  "message": ["ERROR_CODE"]
}

Depending on what happened.

I'd like to have one struct to hold this response so later I can check if the message is a string or array, and properly follow a flow.

Is it possible to do it in Go? The first approach I thought was to have two structs and try to decode to the one with string, and if an error happens, try to decode to the one with array.

Is there a more elegant approach?

</div>

Unmarshal it into a value of interface{} type, and use a type assertion or type switch to inspect the type of value it ends up. Note that by default JSON arrays are unmarshaled into a value of type []interface{}, so you have to check for that to detect the error response.

For example:

type Response struct {
    Message interface{} `json:"message"`
}

func main() {
    inputs := []string{
        `{"message":"Message"}`,
        `{"message":["ERROR_CODE"]}`,
    }

    for _, input := range inputs {
        var r Response
        if err := json.Unmarshal([]byte(input), &r); err != nil {
            panic(err)
        }
        switch x := r.Message.(type) {
        case string:
            fmt.Println("Success, message:", x)
        case []interface{}:
            fmt.Println("Error, code:", x)
        default:
            fmt.Println("Something else:", x)
        }
    }
}

Output (try it on the Go Playground):

Success, message: Message
Error, code: [ERROR_CODE]

You can use a custom type that implements json.Unmarshaller and tries to decode each possible input format in turn:

type Message struct {
    Text string
    Codes []int
}

func (m *Message) UnmarshalJSON(input []byte) error {
    var text string
    err := json.Unmarshal(input, &text)
    if err == nil {
        m.Text = text
        m.Codes = nil
        return nil
    }

    var codes []int
    err := json.Unmarshal(input, &codes)
    if err == nil {
        m.Text = nil
        m.Codes = codes
        return nil
    }

    return err
}

I prefer this approach over unmarshalling into interface{} and type-asserting later because all the type checks are encapsulated in the unmarshalling step.

For a real-world example, check out my type veryFlexibleUint64: https://github.com/sapcc/limes/blob/fb212143c5f5b3e9272994872fcc7b758ae47646/pkg/plugins/client_ironic.go

I would suggest creating a different type for Message and making that implement json.Unmarshaller

here is how the code would be

type message struct {
    Text  string
    Codes []string //or int , assuming array of string as it was not mentioned in the question
}

func (m *message) UnmarshalJSON(input []byte) error {
    if len(input) == 0 {
        return nil
    }
    switch input[0] {
    case '"':
        m.Text = strings.Trim(string(input), `"`)
        return nil
    case '[':
        return json.Unmarshal(input, &m.Codes)
    default:
    return fmt.Errorf(`invalid character %q looking for " or [`, input[0])

    }
}

type Error struct {
    Message message `json:"message"`
}

You may find full code with test here