Assigning null to JSON fields instead of empty strings

Since empty string is the zero/default value for Go string, I decided to define all such fields as interface{} instead. for example

type student struct {
    FirstName  interface{} `json:"first_name"`
    MiddleName interface{} `json:"middle_name"`
    LastName   interface{} `json:"last_name"`
}

The application I am sending my data expect a null instead of an empty string if value is not available for that specific field.

Is this the correct approach or can someone please point me to something better than this.


Solution 1:

In json package documentation :

Pointer values encode as the value pointed to. A nil pointer encodes as the null JSON object.

So you can store a pointer to a string which will be encoded as a string if not nil and will be encoded as "null" if nil

type student struct {
  FirstName  *string `json:"first_name"`
  MiddleName *string `json:"middle_name"`
  LastName   *string `json:"last_name"`
}

Solution 2:

Another way actually is a workaround with using the MarhshalJSON and UnmarshalJSON interface method offered by the json lib of golang. The code is as below:

type MyType string
type MyStruct struct {
    A MyType `json:"my_type"`
}

func (c MyType) MarshalJSON() ([]byte, error) {
    var buf bytes.Buffer
    if len(string(c)) == 0 {
        buf.WriteString(`null`)
    } else {
        buf.WriteString(`"` + string(c) + `"`)   // add double quation mark as json format required
    }
    return buf.Bytes(), nil
}

func (c *MyType)UnmarshalJSON(in []byte) error {
    str := string(in)
    if str == `null` {
        *c = ""
        return nil
    }
    res := MyType(str)
    if len(res) >= 2 {
        res = res[1:len(res)-1]     // remove the wrapped qutation
    }
    *c = res
    return nil
}

then when using json.Marshal, the MyType value will be marshaled as null.

Solution 3:

Can be used https://github.com/guregu/null

type student struct {
FirstName  null.String `json:"first_name"`
MiddleName null.String `json:"middle_name"`
LastName   null.String `json:"last_name"`}

Solution 4:

For the case of a json object with null strings, its easiest to use the omitempty decorator on the field.

type student struct {
  FirstName  string `json:"first_name,omitempty"`
  MiddleName string `json:"middle_name,omitempty"`
  LastName   string `json:"last_name"`
}

With the above declaration, only if first_name was assigned will that key show up in the resultant json. last_name, on the otherhand, will always show up in the result with a value of "" if not assigned.

Now when you start including numeric fields where 0 could be a value, using omitempty doesn't do what one would expect. A 0 value always drops the field, and we need to be able to differentiate between a 0 value and an unassigned value. Here use of library such as https://github.com/guregu/null may be advisable.

More discussion here: https://www.sohamkamani.com/blog/golang/2018-07-19-golang-omitempty/

Solution 5:

You could use something like sql.NullString,use 'Valid' to check if it is a nil value.

NullString represents a string that may be null. NullString implements the Scanner interface so it can be used as a scan destination:

type NullString struct {
    String string
    Valid  bool // Valid is true if String is not NULL
}

var s NullString
err := db.QueryRow("SELECT name FROM foo WHERE id=?", id).Scan(&s)
...
if s.Valid {
   // use s.String
} else {
   // NULL value
}

Please DO NOT use pointers like below:

type student struct {
  FirstName  *string `json:"first_name"`
  MiddleName *string `json:"middle_name"`
  LastName   *string `json:"last_name"`
}

Because you need check nil value and dereference like below everywhere,and maybe cause unexpected crash somewhere:

if obj.FirstName != nil {
   fmt.Print("%s", *obj.FirstName)
}

I have compared two solution and choose former method in my extensive production code... it works ok.