Unmarshal DynamoDBAttributeValues into a struct with different attributes - go

I read some data from dynamodb. This is what I get
{
Item: {
rating: {
N: "6"
},
pk: {
S: "artist-1"
},
gender: {
S: "woman"
},
sk: {
S: "Alexandra A"
}
}
}
Now I have a struct which looks like this:
type Artist struct {
ArtistID string `json:"id"`
Gender string `json:"gender"`
Name string `json:"name"`
Rating float64 `json:"rating"`
}
Now I do
artist := model.Artist{}
err = dynamodbattribute.UnmarshalMap(result.Item, &artist)
Now I can access e.g. gender with artist.gender. So this is fine, but I can't do this for ArtistId because it's called pk in my dynamodb and I use 'id' for my struct. What is a clean way to solve this? I don't want to replace my 'id' with 'pk' in the struct.

UnmarshalMap() didn't support any given tag Unmarshal. If you don't want to change 'id' with 'pk' in the struct then you have to set value of pk in id key before UnmarshalMap in map manually.
func UnmarshalMap(m map[string]*dynamodb.AttributeValue, out interface{}) error {
m["id"] = m["pk"]
dynamodbattribute.UnmarshalMap(m, out)
}
Tt's better to create a own generic Unmarshal function for this special case tweak and then call UnmarshalMap() inside.
artist := model.Artist{}
err = UnmarshalMap(result.Item, &artist)

Related

Unable to scan type of uuid.UUID into UUID in sqlmock Golang

I have the following test function.
func (s *Suite) Test_delete() {
var (
id = uuid.New()
name = "test"
quantity = 2
)
s.mock.ExpectQuery(regexp.QuoteMeta(
`SELECT * FROM "items" WHERE code = $1 AND "items"."deleted_at" IS NULL`,
)).
WithArgs(id).
WillReturnRows(sqlmock.NewRows([]string{"code", "name", "quantity"}).
AddRow(id, name, quantity))
err := s.repository.DeleteItem(id.String())
require.NoError(s.T(), err)
}
Now the problem is trying to scan the id variable into the row for the column code. In my Product struct, I have code defined as follows.
type Item struct {
Code uuid.UUID `gorm:"type:uuid;primaryKey" json:"code"`
Name string `json:"name" validate:"required"`
Quantity int `json:"qty" validate:"required,min=0"`
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
}
func (i *Item) BeforeCreate(tx *gorm.DB) (err error) {
if i.Code == uuid.Nil {
i.Code = uuid.New()
}
return
}
Now the problem is when I try to run the test function, it is somehow unable to scan uuid.UUID into UUID even though they are both the same types. Here is the exact error message.
msql: Scan error on column index 0, name "code": Scan: unable to scan type uuid.UUID into UUID
Could someone help me on this part?
Read this. I assume there is type casting error. Changing WillReturnRows(sqlmock.NewRows([]string{"code", "name", "quantity"}).AddRow(id, name, quantity))->WillReturnRows(sqlmock.NewRows([]string{"code", "name", "quantity"}).AddRow(id.String(), name, quantity)) may help.

How to create the dynamic struct except using interface and Using spread operator

I am trying to create the new API with Go and Fiber framework. I am using MongoDB.
Please refer to my below structure
type Product struct {
Name string `json:"name"`
Code string `json:"code"`
Description string `json:"description"`
Brand string `json:"brand"`
Variants []map[string]string `json:"variants"`
Categories []int `json:"categories"`
}
The Variants field will have another set array of objects. But the fields will be dynamic. So I can't define the structure. Now this one I solved with []map[string][string].
My task here I have to form the CSV file with the below format.
[{
Name: "Rock",
Code: "RRR"
Description: "This is rock"
Brand: "R"
...variants
},
{
Name: "Rock",
Code: "RRR"
Description: "This is rock"
Brand: "R"
...variants
},
{
Name: "Rambo",
Code: "RAM"
Description: "This is Rambo"
Brand: "RA"
...variants
},
{
Name: "Rambo",
Code: "RAM"
Description: "This is Rambo"
Brand: "RA"
...variants
}]
Only variants will change for every row. Others will be remaining same
I formed the other fields except for the Variants. I can't create the struct because the data will be dynamic.
I have a lot of confusion
How to create the struct for dynamic fields
How to spread the Variants at the root level
I am from javascript. So, we used the spread operator. Here I am confused about how to do that in Golang.
Help me out, guys
#mkopriva is right
However you can use reflection to dynamically construct a new struct if you like
follow is a example:
package main
import (
"encoding/json"
"fmt"
"reflect"
)
type DynamicMap map[string]interface{}
type Product struct {
Name string `json:"name"`
Code string `json:"code"`
Description string `json:"description"`
Brand string `json:"brand"`
Variants map[string]string `json:"variants"`
Categories []int `json:"categories"`
}
func (j Product) MarshalJSON() ([]byte, error) {
m := DynamicMap{}
t := reflect.TypeOf(j)
v := reflect.ValueOf(j)
for i := 0; i < t.NumField(); i++ {
if t.Field(i).Tag.Get("json") == "variants" {
dyn := v.Field(i).Interface().(map[string]string)
for key, val := range dyn {
m[key] = val
}
} else if t.Field(i).Type.Kind() == reflect.String {
m[t.Field(i).Tag.Get("json")] = v.Field(i).String()
} else {
m[t.Field(i).Tag.Get("json")] = v.Field(i)
}
}
return json.Marshal(m)
}
func (j Product) UnmarshalJSON(data []byte) error {
fmt.Println("Unmarshal...")
return json.Unmarshal(data, &j)
}
func main() {
p := Product{Name: "aa", Variants: map[string]string{"a": "a", "b": "b"}}
marshal, err := json.Marshal(p)
if err != nil {
fmt.Printf("%v\n", err)
}
fmt.Printf("%s\n", marshal)
}
{"a":"a","b":"b","brand":"","categories":{},"code":"","description":"","name":"aa"}

