package settings import ( "errors" "reflect" "strings" ) type ( // KVDecoder interface for custom decoding logic KVDecoder interface { DecodeKV(KV, string) error } ) // DecodeKV converts key-value (KV) into structs using tags & field names // // Supports decoding into all scalar types, can handle nested structures and simple maps (1 dim, string as key) // // Example: // key-value pairs: // string1: "string" // number: 42 // // struct{ // String1 string `kv:"string1` // Number int // } // func DecodeKV(kv KV, dst interface{}, pp ...string) (err error) { valueOf := reflect.ValueOf(dst) if valueOf.Kind() != reflect.Ptr { return errors.New("expecting a pointer, not a value") } if valueOf.IsNil() { return errors.New("nil pointer passed") } var prefix string if len(pp) > 0 { // If called with prefix, join string slice + 1 empty string (to ensure tailing dot) prefix = strings.Join(append(pp, ""), ".") } valueOf = valueOf.Elem() length := valueOf.NumField() for i := 0; i < length; i++ { var structField = valueOf.Field(i) if !structField.CanSet() { continue } var ( structFType = valueOf.Type().Field(i) // whe nwe use name of the struct field directly, remove upper-case from first letter key = prefix + strings.ToLower(structFType.Name[:1]) + structFType.Name[1:] tag = structFType.Tag.Get("kv") tagFlags []string ) if tag == "-" { // Skip fields with kv tags equal to "-" continue } if tag != "" { tagFlags = strings.Split(tag, ",") if tagFlags[0] != "" { // key explicitly set via tag, use that! key = prefix + tagFlags[0] } for f := 1; f < len(tagFlags); f++ { // @todo resolve i18n and other flags... } } var structValue interface{} if structField.Kind() == reflect.Ptr { structValue = structField.Interface() } else { structValue = structField.Addr().Interface() } // Handle custom KVDecoder if decodeMethod := reflect.ValueOf(structValue).MethodByName("DecodeKV"); decodeMethod.IsValid() { if decode, ok := decodeMethod.Interface().(func(KV, string) error); !ok { panic("invalid DecodeKV() function signature") } else if err = decode(kv, key); err != nil { return } else { continue } } // Handles structs // // It calls DecodeKV recursively if structField.Kind() == reflect.Struct { if err = DecodeKV(kv.Filter(key), structValue, key); err != nil { return } continue } // Handles map values if structField.Kind() == reflect.Map { if structField.IsNil() { // allocate new map structField.Set(reflect.MakeMap(structField.Type())) } // cut KV key prefix and use the rest for the map key for k, val := range kv.CutPrefix(key + ".") { mapValue := reflect.New(structField.Type().Elem()) err = val.Unmarshal(mapValue.Interface()) if err != nil { return } structField.SetMapIndex(reflect.ValueOf(k), mapValue.Elem()) } continue } // Native type if val, ok := kv[key]; ok { // Always use pointer to value if err = val.Unmarshal(structField.Addr().Interface()); err != nil { return } } } return }