123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555 |
- package structure
- // references: https://github.com/mitchellh/mapstructure
- import (
- "encoding/base64"
- "fmt"
- "reflect"
- "strconv"
- "strings"
- )
- // Option is the configuration that is used to create a new decoder
- type Option struct {
- TagName string
- WeaklyTypedInput bool
- KeyReplacer *strings.Replacer
- }
- var DefaultKeyReplacer = strings.NewReplacer("_", "-")
- // Decoder is the core of structure
- type Decoder struct {
- option *Option
- }
- // NewDecoder return a Decoder by Option
- func NewDecoder(option Option) *Decoder {
- if option.TagName == "" {
- option.TagName = "structure"
- }
- return &Decoder{option: &option}
- }
- // Decode transform a map[string]any to a struct
- func (d *Decoder) Decode(src map[string]any, dst any) error {
- if reflect.TypeOf(dst).Kind() != reflect.Ptr {
- return fmt.Errorf("decode must recive a ptr struct")
- }
- t := reflect.TypeOf(dst).Elem()
- v := reflect.ValueOf(dst).Elem()
- for idx := 0; idx < v.NumField(); idx++ {
- field := t.Field(idx)
- if field.Anonymous {
- if err := d.decodeStruct(field.Name, src, v.Field(idx)); err != nil {
- return err
- }
- continue
- }
- tag := field.Tag.Get(d.option.TagName)
- key, omitKey, found := strings.Cut(tag, ",")
- omitempty := found && omitKey == "omitempty"
- value, ok := src[key]
- if !ok {
- if d.option.KeyReplacer != nil {
- key = d.option.KeyReplacer.Replace(key)
- }
- for _strKey := range src {
- strKey := _strKey
- if d.option.KeyReplacer != nil {
- strKey = d.option.KeyReplacer.Replace(strKey)
- }
- if strings.EqualFold(key, strKey) {
- value = src[_strKey]
- ok = true
- break
- }
- }
- }
- if !ok || value == nil {
- if omitempty {
- continue
- }
- return fmt.Errorf("key '%s' missing", key)
- }
- err := d.decode(key, value, v.Field(idx))
- if err != nil {
- return err
- }
- }
- return nil
- }
- func (d *Decoder) decode(name string, data any, val reflect.Value) error {
- kind := val.Kind()
- switch {
- case isInt(kind):
- return d.decodeInt(name, data, val)
- case isUint(kind):
- return d.decodeUint(name, data, val)
- case isFloat(kind):
- return d.decodeFloat(name, data, val)
- }
- switch kind {
- case reflect.Pointer:
- if val.IsNil() {
- val.Set(reflect.New(val.Type().Elem()))
- }
- return d.decode(name, data, val.Elem())
- case reflect.String:
- return d.decodeString(name, data, val)
- case reflect.Bool:
- return d.decodeBool(name, data, val)
- case reflect.Slice:
- return d.decodeSlice(name, data, val)
- case reflect.Map:
- return d.decodeMap(name, data, val)
- case reflect.Interface:
- return d.setInterface(name, data, val)
- case reflect.Struct:
- return d.decodeStruct(name, data, val)
- default:
- return fmt.Errorf("type %s not support", val.Kind().String())
- }
- }
- func isInt(kind reflect.Kind) bool {
- switch kind {
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- return true
- default:
- return false
- }
- }
- func isUint(kind reflect.Kind) bool {
- switch kind {
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
- return true
- default:
- return false
- }
- }
- func isFloat(kind reflect.Kind) bool {
- switch kind {
- case reflect.Float32, reflect.Float64:
- return true
- default:
- return false
- }
- }
- func (d *Decoder) decodeInt(name string, data any, val reflect.Value) (err error) {
- dataVal := reflect.ValueOf(data)
- kind := dataVal.Kind()
- switch {
- case isInt(kind):
- val.SetInt(dataVal.Int())
- case isUint(kind) && d.option.WeaklyTypedInput:
- val.SetInt(int64(dataVal.Uint()))
- case isFloat(kind) && d.option.WeaklyTypedInput:
- val.SetInt(int64(dataVal.Float()))
- case kind == reflect.String && d.option.WeaklyTypedInput:
- var i int64
- i, err = strconv.ParseInt(dataVal.String(), 0, val.Type().Bits())
- if err == nil {
- val.SetInt(i)
- } else {
- err = fmt.Errorf("cannot parse '%s' as int: %s", name, err)
- }
- default:
- err = fmt.Errorf(
- "'%s' expected type '%s', got unconvertible type '%s'",
- name, val.Type(), dataVal.Type(),
- )
- }
- return err
- }
- func (d *Decoder) decodeUint(name string, data any, val reflect.Value) (err error) {
- dataVal := reflect.ValueOf(data)
- kind := dataVal.Kind()
- switch {
- case isUint(kind):
- val.SetUint(dataVal.Uint())
- case isInt(kind) && d.option.WeaklyTypedInput:
- val.SetUint(uint64(dataVal.Int()))
- case isFloat(kind) && d.option.WeaklyTypedInput:
- val.SetUint(uint64(dataVal.Float()))
- case kind == reflect.String && d.option.WeaklyTypedInput:
- var i uint64
- i, err = strconv.ParseUint(dataVal.String(), 0, val.Type().Bits())
- if err == nil {
- val.SetUint(i)
- } else {
- err = fmt.Errorf("cannot parse '%s' as int: %s", name, err)
- }
- default:
- err = fmt.Errorf(
- "'%s' expected type '%s', got unconvertible type '%s'",
- name, val.Type(), dataVal.Type(),
- )
- }
- return err
- }
- func (d *Decoder) decodeFloat(name string, data any, val reflect.Value) (err error) {
- dataVal := reflect.ValueOf(data)
- kind := dataVal.Kind()
- switch {
- case isFloat(kind):
- val.SetFloat(dataVal.Float())
- case isUint(kind):
- val.SetFloat(float64(dataVal.Uint()))
- case isInt(kind) && d.option.WeaklyTypedInput:
- val.SetFloat(float64(dataVal.Int()))
- case kind == reflect.String && d.option.WeaklyTypedInput:
- var i float64
- i, err = strconv.ParseFloat(dataVal.String(), val.Type().Bits())
- if err == nil {
- val.SetFloat(i)
- } else {
- err = fmt.Errorf("cannot parse '%s' as int: %s", name, err)
- }
- default:
- err = fmt.Errorf(
- "'%s' expected type '%s', got unconvertible type '%s'",
- name, val.Type(), dataVal.Type(),
- )
- }
- return err
- }
- func (d *Decoder) decodeString(name string, data any, val reflect.Value) (err error) {
- dataVal := reflect.ValueOf(data)
- kind := dataVal.Kind()
- switch {
- case kind == reflect.String:
- val.SetString(dataVal.String())
- case isInt(kind) && d.option.WeaklyTypedInput:
- val.SetString(strconv.FormatInt(dataVal.Int(), 10))
- case isUint(kind) && d.option.WeaklyTypedInput:
- val.SetString(strconv.FormatUint(dataVal.Uint(), 10))
- case isFloat(kind) && d.option.WeaklyTypedInput:
- val.SetString(strconv.FormatFloat(dataVal.Float(), 'E', -1, dataVal.Type().Bits()))
- default:
- err = fmt.Errorf(
- "'%s' expected type '%s', got unconvertible type '%s'",
- name, val.Type(), dataVal.Type(),
- )
- }
- return err
- }
- func (d *Decoder) decodeBool(name string, data any, val reflect.Value) (err error) {
- dataVal := reflect.ValueOf(data)
- kind := dataVal.Kind()
- switch {
- case kind == reflect.Bool:
- val.SetBool(dataVal.Bool())
- case isInt(kind) && d.option.WeaklyTypedInput:
- val.SetBool(dataVal.Int() != 0)
- case isUint(kind) && d.option.WeaklyTypedInput:
- val.SetString(strconv.FormatUint(dataVal.Uint(), 10))
- default:
- err = fmt.Errorf(
- "'%s' expected type '%s', got unconvertible type '%s'",
- name, val.Type(), dataVal.Type(),
- )
- }
- return err
- }
- func (d *Decoder) decodeSlice(name string, data any, val reflect.Value) error {
- dataVal := reflect.Indirect(reflect.ValueOf(data))
- valType := val.Type()
- valElemType := valType.Elem()
- if dataVal.Kind() == reflect.String && valElemType.Kind() == reflect.Uint8 { // from encoding/json
- s := []byte(dataVal.String())
- b := make([]byte, base64.StdEncoding.DecodedLen(len(s)))
- n, err := base64.StdEncoding.Decode(b, s)
- if err != nil {
- return fmt.Errorf("try decode '%s' by base64 error: %w", name, err)
- }
- val.SetBytes(b[:n])
- return nil
- }
- if dataVal.Kind() != reflect.Slice {
- return fmt.Errorf("'%s' is not a slice", name)
- }
- valSlice := val
- // make a new slice with cap(val)==cap(dataVal)
- // the caller can determine whether the original configuration contains this item by judging whether the value is nil.
- valSlice = reflect.MakeSlice(valType, 0, dataVal.Len())
- for i := 0; i < dataVal.Len(); i++ {
- currentData := dataVal.Index(i).Interface()
- for valSlice.Len() <= i {
- valSlice = reflect.Append(valSlice, reflect.Zero(valElemType))
- }
- fieldName := fmt.Sprintf("%s[%d]", name, i)
- if currentData == nil {
- // in weakly type mode, null will convert to zero value
- if d.option.WeaklyTypedInput {
- continue
- }
- // in non-weakly type mode, null will convert to nil if element's zero value is nil, otherwise return an error
- if elemKind := valElemType.Kind(); elemKind == reflect.Map || elemKind == reflect.Slice {
- continue
- }
- return fmt.Errorf("'%s' can not be null", fieldName)
- }
- currentField := valSlice.Index(i)
- if err := d.decode(fieldName, currentData, currentField); err != nil {
- return err
- }
- }
- val.Set(valSlice)
- return nil
- }
- func (d *Decoder) decodeMap(name string, data any, val reflect.Value) error {
- valType := val.Type()
- valKeyType := valType.Key()
- valElemType := valType.Elem()
- valMap := val
- if valMap.IsNil() {
- mapType := reflect.MapOf(valKeyType, valElemType)
- valMap = reflect.MakeMap(mapType)
- }
- dataVal := reflect.Indirect(reflect.ValueOf(data))
- if dataVal.Kind() != reflect.Map {
- return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind())
- }
- return d.decodeMapFromMap(name, dataVal, val, valMap)
- }
- func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error {
- valType := val.Type()
- valKeyType := valType.Key()
- valElemType := valType.Elem()
- errors := make([]string, 0)
- if dataVal.Len() == 0 {
- if dataVal.IsNil() {
- if !val.IsNil() {
- val.Set(dataVal)
- }
- } else {
- val.Set(valMap)
- }
- return nil
- }
- for _, k := range dataVal.MapKeys() {
- fieldName := fmt.Sprintf("%s[%s]", name, k)
- currentKey := reflect.Indirect(reflect.New(valKeyType))
- if err := d.decode(fieldName, k.Interface(), currentKey); err != nil {
- errors = append(errors, err.Error())
- continue
- }
- v := dataVal.MapIndex(k).Interface()
- if v == nil {
- errors = append(errors, fmt.Sprintf("filed %s invalid", fieldName))
- continue
- }
- currentVal := reflect.Indirect(reflect.New(valElemType))
- if err := d.decode(fieldName, v, currentVal); err != nil {
- errors = append(errors, err.Error())
- continue
- }
- valMap.SetMapIndex(currentKey, currentVal)
- }
- val.Set(valMap)
- if len(errors) > 0 {
- return fmt.Errorf(strings.Join(errors, ","))
- }
- return nil
- }
- func (d *Decoder) decodeStruct(name string, data any, val reflect.Value) error {
- dataVal := reflect.Indirect(reflect.ValueOf(data))
- // If the type of the value to write to and the data match directly,
- // then we just set it directly instead of recursing into the structure.
- if dataVal.Type() == val.Type() {
- val.Set(dataVal)
- return nil
- }
- dataValKind := dataVal.Kind()
- switch dataValKind {
- case reflect.Map:
- return d.decodeStructFromMap(name, dataVal, val)
- default:
- return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind())
- }
- }
- func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) error {
- dataValType := dataVal.Type()
- if kind := dataValType.Key().Kind(); kind != reflect.String && kind != reflect.Interface {
- return fmt.Errorf(
- "'%s' needs a map with string keys, has '%s' keys",
- name, dataValType.Key().Kind())
- }
- dataValKeys := make(map[reflect.Value]struct{})
- dataValKeysUnused := make(map[any]struct{})
- for _, dataValKey := range dataVal.MapKeys() {
- dataValKeys[dataValKey] = struct{}{}
- dataValKeysUnused[dataValKey.Interface()] = struct{}{}
- }
- errors := make([]string, 0)
- // This slice will keep track of all the structs we'll be decoding.
- // There can be more than one struct if there are embedded structs
- // that are squashed.
- structs := make([]reflect.Value, 1, 5)
- structs[0] = val
- // Compile the list of all the fields that we're going to be decoding
- // from all the structs.
- type field struct {
- field reflect.StructField
- val reflect.Value
- }
- var fields []field
- for len(structs) > 0 {
- structVal := structs[0]
- structs = structs[1:]
- structType := structVal.Type()
- for i := 0; i < structType.NumField(); i++ {
- fieldType := structType.Field(i)
- fieldKind := fieldType.Type.Kind()
- // If "squash" is specified in the tag, we squash the field down.
- squash := false
- tagParts := strings.Split(fieldType.Tag.Get(d.option.TagName), ",")
- for _, tag := range tagParts[1:] {
- if tag == "squash" {
- squash = true
- break
- }
- }
- if squash {
- if fieldKind != reflect.Struct {
- errors = append(errors,
- fmt.Errorf("%s: unsupported type for squash: %s", fieldType.Name, fieldKind).Error())
- } else {
- structs = append(structs, structVal.FieldByName(fieldType.Name))
- }
- continue
- }
- // Normal struct field, store it away
- fields = append(fields, field{fieldType, structVal.Field(i)})
- }
- }
- // for fieldType, field := range fields {
- for _, f := range fields {
- field, fieldValue := f.field, f.val
- fieldName := field.Name
- tagValue := field.Tag.Get(d.option.TagName)
- tagValue = strings.SplitN(tagValue, ",", 2)[0]
- if tagValue != "" {
- fieldName = tagValue
- }
- rawMapKey := reflect.ValueOf(fieldName)
- rawMapVal := dataVal.MapIndex(rawMapKey)
- if !rawMapVal.IsValid() {
- // Do a slower search by iterating over each key and
- // doing case-insensitive search.
- if d.option.KeyReplacer != nil {
- fieldName = d.option.KeyReplacer.Replace(fieldName)
- }
- for dataValKey := range dataValKeys {
- mK, ok := dataValKey.Interface().(string)
- if !ok {
- // Not a string key
- continue
- }
- if d.option.KeyReplacer != nil {
- mK = d.option.KeyReplacer.Replace(mK)
- }
- if strings.EqualFold(mK, fieldName) {
- rawMapKey = dataValKey
- rawMapVal = dataVal.MapIndex(dataValKey)
- break
- }
- }
- if !rawMapVal.IsValid() {
- // There was no matching key in the map for the value in
- // the struct. Just ignore.
- continue
- }
- }
- // Delete the key we're using from the unused map so we stop tracking
- delete(dataValKeysUnused, rawMapKey.Interface())
- if !fieldValue.IsValid() {
- // This should never happen
- panic("field is not valid")
- }
- // If we can't set the field, then it is unexported or something,
- // and we just continue onwards.
- if !fieldValue.CanSet() {
- continue
- }
- // If the name is empty string, then we're at the root, and we
- // don't dot-join the fields.
- if name != "" {
- fieldName = fmt.Sprintf("%s.%s", name, fieldName)
- }
- if err := d.decode(fieldName, rawMapVal.Interface(), fieldValue); err != nil {
- errors = append(errors, err.Error())
- }
- }
- if len(errors) > 0 {
- return fmt.Errorf(strings.Join(errors, ","))
- }
- return nil
- }
- func (d *Decoder) setInterface(name string, data any, val reflect.Value) (err error) {
- dataVal := reflect.ValueOf(data)
- val.Set(dataVal)
- return nil
- }
|