| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388 |
- // Copyright 2019 Yunion
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package reflectutils
- import (
- "reflect"
- "strings"
- "sync"
- "yunion.io/x/pkg/gotypes"
- "yunion.io/x/pkg/utils"
- )
- // SStructFieldInfo describes struct field, especially behavior for (json)
- // marshal
- //
- // This struct has unexported fields initialized by exported functions in this
- // package. Do not construct a literal or modify the exported fields in an
- // unmanaged way
- type SStructFieldInfo struct {
- // True if the field has json tag `json:"-"`
- Ignore bool
- OmitEmpty bool
- OmitFalse bool
- OmitZero bool
- // Name can take the following values, in descreasing preference
- //
- // 1. value of "name" tag, e.g. `name:"a-name"`
- // 2. name of "json" tag, when it's not for ignoration
- // 3. kebab form of FieldName concatenated with "_" when Ignore is false
- // 4. empty string
- Name string
- FieldName string
- kebabFieldName string
- ForceString bool
- Tags map[string]string
- Aliases []string
- }
- func (s *SStructFieldInfo) updateTags(k, v string) {
- s.Tags[k] = v
- }
- func (s SStructFieldInfo) deepCopy() *SStructFieldInfo {
- scopy := SStructFieldInfo{
- Ignore: s.Ignore,
- OmitEmpty: s.OmitEmpty,
- OmitFalse: s.OmitFalse,
- OmitZero: s.OmitZero,
- Name: s.Name,
- FieldName: s.FieldName,
- ForceString: s.ForceString,
- kebabFieldName: s.kebabFieldName,
- }
- tags := make(map[string]string, len(s.Tags))
- for k, v := range s.Tags {
- tags[k] = v
- }
- scopy.Tags = tags
- aliases := make([]string, len(s.Aliases))
- copy(aliases, s.Aliases)
- scopy.Aliases = aliases
- return &scopy
- }
- func ParseStructFieldJsonInfo(sf reflect.StructField) SStructFieldInfo {
- return ParseFieldJsonInfo(sf.Name, sf.Tag)
- }
- func ParseFieldJsonInfo(name string, tag reflect.StructTag) SStructFieldInfo {
- info := SStructFieldInfo{}
- info.FieldName = name
- info.kebabFieldName = utils.CamelSplit(name, "_")
- info.OmitEmpty = true
- info.OmitZero = false
- info.OmitFalse = false
- info.Tags = utils.TagMap(tag)
- if val, ok := info.Tags["json"]; ok {
- keys := strings.Split(val, ",")
- if len(keys) > 0 {
- if keys[0] == "-" {
- if len(keys) > 1 {
- info.Name = keys[0]
- } else {
- info.Ignore = true
- }
- } else {
- info.Name = keys[0]
- }
- }
- if len(keys) > 1 {
- for _, k := range keys[1:] {
- switch strings.ToLower(k) {
- case "omitempty":
- info.OmitEmpty = true
- case "allowempty":
- info.OmitEmpty = false
- case "omitzero":
- info.OmitZero = true
- case "allowzero":
- info.OmitZero = false
- case "omitfalse":
- info.OmitFalse = true
- case "allowfalse":
- info.OmitFalse = false
- case "string":
- info.ForceString = true
- }
- }
- }
- }
- if val, ok := info.Tags["name"]; ok {
- info.Name = val
- }
- if !info.Ignore && len(info.Name) == 0 {
- info.Name = info.kebabFieldName
- }
- if val, ok := info.Tags["alias"]; !info.Ignore && ok {
- info.Aliases = strings.Split(val, ",")
- }
- return info
- }
- // MarshalName returns Name when it's not empty, otherwise it returns kebab
- // form of the field name concatenated with "_"
- func (info *SStructFieldInfo) MarshalName() string {
- if len(info.Name) > 0 {
- return info.Name
- }
- return info.kebabFieldName
- }
- type SStructFieldValue struct {
- Info *SStructFieldInfo
- Value reflect.Value
- Parent *SEmbedStructFieldValue
- }
- type SEmbedStructFieldValue struct {
- Field reflect.Value
- Value reflect.Value
- }
- type SStructFieldValueSet []SStructFieldValue
- func FetchStructFieldValueSet(dataValue reflect.Value) SStructFieldValueSet {
- return expandAmbiguousPrefix(fetchStructFieldValueSet(dataValue, false))
- }
- func FetchStructFieldValueSetForWrite(dataValue reflect.Value) SStructFieldValueSet {
- return expandAmbiguousPrefix(fetchStructFieldValueSet(dataValue, true))
- }
- func FetchAllStructFieldValueSet(dataValue reflect.Value) SStructFieldValueSet {
- return expandAmbiguousPrefix(fetchStructFieldValueSet2(dataValue, false, nil, true))
- }
- func FetchAllStructFieldValueSetForWrite(dataValue reflect.Value) SStructFieldValueSet {
- return expandAmbiguousPrefix(fetchStructFieldValueSet2(dataValue, true, nil, true))
- }
- type sStructFieldInfoMap map[string]SStructFieldInfo
- func newStructFieldInfoMap(caps int) sStructFieldInfoMap {
- return make(map[string]SStructFieldInfo, caps)
- }
- var structFieldInfoCache sync.Map
- func fetchCacheStructFieldInfos(dataType reflect.Type) sStructFieldInfoMap {
- if r, ok := structFieldInfoCache.Load(dataType); ok {
- return r.(sStructFieldInfoMap)
- }
- infos := fetchStructFieldInfos(dataType)
- structFieldInfoCache.Store(dataType, infos)
- return infos
- }
- func fetchStructFieldInfos(dataType reflect.Type) sStructFieldInfoMap {
- smap := newStructFieldInfoMap(dataType.NumField())
- for i := 0; i < dataType.NumField(); i += 1 {
- sf := dataType.Field(i)
- if !gotypes.IsFieldExportable(sf.Name) {
- continue
- }
- if sf.Anonymous {
- // call ParseStructFieldJsonInfo for sf if sft.Kind() is reflect.Interface:
- // if the corresponding value is reflect.Struct, this item in fieldInfos will be ignored,
- // otherwise this item in fieldInfos will be used correctly
- sft := sf.Type
- if sft.Kind() == reflect.Ptr {
- sft = sft.Elem()
- }
- if sft.Kind() == reflect.Struct && sft != gotypes.TimeType {
- continue
- }
- }
- smap[sf.Name] = ParseStructFieldJsonInfo(sf)
- }
- return smap
- }
- func fetchStructFieldValueSet(dataValue reflect.Value, allocatePtr bool) SStructFieldValueSet {
- return fetchStructFieldValueSet2(dataValue, allocatePtr, nil, false)
- }
- func fetchStructFieldValueSet2(dataValue reflect.Value, allocatePtr bool, tags map[string]string, includeIgnore bool) SStructFieldValueSet {
- return fetchStructFieldValueSet3(dataValue, allocatePtr, tags, includeIgnore, nil)
- }
- func fetchStructFieldValueSet3(dataValue reflect.Value, allocatePtr bool, tags map[string]string, includeIgnore bool, parent *SEmbedStructFieldValue) SStructFieldValueSet {
- fields := SStructFieldValueSet{}
- dataType := dataValue.Type()
- fieldInfos := fetchCacheStructFieldInfos(dataType)
- for i := 0; i < dataType.NumField(); i += 1 {
- sf := dataType.Field(i)
- // ignore unexported field altogether
- if !gotypes.IsFieldExportable(sf.Name) {
- continue
- }
- fv := dataValue.Field(i)
- if !fv.IsValid() {
- continue
- }
- var efv *SEmbedStructFieldValue
- if sf.Anonymous {
- // T, *T
- switch fv.Kind() {
- case reflect.Ptr, reflect.Interface:
- if !fv.IsValid() {
- continue
- }
- if fv.IsNil() {
- if fv.Kind() == reflect.Ptr && allocatePtr {
- fv.Set(reflect.New(fv.Type().Elem()))
- } else if fv.Kind() == reflect.Ptr && !allocatePtr {
- efv = &SEmbedStructFieldValue{
- Field: fv,
- Value: reflect.New(fv.Type().Elem()),
- }
- fv = efv.Value
- } else {
- continue
- }
- }
- fv = fv.Elem()
- }
- // note that we regard anonymous interface field the
- // same as with anonymous struct field. This is
- // different from how encoding/json handles struct
- // field of interface type.
- if fv.Kind() == reflect.Struct && sf.Type != gotypes.TimeType {
- anonymousTags := utils.TagMap(sf.Tag)
- subfields := fetchStructFieldValueSet3(fv, allocatePtr, anonymousTags, includeIgnore, efv)
- fields = append(fields, subfields...)
- continue
- }
- }
- fieldInfo := fieldInfos[sf.Name].deepCopy()
- if !fieldInfo.Ignore || includeIgnore {
- structFieldVaule := SStructFieldValue{
- Info: fieldInfo,
- Value: fv,
- }
- if parent != nil {
- structFieldVaule.Parent = parent
- }
- fields = append(fields, structFieldVaule)
- }
- }
- if len(tags) > 0 {
- for i := range fields {
- fieldName := fields[i].Info.MarshalName()
- for k, v := range tags {
- target := ""
- pos := strings.Index(k, "->")
- if pos > 0 {
- target = k[:pos]
- k = k[pos+2:]
- }
- if len(target) > 0 && target != fieldName {
- continue
- }
- fields[i].Info.updateTags(k, v)
- }
- }
- }
- return fields
- }
- func (fields SStructFieldValueSet) GetStructFieldIndex(name string) int {
- indexes := fields.GetStructFieldIndexes(name)
- if len(indexes) > 0 {
- return indexes[0]
- }
- return -1
- }
- func (fields SStructFieldValueSet) GetStructFieldIndexes(name string) []int {
- return fields.GetStructFieldIndexes2(name, false)
- }
- func (fields SStructFieldValueSet) GetStructFieldIndexes2(name string, strictMode bool) []int {
- var (
- ret []int
- kebabName string
- capName string
- )
- if !strictMode && len(fields) > 0 {
- kebabName = utils.CamelSplit(name, "_")
- capName = utils.Capitalize(name)
- }
- for i := range fields {
- info := fields[i].Info
- if info.Ignore {
- continue
- }
- if info.MarshalName() == name {
- ret = append(ret, i)
- continue
- }
- if !strictMode {
- if info.kebabFieldName == kebabName {
- ret = append(ret, i)
- } else if info.FieldName == name {
- ret = append(ret, i)
- } else if info.FieldName == capName {
- ret = append(ret, i)
- } else if len(info.Aliases) > 0 && utils.IsInArray(name, info.Aliases) {
- ret = append(ret, i)
- }
- }
- }
- return ret
- }
- func (fields SStructFieldValueSet) GetStructFieldIndexesMap() map[string][]int {
- keyIndexMap := make(map[string][]int)
- for i := range fields {
- if fields[i].Info.Ignore {
- continue
- }
- key := fields[i].Info.MarshalName()
- values, ok := keyIndexMap[key]
- if !ok {
- values = make([]int, 0, 2)
- }
- keyIndexMap[key] = append(values, i)
- }
- return keyIndexMap
- }
- func (set SStructFieldValueSet) GetValue(name string) (reflect.Value, bool) {
- idx := set.GetStructFieldIndex(name)
- if idx < 0 {
- return reflect.Value{}, false
- }
- return set[idx].Value, true
- }
- func (set SStructFieldValueSet) GetInterface(name string) (interface{}, bool) {
- idx := set.GetStructFieldIndex(name)
- if idx < 0 {
- return nil, false
- }
- if set[idx].Value.CanInterface() {
- return set[idx].Value.Interface(), true
- }
- return nil, false
- }
|