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 }