Check if any field of struct is nil - go

If there any clean way to check if any field value of struct is nil?
Suppose i have
type test_struct struct {
Name string `json:"name" binding:"required"`
Email string `json:"email" binding:"required"`
Message string `json:"message" binding:"required"`
}
And with Gin i am filling the struct with values as
var temp test_struct
c.Bind(&temp)
And everything works fine, but i want to check if any of temp.Name, temp.Email, temp.Message is nil, sure we can check it by simply comparing each field with nil: if temp.Name == nil and so on, but i am looking for a cleaner version of that, is there any?
UPD: Due to a lack of knowledge in Go language i didn't know that the Bind function of gin package return an error if we pass a strucure with binding:"required" fields. Thanks to #matt.s i get it. So the answer would be to check the err:
var temp test_struct
err := c.Bind(&temp)
if err != nil {
// Do my stuff(some of fields are not set)
}

You should first check that Bind doesn't return an error. If it doesn't then all fields will be set to their appropriate values or initialized to zero values if not. This means that the strings are guaranteed to not be nil (though they will be set to "" if they did not have a value).

Related

Why does *time.Time display as the timestamp instead of memory location?

I've been working on a hobby project and have gotten to the point where I need to differentiate between an initialized zero value, and an intentional zero value for any partial update capable requests. After a lot of reading, I went with the route of making all of my incoming struct fields pointers. Since pointers initialize to nil, and the JSON marshaller binds zero values, it lets me make that distinguishment. I started sending some API requests over, and was seeing what I expect at first, results looking like:
{0xc00058e240 <nil>}
When I added a time.Time field to the struct though, and sent a timestamp over, I see this:
{0xc0004060d0 <nil> 2004-10-16 00:00:00 +0000 UTC}
I would have expected the timestamp to also be a pointer to some memory location. I tested a little more with the methods below, and it still prints a timestamp. I was under the impression if you tried to use *variable in an operation, it would throw an error for you. The code below works as expected, so it's like time.Time is a pointer even though it prints right from the struct in its normal format.
type updateTestRequest struct {
TestString *string `json:"test_string" binding:"alphanum"`
TestName *string `json:"test_name,omitempty"`
TestTime *time.Time `json:"test_time" time_format:"2006-01-02" binding:"required"`
}
func (server *Server) updateTest(ctx *gin.Context) {
var req updateUserRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
ctx.JSON(http.StatusBadRequest, errorResponse(err))
return
}
fmt.Printf("%v", req)
if req.TestTime != nil {
fmt.Printf("value: '%v'", *req.TestTime)
} else {
println("it was nil.")
}
}
{
"test_string":"Testing",
"first_name": "",
"date_of_birth": "2004-10-16T00:00:00.00Z"
}
I'm still pretty new to Golang, so it could be my misunderstanding of the language. Why does timestamp not print out as a memory address like the other pointers?
The time package documentation shows that time.Time has a String() string method. Because the pointer receiver method set includes the value receiver methods, a *time.Time also has a String() string method.
The fmt package documentation says:
If the format (which is implicitly %v for Println etc.) is valid for a string (%s %q %v %x %X), the following two rules apply:
If an operand implements method String() string, that method will be invoked to convert the object to a string, which will then be formatted as required by the verb (if any).
It follows that the *time.Time is displayed using the result of the time's String() method.

How to differentiate int null and defaulted to zero from int actually equal to zero?

