I am unable to json.Unmarshal
a null value into a *NullString
field within a struct. Here is a simplified example of what I mean:
package main
import (
"database/sql"
"encoding/json"
"log"
)
// NullString
type NullString struct {
sql.NullString
}
func (n *NullString) UnmarshalJSON(b []byte) error {
n.Valid = string(b) != "null"
e := json.Unmarshal(b, &n.String)
return e
}
type Person struct {
Name *NullString `json:"name"`
}
func BuildUpdateSQL(jsonString string) string {
p := Person{}
e := json.Unmarshal([]byte(jsonString),&p)
if e != nil {
log.Println(e)
}
if p.Name != nil {
log.Println(p,p.Name)
} else {
log.Println(p)
}
return ""
}
func main() {
// Correctly leaves p.Name unset
BuildUpdateSQL(`{"field_not_exist":"samantha"}`)
// Correctly sets p.Name
BuildUpdateSQL(`{"name":"samantha"}`)
// Incorrectly leaves p.Name as nil when I really want p.Name to have a NullString with .Valid == false
BuildUpdateSQL(`{"name":null}`)
}
As you can see, unmarshalling works for non-null json values. But when I pass in a null json value, the NullString unmarshaller doesn't seem to even fire.
Anyone know what I'm doing wrong?
Background
The reason I'm trying to do this is because I plan to get JSON value from a REST API. Not all fields in the API are required fields. Hence I use pointers for my struct fields to help me build my SQL Update statement because:
SET name = ?
)SET name = NULL
)SET name = ?
)Yes, this is because of the following unmarshaling rule:
To unmarshal JSON into a pointer, Unmarshal first handles the case of the JSON being the JSON literal null. In that case, Unmarshal sets the pointer to nil. Otherwise, Unmarshal unmarshals the JSON into the value pointed at by the pointer.
(Documentation for encoding/json
).
What I suggest to do is to add a Set
field which is changed to true when UnmarshalJSON is fired (which if there is any value, is guaranteed to be fired), and then to change the *NullString
to a simple NullString
, like so:
package main
import (
"database/sql"
"encoding/json"
"log"
)
// NullString
type NullString struct {
Set bool
sql.NullString
}
func (n *NullString) UnmarshalJSON(b []byte) error {
n.Set = true
n.Valid = string(b) != "null"
e := json.Unmarshal(b, &n.String)
return e
}
type Person struct {
Name NullString `json:"name"`
}
func BuildUpdateSQL(jsonString string) string {
p := Person{}
e := json.Unmarshal([]byte(jsonString), &p)
if e != nil {
log.Println(e)
}
log.Printf("%#v", p)
return ""
}
func main() {
BuildUpdateSQL(`{"field_not_exist":"samantha"}`)
BuildUpdateSQL(`{"name":"samantha"}`)
BuildUpdateSQL(`{"name":null}`)
}