/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 reflectx import ( "fmt" "github.com/pkg/errors" "reflect" "unicode" ) // MapToStruct some state can use this util to parse // TODO 性能测试,性能差的话,直接去解析,不使用反射 func MapToStruct(stateName string, obj interface{}, stateMap map[string]interface{}) error { objVal := reflect.ValueOf(obj) if objVal.Kind() != reflect.Pointer { return errors.New(fmt.Sprintf("State [%s] value required a pointer", stateName)) } structValue := objVal.Elem() if structValue.Kind() != reflect.Struct { return errors.New(fmt.Sprintf("State [%s] value elem required a struct", stateName)) } structType := structValue.Type() for key, value := range stateMap { //Get field, get alias first field, found := getField(structType, key) if !found { continue } fieldVal := structValue.FieldByName(field.Name) if !fieldVal.IsValid() { return errors.New(fmt.Sprintf("State [%s] not support [%s] filed", stateName, key)) } //Get setMethod var setMethod reflect.Value if !fieldVal.CanSet() { setMethod = getFiledSetMethod(field.Name, objVal) if !setMethod.IsValid() { fieldAliasName := field.Tag.Get("alias") setMethod = getFiledSetMethod(fieldAliasName, objVal) } if !setMethod.IsValid() { return errors.New(fmt.Sprintf("State [%s] [%s] field not support setMethod", stateName, key)) } setMethodType := setMethod.Type() if !(setMethodType.NumIn() == 1 && setMethodType.In(0) == fieldVal.Type()) { return errors.New(fmt.Sprintf("State [%s] [%s] field setMethod illegal", stateName, key)) } } val := reflect.ValueOf(value) if fieldVal.Kind() == reflect.Struct { //map[string]interface{} if val.Kind() != reflect.Map { return errors.New(fmt.Sprintf("State [%s] [%s] field type required map", stateName, key)) } err := MapToStruct(stateName, fieldVal.Addr().Interface(), value.(map[string]interface{})) if err != nil { return err } } else if fieldVal.Kind() == reflect.Slice { if val.Kind() != reflect.Slice { return errors.New(fmt.Sprintf("State [%s] [%s] field type required slice", stateName, key)) } sliceType := fieldVal.Type().Elem() newSlice := reflect.MakeSlice(fieldVal.Type(), 0, val.Len()) for i := 0; i < val.Len(); i++ { newElem := reflect.New(sliceType.Elem()) elemMap := val.Index(i).Interface().(map[string]interface{}) err := MapToStruct(stateName, newElem.Interface(), elemMap) if err != nil { return err } reflect.Append(newSlice, newElem.Elem()) } setFiled(fieldVal, setMethod, newSlice) } else if fieldVal.Kind() == reflect.Map { if val.Kind() != reflect.Map { return errors.New(fmt.Sprintf("State [%s] [%s] field type required map", stateName, key)) } mapType := field.Type newMap := reflect.MakeMap(mapType) for _, key := range val.MapKeys() { newVal := reflect.New(mapType.Elem().Elem()) elemMap := val.MapIndex(key).Interface().(map[string]interface{}) err := MapToStruct(stateName, newVal.Interface(), elemMap) if err != nil { return err } newMap.SetMapIndex(key, newVal.Elem()) } setFiled(fieldVal, setMethod, newMap) } else { setFiled(fieldVal, setMethod, val) } } return nil } func getField(t reflect.Type, name string) (reflect.StructField, bool) { for i := 0; i < t.NumField(); i++ { field := t.Field(i) tag, hasAliasTag := field.Tag.Lookup("alias") if (hasAliasTag && tag == name) || (!hasAliasTag && field.Name == name) { return field, true } if field.Anonymous { embeddedField, ok := getField(field.Type, name) if ok { return embeddedField, true } } } return reflect.StructField{}, false } func getFiledSetMethod(name string, structValue reflect.Value) reflect.Value { fieldNameSlice := []rune(name) fieldNameSlice[0] = unicode.ToUpper(fieldNameSlice[0]) setMethodName := "Set" + string(fieldNameSlice) setMethod := structValue.MethodByName(setMethodName) return setMethod } func setFiled(fieldVal reflect.Value, setMethod reflect.Value, val reflect.Value) { if !fieldVal.CanSet() { setMethod.Call([]reflect.Value{ val, }) } else { fieldVal.Set(val) } }