Unmarshaling json in Go: required field?

huangapple go评论104阅读模式
英文:

Unmarshaling json in Go: required field?

问题

在使用Go解析JSON输入时,如果找不到字段,是否可能生成错误?我在文档中找不到相关信息。是否有任何标签可以指定字段为必需的?

英文:

Is it possible to generate an error if a field was not found while parsing a JSON input using Go?

I could not find it in documentation.

Is there any tag that specifies the field as required?

答案1

得分: 86

encoding/json包中没有设置字段为"required"的标签。你要么需要编写自己的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

英文:

There is no tag in the encoding/json package that sets a field to "required". You will either have to write your own MarshalJSON() method, or do a post check for missing fields.

To check for missing fields, you will have to use pointers in order to distinguish between missing/null and zero values:

type JsonStruct struct {
	String *string
	Number *float64
}

Full working example:

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

答案2

得分: 25

你还可以覆盖特定类型的非编组(unmarshalling)操作(即在几个 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("EnumItem 缺少必需字段")
    } else {
        err = json.Unmarshal(data, &all)
        item.Named = all.Named
        item.Value = all.Value
    }
    return
}
英文:

You can also override the unmarshalling for a specific type (so a required field buried in a few json layers) without having to make the field a pointer. UnmarshalJSON is defined by the Unmarshaler interface.

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

得分: 10

以下是对你提供的代码的翻译:

这是另一种通过检查自定义的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++ {

		yourprojectTags := fields.Type().Field(i).Tag.Get("yourprojectname")
		if strings.Contains(yourprojectTags, "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查看完整的代码。

英文:

Here is another way by checking your customized tag

you can create a tag for your struct like:

type Profile struct {
	Name string `yourprojectname:&quot;required&quot;`
	Age  int
}

Use reflect to check if the tag is assigned required value

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 &lt; fields.NumField(); i++ {

		yourpojectTags := fields.Type().Field(i).Tag.Get(&quot;yourprojectname&quot;)
		if strings.Contains(yourpojectTags, &quot;required&quot;) &amp;&amp; fields.Field(i).IsZero() {
			return errors.New(&quot;required field is missing&quot;)
		}

	}
	return nil
}

And test cases are like:

func main() {

	profile1 := `{&quot;Name&quot;:&quot;foo&quot;, &quot;Age&quot;:20}`
	profile2 := `{&quot;Name&quot;:&quot;&quot;, &quot;Age&quot;:21}`

	var profile Profile

	err := profile.Unmarshal([]byte(profile1))
	if err != nil {
		log.Printf(&quot;profile1 unmarshal error: %s\n&quot;, err.Error())
		return
	}
	fmt.Printf(&quot;profile1 unmarshal: %v\n&quot;, profile)

	err = profile.Unmarshal([]byte(profile2))
	if err != nil {
		log.Printf(&quot;profile2 unmarshal error: %s\n&quot;, err.Error())
		return
	}
	fmt.Printf(&quot;profile2 unmarshal: %v\n&quot;, profile)

}

Result:

profile1 unmarshal: {foo 20}

2009/11/10 23:00:00 profile2 unmarshal error: required field is missing

You can go to Playground to have a look at the completed code

答案4

得分: 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

代码示例来源:https://stackoverflow.com/questions/68935144/strict-json-parsing#68935144

英文:

you can also make use of JSON schema validation.

package main

import (
	&quot;encoding/json&quot;
	&quot;fmt&quot;

	&quot;github.com/alecthomas/jsonschema&quot;
	&quot;github.com/xeipuuv/gojsonschema&quot;
)

type Bird struct {
	Species     string `json:&quot;birdType&quot;`
	Description string `json:&quot;what it does&quot; jsonschema:&quot;required&quot;`
}

func main() {
	var bird Bird
	sc := jsonschema.Reflect(&amp;bird)
	b, _ := json.Marshal(sc)

	fmt.Println(string(b))

	loader := gojsonschema.NewStringLoader(string(b))
	documentLoader := gojsonschema.NewStringLoader(`{&quot;birdType&quot;: &quot;pigeon&quot;}`)

	schema, err := gojsonschema.NewSchema(loader)
	if err != nil {
		panic(&quot;nop&quot;)
	}
	result, err := schema.Validate(documentLoader)
	if err != nil {
		panic(&quot;nop&quot;)
	}

	if result.Valid() {
		fmt.Printf(&quot;The document is valid\n&quot;)
	} else {
		fmt.Printf(&quot;The document is not valid. see errors :\n&quot;)
		for _, err := range result.Errors() {
			// Err implements the ResultError interface
			fmt.Printf(&quot;- %s\n&quot;, err)
		}
	}

}

Outputs

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

code example taken from https://stackoverflow.com/questions/68935144/strict-json-parsing#68935144

答案5

得分: 2

你可以实现Unmarshaler接口来自定义JSON的反序列化过程。

英文:

You can just implement the Unmarshaler interface to customize how your JSON gets unmarshalled.

答案6

得分: 0

encoding/json包没有这样的字段标签。

但是go-playground/validator有一个。

type Person struct {
	Name       string    `json:"name"    validate:"required"`
	Age        uint      `json:"age"     validate:"omitempty,gte=18"`
}

然后你可以像下面这样验证它

import (
    "encoding/json"
    "github.com/go-playground/validator/v10"
)

func Handler(w http.ResponseWriter, r *http.Request) {
    p := &Person{}

    // 反序列化
    decoder := json.NewDecoder(r.Body)
    decoder.DisallowUnknownFields()
    err := decoder.Decode(&p)

    // 验证
    validate := validator.New()
    err = validate.Struct(p)

}
英文:

encoding/json package has no such field tag.

But go-playground/validator has one.

type Person struct {
Name       string    `json:&quot;name&quot;    validate:&quot;required&quot;`
Age        uint      `json:&quot;age&quot;     validate:&quot;omitempty,gte=18&quot;`
}

Then you can validate it like following

import (
&quot;encoding/json&quot;
&quot;github.com/go-playground/validator/v10&quot;
)
func Handler(w http.ResponseWriter, r *http.Request) {
p := &amp;Person{}
// unmarshall
decoder := json.NewDecoder(r.Body)
decoder.DisallowUnknownFields()
err := decoder.Decode(&amp;p)
// validate
validate := validator.New()
err = validate.Struct(p)
}

huangapple
  • 本文由 发表于 2013年10月28日 19:35:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/19633763.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定