Trying to implement Redigo ScanStruct but limited examples to follow - go

I have a code:
values, err := redis.Values(c.Do("hgetall", value))
if err != nil {
fmt.Println("HGETALL", err)
}
/*
type UD struct {
created_at string
B time.Time
ended_at string
data string
status string
}
*/
if err := redis.ScanStruct(values, &UD); err != nil {
fmt.Println(err)
}
The error I got is
redigo.ScanStruct: cannot assign field B: cannot convert from Redis
bulk string to time.Time
How do I resolve this? Any examples of ScanStruct in detail for a variety of field types for Struct for reference?

The documentation for ScanStruct is quite clear:
Integer, float, boolean, string and []byte fields are supported.
Other field types are not supported time.Time included.
To resolve this, I'd go and make my own version of ScanStruct that can deal with the conversion between Redis' and whatever types I need to throw at it.

You can simply add an ignore tag to make time field avoid being marshaled.
type UD struct {
created_at string
B time.Time `redis:"-"`
ended_at string
data string
status string
}

Related

Transform values when umarshaling

Given the code below, is it possible to transform first name while it's being unmarshaled? Say I want to always have it be lowercase whether it is in the actual json or not.
type Person struct {
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
}
func main() {
jsonText := GetJsonFromSomewhere()
var v Person
json.Unmarshal(jsonText, &v)
}
One way to do this is to create a custom type that implements the Unmarshaler interface from the encoding/json package. Here's a link to this interface. Any type which implements Unmarshaler can be used as the type of a struct field when doing JSON unmarshalling. When doing the unmarshalling, encoding/json will use your implementation of the interface's UnmarshalJSON function to convert the JSON bytes to the field type.
So you could write an UnmarshalJSON function which includes changing the string values to lowercase.
Here's an example of what that could look like:
type LowerCaseString string
func (l *LowerCaseString) UnmarshalJSON(bytes []byte) error {
lowerCasedString := strings.ToLower(string(bytes))
*l = LowerCaseString(lowerCasedString)
return nil
}
Then, in your struct for JSON mapping, you can use your custom type instead of string:
type Person struct {
FirstName LowerCaseString `json:"first_name"`
LastName LowerCaseString `json:"last_name"`
}
If you unmarshal into this struct, the values of FirstName and LastName will be lowercased (also note that you'll need to type convert them back to string to use them as strings).
testJSON := `{"first_name" : "TestFirstNAME", "last_name": "TestLastNAME"}`
var result Person
err := json.Unmarshal([]byte(testJSON), &result)
if err != nil { /*handle the error*/ }
fmt.Println(result.FirstName) // prints "testfirstname"
var stringLastName string
stringLastName = string(result.LastName) // need to type convert from LowerCaseString to string
fmt.Println(stringLastName) // prints "testlastname"
Here is the above code running in Go Playground.

"reflect: Field index out of range" panic on update

I get a panic implementing a basic update with Gorm and I couldn't find any information about it. Following Gorm's documentation, I'm not looking like doing wrong.
module achiever
go 1.15
require (
gorm.io/driver/postgres v1.0.5
gorm.io/gorm v1.20.7
)
type Task struct {
ID uint `json:"id" gorm:"primary_key"`
Title string `json:"title"`
}
type UpdateTaskInput struct {
Title string `json:"title"`
}
func UpdateTask(id uint, input UpdateTaskInput) bool {
var task Task
if err := models.DB.Where("id = ?", id).First(&task).Error; err != nil {
return false
}
models.DB.Model(&task).Updates(input)
return true
}
reflect: Field index out of range
/usr/local/go/src/reflect/value.go:854 (0x4bf758)
Value.Field: panic("reflect: Field index out of range")
/home/raphael/gows/pkg/mod/gorm.io/gorm#v1.20.7/schema/field.go:388 (0x57b984)
(*Field).setupValuerAndSetter.func1: fieldValue := reflect.Indirect(value).Field(field.StructField.Index[0])
/home/raphael/gows/pkg/mod/gorm.io/gorm#v1.20.7/callbacks/update.go:230 (0x881670)
ConvertToAssignments: value, isZero := field.ValueOf(updatingValue)
/home/raphael/gows/pkg/mod/gorm.io/gorm#v1.20.7/callbacks/update.go:64 (0x87f259)
Update: if set := ConvertToAssignments(db.Statement); len(set) != 0 {
/home/raphael/gows/pkg/mod/gorm.io/gorm#v1.20.7/callbacks.go:105 (0x5a65bc)
(*processor).Execute: f(db)
/home/raphael/gows/pkg/mod/gorm.io/gorm#v1.20.7/finisher_api.go:303 (0x5ae2c6)
(*DB).Updates: tx.callbacks.Update().Execute(tx)
/home/raphael/ws/achiever/server/controllers/tasks.go:74 (0xad62cd)
UpdateTask: models.DB.Model(&task).Updates(input)
I can't figure out where the error is coming from.
I was stuck with the same problem, as jugendhacker says: you need to construct the model's struct inside the update function:
models.DB.Model(&task).Updates(&Task{Title: input.Title})
This is stated in the GORM docs, if you are building an API, it would be more adequate to implement this as a PUT method, as you are passing a new object instead of patching an existing one.
Adding ID field to the UpdateTaskInput struct can fix this issue
type UpdateTaskInput struct {
ID uint `json:"-"`
Title string `json:"title"`
}
As far as I know the Update does not support "Smart Select Fields" so you need to construct the original struct first.
models.DB.Model(&task).Updates(&Task{Title: input.Title})

Access Type Declaration Parent Methods

I'm unmarshalling a GET request from a JSON API. Soem of the fields are a Unix timestamp (in ms). For those fields, I wrote a method to handle unmarshalling. For that to work, I needed to create a new local type Datetime.
type Datetime time.Time
type Response struct {
Status string `json:"status"`
CreatedAt Datetime `json:"created_at"`
}
// Handles unmarshalling of Time from the API
func (dt *Datetime) UnmarshalJSON(b []byte) error {
var unixTimeMs int64
if err := json.Unmarshal(b, &unixTimeMs); err != nil {
return err
}
*dt = Datetime(time.Unix(0, unixTimeMs*1000000))
return nil
}
Now that I have this new Response object, I'd like to call time.Time methods on the Datetime. I've tried casting the object (i.e. response.CreatedAt.(time.Time)), but that leads to a panic.
How can I call methods like time.Time#string on Datetime?
Convert the Datetime to time.Time to call a Time method. Example:
fmt.Println(time.Time(dt).String())
Another approach is use a struct with an embedded time field:
type Datetime struct {
time.Time
}
All time.Time methods are promoted to the Datetime type. Modify the UnmarshalJSON method to set dt.Time instead of *dt.

How return null value to empty string field

I have such struct in Golang application:
type Employee struct {
ID int `json:"employee_id"`
Email *string `json:"employee_email"`
LastName *string `json:"employee_last_name"`
FirstName *string `json:"employee_first_name"`
Sex *string `json:"employee_sex"`
}
Some string fields of this struct can be empty. If I use *string application return me "". If use sql.NullString it return me for example such result:
"employee_last_name": {
String: "",
Valid: true
}
I want to show null is string field is empty.
In documentation I found such code:
type NullString struct {
String string
Valid bool
}
func (ns *NullString) Scan(value interface{}) error {
if value == nil {
ns.String, ns.Valid = "", false
return nil
}
ns.Valid = true
return convertAssign(&ns.String, value)
}
func (ns NullString) Value() (driver.Value, error) {
if !ns.Valid {
return nil, nil
}
return ns.String, nil
}
As I understand this code could help me to solve my problem, right?! What I need to import in my application to use convertAssign function?
In the Go language specification nil is not a valid value for type string. the default, zero value is "".
The top answer for this question goes into more detail.
Edit:
What you want to do is not possible according to the oracle go driver docs:
sql.NullString
sql.NullString is not supported: Oracle DB does not
differentiate between an empty string ("") and a NULL
Original Answer:
Need more details on the SQL database type you are connecting to. I know the go SQLite3 pkg - github.com/mattn/go-sqlite3 - supports setting nil for values of type *string.
Check the details of the driver you are using to connect to the database. For instance with MySQL, getting SQL date fields into go's native time.Time struct type is not set by default - it must be turned on at the driver level.

Using struct to update values

I'm stuck when I'm updating an empty string values in a struct for updating dynamodb table.
Currently I have this struct
type Client struct {
ClientID *string `json:"client_key,omitempty"`
Name *string `json:"client_name,omitempty"`
Address *string `json:"address,omitempty"`
Country *string `json:"country,omitempty"`
City *string `json:"city,omitempty"`
State *string `json:"state,omitempty"`
PostCode *string `json:"post_code,omitempty"`
CreatedAt *time.Time `json:"created_at,omitempty"`
}
And this code when updating an item
keyAttr, err := dynamodbattribute.MarshalMap(key)
if err != nil {
return nil, err
}
valAttr, err := dynamodbattribute.MarshalMap(attributes)
if err != nil {
return nil, err
}
keyAttr will be used for the Key field and valAttr will be used in ExpressionAttributeValues field. Note that I didn't include the complete updating fields function to save space. But I will do that if you ask for it.
Currently the function is running fine except when I updated one of the field with empty string. E.g. client.Address = aws.String(""). While I'm fine with dynamodb converting my empty string to null, I can't seem to find a way to update it because of the omitempty tag.
I need the omitempty tag to ignore all nil values. However, I just researched that the omitempty tag also omits empty string values. Currently I have to make a struct in my function like this.
type client struct {
Name *string `json:"client_name"`
Address *string `json:"address"`
Country *string `json:"country"`
City *string `json:"city"`
State *string `json:"state"`
PostCode *string `json:"post_code"`
}
But i'm not a fan of repeating things. So, the question is: is there any better way of doing this? How do you guys use structs with dynamodb?
EDIT
Based on #Peter's comment, it seems that json.Encode() does print the empty string if it's not nil.
{"client_key":"test","username":"test","email":"","first_name":"test","last_name":"","phone":"","title":"","email_verified":false,"phone_verified":false,"updated_at":"2018-12-06T14:04:56.2743841+11:00"}
The problem seems to be in dynamodbattribute.MarshalMap function
After several trials, I finally got it. I didn't test it so I don't know whether it's buggy or not. But it seems to work for me right now.
So what I did was encode the struct with json.Marshal first then use json.Unmarshal with a map[string]interface{}. Then, I use dynamodbattribute.Marshal to convert it to map[string]*AttributeValue
Here's the code:
var temp map[string]interface{}
json.Unmarshal(tempStr, &temp)
valAttr, err := dynamodbattribute.MarshalMap(temp)
if err != nil {
return nil, err
}

Resources