解组 json 时处理不同的类型

Handling different types when unmarshalling a json

我正在使用一个端点(我不拥有并且无法修复)和这个端点 returns JSON.

问题是这个 JSON 可以有不同的格式:

格式 1:

{
  "message": "Message"
}

{
  "message": ["ERROR_CODE"]
}

取决于发生的事情。

我想要一个结构来保存此响应,以便稍后我可以检查 message 是字符串还是数组,并正确地遵循流程。

可以用 Go 实现吗?我认为的第一种方法是有两个结构并尝试解码为 string 的结构,如果发生错误,则尝试解码为 array.

的结构

有没有更优雅的方法?

将其解组为 interface{} 类型的值,并使用 type assertion or type switch 来检查其最终值的类型。请注意,默认情况下 JSON 数组被解组为 []interface{} 类型的值,因此您必须检查它以检测错误响应。

例如:

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)
        }
    }
}

输出(在 Go Playground 上尝试):

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

您可以使用实现 json.Unmarshaller 的自定义类型并尝试依次解码每种可能的输入格式:

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
}

比起解组到 interface{} 和稍后类型断言,我更喜欢这种方法,因为所有类型检查都封装在解组步骤中。

有关真实示例,请查看我的 type veryFlexibleUint64https://github.com/sapcc/limes/blob/fb212143c5f5b3e9272994872fcc7b758ae47646/pkg/plugins/client_ironic.go

我建议为 Message 创建一个不同的类型并制作那个工具 json.Unmarshaller

代码如下

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"`
}

您可能会找到带有测试的完整代码 here