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