【问题标题】:Handling different types when unmarshalling a json [duplicate]解组json时处理不同的类型[重复]
【发布时间】:2019-08-02 21:57:18
【问题描述】:

我正在使用一个端点(我不拥有它并且我无法修复它)并且这个端点返回 JSON。

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

格式一:

{
  "message": "Message"
}

{
  "message": ["ERROR_CODE"]
}

取决于发生了什么。

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

在 Go 中可以做到吗?我认为的第一种方法是有两个结构并尝试解码为带有string 的结构,如果发生错误,尝试解码为带有array 的结构。

有没有更优雅的方法?

【问题讨论】:

    标签: json go decode


    【解决方案1】:

    将其解组为interface{} 类型的值,并使用type assertiontype 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]
    

    【讨论】:

      【解决方案2】:

      您可以使用实现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

      【讨论】:

        【解决方案3】:

        我建议为 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

        【讨论】:

          猜你喜欢
          • 2013-09-02
          • 1970-01-01
          • 2020-08-28
          • 1970-01-01
          • 2020-03-02
          • 1970-01-01
          • 1970-01-01
          • 2023-03-13
          • 1970-01-01
          相关资源
          最近更新 更多