github.com/gogf/gf/v2@v2.7.4/util/gconv/gconv_scan_list.go (about)

     1  // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the MIT License.
     4  // If a copy of the MIT was not distributed with this file,
     5  // You can obtain one at https://github.com/gogf/gf.
     6  
     7  package gconv
     8  
     9  import (
    10  	"reflect"
    11  
    12  	"github.com/gogf/gf/v2/errors/gcode"
    13  	"github.com/gogf/gf/v2/errors/gerror"
    14  	"github.com/gogf/gf/v2/internal/utils"
    15  	"github.com/gogf/gf/v2/os/gstructs"
    16  )
    17  
    18  // ScanList converts `structSlice` to struct slice which contains other complex struct attributes.
    19  // Note that the parameter `structSlicePointer` should be type of *[]struct/*[]*struct.
    20  //
    21  // Usage example 1: Normal attribute struct relation:
    22  //
    23  //	type EntityUser struct {
    24  //	    Uid  int
    25  //	    Name string
    26  //	}
    27  //
    28  //	type EntityUserDetail struct {
    29  //	    Uid     int
    30  //	    Address string
    31  //	}
    32  //
    33  //	type EntityUserScores struct {
    34  //	    Id     int
    35  //	    Uid    int
    36  //	    Score  int
    37  //	    Course string
    38  //	}
    39  //
    40  //	type Entity struct {
    41  //	    User       *EntityUser
    42  //	    UserDetail *EntityUserDetail
    43  //	    UserScores []*EntityUserScores
    44  //	}
    45  //
    46  // var users []*Entity
    47  // var userRecords   = EntityUser{Uid: 1, Name:"john"}
    48  // var detailRecords = EntityUser{Uid: 1, Address: "chengdu"}
    49  // var scoresRecords = EntityUser{Id: 1, Uid: 1, Score: 100, Course: "math"}
    50  // ScanList(userRecords, &users, "User")
    51  // ScanList(userRecords, &users, "User", "uid")
    52  // ScanList(detailRecords, &users, "UserDetail", "User", "uid:Uid")
    53  // ScanList(scoresRecords, &users, "UserScores", "User", "uid:Uid")
    54  // ScanList(scoresRecords, &users, "UserScores", "User", "uid")
    55  //
    56  // Usage example 2: Embedded attribute struct relation:
    57  //
    58  //	type EntityUser struct {
    59  //		   Uid  int
    60  //		   Name string
    61  //	}
    62  //
    63  //	type EntityUserDetail struct {
    64  //		   Uid     int
    65  //		   Address string
    66  //	}
    67  //
    68  //	type EntityUserScores struct {
    69  //		   Id    int
    70  //		   Uid   int
    71  //		   Score int
    72  //	}
    73  //
    74  //	type Entity struct {
    75  //		   EntityUser
    76  //		   UserDetail EntityUserDetail
    77  //		   UserScores []EntityUserScores
    78  //	}
    79  //
    80  // var userRecords   = EntityUser{Uid: 1, Name:"john"}
    81  // var detailRecords = EntityUser{Uid: 1, Address: "chengdu"}
    82  // var scoresRecords = EntityUser{Id: 1, Uid: 1, Score: 100, Course: "math"}
    83  // ScanList(userRecords, &users)
    84  // ScanList(detailRecords, &users, "UserDetail", "uid")
    85  // ScanList(scoresRecords, &users, "UserScores", "uid")
    86  //
    87  // The parameters "User/UserDetail/UserScores" in the example codes specify the target attribute struct
    88  // that current result will be bound to.
    89  //
    90  // The "uid" in the example codes is the table field name of the result, and the "Uid" is the relational
    91  // struct attribute name - not the attribute name of the bound to target. In the example codes, it's attribute
    92  // name "Uid" of "User" of entity "Entity". It automatically calculates the HasOne/HasMany relationship with
    93  // given `relation` parameter.
    94  //
    95  // See the example or unit testing cases for clear understanding for this function.
    96  func ScanList(structSlice interface{}, structSlicePointer interface{}, bindToAttrName string, relationAttrNameAndFields ...string) (err error) {
    97  	var (
    98  		relationAttrName string
    99  		relationFields   string
   100  	)
   101  	switch len(relationAttrNameAndFields) {
   102  	case 2:
   103  		relationAttrName = relationAttrNameAndFields[0]
   104  		relationFields = relationAttrNameAndFields[1]
   105  	case 1:
   106  		relationFields = relationAttrNameAndFields[0]
   107  	}
   108  	return doScanList(structSlice, structSlicePointer, bindToAttrName, relationAttrName, relationFields)
   109  }
   110  
   111  // doScanList converts `structSlice` to struct slice which contains other complex struct attributes recursively.
   112  // Note that the parameter `structSlicePointer` should be type of *[]struct/*[]*struct.
   113  func doScanList(
   114  	structSlice interface{}, structSlicePointer interface{}, bindToAttrName, relationAttrName, relationFields string,
   115  ) (err error) {
   116  	var (
   117  		maps    = Maps(structSlice)
   118  		lenMaps = len(maps)
   119  	)
   120  	if lenMaps == 0 {
   121  		return nil
   122  	}
   123  	// Necessary checks for parameters.
   124  	if bindToAttrName == "" {
   125  		return gerror.NewCode(gcode.CodeInvalidParameter, `bindToAttrName should not be empty`)
   126  	}
   127  
   128  	if relationAttrName == "." {
   129  		relationAttrName = ""
   130  	}
   131  
   132  	var (
   133  		reflectValue = reflect.ValueOf(structSlicePointer)
   134  		reflectKind  = reflectValue.Kind()
   135  	)
   136  	if reflectKind == reflect.Interface {
   137  		reflectValue = reflectValue.Elem()
   138  		reflectKind = reflectValue.Kind()
   139  	}
   140  	if reflectKind != reflect.Ptr {
   141  		return gerror.NewCodef(
   142  			gcode.CodeInvalidParameter,
   143  			"structSlicePointer should be type of *[]struct/*[]*struct, but got: %v",
   144  			reflectKind,
   145  		)
   146  	}
   147  	reflectValue = reflectValue.Elem()
   148  	reflectKind = reflectValue.Kind()
   149  	if reflectKind != reflect.Slice && reflectKind != reflect.Array {
   150  		return gerror.NewCodef(
   151  			gcode.CodeInvalidParameter,
   152  			"structSlicePointer should be type of *[]struct/*[]*struct, but got: %v",
   153  			reflectKind,
   154  		)
   155  	}
   156  	var (
   157  		arrayValue    reflect.Value // Like: []*Entity
   158  		arrayItemType reflect.Type  // Like: *Entity
   159  		reflectType   = reflect.TypeOf(structSlicePointer)
   160  	)
   161  	if reflectValue.Len() > 0 {
   162  		arrayValue = reflectValue
   163  	} else {
   164  		arrayValue = reflect.MakeSlice(reflectType.Elem(), lenMaps, lenMaps)
   165  	}
   166  
   167  	// Slice element item.
   168  	arrayItemType = arrayValue.Index(0).Type()
   169  
   170  	// Relation variables.
   171  	var (
   172  		relationDataMap         map[string]interface{}
   173  		relationFromFieldName   string // Eg: relationKV: id:uid  -> id
   174  		relationBindToFieldName string // Eg: relationKV: id:uid  -> uid
   175  	)
   176  	if len(relationFields) > 0 {
   177  		// The relation key string of table field name and attribute name
   178  		// can be joined with char '=' or ':'.
   179  		array := utils.SplitAndTrim(relationFields, "=")
   180  		if len(array) == 1 {
   181  			// Compatible with old splitting char ':'.
   182  			array = utils.SplitAndTrim(relationFields, ":")
   183  		}
   184  		if len(array) == 1 {
   185  			// The relation names are the same.
   186  			array = []string{relationFields, relationFields}
   187  		}
   188  		if len(array) == 2 {
   189  			// Defined table field to relation attribute name.
   190  			// Like:
   191  			// uid:Uid
   192  			// uid:UserId
   193  			relationFromFieldName = array[0]
   194  			relationBindToFieldName = array[1]
   195  			if key, _ := utils.MapPossibleItemByKey(maps[0], relationFromFieldName); key == "" {
   196  				return gerror.NewCodef(
   197  					gcode.CodeInvalidParameter,
   198  					`cannot find possible related table field name "%s" from given relation fields "%s"`,
   199  					relationFromFieldName,
   200  					relationFields,
   201  				)
   202  			} else {
   203  				relationFromFieldName = key
   204  			}
   205  		} else {
   206  			return gerror.NewCode(
   207  				gcode.CodeInvalidParameter,
   208  				`parameter relationKV should be format of "ResultFieldName:BindToAttrName"`,
   209  			)
   210  		}
   211  		if relationFromFieldName != "" {
   212  			// Note that the value might be type of slice.
   213  			relationDataMap = utils.ListToMapByKey(maps, relationFromFieldName)
   214  		}
   215  		if len(relationDataMap) == 0 {
   216  			return gerror.NewCodef(
   217  				gcode.CodeInvalidParameter,
   218  				`cannot find the relation data map, maybe invalid relation fields given "%v"`,
   219  				relationFields,
   220  			)
   221  		}
   222  	}
   223  	// Bind to target attribute.
   224  	var (
   225  		ok              bool
   226  		bindToAttrValue reflect.Value
   227  		bindToAttrKind  reflect.Kind
   228  		bindToAttrType  reflect.Type
   229  		bindToAttrField reflect.StructField
   230  	)
   231  	if arrayItemType.Kind() == reflect.Ptr {
   232  		if bindToAttrField, ok = arrayItemType.Elem().FieldByName(bindToAttrName); !ok {
   233  			return gerror.NewCodef(
   234  				gcode.CodeInvalidParameter,
   235  				`invalid parameter bindToAttrName: cannot find attribute with name "%s" from slice element`,
   236  				bindToAttrName,
   237  			)
   238  		}
   239  	} else {
   240  		if bindToAttrField, ok = arrayItemType.FieldByName(bindToAttrName); !ok {
   241  			return gerror.NewCodef(
   242  				gcode.CodeInvalidParameter,
   243  				`invalid parameter bindToAttrName: cannot find attribute with name "%s" from slice element`,
   244  				bindToAttrName,
   245  			)
   246  		}
   247  	}
   248  	bindToAttrType = bindToAttrField.Type
   249  	bindToAttrKind = bindToAttrType.Kind()
   250  
   251  	// Bind to relation conditions.
   252  	var (
   253  		relationFromAttrValue          reflect.Value
   254  		relationFromAttrField          reflect.Value
   255  		relationBindToFieldNameChecked bool
   256  	)
   257  	for i := 0; i < arrayValue.Len(); i++ {
   258  		arrayElemValue := arrayValue.Index(i)
   259  		// The FieldByName should be called on non-pointer reflect.Value.
   260  		if arrayElemValue.Kind() == reflect.Ptr {
   261  			// Like: []*Entity
   262  			arrayElemValue = arrayElemValue.Elem()
   263  			if !arrayElemValue.IsValid() {
   264  				// The element is nil, then create one and set it to the slice.
   265  				// The "reflect.New(itemType.Elem())" creates a new element and returns the address of it.
   266  				// For example:
   267  				// reflect.New(itemType.Elem())        => *Entity
   268  				// reflect.New(itemType.Elem()).Elem() => Entity
   269  				arrayElemValue = reflect.New(arrayItemType.Elem()).Elem()
   270  				arrayValue.Index(i).Set(arrayElemValue.Addr())
   271  			}
   272  		} else {
   273  			// Like: []Entity
   274  		}
   275  		bindToAttrValue = arrayElemValue.FieldByName(bindToAttrName)
   276  		if relationAttrName != "" {
   277  			// Attribute value of current slice element.
   278  			relationFromAttrValue = arrayElemValue.FieldByName(relationAttrName)
   279  			if relationFromAttrValue.Kind() == reflect.Ptr {
   280  				relationFromAttrValue = relationFromAttrValue.Elem()
   281  			}
   282  		} else {
   283  			// Current slice element.
   284  			relationFromAttrValue = arrayElemValue
   285  		}
   286  		if len(relationDataMap) > 0 && !relationFromAttrValue.IsValid() {
   287  			return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
   288  		}
   289  		// Check and find possible bind to attribute name.
   290  		if relationFields != "" && !relationBindToFieldNameChecked {
   291  			relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
   292  			if !relationFromAttrField.IsValid() {
   293  				var (
   294  					fieldMap, _ = gstructs.FieldMap(gstructs.FieldMapInput{
   295  						Pointer:         relationFromAttrValue,
   296  						RecursiveOption: gstructs.RecursiveOptionEmbeddedNoTag,
   297  					})
   298  				)
   299  				if key, _ := utils.MapPossibleItemByKey(Map(fieldMap), relationBindToFieldName); key == "" {
   300  					return gerror.NewCodef(
   301  						gcode.CodeInvalidParameter,
   302  						`cannot find possible related attribute name "%s" from given relation fields "%s"`,
   303  						relationBindToFieldName,
   304  						relationFields,
   305  					)
   306  				} else {
   307  					relationBindToFieldName = key
   308  				}
   309  			}
   310  			relationBindToFieldNameChecked = true
   311  		}
   312  		switch bindToAttrKind {
   313  		case reflect.Array, reflect.Slice:
   314  			if len(relationDataMap) > 0 {
   315  				relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
   316  				if relationFromAttrField.IsValid() {
   317  					// results := make(Result, 0)
   318  					results := make([]interface{}, 0)
   319  					for _, v := range SliceAny(relationDataMap[String(relationFromAttrField.Interface())]) {
   320  						item := v
   321  						results = append(results, item)
   322  					}
   323  					if err = Structs(results, bindToAttrValue.Addr()); err != nil {
   324  						return err
   325  					}
   326  				} else {
   327  					// Maybe the attribute does not exist yet.
   328  					return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
   329  				}
   330  			} else {
   331  				return gerror.NewCodef(
   332  					gcode.CodeInvalidParameter,
   333  					`relationKey should not be empty as field "%s" is slice`,
   334  					bindToAttrName,
   335  				)
   336  			}
   337  
   338  		case reflect.Ptr:
   339  			var element reflect.Value
   340  			if bindToAttrValue.IsNil() {
   341  				element = reflect.New(bindToAttrType.Elem()).Elem()
   342  			} else {
   343  				element = bindToAttrValue.Elem()
   344  			}
   345  			if len(relationDataMap) > 0 {
   346  				relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
   347  				if relationFromAttrField.IsValid() {
   348  					v := relationDataMap[String(relationFromAttrField.Interface())]
   349  					if v == nil {
   350  						// There's no relational data.
   351  						continue
   352  					}
   353  					if utils.IsSlice(v) {
   354  						if err = Struct(SliceAny(v)[0], element); err != nil {
   355  							return err
   356  						}
   357  					} else {
   358  						if err = Struct(v, element); err != nil {
   359  							return err
   360  						}
   361  					}
   362  				} else {
   363  					// Maybe the attribute does not exist yet.
   364  					return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
   365  				}
   366  			} else {
   367  				if i >= len(maps) {
   368  					// There's no relational data.
   369  					continue
   370  				}
   371  				v := maps[i]
   372  				if v == nil {
   373  					// There's no relational data.
   374  					continue
   375  				}
   376  				if err = Struct(v, element); err != nil {
   377  					return err
   378  				}
   379  			}
   380  			bindToAttrValue.Set(element.Addr())
   381  
   382  		case reflect.Struct:
   383  			if len(relationDataMap) > 0 {
   384  				relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
   385  				if relationFromAttrField.IsValid() {
   386  					relationDataItem := relationDataMap[String(relationFromAttrField.Interface())]
   387  					if relationDataItem == nil {
   388  						// There's no relational data.
   389  						continue
   390  					}
   391  					if utils.IsSlice(relationDataItem) {
   392  						if err = Struct(SliceAny(relationDataItem)[0], bindToAttrValue); err != nil {
   393  							return err
   394  						}
   395  					} else {
   396  						if err = Struct(relationDataItem, bindToAttrValue); err != nil {
   397  							return err
   398  						}
   399  					}
   400  				} else {
   401  					// Maybe the attribute does not exist yet.
   402  					return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
   403  				}
   404  			} else {
   405  				if i >= len(maps) {
   406  					// There's no relational data.
   407  					continue
   408  				}
   409  				relationDataItem := maps[i]
   410  				if relationDataItem == nil {
   411  					// There's no relational data.
   412  					continue
   413  				}
   414  				if err = Struct(relationDataItem, bindToAttrValue); err != nil {
   415  					return err
   416  				}
   417  			}
   418  
   419  		default:
   420  			return gerror.NewCodef(gcode.CodeInvalidParameter, `unsupported attribute type: %s`, bindToAttrKind.String())
   421  		}
   422  	}
   423  	reflect.ValueOf(structSlicePointer).Elem().Set(arrayValue)
   424  	return nil
   425  }