Unmarshaling json in Go: required field?

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?


Solution 1:

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

Solution 2:

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                                                                                                        
}                                                       

Solution 3:

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

Solution 4:

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