【问题标题】:Unmarshaling json in Go: required field?在 Go 中解组 json:必填字段?
【发布时间】:2023-03-14 01:30:01
【问题描述】:

如果在使用 Go 解析 JSON 输入时未找到字段,是否会产生错误?

我在文档中找不到它。

是否有标签根据需要指定字段?

【问题讨论】:

    标签: json go


    【解决方案1】:

    encoding/json 包中没有将字段设置为“必需”的标记。您要么必须编写自己的 MarshalJSON() 方法,要么对缺失的字段进行后期检查。

    要检查缺失的字段,您必须使用指针来区分缺失/空值和零值:

    type JsonStruct struct {
        String *string
        Number *float64
    }
    

    完整的工作示例:

    package main
    
    import (
        "fmt"
        "encoding/json"
    )
    
    type JsonStruct struct {
        String *string
        Number *float64
    }
    
    var rawJson = []byte(`{
        "string":"We do not provide a number"
    }`)
    
    
    func main() {
        var s *JsonStruct
        err := json.Unmarshal(rawJson, &s)
        if err != nil {
            panic(err)
        }
    
        if s.String == nil {
            panic("String is missing or null!")
        }
    
        if s.Number == nil {
            panic("Number is missing or null!")
        }
    
        fmt.Printf("String: %s  Number: %f\n", *s.String, *s.Number)
    }
    

    Playground

    【讨论】:

    • 或者您可以只检查默认值,这对于大多数情况来说可能就足够了(除非您使用的是数字)。
    • 但是如果“必填字段”的值是默认值呢?
    • 如果一个数字的必填字段可能为 0 并且您还需要检查它是否提供,然后使用字符串来允许检查没有输入,然后将字符串转换为数字之后。
    • 如果必填字段是struct 怎么办?
    • @DeepakSah 结构相同。使用指向结构的指针。例如。 Object *Foo,然后检查是否s.Object == nil
    【解决方案2】:

    您还可以覆盖特定类型的解组(因此,一个必填字段隐藏在几个 json 层中),而不必使该字段成为指针。 UnmarshalJSON 由Unmarshaler 接口定义。

    type EnumItem struct {                                                                                            
        Named                                                                                                         
        Value string                                                                                                  
    }                                                                                                                 
    
    func (item *EnumItem) UnmarshalJSON(data []byte) (err error) {                                                    
        required := struct {                                                                                          
            Value *string `json:"value"`                                                                              
        }{}                                                                                                           
        all := struct {                                                                                               
            Named                                                                                                     
            Value string `json:"value"`                                                                               
        }{}                                                                                                           
        err = json.Unmarshal(data, &required)                                                                         
        if err != nil {                                                                                               
            return                                                                                                    
        } else if required.Value == nil {                                                                             
            err = fmt.Errorf("Required field for EnumItem missing")                                                   
        } else {                                                                                                      
            err = json.Unmarshal(data, &all)                                                                          
            item.Named = all.Named                                                                                    
            item.Value = all.Value                                                                                    
        }                                                                                                             
        return                                                                                                        
    }                                                       
    

    【讨论】:

      【解决方案3】:

      这是另一种方法,检查您自定义的tag

      您可以为您的结构创建一个标签,例如:

      type Profile struct {
          Name string `yourprojectname:"required"`
          Age  int
      }
      

      使用reflect检查标签是否分配了required

      func (p *Profile) Unmarshal(data []byte) error {
          err := json.Unmarshal(data, p)
          if err != nil {
              return err
          }
      
          fields := reflect.ValueOf(p).Elem()
          for i := 0; i < fields.NumField(); i++ {
      
              yourpojectTags := fields.Type().Field(i).Tag.Get("yourprojectname")
              if strings.Contains(yourpojectTags, "required") && fields.Field(i).IsZero() {
                  return errors.New("required field is missing")
              }
      
          }
          return nil
      }
      

      而测试用例是这样的:

      func main() {
      
          profile1 := `{"Name":"foo", "Age":20}`
          profile2 := `{"Name":"", "Age":21}`
      
          var profile Profile
      
          err := profile.Unmarshal([]byte(profile1))
          if err != nil {
              log.Printf("profile1 unmarshal error: %s\n", err.Error())
              return
          }
          fmt.Printf("profile1 unmarshal: %v\n", profile)
      
          err = profile.Unmarshal([]byte(profile2))
          if err != nil {
              log.Printf("profile2 unmarshal error: %s\n", err.Error())
              return
          }
          fmt.Printf("profile2 unmarshal: %v\n", profile)
      
      }
      

      结果:

      profile1 unmarshal: {foo 20}
      
      2009/11/10 23:00:00 profile2 unmarshal error: required field is missing
      

      你可以去Playground看看完成的代码

      【讨论】:

      • 这不会捕获负载中存在的必填字段:{"Age":21}
      • @Justin 你贴上标签了吗?您需要添加yourprojectname:"required"。见第一个示例代码
      • play.golang.org/p/BaqxrMLD5VNprofile2 中缺少“名称”字段的操场代码。除非有效载荷中存在“名称”,否则不会返回错误。
      • @Justin 哦,很有趣。不同之处在于 json 字符串是如何定义的。在您的代码中,它是 profile2 := {"Age":21}。在我的示例中,它是 profile2 := {"Name":"", "Age":21}。然而,这不是根本原因。根本原因是主函数中只声明了一个Profile 变量。在第一个 profile.Unmarshal([]byte(profile1)) 之后,填写名称字段。这是一个示例代码,您可以清空配置文件变量或在第一次解组调用后声明另一个变量。 Example
      【解决方案4】:

      您只需实现 Unmarshaler 接口即可自定义 JSON 的解组方式。

      【讨论】:

        【解决方案5】:

        您还可以使用 JSON 模式验证。

        package main
        
        import (
            "encoding/json"
            "fmt"
        
            "github.com/alecthomas/jsonschema"
            "github.com/xeipuuv/gojsonschema"
        )
        
        type Bird struct {
            Species     string `json:"birdType"`
            Description string `json:"what it does" jsonschema:"required"`
        }
        
        func main() {
            var bird Bird
            sc := jsonschema.Reflect(&bird)
            b, _ := json.Marshal(sc)
        
            fmt.Println(string(b))
        
            loader := gojsonschema.NewStringLoader(string(b))
            documentLoader := gojsonschema.NewStringLoader(`{"birdType": "pigeon"}`)
        
            schema, err := gojsonschema.NewSchema(loader)
            if err != nil {
                panic("nop")
            }
            result, err := schema.Validate(documentLoader)
            if err != nil {
                panic("nop")
            }
        
            if result.Valid() {
                fmt.Printf("The document is valid\n")
            } else {
                fmt.Printf("The document is not valid. see errors :\n")
                for _, err := range result.Errors() {
                    // Err implements the ResultError interface
                    fmt.Printf("- %s\n", err)
                }
            }
        
        }
        

        输出

        {"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/Bird","definitions":{"Bird":{"required":["birdType","what it does"],"properties":{"birdType":{"type":"string"},"what it does":{"type":"string"}},"additionalProperties":false,"type":"object"}}}
        The document is not valid. see errors :
        - (root): what it does is required
        
        

        取自Strict JSON parsing的代码示例

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2018-06-12
          • 2014-08-02
          • 1970-01-01
          • 2017-07-11
          • 2013-05-12
          • 2017-07-08
          • 1970-01-01
          相关资源
          最近更新 更多