Take a look at this snip found at here
import (
"encoding/xml"
"fmt"
"os"
)
func main() {
type Address struct {
City, State string
}
type Person struct {
XMLName xml.Name `xml:"person"`
Id int `xml:"id,attr"`
FirstName string `xml:"name>first"`
LastName string `xml:"name>last"`
Age int `xml:"age"`
Height float32 `xml:"height,omitempty"`
Married bool
Address
Comment string `xml:",comment"`
}
v := &Person{Id: 13, FirstName: "John", LastName: "Doe", Age: 42}
v.Comment = " Need more details. "
v.Address = Address{"Hanga Roa", "Easter Island"}
enc := xml.NewEncoder(os.Stdout)
enc.Indent(" ", " ")
if err := enc.Encode(v); err != nil {
fmt.Printf("error: %v\n", err)
}
}
I can understand in the struct Person, It has a var called Id, which is of type int, but what about the stuff xml:"person" after int? What does it mean? Thanks.
It's a struct tag. Libraries use these to annotate struct fields with extra information; in this case, the module encoding/xml uses these struct tags to denote which tags correspond to the struct fields.
which mean that variable will present in the name of Person example
type sample struct {
dateofbirth string `xml:"dob"`
}
In the above example, the field 'dateofbirth' will present in the name of 'dob' in the XML.
you will see this notation often in go struct.
Related
What's the best way to produce a struct result with 2 or more models combined in Gorm?
Given these example models:
type Book struct {
gorm.Model
Title string
Description string
AuthorID uint
}
type Author struct {
gorm.Model
FirstName string
LastName string
Books []Book
}
I want to create a query to find books by Title
db.Where("title=?", "foo").Find(&books)
So far no problem, but I would also like to include Author.FirstName and Author.LastName in the result. This does not work with any method I tried, since Book struct does not include those fields. The desired result should include all fields from a matching Book plus all fields from Author related to that Book.
Tried to use Select() and Join() functions to specify all the desired fields, which produced the correct SQL statement, but the resulting Book struct still does not contain any Author fields.
I was able to accomplish your request in this way.
First, I added Author Author field to the Book struct. In this way, you can save the information of the author together with his books.
In the query, you've to use Preload("Auhtor") to let GORM load also the information from the authors table. This practice is called eager-loading. Below, you can find my working solution:
package main
import (
"fmt"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
type Book struct {
gorm.Model
Title string
Description string
AuthorID uint
Author Author
}
type Author struct {
gorm.Model
FirstName string
LastName string
Books []Book
}
func main() {
dsn := "host=localhost user=postgres password=postgres dbname=postgres port=5432 sslmode=disable"
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
db.AutoMigrate(&Book{})
db.AutoMigrate(&Author{})
book1 := &Book{Title: "Go", Description: "Intro to Golang", AuthorID: 1}
book2 := &Book{Title: "GORM", Description: "Intro to GORM", AuthorID: 1}
author := &Author{FirstName: "John", LastName: "Doe", Books: []Book{*book1, *book2}}
db.Create(author)
var books []Book
db.Preload("Author").Where("title=?", "Go").Find(&books)
for _, v := range books {
fmt.Println("book 1:")
fmt.Printf("title: %q\n\n", v.Title)
fmt.Printf("author details:\n")
fmt.Printf("first name: %q\n", v.Author.FirstName)
fmt.Printf("last name: %q\n", v.Author.LastName)
}
}
Hope this helps you understand.
Please look at the example below to have a look at how we can produce a struct with two or more models combined in Gorm.
type Driver struct {
gorm.Model
Name string
License string
Cars []Car
}
type Car struct {
gorm.Model
Year int
Make string
ModelName string
DriverID int
}
var (
drivers = []Driver{
{Name: "Shashank", License: "India123"},
{Name: "Tom", License: "India321"},
}
cars = []Car{
{Year: 2000, Make: "Toyota", ModelName: "Tundra", DriverID: 1},
{Year: 2001, Make: "Honda", ModelName: "Accord", DriverID: 1},
}
)
func GetCars(w http.ResponseWriter, r *http.Request) {
var cars []Car
db.Find(&cars)
json.NewEncoder(w).Encode(&cars)
}
// Getting cars with the id, where it will include name of driver & license
func GetCar(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
var car Car
db.First(&car, params["id"])
json.NewEncoder(w).Encode(&car)
}
Please note this is not the full code, It is basically a reference for your solution for the full code you can check my repository.
https://github.com/amshashankk/GO_Postgres_Testing/blob/main/main.go
I have different types for a REST API which have some things in common:
All types have Id
All types have Type
All types have Attributes, but Attributes differ
As I would not like to have a big count of unnecessary types I came up with a type format but which does not seem to be scannable by sqlx.
type Pool struct {
Id *string `json:"id" db:"pool_id"`
Type *string `json:"type"`
Attributes struct {
Name *string `json:"name" db:"name"`
Description *string `json:"description" db:"description"`
} `json:"attributes"`
}
type User struct {
Id *string `json:"id" db:"user_id"`
Type *string `json:"type"`
Attributes struct {
FirstName *string `json:"first_name" db:"first_name"`
LastName *string `json:"last_name" db:"last_name"`
} `json:"attributes"`
}
When trying to load the SQL result into the variable I get an error: missing destination name name in *main.Pool
package main
import (
_ "github.com/jmoiron/sqlx"
)
func selectOne() interface{} {
pa := Pool{}
err := dbx.Get(&pa, "SELECT * FROM pools LIMIT 1")
if err != nil {
panic(err)
}
return pa
}
As name is present I do not see why this does not work. Any advice how to solve this is highly appreciated!
package main
import (
_ "github.com/jmoiron/sqlx"
)
func selectOne() interface{} {
pa := Pool{}
err := dbx.Get(&pa, `SELECT p.id, p.type, a.name AS "attributes.name" FROM pools p left join attributes a on a.p LIMIT 1`)
if err != nil {
panic(err)
}
return pa
}
I have the following structs...
type Menu struct {
Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"`
Description string `protobuf:"bytes,3,opt,name=description" json:"description,omitempty"`
Mixers []*Mixer `protobuf:"bytes,4,rep,name=mixers" json:"mixers,omitempty"`
Sections []*Section `protobuf:"bytes,5,rep,name=sections" json:"sections,omitempty"`
}
And...
type Menu struct {
ID bson.ObjectId `json:"id" bson:"_id"`
Name string `json:"name" bson:"name"`
Description string `json:"description" bson:"description"`
Mixers []Mixer `json:"mixers" bson:"mixers"`
Sections []Section `json:"sections" bson:"sections"`
}
I basically need to convert between the two struct types, I've attempted to use mergo, but that can only merge structs that are assignable to one another. The only solution I have so far is iterating through each struct, converting the ID by re-assigning it and converting its type between string and bson.ObjectId. Then iterating through each map field and doing the same. Which feels like an inefficient solution.
So I'm attempting to use reflection to be more generic in converting between the two ID's. But I can't figure out how I can effectively merge all of the other fields that do match automatically, so I can just worry about converting between the ID types.
Here's the code I have so far...
package main
import (
"fmt"
"reflect"
"gopkg.in/mgo.v2/bson"
)
type Sub struct {
Id bson.ObjectId
}
type PbSub struct {
Id string
}
type PbMenu struct {
Id string
Subs []PbSub
}
type Menu struct {
Id bson.ObjectId
Subs []Sub
}
func main() {
pbMenus := []*PbMenu{
&PbMenu{"1", []PbSub{PbSub{"1"}}},
&PbMenu{"2", []PbSub{PbSub{"1"}}},
&PbMenu{"3", []PbSub{PbSub{"1"}}},
}
newMenus := Serialise(pbMenus)
fmt.Println(newMenus)
}
type union struct {
PbMenu
Menu
}
func Serialise(menus []*PbMenu) []Menu {
newMenus := []Menu{}
for _, v := range menus {
m := reflect.TypeOf(*v)
fmt.Println(m)
length := m.NumField()
for i := 0; i < length; i++ {
field := reflect.TypeOf(v).Field(i)
fmt.Println(field.Type.Kind())
if field.Type.Kind() == reflect.Map {
fmt.Println("is map")
}
if field.Name == "Id" && field.Type.String() == "string" {
// Convert ID type
id := bson.ObjectId(v.Id)
var dst Menu
dst.Id = id
// Need to merge other matching struct fields
newMenus = append(newMenus, dst)
}
}
}
return newMenus
}
I'm can't just manually re-assign the fields because I'm hoping to detect maps on the structs fields and recursively perform this function on them, but the fields won't be the same on embedded structs.
Hope this makes sense!
I think that it is probably better to write your own converter, because you will always have some cases that are not covered by existing libs\tools for that.
My initial implementation of it would be something like this: basic impl of structs merger
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}]}
As per the example below, I need to get email and firstname and dateofbirth and so on as NullStrings and NullTimes as required, as my User struct is using them. How do I declare variables as NULLs
package entities
import (
"database/sql"
"github.com/go-sql-driver/mysql"
"testing"
"time"
)
var (
email = sql.NullString("mail#gmail.com") << Does not work
hashedPassword = "password"
firstName = "Lee"
lastName = "Brooks"
dateOfBirth = time.Now
height = 1.85
weight = 101.3
)
func privacyConcernedUser() *User {
return &User{
Email: email, << These all complain eg: cannot use Email (type string) as type sql.NullString in field value
HashedPassword: hashedPassword,
FirstName: firstName,
LastName: lastName,
}
}
sql.NullString isn't a drop-in replacement for the string type, you have to some work with it.
package main
import "database/sql"
import "fmt"
type User struct {
Name string
}
func main() {
var nsName sql.NullString
if err := nsName.Scan("User's Name"); err != nil {
panic(err)
}
user := &User{Name: nsName.String}
fmt.Println(user)
}
You can check if the NullString is valid with nsName.Valid.
http://golang.org/pkg/database/sql/#NullString
sql.NullString("mail#gmail.com") << Does not work
Try:
sql.NullString{"mail#gmail.com", true}
see http://golang.org/pkg/database/sql/#NullString