95

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?

Alexander Ponomarev
  • 2,598
  • 3
  • 24
  • 31

6 Answers6

86

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

ANisus
  • 74,460
  • 29
  • 162
  • 158
  • 5
    Or you could just check for the default value, which might be enough for most cases (unless you're working with numbers). – Matt3o12 Apr 18 '15 at 15:50
  • 3
    But what if the value of the "required field" is the default value? – Anfernee May 27 '16 at 13:40
  • 1
    if the required field of a number is possible to be 0 and you also need to check if its provided, then use string's instead to allow checking of no input and then convert the string to a number after. – Zanven Feb 06 '17 at 23:08
  • What if the required field is a `struct` ? – Deepak Sah Dec 03 '19 at 07:06
  • 2
    @DeepakSah Same for structs. Use a pointer to a struct. Eg. `Object *Foo`, and then check if `s.Object == nil`. – ANisus Dec 03 '19 at 07:58
25

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                                                                                                        
}                                                       
Marco
  • 4,817
  • 5
  • 34
  • 75
Ian Walters
  • 1,769
  • 1
  • 15
  • 9
  • 3
    Is there no faster way to decode a json into a structure? This looks like a lot of code for something that should be simple and is a common use case. – Jonny Jul 14 '22 at 14:27
10

Here is another way by checking your customized tag

you can create a tag for your struct like:

type Profile struct {
    Name string `yourprojectname:"required"`
    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 < 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
}

And test cases are like:

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)

}

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

oscarz
  • 1,184
  • 11
  • 19
  • This doesn't catch required fields that are _not_ present within the payload: `{"Age":21}` – Justin Jul 06 '21 at 16:32
  • @Justin did you put the tag on? You need add `yourprojectname:"required"`. See the first example code – oscarz Jul 07 '21 at 22:36
  • https://play.golang.org/p/BaqxrMLD5VN ←The playground code with the "Name" field absent from `profile2`. The error doesn't get returned unless "Name" is present within the payload. – Justin Jul 08 '21 at 00:59
  • @Justin Oh, very interesting. The difference is how the json string is defined. In your code, it is profile2 := `{"Age":21}`. In my example, it is profile2 := `{"Name":"", "Age":21}`. However, it is not the root cause. The root cause is only one `Profile` variable is declared in main function. After first `profile.Unmarshal([]byte(profile1))`, the Name field is filled. This is an example code, you can either empty the profile variable or declare another one after first unmarshal call. [Example](https://play.golang.org/p/TkQvjTLv2J9) – oscarz Jul 09 '21 at 21:57
5

you can also make use of JSON schema validation.

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

}

Outputs

{"$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

code example taken from Strict JSON parsing

2

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

Deepak Sah
  • 313
  • 1
  • 3
  • 14
0

encoding/json package has no such field tag.

But go-playground/validator has one.

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

Then you can validate it like following

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

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

    // unmarshall
    decoder := json.NewDecoder(r.Body)
    decoder.DisallowUnknownFields()
    err := decoder.Decode(&p)

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

}
mrpandey
  • 627
  • 2
  • 9
  • 17