I am a long time python user moving to Go, and I still have some issues to reacquire basic skill to manage typing and pointer.
I have a program receiving event from RabbitMq (But the problem would be the same no matter what transport we are talking about). One of the even contain an optional field F1 typed as int.
My understanding is, if the field is not present in the event, then go will default it to 0. But 0 is a valid value for that field, and I need to differentiate cases where the value is 0, and cases where the value is non defined.
I thought to make my field a *int to actually have "nil" as a value. But then when when a receive an event, will F1 be set to the actual pointed value, or the value address from the sender?
Do I have any other alternative?
In most cases, using a pointer to the value makes sense. E.g
type RabbitMessage struct {
F1 *int `json:"f1"`
}
The exact details of how this will work depends on how you serialise your data before sending it over RabbitMQ. If you are using JSON, then there should be no issue with this as both a null value, and an omitted value, will be represented in Go as nil. When the value is provided, it will be set to the value you expect (it will not use the address from the sender).
If you control only the receiver program, then AFAICT you can not differentiate between an int that has been automatically initialized to 0 by go from an int that has been set to 0 by the sender.
If you can modify the sender program though, an alternative could be to add a boolean field along with your int, telling whether the int is set or not. Then on the receiving end you can check whether the boolean is true or not.
You can also send a pointer to an int:
type Message struct {
Value *int `json:"value"`
}
message := Message{Value: 4}
Be aware though that when unmarshalling this you'll get an int pointer you'll need to dereference.
"Do I have any other alternative?" -- Yes, you can define a custom type, similar to sql.NullInt64.
type OptionalInt struct {
Int int
IsValid bool
}
func NewOptionalInt(i int) OptionalInt {
return OptionalInt{Int: i, IsValid: true}
}
func (o *OptionalInt) UnmarshalJSON(data []byte) error {
if string(data) != "null" {
if err := json.Unmarshal(data, &o.Int); err != nil {
return err
}
o.IsValid = true
}
return nil
}
func (o OptionalInt) MarshalJSON() ([]byte, error) {
if o.IsValid {
return json.Marshal(o.Int)
}
return json.Marshal(nil)
}

Could't convert <nil> into type ...?

I tried to using database/sql for query database row into a Go type, my codes snippet following:
type User struct {
user_id int64
user_name string
user_mobile string
password string
email interface{}
nickname string
level byte
locked bool
create_time string
comment string // convert <nil> to *string error
}
func TestQueryUser(t *testing.T) {
db := QueryUser(driverName, dataSourceName)
stmtResults, err := db.Prepare(queryAll)
defer stmtResults.Close()
var r *User = new(User)
arr := []interface{}{
&r.user_id, &r.user_name, &r.user_mobile, &r.password, &r.email,
&r.nickname, &r.level, &r.locked, &r.create_time, &r.comment,
}
err = stmtResults.QueryRow(username).Scan(arr...)
if err != nil {
t.Error(err.Error())
}
fmt.Println(r.email)
}
MySQL:
As your see, some fields that has NULL value, so I have to set interface{} type into User struct of Go, which convert NULL to nil.
--- FAIL: TestQueryUser (0.00s)
user_test.go:48: sql: Scan error on column index 9: unsupported Scan, storing driver.Value type <nil> into type *string
Somebody has a better way? or I must change the MySQL field and set its DEFAULT ' '
First the short answer : There is some types in sql package for example sql.NullString (for nullable string in your table and guess Nullint64 and NullBool and ... usage :) ) and you should use them in your struct.
The long one : There is two interface for this available in go , first is Scanner and the other is Valuer for any special type in database, (for example,I use this mostly with JSONB in postgres) you need to create a type, and implement this two(or one of them) interface on that type.
the scanner is used when you call Scan function. the data from the database driver, normally in []byte is the input and you are responsible for handling it. the other one, is used when the value is used as input in query. the result "normally" is a slice of byte (and an error) if you need to only read data, Scanner is enough, and vice-versa, if you need to write parameter in query the Valuer is enough
for an example of implementation, I recommend to see the types in sql package.
Also there is an example of a type to use with JSONB/JSON type in postgresql
// GenericJSONField is used to handle generic json data in postgres
type GenericJSONField map[string]interface{}
// Scan convert the json field into our type
func (v *GenericJSONField) Scan(src interface{}) error {
var b []byte
switch src.(type) {
case []byte:
b = src.([]byte)
case string:
b = []byte(src.(string))
case nil:
b = make([]byte, 0)
default:
return errors.New("unsupported type")
}
return json.Unmarshal(b, v)
}
// Value try to get the string slice representation in database
func (v GenericJSONField) Value() (driver.Value, error) {
return json.Marshal(v)
}
driver value is often []byte but string and nil is acceptable. so this could handle null-able fields too.

Golang variable struct field

