great! completed 'with' feature for package gdb

This commit is contained in:
John Guo
2021-02-09 18:00:43 +08:00
parent acf47f3907
commit a3fa10d820
21 changed files with 1128 additions and 283 deletions

View File

@ -783,25 +783,3 @@ func FormatSqlWithArgs(sql string, args []interface{}) string {
})
return newQuery
}
// convertMapToStruct maps the `data` to given struct.
// Note that the given parameter `pointer` should be a pointer to s struct.
func convertMapToStruct(data map[string]interface{}, pointer interface{}) error {
tagNameMap, err := structs.TagMapName(pointer, []string{OrmTagForStruct})
if err != nil {
return err
}
// It retrieves and returns the mapping between orm tag and the struct attribute name.
var (
mapping = make(map[string]string)
tagFieldName string
)
for tag, attr := range tagNameMap {
tagFieldName = strings.Split(tag, ",")[0]
if !gregex.IsMatchString(regularFieldNameRegPattern, tagFieldName) {
continue
}
mapping[tagFieldName] = attr
}
return gconv.Struct(data, pointer, mapping)
}

View File

@ -26,6 +26,7 @@ type Model struct {
fields string // Operation fields, multiple fields joined using char ','.
fieldsEx string // Excluded operation fields, multiple fields joined using char ','.
withArray []interface{} // Arguments for With feature.
withAll bool // Enable model association operations on all objects that have "with" tag in the struct.
extraArgs []interface{} // Extra custom arguments for sql.
whereHolder []*whereHolder // Condition strings for where operation.
groupBy string // Used for "group by" statement.

View File

@ -228,7 +228,7 @@ func (m *Model) Struct(pointer interface{}, where ...interface{}) error {
if err = one.Struct(pointer); err != nil {
return err
}
return m.doWithScan(pointer)
return m.doWithScanStruct(pointer)
}
// Structs retrieves records from table and converts them into given struct slice.
@ -252,7 +252,10 @@ func (m *Model) Structs(pointer interface{}, where ...interface{}) error {
if err != nil {
return err
}
return all.Structs(pointer)
if err = all.Structs(pointer); err != nil {
return err
}
return m.doWithScanStructs(pointer)
}
// Scan automatically calls Struct or Structs function according to the type of parameter `pointer`.

View File

@ -13,27 +13,89 @@ import (
"github.com/gogf/gf/internal/utils"
"github.com/gogf/gf/text/gregex"
"github.com/gogf/gf/text/gstr"
"reflect"
)
func (m *Model) With(structAttrPointer interface{}) *Model {
// With creates and returns an ORM model based on meta data of given object.
// It also enables model association operations feature on given `object`.
// It can be called multiple times to add one or more objects to model and enable
// their mode association operations feature.
// For example, if given struct definition:
// type User struct {
// gmeta.Meta `orm:"table:user"`
// Id int `json:"id"`
// Name string `json:"name"`
// UserDetail *UserDetail `orm:"with:uid=id"`
// UserScores []*UserScores `orm:"with:uid=id"`
// }
// We can enable model association operations on attribute `UserDetail` and `UserScores` by:
// db.With(User{}.UserDetail).With(User{}.UserDetail).Scan(xxx)
// Or:
// db.With(UserDetail{}).With(UserDetail{}).Scan(xxx)
func (m *Model) With(object interface{}) *Model {
model := m.getModel()
if m.tables == "" {
m.tables = m.db.QuotePrefixTableName(getTableNameFromOrmTag(structAttrPointer))
m.tables = m.db.QuotePrefixTableName(getTableNameFromOrmTag(object))
return model
}
model.withArray = append(model.withArray, structAttrPointer)
model.withArray = append(model.withArray, object)
return model
}
func (m *Model) doWithScan(pointer interface{}) error {
if len(m.withArray) == 0 {
// WithAll enables model association operations on all objects that have "with" tag in the struct.
func (m *Model) WithAll() *Model {
model := m.getModel()
model.withAll = true
return model
}
// getWithTagObjectArrayFrom retrieves and returns object array that have "with" tag in the struct.
func (m *Model) getWithTagObjectArrayFrom(pointer interface{}) ([]interface{}, error) {
fieldMap, err := structs.FieldMap(pointer, nil)
if err != nil {
return nil, err
}
withTagObjectArray := make([]interface{}, 0)
for _, fieldValue := range fieldMap {
var (
withTag string
ormTag = fieldValue.Tag(OrmTagForStruct)
match, _ = gregex.MatchString(
fmt.Sprintf(`%s\s*:\s*([^,]+)`, OrmTagForWith),
ormTag,
)
)
if len(match) > 1 {
withTag = match[1]
}
if withTag == "" {
continue
}
withTagObjectArray = append(withTagObjectArray, fieldValue.Value.Interface())
}
return withTagObjectArray, nil
}
// doWithScanStruct handles model association operations feature for single struct.
func (m *Model) doWithScanStruct(pointer interface{}) error {
var (
err error
withArray = m.withArray
)
if m.withAll {
withArray, err = m.getWithTagObjectArrayFrom(pointer)
if err != nil {
return err
}
}
if len(withArray) == 0 {
return nil
}
fieldMap, err := structs.FieldMap(pointer, nil)
if err != nil {
return err
}
for _, withItem := range m.withArray {
for withIndex, withItem := range withArray {
withItemReflectValueType, err := structs.StructType(withItem)
if err != nil {
return err
@ -81,10 +143,109 @@ func (m *Model) doWithScan(pointer interface{}) error {
relatedAttrName, withTag,
)
}
err = m.db.With(fieldValue.Value).
Fields(withItemReflectValueType.FieldKeys()).
bindToReflectValue := fieldValue.Value
switch bindToReflectValue.Kind() {
case reflect.Array, reflect.Slice:
if bindToReflectValue.CanAddr() {
bindToReflectValue = bindToReflectValue.Addr()
}
}
model := m.db.With(fieldValue.Value)
for i, v := range withArray {
if i == withIndex {
continue
}
model = model.With(v)
}
err = model.Fields(withItemReflectValueType.FieldKeys()).
Where(relatedFieldName, relatedFieldValue).
Scan(fieldValue.Value)
Scan(bindToReflectValue)
if err != nil {
return err
}
}
}
}
return nil
}
// doWithScanStructs handles model association operations feature for struct slice.
func (m *Model) doWithScanStructs(pointer interface{}) error {
var (
err error
withArray = m.withArray
)
if m.withAll {
withArray, err = m.getWithTagObjectArrayFrom(pointer)
if err != nil {
return err
}
}
if len(withArray) == 0 {
return nil
}
fieldMap, err := structs.FieldMap(pointer, nil)
if err != nil {
return err
}
for withIndex, withItem := range withArray {
withItemReflectValueType, err := structs.StructType(withItem)
if err != nil {
return err
}
withItemReflectValueTypeStr := gstr.TrimAll(withItemReflectValueType.String(), "*[]")
for fieldName, fieldValue := range fieldMap {
var (
fieldType = fieldValue.Type()
fieldTypeStr = gstr.TrimAll(fieldType.String(), "*[]")
)
if gstr.Compare(fieldTypeStr, withItemReflectValueTypeStr) == 0 {
var (
withTag string
ormTag = fieldValue.Tag(OrmTagForStruct)
match, _ = gregex.MatchString(
fmt.Sprintf(`%s\s*:\s*([^,]+)`, OrmTagForWith),
ormTag,
)
)
if len(match) > 1 {
withTag = match[1]
}
if withTag == "" {
continue
}
array := gstr.SplitAndTrim(withTag, "=")
if len(array) != 2 {
return gerror.Newf(`invalid with tag "%s"`, withTag)
}
var (
relatedFieldName = array[0]
relatedAttrName = array[1]
relatedFieldValue interface{}
)
// Find the value slice of related attribute from `pointer`.
for attributeName, _ := range fieldMap {
if utils.EqualFoldWithoutChars(attributeName, relatedAttrName) {
relatedFieldValue = ListItemValuesUnique(pointer, attributeName)
break
}
}
if relatedFieldValue == nil {
return gerror.Newf(
`cannot find the related value for attribute name "%s" of with tag "%s"`,
relatedAttrName, withTag,
)
}
model := m.db.With(fieldValue.Value)
for i, v := range withArray {
if i == withIndex {
continue
}
model = model.With(v)
}
err = model.Fields(withItemReflectValueType.FieldKeys()).
Where(relatedFieldName, relatedFieldValue).
ScanList(pointer, fieldName, withTag)
if err != nil {
return err
}

View File

@ -10,10 +10,8 @@ import (
"database/sql"
"github.com/gogf/gf/container/gmap"
"github.com/gogf/gf/encoding/gparser"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/internal/empty"
"github.com/gogf/gf/util/gconv"
"reflect"
)
// Json converts `r` to JSON format content.
@ -54,26 +52,7 @@ func (r Record) Struct(pointer interface{}) error {
}
return nil
}
// Special handling for parameter type: reflect.Value
if _, ok := pointer.(reflect.Value); ok {
return convertMapToStruct(r.Map(), pointer)
}
var (
reflectValue = reflect.ValueOf(pointer)
reflectKind = reflectValue.Kind()
)
if reflectKind != reflect.Ptr {
return gerror.New("parameter should be type of *struct/**struct")
}
reflectValue = reflectValue.Elem()
reflectKind = reflectValue.Kind()
if reflectKind == reflect.Invalid {
return gerror.New("parameter is an invalid pointer, maybe nil")
}
if reflectKind != reflect.Ptr && reflectKind != reflect.Struct {
return gerror.New("parameter should be type of *struct/**struct")
}
return convertMapToStruct(r.Map(), pointer)
return gconv.StructTag(r.Map(), pointer, OrmTagForStruct)
}
// IsEmpty checks and returns whether `r` is empty.

View File

@ -7,13 +7,10 @@
package gdb
import (
"database/sql"
"fmt"
"github.com/gogf/gf/container/gvar"
"math"
"reflect"
"github.com/gogf/gf/encoding/gparser"
"github.com/gogf/gf/util/gconv"
"math"
)
// IsEmpty checks and returns whether `r` is empty.
@ -192,49 +189,5 @@ func (r Result) RecordKeyUint(key string) map[uint]Record {
// Structs converts `r` to struct slice.
// Note that the parameter `pointer` should be type of *[]struct/*[]*struct.
func (r Result) Structs(pointer interface{}) (err error) {
var (
reflectValue = reflect.ValueOf(pointer)
reflectKind = reflectValue.Kind()
)
if reflectKind != reflect.Ptr {
return fmt.Errorf("parameter should be type of *[]struct/*[]*struct, but got: %v", reflectKind)
}
reflectValue = reflectValue.Elem()
reflectKind = reflectValue.Kind()
if reflectKind != reflect.Slice && reflectKind != reflect.Array {
return fmt.Errorf("parameter should be type of *[]struct/*[]*struct, but got: %v", reflectKind)
}
length := len(r)
if length == 0 {
// The pointed slice is not empty.
if reflectValue.Len() > 0 {
// It here checks if it has struct item, which is already initialized.
// It then returns error to warn the developer its empty and no conversion.
if v := reflectValue.Index(0); v.Kind() != reflect.Ptr {
return sql.ErrNoRows
}
}
// Do nothing for empty struct slice.
return nil
}
var (
reflectType = reflect.TypeOf(pointer)
array = reflect.MakeSlice(reflectType.Elem(), length, length)
itemType = array.Index(0).Type()
itemKind = itemType.Kind()
)
for i := 0; i < length; i++ {
var elem reflect.Value
if itemKind == reflect.Ptr {
elem = reflect.New(itemType.Elem())
} else {
elem = reflect.New(itemType).Elem()
}
if err = r[i].Struct(elem); err != nil {
return fmt.Errorf(`slice element conversion failed: %s`, err.Error())
}
array.Index(i).Set(elem)
}
reflect.ValueOf(pointer).Elem().Set(array)
return nil
return gconv.StructsTag(r, pointer, OrmTagForStruct)
}

View File

@ -14,7 +14,7 @@ import (
"testing"
)
func Test_Table_Relation_With(t *testing.T) {
func Test_Table_Relation_With_Scan(t *testing.T) {
var (
tableUser = "user"
tableUserDetail = "user_detail"
@ -26,7 +26,7 @@ id int(10) unsigned NOT NULL AUTO_INCREMENT,
name varchar(45) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUser)); err != nil {
`, tableUser)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUser)
@ -37,7 +37,7 @@ uid int(10) unsigned NOT NULL AUTO_INCREMENT,
address varchar(45) NOT NULL,
PRIMARY KEY (uid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUserDetail)); err != nil {
`, tableUserDetail)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUserDetail)
@ -49,7 +49,7 @@ uid int(10) unsigned NOT NULL,
score int(10) unsigned NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUserScores)); err != nil {
`, tableUserScores)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUserScores)
@ -101,7 +101,6 @@ PRIMARY KEY (id)
}
gtest.C(t, func(t *gtest.T) {
var user *User
db.SetDebug(true)
err := db.With(User{}).
With(User{}.UserDetail).
With(User{}.UserScores).
@ -112,5 +111,531 @@ PRIMARY KEY (id)
t.AssertNE(user.UserDetail, nil)
t.Assert(user.UserDetail.Uid, 3)
t.Assert(user.UserDetail.Address, `address_3`)
t.Assert(len(user.UserScores), 5)
t.Assert(user.UserScores[0].Uid, 3)
t.Assert(user.UserScores[0].Score, 1)
t.Assert(user.UserScores[4].Uid, 3)
t.Assert(user.UserScores[4].Score, 5)
})
gtest.C(t, func(t *gtest.T) {
var user User
err := db.With(user).
With(user.UserDetail).
With(user.UserScores).
Where("id", 4).
Scan(&user)
t.AssertNil(err)
t.Assert(user.Id, 4)
t.AssertNE(user.UserDetail, nil)
t.Assert(user.UserDetail.Uid, 4)
t.Assert(user.UserDetail.Address, `address_4`)
t.Assert(len(user.UserScores), 5)
t.Assert(user.UserScores[0].Uid, 4)
t.Assert(user.UserScores[0].Score, 1)
t.Assert(user.UserScores[4].Uid, 4)
t.Assert(user.UserScores[4].Score, 5)
})
gtest.C(t, func(t *gtest.T) {
var user *User
err := db.With(User{}).
With(UserDetail{}).
With(UserScores{}).
Where("id", 4).
Scan(&user)
t.AssertNil(err)
t.Assert(user.Id, 4)
t.AssertNE(user.UserDetail, nil)
t.Assert(user.UserDetail.Uid, 4)
t.Assert(user.UserDetail.Address, `address_4`)
t.Assert(len(user.UserScores), 5)
t.Assert(user.UserScores[0].Uid, 4)
t.Assert(user.UserScores[0].Score, 1)
t.Assert(user.UserScores[4].Uid, 4)
t.Assert(user.UserScores[4].Score, 5)
})
// With part attribute: UserDetail.
gtest.C(t, func(t *gtest.T) {
var user User
err := db.With(user).
With(user.UserDetail).
Where("id", 4).
Scan(&user)
t.AssertNil(err)
t.Assert(user.Id, 4)
t.AssertNE(user.UserDetail, nil)
t.Assert(user.UserDetail.Uid, 4)
t.Assert(user.UserDetail.Address, `address_4`)
t.Assert(len(user.UserScores), 0)
})
// With part attribute: UserScores.
gtest.C(t, func(t *gtest.T) {
var user User
err := db.With(user).
With(user.UserScores).
Where("id", 4).
Scan(&user)
t.AssertNil(err)
t.Assert(user.Id, 4)
t.Assert(user.UserDetail, nil)
t.Assert(len(user.UserScores), 5)
t.Assert(user.UserScores[0].Uid, 4)
t.Assert(user.UserScores[0].Score, 1)
t.Assert(user.UserScores[4].Uid, 4)
t.Assert(user.UserScores[4].Score, 5)
})
}
func Test_Table_Relation_With_ScanList(t *testing.T) {
var (
tableUser = "user"
tableUserDetail = "user_detail"
tableUserScores = "user_scores"
)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
name varchar(45) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUser)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUser)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
uid int(10) unsigned NOT NULL AUTO_INCREMENT,
address varchar(45) NOT NULL,
PRIMARY KEY (uid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUserDetail)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUserDetail)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
uid int(10) unsigned NOT NULL,
score int(10) unsigned NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUserScores)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUserScores)
type UserDetail struct {
gmeta.Meta `orm:"table:user_detail"`
Uid int `json:"uid"`
Address string `json:"address"`
}
type UserScores struct {
gmeta.Meta `orm:"table:user_scores"`
Id int `json:"id"`
Uid int `json:"uid"`
Score int `json:"score"`
}
type User struct {
gmeta.Meta `orm:"table:user"`
Id int `json:"id"`
Name string `json:"name"`
UserDetail *UserDetail `orm:"with:uid=id"`
UserScores []*UserScores `orm:"with:uid=id"`
}
// Initialize the data.
var err error
for i := 1; i <= 5; i++ {
// User.
_, err = db.Insert(tableUser, g.Map{
"id": i,
"name": fmt.Sprintf(`name_%d`, i),
})
gtest.Assert(err, nil)
// Detail.
_, err = db.Insert(tableUserDetail, g.Map{
"uid": i,
"address": fmt.Sprintf(`address_%d`, i),
})
gtest.Assert(err, nil)
// Scores.
for j := 1; j <= 5; j++ {
_, err = db.Insert(tableUserScores, g.Map{
"uid": i,
"score": j,
})
gtest.Assert(err, nil)
}
}
gtest.C(t, func(t *gtest.T) {
var users []*User
err := db.With(User{}).
With(User{}.UserDetail).
With(User{}.UserScores).
Where("id", []int{3, 4}).
Scan(&users)
t.AssertNil(err)
t.Assert(len(users), 2)
t.Assert(users[0].Id, 3)
t.Assert(users[0].Name, "name_3")
t.AssertNE(users[0].UserDetail, nil)
t.Assert(users[0].UserDetail.Uid, 3)
t.Assert(users[0].UserDetail.Address, "address_3")
t.Assert(len(users[0].UserScores), 5)
t.Assert(users[0].UserScores[0].Uid, 3)
t.Assert(users[0].UserScores[0].Score, 1)
t.Assert(users[0].UserScores[4].Uid, 3)
t.Assert(users[0].UserScores[4].Score, 5)
t.Assert(users[1].Id, 4)
t.Assert(users[1].Name, "name_4")
t.AssertNE(users[1].UserDetail, nil)
t.Assert(users[1].UserDetail.Uid, 4)
t.Assert(users[1].UserDetail.Address, "address_4")
t.Assert(len(users[1].UserScores), 5)
t.Assert(users[1].UserScores[0].Uid, 4)
t.Assert(users[1].UserScores[0].Score, 1)
t.Assert(users[1].UserScores[4].Uid, 4)
t.Assert(users[1].UserScores[4].Score, 5)
})
gtest.C(t, func(t *gtest.T) {
var users []User
err := db.With(User{}).
With(User{}.UserDetail).
With(User{}.UserScores).
Where("id", []int{3, 4}).
Scan(&users)
t.AssertNil(err)
t.Assert(len(users), 2)
t.Assert(users[0].Id, 3)
t.Assert(users[0].Name, "name_3")
t.AssertNE(users[0].UserDetail, nil)
t.Assert(users[0].UserDetail.Uid, 3)
t.Assert(users[0].UserDetail.Address, "address_3")
t.Assert(len(users[0].UserScores), 5)
t.Assert(users[0].UserScores[0].Uid, 3)
t.Assert(users[0].UserScores[0].Score, 1)
t.Assert(users[0].UserScores[4].Uid, 3)
t.Assert(users[0].UserScores[4].Score, 5)
t.Assert(users[1].Id, 4)
t.Assert(users[1].Name, "name_4")
t.AssertNE(users[1].UserDetail, nil)
t.Assert(users[1].UserDetail.Uid, 4)
t.Assert(users[1].UserDetail.Address, "address_4")
t.Assert(len(users[1].UserScores), 5)
t.Assert(users[1].UserScores[0].Uid, 4)
t.Assert(users[1].UserScores[0].Score, 1)
t.Assert(users[1].UserScores[4].Uid, 4)
t.Assert(users[1].UserScores[4].Score, 5)
})
// With part attribute: UserDetail.
gtest.C(t, func(t *gtest.T) {
var users []*User
err := db.With(User{}).
With(User{}.UserDetail).
Where("id", []int{3, 4}).
Scan(&users)
t.AssertNil(err)
t.Assert(len(users), 2)
t.Assert(users[0].Id, 3)
t.Assert(users[0].Name, "name_3")
t.AssertNE(users[0].UserDetail, nil)
t.Assert(users[0].UserDetail.Uid, 3)
t.Assert(users[0].UserDetail.Address, "address_3")
t.Assert(len(users[0].UserScores), 0)
t.Assert(users[1].Id, 4)
t.Assert(users[1].Name, "name_4")
t.AssertNE(users[1].UserDetail, nil)
t.Assert(users[1].UserDetail.Uid, 4)
t.Assert(users[1].UserDetail.Address, "address_4")
t.Assert(len(users[1].UserScores), 0)
})
// With part attribute: UserScores.
gtest.C(t, func(t *gtest.T) {
var users []*User
err := db.With(User{}).
With(User{}.UserScores).
Where("id", []int{3, 4}).
Scan(&users)
t.AssertNil(err)
t.Assert(len(users), 2)
t.Assert(users[0].Id, 3)
t.Assert(users[0].Name, "name_3")
t.Assert(users[0].UserDetail, nil)
t.Assert(len(users[0].UserScores), 5)
t.Assert(users[0].UserScores[0].Uid, 3)
t.Assert(users[0].UserScores[0].Score, 1)
t.Assert(users[0].UserScores[4].Uid, 3)
t.Assert(users[0].UserScores[4].Score, 5)
t.Assert(users[1].Id, 4)
t.Assert(users[1].Name, "name_4")
t.Assert(users[1].UserDetail, nil)
t.Assert(len(users[1].UserScores), 5)
t.Assert(users[1].UserScores[0].Uid, 4)
t.Assert(users[1].UserScores[0].Score, 1)
t.Assert(users[1].UserScores[4].Uid, 4)
t.Assert(users[1].UserScores[4].Score, 5)
})
}
func Test_Table_Relation_WithAll_Scan(t *testing.T) {
var (
tableUser = "user"
tableUserDetail = "user_detail"
tableUserScores = "user_scores"
)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
name varchar(45) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUser)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUser)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
uid int(10) unsigned NOT NULL AUTO_INCREMENT,
address varchar(45) NOT NULL,
PRIMARY KEY (uid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUserDetail)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUserDetail)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
uid int(10) unsigned NOT NULL,
score int(10) unsigned NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUserScores)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUserScores)
type UserDetail struct {
gmeta.Meta `orm:"table:user_detail"`
Uid int `json:"uid"`
Address string `json:"address"`
}
type UserScores struct {
gmeta.Meta `orm:"table:user_scores"`
Id int `json:"id"`
Uid int `json:"uid"`
Score int `json:"score"`
}
type User struct {
gmeta.Meta `orm:"table:user"`
Id int `json:"id"`
Name string `json:"name"`
UserDetail *UserDetail `orm:"with:uid=id"`
UserScores []*UserScores `orm:"with:uid=id"`
}
// Initialize the data.
var err error
for i := 1; i <= 5; i++ {
// User.
_, err = db.Insert(tableUser, g.Map{
"id": i,
"name": fmt.Sprintf(`name_%d`, i),
})
gtest.Assert(err, nil)
// Detail.
_, err = db.Insert(tableUserDetail, g.Map{
"uid": i,
"address": fmt.Sprintf(`address_%d`, i),
})
gtest.Assert(err, nil)
// Scores.
for j := 1; j <= 5; j++ {
_, err = db.Insert(tableUserScores, g.Map{
"uid": i,
"score": j,
})
gtest.Assert(err, nil)
}
}
gtest.C(t, func(t *gtest.T) {
var user *User
err := db.Model(tableUser).WithAll().Where("id", 3).Scan(&user)
t.AssertNil(err)
t.Assert(user.Id, 3)
t.AssertNE(user.UserDetail, nil)
t.Assert(user.UserDetail.Uid, 3)
t.Assert(user.UserDetail.Address, `address_3`)
t.Assert(len(user.UserScores), 5)
t.Assert(user.UserScores[0].Uid, 3)
t.Assert(user.UserScores[0].Score, 1)
t.Assert(user.UserScores[4].Uid, 3)
t.Assert(user.UserScores[4].Score, 5)
})
gtest.C(t, func(t *gtest.T) {
var user User
err := db.Model(tableUser).WithAll().Where("id", 4).Scan(&user)
t.AssertNil(err)
t.Assert(user.Id, 4)
t.AssertNE(user.UserDetail, nil)
t.Assert(user.UserDetail.Uid, 4)
t.Assert(user.UserDetail.Address, `address_4`)
t.Assert(len(user.UserScores), 5)
t.Assert(user.UserScores[0].Uid, 4)
t.Assert(user.UserScores[0].Score, 1)
t.Assert(user.UserScores[4].Uid, 4)
t.Assert(user.UserScores[4].Score, 5)
})
}
func Test_Table_Relation_WithAll_ScanList(t *testing.T) {
var (
tableUser = "user"
tableUserDetail = "user_detail"
tableUserScores = "user_scores"
)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
name varchar(45) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUser)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUser)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
uid int(10) unsigned NOT NULL AUTO_INCREMENT,
address varchar(45) NOT NULL,
PRIMARY KEY (uid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUserDetail)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUserDetail)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
uid int(10) unsigned NOT NULL,
score int(10) unsigned NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUserScores)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUserScores)
type UserDetail struct {
gmeta.Meta `orm:"table:user_detail"`
Uid int `json:"uid"`
Address string `json:"address"`
}
type UserScores struct {
gmeta.Meta `orm:"table:user_scores"`
Id int `json:"id"`
Uid int `json:"uid"`
Score int `json:"score"`
}
type User struct {
gmeta.Meta `orm:"table:user"`
Id int `json:"id"`
Name string `json:"name"`
UserDetail *UserDetail `orm:"with:uid=id"`
UserScores []*UserScores `orm:"with:uid=id"`
}
// Initialize the data.
var err error
for i := 1; i <= 5; i++ {
// User.
_, err = db.Insert(tableUser, g.Map{
"id": i,
"name": fmt.Sprintf(`name_%d`, i),
})
gtest.Assert(err, nil)
// Detail.
_, err = db.Insert(tableUserDetail, g.Map{
"uid": i,
"address": fmt.Sprintf(`address_%d`, i),
})
gtest.Assert(err, nil)
// Scores.
for j := 1; j <= 5; j++ {
_, err = db.Insert(tableUserScores, g.Map{
"uid": i,
"score": j,
})
gtest.Assert(err, nil)
}
}
gtest.C(t, func(t *gtest.T) {
var users []*User
err := db.Model(tableUser).WithAll().Where("id", []int{3, 4}).Scan(&users)
t.AssertNil(err)
t.Assert(len(users), 2)
t.Assert(users[0].Id, 3)
t.Assert(users[0].Name, "name_3")
t.AssertNE(users[0].UserDetail, nil)
t.Assert(users[0].UserDetail.Uid, 3)
t.Assert(users[0].UserDetail.Address, "address_3")
t.Assert(len(users[0].UserScores), 5)
t.Assert(users[0].UserScores[0].Uid, 3)
t.Assert(users[0].UserScores[0].Score, 1)
t.Assert(users[0].UserScores[4].Uid, 3)
t.Assert(users[0].UserScores[4].Score, 5)
t.Assert(users[1].Id, 4)
t.Assert(users[1].Name, "name_4")
t.AssertNE(users[1].UserDetail, nil)
t.Assert(users[1].UserDetail.Uid, 4)
t.Assert(users[1].UserDetail.Address, "address_4")
t.Assert(len(users[1].UserScores), 5)
t.Assert(users[1].UserScores[0].Uid, 4)
t.Assert(users[1].UserScores[0].Score, 1)
t.Assert(users[1].UserScores[4].Uid, 4)
t.Assert(users[1].UserScores[4].Score, 5)
})
gtest.C(t, func(t *gtest.T) {
var users []User
err := db.Model(tableUser).WithAll().Where("id", []int{3, 4}).Scan(&users)
t.AssertNil(err)
t.Assert(len(users), 2)
t.Assert(users[0].Id, 3)
t.Assert(users[0].Name, "name_3")
t.AssertNE(users[0].UserDetail, nil)
t.Assert(users[0].UserDetail.Uid, 3)
t.Assert(users[0].UserDetail.Address, "address_3")
t.Assert(len(users[0].UserScores), 5)
t.Assert(users[0].UserScores[0].Uid, 3)
t.Assert(users[0].UserScores[0].Score, 1)
t.Assert(users[0].UserScores[4].Uid, 3)
t.Assert(users[0].UserScores[4].Score, 5)
t.Assert(users[1].Id, 4)
t.Assert(users[1].Name, "name_4")
t.AssertNE(users[1].UserDetail, nil)
t.Assert(users[1].UserDetail.Uid, 4)
t.Assert(users[1].UserDetail.Address, "address_4")
t.Assert(len(users[1].UserScores), 5)
t.Assert(users[1].UserScores[0].Uid, 4)
t.Assert(users[1].UserScores[0].Score, 1)
t.Assert(users[1].UserScores[4].Uid, 4)
t.Assert(users[1].UserScores[4].Score, 5)
})
}

View File

@ -821,7 +821,7 @@ func Test_DB_ToJson(t *testing.T) {
result = nil
err = result.Structs(&users)
t.AssertNE(err, nil)
t.AssertNil(err)
})
gtest.C(t, func(t *gtest.T) {

View File

@ -296,7 +296,7 @@ func Test_Structs_Empty(t *testing.T) {
all, err := db.Table(table).Where("id>100").All()
t.AssertNil(err)
users := make([]User, 10)
t.AssertNE(all.Structs(&users), nil)
t.Assert(all.Structs(&users), nil)
})
gtest.C(t, func(t *gtest.T) {
all, err := db.Table(table).Where("id>100").All()

View File

@ -91,12 +91,11 @@ func TagMapName(pointer interface{}, priority []string) (map[string]string, erro
}
// TagMapField retrieves struct tags as map[tag]*Field from `pointer`, and returns it.
//
// The parameter `pointer` should be type of struct/*struct.
// The parameter `object` should be either type of struct/*struct/[]struct/[]*struct.
//
// Note that it only retrieves the exported attributes with first letter up-case from struct.
func TagMapField(pointer interface{}, priority []string) (map[string]*Field, error) {
fields, err := TagFields(pointer, priority)
func TagMapField(object interface{}, priority []string) (map[string]*Field, error) {
fields, err := TagFields(object, priority)
if err != nil {
return nil, err
}
@ -120,19 +119,31 @@ func getFieldValues(value interface{}) ([]*Field, error) {
reflectValue = reflect.ValueOf(value)
reflectKind = reflectValue.Kind()
}
for reflectKind == reflect.Ptr {
if !reflectValue.IsValid() || reflectValue.IsNil() {
// If pointer is type of *struct and nil, then automatically create a temporary struct.
for {
switch reflectKind {
case reflect.Ptr:
if !reflectValue.IsValid() || reflectValue.IsNil() {
// If pointer is type of *struct and nil, then automatically create a temporary struct.
reflectValue = reflect.New(reflectValue.Type().Elem()).Elem()
reflectKind = reflectValue.Kind()
} else {
reflectValue = reflectValue.Elem()
reflectKind = reflectValue.Kind()
}
case reflect.Array, reflect.Slice:
reflectValue = reflect.New(reflectValue.Type().Elem()).Elem()
reflectKind = reflectValue.Kind()
} else {
reflectValue = reflectValue.Elem()
reflectKind = reflectValue.Kind()
default:
goto exitLoop
}
}
exitLoop:
for reflectKind == reflect.Ptr {
reflectValue = reflectValue.Elem()
reflectKind = reflectValue.Kind()
}
if reflectKind != reflect.Struct {
return nil, errors.New("given value should be type of struct/*struct")
return nil, errors.New("given value should be either type of struct/*struct/[]struct/[]*struct")
}
var (
structType = reflectValue.Type()

View File

@ -12,6 +12,7 @@ import (
)
// StructType retrieves and returns the struct Type of specified struct/*struct.
// The parameter `object` should be either type of struct/*struct/[]struct/[]*struct.
func StructType(object interface{}) (*Type, error) {
var (
reflectValue reflect.Value

View File

@ -10,6 +10,21 @@ import (
"strings"
)
var (
// DefaultTrimChars are the characters which are stripped by Trim* functions in default.
DefaultTrimChars = string([]byte{
'\t', // Tab.
'\v', // Vertical tab.
'\n', // New line (line feed).
'\r', // Carriage return.
'\f', // New page.
' ', // Ordinary space.
0x00, // NUL-byte.
0x85, // Delete.
0xA0, // Non-breaking space.
})
)
// IsLetterUpper checks whether the given byte b is in upper case.
func IsLetterUpper(b byte) bool {
if b >= byte('A') && b <= byte('Z') {
@ -92,3 +107,27 @@ func RemoveSymbols(s string) string {
func EqualFoldWithoutChars(s1, s2 string) bool {
return strings.EqualFold(RemoveSymbols(s1), RemoveSymbols(s2))
}
// SplitAndTrim splits string <str> by a string <delimiter> to an array,
// and calls Trim to every element of this array. It ignores the elements
// which are empty after Trim.
func SplitAndTrim(str, delimiter string, characterMask ...string) []string {
array := make([]string, 0)
for _, v := range strings.Split(str, delimiter) {
v = Trim(v, characterMask...)
if v != "" {
array = append(array, v)
}
}
return array
}
// Trim strips whitespace (or other characters) from the beginning and end of a string.
// The optional parameter <characterMask> specifies the additional stripped characters.
func Trim(str string, characterMask ...string) string {
trimChars := DefaultTrimChars
if len(characterMask) > 0 {
trimChars += characterMask[0]
}
return strings.Trim(str, trimChars)
}

View File

@ -7,32 +7,14 @@
package gstr
import (
"github.com/gogf/gf/internal/utils"
"strings"
)
var (
// defaultTrimChars are the characters which are stripped by Trim* functions in default.
defaultTrimChars = string([]byte{
'\t', // Tab.
'\v', // Vertical tab.
'\n', // New line (line feed).
'\r', // Carriage return.
'\f', // New page.
' ', // Ordinary space.
0x00, // NUL-byte.
0x85, // Delete.
0xA0, // Non-breaking space.
})
)
// Trim strips whitespace (or other characters) from the beginning and end of a string.
// The optional parameter <characterMask> specifies the additional stripped characters.
func Trim(str string, characterMask ...string) string {
trimChars := defaultTrimChars
if len(characterMask) > 0 {
trimChars += characterMask[0]
}
return strings.Trim(str, trimChars)
return utils.Trim(str, characterMask...)
}
// TrimStr strips all of the given <cut> string from the beginning and end of a string.
@ -43,7 +25,7 @@ func TrimStr(str string, cut string, count ...int) string {
// TrimLeft strips whitespace (or other characters) from the beginning of a string.
func TrimLeft(str string, characterMask ...string) string {
trimChars := defaultTrimChars
trimChars := utils.DefaultTrimChars
if len(characterMask) > 0 {
trimChars += characterMask[0]
}
@ -69,7 +51,7 @@ func TrimLeftStr(str string, cut string, count ...int) string {
// TrimRight strips whitespace (or other characters) from the end of a string.
func TrimRight(str string, characterMask ...string) string {
trimChars := defaultTrimChars
trimChars := utils.DefaultTrimChars
if len(characterMask) > 0 {
trimChars += characterMask[0]
}
@ -97,7 +79,7 @@ func TrimRightStr(str string, cut string, count ...int) string {
// TrimAll trims all characters in string `str`.
func TrimAll(str string, characterMask ...string) string {
trimChars := defaultTrimChars
trimChars := utils.DefaultTrimChars
if len(characterMask) > 0 {
trimChars += characterMask[0]
}

View File

@ -232,7 +232,7 @@ func doMapConvertForMapOrStructValue(isRoot bool, value interface{}, recursive b
rvField reflect.Value
dataMap = make(map[string]interface{}) // result map.
reflectType = reflectValue.Type() // attribute value type.
name = "" // name may be the tag name or the struct attribute name.
mapKey = "" // mapKey may be the tag name or the struct attribute name.
)
for i := 0; i < reflectValue.NumField(); i++ {
rtField = reflectType.Field(i)
@ -242,32 +242,32 @@ func doMapConvertForMapOrStructValue(isRoot bool, value interface{}, recursive b
if !utils.IsLetterUpper(fieldName[0]) {
continue
}
name = ""
mapKey = ""
fieldTag := rtField.Tag
for _, tag := range tags {
if name = fieldTag.Get(tag); name != "" {
if mapKey = fieldTag.Get(tag); mapKey != "" {
break
}
}
if name == "" {
name = fieldName
if mapKey == "" {
mapKey = fieldName
} else {
// Support json tag feature: -, omitempty
name = strings.TrimSpace(name)
if name == "-" {
mapKey = strings.TrimSpace(mapKey)
if mapKey == "-" {
continue
}
array := strings.Split(name, ",")
array := strings.Split(mapKey, ",")
if len(array) > 1 {
switch strings.TrimSpace(array[1]) {
case "omitempty":
if empty.IsEmpty(rvField.Interface()) {
continue
} else {
name = strings.TrimSpace(array[0])
mapKey = strings.TrimSpace(array[0])
}
default:
name = strings.TrimSpace(array[0])
mapKey = strings.TrimSpace(array[0])
}
}
}
@ -284,7 +284,7 @@ func doMapConvertForMapOrStructValue(isRoot bool, value interface{}, recursive b
switch rvAttrKind {
case reflect.Struct:
var (
hasNoTag = name == fieldName
hasNoTag = mapKey == fieldName
rvAttrInterface = rvAttrField.Interface()
)
if hasNoTag && rtField.Anonymous {
@ -296,41 +296,41 @@ func doMapConvertForMapOrStructValue(isRoot bool, value interface{}, recursive b
dataMap[k] = v
}
} else {
dataMap[name] = rvAttrInterface
dataMap[mapKey] = rvAttrInterface
}
} else if !hasNoTag && rtField.Anonymous {
// It means this attribute field has desired tag.
dataMap[name] = doMapConvertForMapOrStructValue(false, rvAttrInterface, true, tags...)
dataMap[mapKey] = doMapConvertForMapOrStructValue(false, rvAttrInterface, true, tags...)
} else {
dataMap[name] = doMapConvertForMapOrStructValue(false, rvAttrInterface, false, tags...)
dataMap[mapKey] = doMapConvertForMapOrStructValue(false, rvAttrInterface, false, tags...)
}
// The struct attribute is type of slice.
case reflect.Array, reflect.Slice:
length := rvField.Len()
if length == 0 {
dataMap[name] = rvField.Interface()
dataMap[mapKey] = rvField.Interface()
break
}
array := make([]interface{}, length)
for i := 0; i < length; i++ {
array[i] = doMapConvertForMapOrStructValue(false, rvField.Index(i), recursive, tags...)
}
dataMap[name] = array
dataMap[mapKey] = array
default:
if rvField.IsValid() {
dataMap[name] = reflectValue.Field(i).Interface()
dataMap[mapKey] = reflectValue.Field(i).Interface()
} else {
dataMap[name] = nil
dataMap[mapKey] = nil
}
}
} else {
// No recursive map value converting
if rvField.IsValid() {
dataMap[name] = reflectValue.Field(i).Interface()
dataMap[mapKey] = reflectValue.Field(i).Interface()
} else {
dataMap[name] = nil
dataMap[mapKey] = nil
}
}
}

View File

@ -32,17 +32,32 @@ import (
// in mapping procedure to do the matching.
// It ignores the map key, if it does not match.
func Struct(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
return doStruct(params, pointer, mapping...)
var keyToAttributeNameMapping map[string]string
if len(mapping) > 0 {
keyToAttributeNameMapping = mapping[0]
}
return doStruct(params, pointer, keyToAttributeNameMapping, "")
}
// StructTag acts as Struct but also with support for priority tag feature, which retrieves the
// specified tags for `params` key-value items to struct attribute names mapping.
// The parameter `priorityTag` supports multiple tags that can be joined with char ','.
func StructTag(params interface{}, pointer interface{}, priorityTag string) (err error) {
return doStruct(params, pointer, nil, priorityTag)
}
// StructDeep do Struct function recursively.
// Deprecated, use Struct instead.
func StructDeep(params interface{}, pointer interface{}, mapping ...map[string]string) error {
return doStruct(params, pointer, mapping...)
var keyToAttributeNameMapping map[string]string
if len(mapping) > 0 {
keyToAttributeNameMapping = mapping[0]
}
return doStruct(params, pointer, keyToAttributeNameMapping, "")
}
// doStruct is the core internal converting function for any data to struct.
func doStruct(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
func doStruct(params interface{}, pointer interface{}, mapping map[string]string, priorityTag string) (err error) {
if params == nil {
// If <params> is nil, no conversion.
return nil
@ -179,7 +194,7 @@ func doStruct(params interface{}, pointer interface{}, mapping ...map[string]str
continue
}
}
if err = doStruct(paramsMap, elemFieldValue, mapping...); err != nil {
if err = doStruct(paramsMap, elemFieldValue, mapping, priorityTag); err != nil {
return err
}
} else {
@ -193,13 +208,26 @@ func doStruct(params interface{}, pointer interface{}, mapping ...map[string]str
// The key of the tagMap is the attribute name of the struct,
// and the value is its replaced tag name for later comparison to improve performance.
tagMap := make(map[string]string)
tagToNameMap, err := structs.TagMapName(pointerElemReflectValue, StructTagPriority)
var (
tagMap = make(map[string]string)
priorityTagArray []string
)
if priorityTag != "" {
priorityTagArray = append(utils.SplitAndTrim(priorityTag, ","), StructTagPriority...)
} else {
priorityTagArray = StructTagPriority
}
tagToNameMap, err := structs.TagMapName(pointerElemReflectValue, priorityTagArray)
if err != nil {
return err
}
for k, v := range tagToNameMap {
tagMap[v] = utils.RemoveSymbols(k)
for tagName, attributeName := range tagToNameMap {
// If there's something else in the tag string,
// it uses the first part which is split using char ','.
// Eg:
// orm:"id, priority"
// orm:"name, with:uid=id"
tagMap[attributeName] = utils.RemoveSymbols(strings.Split(tagName, ",")[0])
}
var (
@ -209,8 +237,8 @@ func doStruct(params interface{}, pointer interface{}, mapping ...map[string]str
for mapK, mapV := range paramsMap {
attrName = ""
// It firstly checks the passed mapping rules.
if len(mapping) > 0 && len(mapping[0]) > 0 {
if passedAttrKey, ok := mapping[0][mapK]; ok {
if len(mapping) > 0 {
if passedAttrKey, ok := mapping[mapK]; ok {
attrName = passedAttrKey
}
}
@ -254,7 +282,7 @@ func doStruct(params interface{}, pointer interface{}, mapping ...map[string]str
}
// Mark it done.
doneMap[attrName] = struct{}{}
if err := bindVarToStructAttr(pointerElemReflectValue, attrName, mapV, mapping...); err != nil {
if err := bindVarToStructAttr(pointerElemReflectValue, attrName, mapV, mapping, priorityTag); err != nil {
return err
}
}
@ -262,7 +290,7 @@ func doStruct(params interface{}, pointer interface{}, mapping ...map[string]str
}
// bindVarToStructAttr sets value to struct object attribute by name.
func bindVarToStructAttr(elem reflect.Value, name string, value interface{}, mapping ...map[string]string) (err error) {
func bindVarToStructAttr(elem reflect.Value, name string, value interface{}, mapping map[string]string, priorityTag string) (err error) {
structFieldValue := elem.FieldByName(name)
if !structFieldValue.IsValid() {
return nil
@ -273,7 +301,7 @@ func bindVarToStructAttr(elem reflect.Value, name string, value interface{}, map
}
defer func() {
if e := recover(); e != nil {
if err = bindVarToReflectValue(structFieldValue, value, mapping...); err != nil {
if err = bindVarToReflectValue(structFieldValue, value, mapping, priorityTag); err != nil {
err = gerror.Wrapf(err, `error binding value to attribute "%s"`, name)
}
}
@ -322,7 +350,7 @@ func bindVarToReflectValueWithInterfaceCheck(reflectValue reflect.Value, value i
}
// bindVarToReflectValue sets <value> to reflect value object <structFieldValue>.
func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, mapping ...map[string]string) (err error) {
func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, mapping map[string]string, priorityTag string) (err error) {
if err, ok := bindVarToReflectValueWithInterfaceCheck(structFieldValue, value); ok {
return err
}
@ -342,7 +370,7 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, ma
switch kind {
case reflect.Struct:
// Recursively converting for struct attribute.
if err := doStruct(value, structFieldValue); err != nil {
if err := doStruct(value, structFieldValue, nil, ""); err != nil {
// Note there's reflect conversion mechanism here.
structFieldValue.Set(reflect.ValueOf(value).Convert(structFieldValue.Type()))
}
@ -359,14 +387,14 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, ma
for i := 0; i < v.Len(); i++ {
if t.Kind() == reflect.Ptr {
e := reflect.New(t.Elem()).Elem()
if err := doStruct(v.Index(i).Interface(), e); err != nil {
if err := doStruct(v.Index(i).Interface(), e, nil, ""); err != nil {
// Note there's reflect conversion mechanism here.
e.Set(reflect.ValueOf(v.Index(i).Interface()).Convert(t))
}
a.Index(i).Set(e.Addr())
} else {
e := reflect.New(t).Elem()
if err := doStruct(v.Index(i).Interface(), e); err != nil {
if err := doStruct(v.Index(i).Interface(), e, nil, ""); err != nil {
// Note there's reflect conversion mechanism here.
e.Set(reflect.ValueOf(v.Index(i).Interface()).Convert(t))
}
@ -379,14 +407,14 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, ma
t := a.Index(0).Type()
if t.Kind() == reflect.Ptr {
e := reflect.New(t.Elem()).Elem()
if err := doStruct(value, e); err != nil {
if err := doStruct(value, e, nil, ""); err != nil {
// Note there's reflect conversion mechanism here.
e.Set(reflect.ValueOf(value).Convert(t))
}
a.Index(0).Set(e.Addr())
} else {
e := reflect.New(t).Elem()
if err := doStruct(value, e); err != nil {
if err := doStruct(value, e, nil, ""); err != nil {
// Note there's reflect conversion mechanism here.
e.Set(reflect.ValueOf(value).Convert(t))
}
@ -402,7 +430,7 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, ma
return err
}
elem := item.Elem()
if err = bindVarToReflectValue(elem, value, mapping...); err == nil {
if err = bindVarToReflectValue(elem, value, mapping, priorityTag); err == nil {
structFieldValue.Set(elem.Addr())
}

View File

@ -14,13 +14,28 @@ import (
// Structs converts any slice to given struct slice.
func Structs(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
return doStructs(params, pointer, mapping...)
var keyToAttributeNameMapping map[string]string
if len(mapping) > 0 {
keyToAttributeNameMapping = mapping[0]
}
return doStructs(params, pointer, keyToAttributeNameMapping, "")
}
// StructsTag acts as Structs but also with support for priority tag feature, which retrieves the
// specified tags for `params` key-value items to struct attribute names mapping.
// The parameter `priorityTag` supports multiple tags that can be joined with char ','.
func StructsTag(params interface{}, pointer interface{}, priorityTag string) (err error) {
return doStructs(params, pointer, nil, priorityTag)
}
// StructsDeep converts any slice to given struct slice recursively.
// Deprecated, use Structs instead.
func StructsDeep(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
return doStructs(params, pointer, mapping...)
var keyToAttributeNameMapping map[string]string
if len(mapping) > 0 {
keyToAttributeNameMapping = mapping[0]
}
return doStructs(params, pointer, keyToAttributeNameMapping, "")
}
// doStructs converts any slice to given struct slice.
@ -30,7 +45,7 @@ func StructsDeep(params interface{}, pointer interface{}, mapping ...map[string]
// The parameter <pointer> should be type of pointer to slice of struct.
// Note that if <pointer> is a pointer to another pointer of type of slice of struct,
// it will create the struct/pointer internally.
func doStructs(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
func doStructs(params interface{}, pointer interface{}, mapping map[string]string, priorityTag string) (err error) {
if params == nil {
// If <params> is nil, no conversion.
return nil
@ -102,11 +117,13 @@ func doStructs(params interface{}, pointer interface{}, mapping ...map[string]st
for i := 0; i < len(paramsMaps); i++ {
var tempReflectValue reflect.Value
if i < pointerRvLength {
// Might be nil.
tempReflectValue = pointerRvElem.Index(i).Elem()
} else {
}
if !tempReflectValue.IsValid() {
tempReflectValue = reflect.New(itemType.Elem()).Elem()
}
if err = Struct(paramsMaps[i], tempReflectValue, mapping...); err != nil {
if err = doStruct(paramsMaps[i], tempReflectValue, mapping, priorityTag); err != nil {
return err
}
reflectElemArray.Index(i).Set(tempReflectValue.Addr())
@ -120,7 +137,7 @@ func doStructs(params interface{}, pointer interface{}, mapping ...map[string]st
} else {
tempReflectValue = reflect.New(itemType).Elem()
}
if err = Struct(paramsMaps[i], tempReflectValue, mapping...); err != nil {
if err = doStruct(paramsMaps[i], tempReflectValue, mapping, priorityTag); err != nil {
return err
}
reflectElemArray.Index(i).Set(tempReflectValue)

View File

@ -0,0 +1,77 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gconv_test
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/test/gtest"
"github.com/gogf/gf/util/gconv"
"testing"
)
func Test_StructTag(t *testing.T) {
type User struct {
Uid int
Name string
Pass1 string `orm:"password1"`
Pass2 string `orm:"password2"`
}
gtest.C(t, func(t *gtest.T) {
user := new(User)
params1 := g.Map{
"uid": 1,
"Name": "john",
"password1": "123",
"password2": "456",
}
if err := gconv.Struct(params1, user); err != nil {
t.Error(err)
}
t.Assert(user, &User{
Uid: 1,
Name: "john",
Pass1: "",
Pass2: "",
})
})
gtest.C(t, func(t *gtest.T) {
user := new(User)
params1 := g.Map{
"uid": 1,
"Name": "john",
"password1": "123",
"password2": "456",
}
if err := gconv.StructTag(params1, user, "orm"); err != nil {
t.Error(err)
}
t.Assert(user, &User{
Uid: 1,
Name: "john",
Pass1: "123",
Pass2: "456",
})
})
gtest.C(t, func(t *gtest.T) {
user := new(User)
params2 := g.Map{
"uid": 2,
"name": "smith",
"password1": "111",
"password2": "222",
}
if err := gconv.StructTag(params2, user, "orm"); err != nil {
t.Error(err)
}
t.Assert(user, &User{
Uid: 2,
Name: "smith",
Pass1: "111",
Pass2: "222",
})
})
}

View File

@ -7,14 +7,13 @@
package gconv_test
import (
"github.com/gogf/gf/internal/json"
"testing"
"time"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/internal/json"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/test/gtest"
"github.com/gogf/gf/util/gconv"
"testing"
"time"
)
func Test_Struct_Basic1(t *testing.T) {
@ -904,6 +903,89 @@ func Test_Struct_Embedded(t *testing.T) {
})
}
func Test_Struct_Slice(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
type User struct {
Scores []int
}
user := new(User)
array := g.Slice{1, 2, 3}
err := gconv.Struct(g.Map{"scores": array}, user)
t.Assert(err, nil)
t.Assert(user.Scores, array)
})
gtest.C(t, func(t *gtest.T) {
type User struct {
Scores []int32
}
user := new(User)
array := g.Slice{1, 2, 3}
err := gconv.Struct(g.Map{"scores": array}, user)
t.Assert(err, nil)
t.Assert(user.Scores, array)
})
gtest.C(t, func(t *gtest.T) {
type User struct {
Scores []int64
}
user := new(User)
array := g.Slice{1, 2, 3}
err := gconv.Struct(g.Map{"scores": array}, user)
t.Assert(err, nil)
t.Assert(user.Scores, array)
})
gtest.C(t, func(t *gtest.T) {
type User struct {
Scores []uint
}
user := new(User)
array := g.Slice{1, 2, 3}
err := gconv.Struct(g.Map{"scores": array}, user)
t.Assert(err, nil)
t.Assert(user.Scores, array)
})
gtest.C(t, func(t *gtest.T) {
type User struct {
Scores []uint32
}
user := new(User)
array := g.Slice{1, 2, 3}
err := gconv.Struct(g.Map{"scores": array}, user)
t.Assert(err, nil)
t.Assert(user.Scores, array)
})
gtest.C(t, func(t *gtest.T) {
type User struct {
Scores []uint64
}
user := new(User)
array := g.Slice{1, 2, 3}
err := gconv.Struct(g.Map{"scores": array}, user)
t.Assert(err, nil)
t.Assert(user.Scores, array)
})
gtest.C(t, func(t *gtest.T) {
type User struct {
Scores []float32
}
user := new(User)
array := g.Slice{1, 2, 3}
err := gconv.Struct(g.Map{"scores": array}, user)
t.Assert(err, nil)
t.Assert(user.Scores, array)
})
gtest.C(t, func(t *gtest.T) {
type User struct {
Scores []float64
}
user := new(User)
array := g.Slice{1, 2, 3}
err := gconv.Struct(g.Map{"scores": array}, user)
t.Assert(err, nil)
t.Assert(user.Scores, array)
})
}
func Test_Struct_To_Struct(t *testing.T) {
var TestA struct {
Id int `p:"id"`

View File

@ -14,90 +14,7 @@ import (
"github.com/gogf/gf/util/gconv"
)
func Test_Struct_Slice(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
type User struct {
Scores []int
}
user := new(User)
array := g.Slice{1, 2, 3}
err := gconv.Struct(g.Map{"scores": array}, user)
t.Assert(err, nil)
t.Assert(user.Scores, array)
})
gtest.C(t, func(t *gtest.T) {
type User struct {
Scores []int32
}
user := new(User)
array := g.Slice{1, 2, 3}
err := gconv.Struct(g.Map{"scores": array}, user)
t.Assert(err, nil)
t.Assert(user.Scores, array)
})
gtest.C(t, func(t *gtest.T) {
type User struct {
Scores []int64
}
user := new(User)
array := g.Slice{1, 2, 3}
err := gconv.Struct(g.Map{"scores": array}, user)
t.Assert(err, nil)
t.Assert(user.Scores, array)
})
gtest.C(t, func(t *gtest.T) {
type User struct {
Scores []uint
}
user := new(User)
array := g.Slice{1, 2, 3}
err := gconv.Struct(g.Map{"scores": array}, user)
t.Assert(err, nil)
t.Assert(user.Scores, array)
})
gtest.C(t, func(t *gtest.T) {
type User struct {
Scores []uint32
}
user := new(User)
array := g.Slice{1, 2, 3}
err := gconv.Struct(g.Map{"scores": array}, user)
t.Assert(err, nil)
t.Assert(user.Scores, array)
})
gtest.C(t, func(t *gtest.T) {
type User struct {
Scores []uint64
}
user := new(User)
array := g.Slice{1, 2, 3}
err := gconv.Struct(g.Map{"scores": array}, user)
t.Assert(err, nil)
t.Assert(user.Scores, array)
})
gtest.C(t, func(t *gtest.T) {
type User struct {
Scores []float32
}
user := new(User)
array := g.Slice{1, 2, 3}
err := gconv.Struct(g.Map{"scores": array}, user)
t.Assert(err, nil)
t.Assert(user.Scores, array)
})
gtest.C(t, func(t *gtest.T) {
type User struct {
Scores []float64
}
user := new(User)
array := g.Slice{1, 2, 3}
err := gconv.Struct(g.Map{"scores": array}, user)
t.Assert(err, nil)
t.Assert(user.Scores, array)
})
}
func Test_Struct_SliceWithTag(t *testing.T) {
func Test_Structs_WithTag(t *testing.T) {
type User struct {
Uid int `json:"id"`
NickName string `json:"name"`
@ -144,6 +61,97 @@ func Test_Struct_SliceWithTag(t *testing.T) {
})
}
func Test_Structs_WithoutTag(t *testing.T) {
type User struct {
Uid int
NickName string
}
gtest.C(t, func(t *gtest.T) {
var users []User
params := g.Slice{
g.Map{
"uid": 1,
"nick-name": "name1",
},
g.Map{
"uid": 2,
"nick-name": "name2",
},
}
err := gconv.Structs(params, &users)
t.Assert(err, nil)
t.Assert(len(users), 2)
t.Assert(users[0].Uid, 1)
t.Assert(users[0].NickName, "name1")
t.Assert(users[1].Uid, 2)
t.Assert(users[1].NickName, "name2")
})
gtest.C(t, func(t *gtest.T) {
var users []*User
params := g.Slice{
g.Map{
"uid": 1,
"nick-name": "name1",
},
g.Map{
"uid": 2,
"nick-name": "name2",
},
}
err := gconv.Structs(params, &users)
t.Assert(err, nil)
t.Assert(len(users), 2)
t.Assert(users[0].Uid, 1)
t.Assert(users[0].NickName, "name1")
t.Assert(users[1].Uid, 2)
t.Assert(users[1].NickName, "name2")
})
}
func Test_Structs_SliceParameter(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
type User struct {
Uid int
NickName string
}
var users []User
params := g.Slice{
g.Map{
"uid": 1,
"nick-name": "name1",
},
g.Map{
"uid": 2,
"nick-name": "name2",
},
}
err := gconv.Structs(params, users)
t.AssertNE(err, nil)
})
gtest.C(t, func(t *gtest.T) {
type User struct {
Uid int
NickName string
}
type A struct {
Users []User
}
var a A
params := g.Slice{
g.Map{
"uid": 1,
"nick-name": "name1",
},
g.Map{
"uid": 2,
"nick-name": "name2",
},
}
err := gconv.Structs(params, a.Users)
t.AssertNE(err, nil)
})
}
func Test_Structs_DirectReflectSet(t *testing.T) {
type A struct {
Id int
@ -175,7 +183,7 @@ func Test_Structs_DirectReflectSet(t *testing.T) {
})
}
func Test_Structs_SliceIntAttribute(t *testing.T) {
func Test_Structs_IntSliceAttribute(t *testing.T) {
type A struct {
Id []int
}

View File

@ -55,7 +55,7 @@ func Test_Time_Slice_Attribute(t *testing.T) {
"arr": g.Slice{"2021-01-12 12:34:56", "2021-01-12 12:34:57"},
"one": "2021-01-12 12:34:58",
}, &s)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(s.One, "2021-01-12 12:34:58")
t.Assert(s.Arr[0], "2021-01-12 12:34:56")
t.Assert(s.Arr[1], "2021-01-12 12:34:57")