// 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 import ( "reflect" "github.com/gogf/gf/v2/errors/gcode" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/internal/json" ) // Structs converts any slice to given struct slice. // Also see Scan, Struct. func Structs(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) { return Scan(params, pointer, mapping...) } // 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) } // doStructs converts any slice to given struct slice. // // It automatically checks and converts json string to []map if `params` is string/[]byte. // // 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, priorityTag string) (err error) { if params == nil { // If `params` is nil, no conversion. return nil } if pointer == nil { return gerror.NewCode(gcode.CodeInvalidParameter, "object pointer cannot be nil") } if doStructsByDirectReflectSet(params, pointer) { return nil } defer func() { // Catch the panic, especially the reflect operation panics. if exception := recover(); exception != nil { if v, ok := exception.(error); ok && gerror.HasStack(v) { err = v } else { err = gerror.NewCodeSkipf(gcode.CodeInternalError, 1, "%+v", exception) } } }() // If given `params` is JSON, it then uses json.Unmarshal doing the converting. switch r := params.(type) { case []byte: if json.Valid(r) { if rv, ok := pointer.(reflect.Value); ok { if rv.Kind() == reflect.Ptr { return json.UnmarshalUseNumber(r, rv.Interface()) } } else { return json.UnmarshalUseNumber(r, pointer) } } case string: if paramsBytes := []byte(r); json.Valid(paramsBytes) { if rv, ok := pointer.(reflect.Value); ok { if rv.Kind() == reflect.Ptr { return json.UnmarshalUseNumber(paramsBytes, rv.Interface()) } } else { return json.UnmarshalUseNumber(paramsBytes, pointer) } } } // Pointer type check. pointerRv, ok := pointer.(reflect.Value) if !ok { pointerRv = reflect.ValueOf(pointer) if kind := pointerRv.Kind(); kind != reflect.Ptr { return gerror.NewCodef(gcode.CodeInvalidParameter, "pointer should be type of pointer, but got: %v", kind) } } // Converting `params` to map slice. var ( paramsList []interface{} paramsRv = reflect.ValueOf(params) paramsKind = paramsRv.Kind() ) for paramsKind == reflect.Ptr { paramsRv = paramsRv.Elem() paramsKind = paramsRv.Kind() } switch paramsKind { case reflect.Slice, reflect.Array: paramsList = make([]interface{}, paramsRv.Len()) for i := 0; i < paramsRv.Len(); i++ { paramsList[i] = paramsRv.Index(i) } default: var paramsMaps = Maps(params) paramsList = make([]interface{}, len(paramsMaps)) for i := 0; i < len(paramsMaps); i++ { paramsList[i] = paramsMaps[i] } } // If `params` is an empty slice, no conversion. if len(paramsList) == 0 { return nil } var ( reflectElemArray = reflect.MakeSlice(pointerRv.Type().Elem(), len(paramsList), len(paramsList)) itemType = reflectElemArray.Index(0).Type() itemTypeKind = itemType.Kind() pointerRvElem = pointerRv.Elem() pointerRvLength = pointerRvElem.Len() ) if itemTypeKind == reflect.Ptr { // Pointer element. for i := 0; i < len(paramsList); i++ { var tempReflectValue reflect.Value if i < pointerRvLength { // Might be nil. tempReflectValue = pointerRvElem.Index(i).Elem() } if !tempReflectValue.IsValid() { tempReflectValue = reflect.New(itemType.Elem()).Elem() } if err = doStruct(paramsList[i], tempReflectValue, mapping, priorityTag); err != nil { return err } reflectElemArray.Index(i).Set(tempReflectValue.Addr()) } } else { // Struct element. for i := 0; i < len(paramsList); i++ { var tempReflectValue reflect.Value if i < pointerRvLength { tempReflectValue = pointerRvElem.Index(i) } else { tempReflectValue = reflect.New(itemType).Elem() } if err = doStruct(paramsList[i], tempReflectValue, mapping, priorityTag); err != nil { return err } reflectElemArray.Index(i).Set(tempReflectValue) } } pointerRv.Elem().Set(reflectElemArray) return nil } // doStructsByDirectReflectSet do the converting directly using reflect Set. // It returns true if success, or else false. func doStructsByDirectReflectSet(params interface{}, pointer interface{}) (ok bool) { v1 := reflect.ValueOf(pointer) v2 := reflect.ValueOf(params) if v1.Kind() == reflect.Ptr { if elem := v1.Elem(); elem.IsValid() && elem.Type() == v2.Type() { elem.Set(v2) ok = true } } return ok }