This may require reflection but I'm not sure. I am trying to loop through an array of required fields in a struct. If any of those fields are nil I want to throw an error essentially. I've got the basic form down but I realized I don't know how in Go to pass a struct field name via a variabel
imagine you have a struct called EmailTemplate and it has a field called template_id
In this case I want to know if EmailTemplate.TemplateId is nil
emailDef.Fields is a string array ["TemplateId"]
I want to check if those fields are in the EmailTemplate struct and if they are nil
for field := range emailDef.Fields {
fmt.Println(emailDef.Fields[field])
if EmailTemplate.[emailDef.Fields[field]] == nil {
missingField := true
}
}
is along the lines of what I am thinking but I know that is wrong as a struct isn't an array. emailDef.Fields[field] would be equivalent to TemplateId
Your loop stuff doesn't make a whole lot of sense to me so I'll give a general example with a single field in a string called field. If you have a slice or array of fields you want to check for then you'll want to range over that using the current value for field.
import "reflect"
st := reflect.TypeOf(EmailTemplate)
v, ok := st.FieldByName(field)
if ok {
// field existed on EmailTemplate, now check if it's nil
if v.IsNil() {
// the field field on instance EmailTemplate was nil, do something
}
}
Now assuming you have a list of fields you need to check are all non-nil, then just add a loop like so;
for field := range requiredFields {
st := reflect.TypeOf(EmailTemplate)
v, ok := st.FieldByName(field)
if ok {
// field existed on EmailTemplate, now check if it's nil
if v.IsNil() {
// the field field on instance EmailTemplate was nil, do something
// maybe raise error since the field was nil
} else {
//the field wasn't found at all, probably time to raise an error
}
}
}
reflect package docs are here; https://golang.org/pkg/reflect/

golang - Rest update request using http patch semantics

type A struct {
Id int64
Email sql.NullString
Phone sql.NullString
}
Assume I have one record in the database
A{1, "x#x.com", "1112223333"}
Send an update request via PUT
curl -X PUT -d '{"Email": "y#y.com", "Phone": null}' http://localhost:3000/a/1
Here is the psuedo algorithm that would work with a full PUT request (i.e. update all fields of the record A - but it will cause difficulties with the PATCH request semantics - delta update)
-- Unmarshal json into empty record
a := A{}
json.Unmarshal([]byte(request.body), &a)
-- Load the record from the database
aFromDb = <assume we got record from db> //A{1, "x#x.com", "1112223333"}
-- Compare a and aFromDB
-- Notice the email change and set it on aFromDb - ok
-- Notice the phone number change -- but wait! Was it set to NULL in the JSON explicitly or was it not even included in the JSON? i.e. was the json request - {"Email": "y#y.com", "Phone": null} or was it {"Email": "y#y.com"}?
How can we tell by just looking at the unmarshaled json into the struct a?
Is there another method to do the update via rest (with patch semantics)? I am looking for a generic way to do it (not tied to a particular struct).
I created a separate datatype for this purpose. This example is for an int64 (actually string-encoded int64), but you can easily change it to a string as well. The idea behind it is, that the UnmarshalJSON method will only be called if the value is present in the JSON. The type will implement the Marshaler and the Unmarshaler.
// Used as a JSON type
//
// The Set flag indicates whether an unmarshaling actually happened on the type
type RequiredInt64 struct {
Set bool
Int64 int64
}
func (r RequiredInt64) MarshalJSON() ([]byte, error) {
lit := strconv.FormatInt(r.Int64, 10)
return json.Marshal(lit)
}
func (r *RequiredInt64) UnmarshalJSON(raw []byte) error {
var lit string
var err error
if err = json.Unmarshal(raw, &lit); err != nil {
return err
}
r.Int64, err = strconv.ParseInt(lit, 10, 64)
if err != nil {
return err
}
r.Set = true
return nil
}
So, if Set is false, you know that the value was not present in the JSON.
Try adding this tags to the struct:
type A struct {
Id int64 `json:"Id,omitempty"`
Email sql.NullString `json:"Email,omitempty"`
Phone sql.NullString `json:"Phone,omitempty"`
}
In this way if you are serializing and the field is empty then the json will not contain the field.
When deserializing though the field will have a either a value or it will have the default value for the type (Nil for the pointer or empty string for strings).
You could potentially write your own marshalling/uinmarshalling of your struct and react to the raw response within, although it might be non-obvious witgh inspection what that those functions are manipulating.
Alternatively, you could not omitempty within your fields and force null populated fields.
Or, maybe leveraging a different flavor of patching, perhaps http://jsonpatch.com/, which is more explicit in the nature of your modifications. This would require the client to be more understanding of the state of changes than say for a put.

Resources