How can I query and return only id array by GORM?

I'm now having a problem with getting an id of array feeds from database (Postgres) with Gorm.
How can I query and return id array feeds? I don't know how to get only id from struct without loop
feeds := []models.Feed{}
feedID := []string{}
db.Select("id").Where("user_id = ?", "admin1").Find(&feeds)
for _, feed := range feeds {
feedID = append(feedID, feed.ID)
}
utils.PrintStruct(feeds)
This is feed model file:
type Feed struct {
Model
Status string `json:"status"`
PublishAt *time.Time `json:"publishAt"`
UserID string `json:"userID,omitempty"`
}
This is model base data model using for data entity:
type Model struct {
ID string `json:"id" gorm:"primary_key"`
}
Result:
[
{
"id": "d95d4be5-b53c-4c70-aa09",
"status": "",
"publishAt": null,
"userID":""
},
{
"id": "84b2d46f-a24d-4854-b44d",
"status": "",
"publishAt": null,
"userID":""
}
]
But I want like this:
["d95d4be5-b53c-4c70-aa09","84b2d46f-a24d-4854-b44d"]
You can use pluck
var ids []string
db.Model(&Feed{}).Where("user_id = ?", "admin1").Pluck("id", &ids)

How to pass a slice element to a function

I'm in the process of cleaning some code up and am trying to pass a slice value which is a struct to a function.
My struct looks like this:
type GetRecipesPaginatedResponse struct {
Total int `json:"total"`
PerPage int `json:"per_page"`
CurrentPage int `json:"current_page"`
LastPage int `json:"last_page"`
NextPageURL string `json:"next_page_url"`
PrevPageURL interface{} `json:"prev_page_url"`
From int `json:"from"`
To int `json:"to"`
Data []struct {
ID int `json:"id"`
ParentRecipeID int `json:"parent_recipe_id"`
UserID int `json:"user_id"`
Name string `json:"name"`
Description string `json:"description"`
IsActive bool `json:"is_active"`
IsPublic bool `json:"is_public"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
BjcpStyle struct {
SubCategoryID string `json:"sub_category_id"`
CategoryName string `json:"category_name"`
SubCategoryName string `json:"sub_category_name"`
} `json:"bjcp_style"`
UnitType struct {
ID int `json:"id"`
Name string `json:"name"`
} `json:"unit_type"`
} `json:"data"`
}
In my code, I fetch some JSON data from an API and get a response back that contains about 100 items in the Data slice.
I am then looping over each item in the Data slice and processing them.
As an example, it looks something like this:
for page := 1; page < 3; page++ {
newRecipes := getFromRecipesEndpoint(page, latestTimeStamp) //this returns an instance of GetRecipesPaginatedResponse
for _, newRecipe := range newRecipes.Data {
if rowExists(db, "SELECT id from community_recipes WHERE id=#id", sql.Named("id", newRecipe.ID)) {
insertIntoRecipes(db, true, newRecipe)
} else {
insertIntoRecipes(db, false, newRecipe)
}
}
}
So I am trying to pass the instance of a recipe to the insertIntoRecipes function, which looks like this:
func insertIntoRecipes(db *sql.DB, exists bool, newRecipe GetRecipesPaginatedResponse.Data) {
if exists {
//update the existing record in the DB
//perform some other updates with the information
} else {
//insert a new record into the DB
//insert some other information using this information
}
}
When I run this, I get the error:
GetRecipesPaginatedResponse.Data undefined (type GetRecipesPaginatedResponse has no method Data)
I can tell that the issue is to do with how I am trying to pass the newRecipe to the insertIntoRecipes function, newRecipe GetRecipesPaginatedResponse.Data, however I am not quite sure how to pass it in and declare the right variable type.
To pass an item inside the Data slice to a function, when I am looping over each item of the Data slice, how do I do that?
You cannot reference the anonymous type of the Data field using a field selector. The fix is to declare a named type for the Data field:
type GetRecipesPaginatedResponse struct {
...
Data []DataItem
...
}
type DataItem struct {
ID int `json:"id"`
ParentRecipeID int `json:"parent_recipe_id"`
UserID int `json:"user_id"`
Name string `json:"name"`
...
}
Use it like this:
func insertIntoRecipes(db *sql.DB, exists bool, newRecipe DataItem) {
...
}
There's probably a better name for DataItem.

Copy one struct to another where structs have same members and different types

I have two struct having the same members, I want to copy one struct to another, see the pseudo code below:
type Common struct {
Gender int
From string
To string
}
type Foo struct {
Id string
Name string
Extra Common
}
type Bar struct {
Id string
Name string
Extra Common
}
Then I have foo of struct Foo, and bar of struct Bar, Is there any way to copy bar from foo?
Use a conversion to change the type. The following code uses a conversion to copy a value of type Foo to a value of type Bar:
foo := Foo{Id: "123", Name: "Joe"}
bar := Bar(foo)
playground example
The conversion only works when the underlying types are identical except for struct tags.
https://github.com/jinzhu/copier (same author of gorm) is also quite a good one, I have nested structs and all I do is:
copier.Copy(&employees, &user)
works great
If you would like to copy or clone to a different struct, I would suggest using deepcopier
It provides nice features like skipping, custom mapping, and forcing. below is an example from github:
Install:
go get -u github.com/ulule/deepcopier
Example:
package main
import (
"fmt"
"github.com/ulule/deepcopier"
)
// Model
type User struct {
// Basic string field
Name string
// Deepcopier supports https://golang.org/pkg/database/sql/driver/#Valuer
Email sql.NullString
}
func (u *User) MethodThatTakesContext(ctx map[string]interface{}) string {
// do whatever you want
return "hello from this method"
}
// Resource
type UserResource struct {
//copy from field "Name"
DisplayName string `deepcopier:"field:Name"`
//this will be skipped in copy
SkipMe string `deepcopier:"skip"`
//this should call method named MethodThatTakesContext
MethodThatTakesContext string `deepcopier:"context"`
Email string `deepcopier:"force"`
}
func main() {
user := &User{
Name: "gilles",
Email: sql.NullString{
Valid: true,
String: "gilles#example.com",
},
}
resource := &UserResource{}
deepcopier.Copy(user).To(resource)
//copied from User's Name field
fmt.Println(resource.DisplayName)//output: gilles
fmt.Println(resource.Email) //output: gilles#example.com
fmt.Println(resource.MethodThatTakesContext) //output: hello from this method
}
Also, some other way you could achieve this is by encoding the source object to JSON and then decode it back to the destination object.
This is another possible answer
type Common struct {
Gender int
From string
To string
}
type Foo struct {
Id string
Name string
Extra Common
}
type Bar struct {
Id string
Name string
Extra Common
}
foo:=Foo{
Id:"123",
Name:"damitha",
Extra: struct {
Gender int
From string
To string
}{Gender:1 , From:"xx", To:"yy" },
}
bar:=*(*Bar)(unsafe.Pointer(&foo))
fmt.Printf("%+v\n",bar)
If you would like to copy or clone to a different struct, I would suggest using deepcopier
It provides nice features like skipping, custom mapping, and forcing.
You can achieve nested struct copy following way.
Install:
go get -u github.com/ulule/deepcopier
Example:
package main
import (
"fmt"
"strconv"
"github.com/ulule/deepcopier"
)
//FieldStruct - Field Struct
type FieldStruct struct {
Name string `deepcopier:"field:TargetName"`
Type string `deepcopier:"field:TargetType"`
}
//SourceStruct - Source Struct
type SourceStruct struct {
Name string `deepcopier:"field:TargetName"`
Age int `deepcopier:"field:TargetAge"`
StringArray []string `deepcopier:"field:TargetStringArray"`
StringToInt string `deepcopier:"context"`
Field FieldStruct
Fields []FieldStruct
}
//TargetFieldStruct - Field Struct
type TargetFieldStruct struct {
TargetName string
TargetType string
}
//TargetStruct - Target Struct
type TargetStruct struct {
TargetName string
TargetAge int
TargetStringArray []string
TargetInt int
TargetField TargetFieldStruct
TargetFields []TargetFieldStruct
}
//write methods
//TargetInt - StringToInt
func (s *SourceStruct) TargetInt() int {
i, _ := strconv.Atoi(s.StringToInt)
return i
}
func main() {
s := &SourceStruct{
Name: "Name",
Age: 12,
StringArray: []string{"1", "2"},
StringToInt: "123",
Field: FieldStruct{
Name: "Field",
Type: "String",
},
Fields: []FieldStruct{
FieldStruct{
Name: "Field1",
Type: "String1",
},
FieldStruct{
Name: "Field2",
Type: "String2",
},
},
}
t := &TargetStruct{}
//coping data into inner struct
deepcopier.Copy(&t.TargetField).From(&s.Field)
// copied array of Struct
for i := range s.Fields {
// init a struct
t.TargetFields = append(t.TargetFields, TargetFieldStruct{})
// coping the data
deepcopier.Copy(&t.TargetFields[i]).From(&s.Fields[i])
}
//Top level copy
deepcopier.Copy(t).From(s)
fmt.Println(t)
}
Output:
&{Name 12 [1 2] 123 {Field String} [{Field1 String1} {Field2 String2}]}

Resources