github.com/influx6/npkg@v0.8.8/nreflect/mapper.go (about) 1 package nreflect 2 3 import ( 4 "errors" 5 "reflect" 6 "time" 7 ) 8 9 // nerror ... 10 var ( 11 ErrNoFieldWithTagFound = errors.New("field with tag name not found in struct") 12 ) 13 14 // MapAdapter defines a function type which takes a Field returning a appropriate 15 // representation value or an error. 16 type MapAdapter func(Field) (interface{}, error) 17 18 // TimeMapper returns a MapAdapter which always formats time into provided layout 19 // and returns the string version of the giving time. 20 func TimeMapper(layout string) MapAdapter { 21 return func(f Field) (interface{}, error) { 22 if timeObj, ok := f.Value.Interface().(time.Time); ok { 23 return timeObj.Format(layout), nil 24 } 25 if timeObj, ok := f.Value.Interface().(*time.Time); ok { 26 return timeObj.Format(layout), nil 27 } 28 return nil, errors.New("not time value") 29 } 30 } 31 32 // InverseMapAdapter defines a function type which takes a Field and concrete value 33 // returning appropriate go value or an error. It does the inverse of a MapAdapter. 34 type InverseMapAdapter func(Field, interface{}) (interface{}, error) 35 36 // TimeInverseMapper returns a InverseMapAdapter for time.Time values which 37 // turns incoming string values of time into Time.Time object. 38 func TimeInverseMapper(layout string) InverseMapAdapter { 39 return func(f Field, val interface{}) (interface{}, error) { 40 if _, ok := val.(time.Time); ok { 41 return val, nil 42 } 43 if dtime, ok := val.(*time.Time); ok { 44 return *dtime, nil 45 } 46 if formatted, ok := val.(string); ok { 47 return time.Parse(layout, formatted) 48 } 49 return nil, errors.New("non supported time type") 50 } 51 } 52 53 // Mapper defines an interface which exposes methods to 54 // map a struct from giving tags to a map and vise-versa. 55 type Mapper interface { 56 MapTo(string, interface{}, map[string]interface{}) error 57 MapFrom(string, interface{}) (map[string]interface{}, error) 58 } 59 60 // StructMapper implements a struct mapping utility which allows mapping struct fields 61 // to a map and vise-versa. 62 // It uses custom adapters which if available for a giving type will handle the necessary 63 // conversion else use the default value's of those fields in the map. This means, no nil 64 // struct pointer instance should be passed for either conversion or mapping back. 65 // WARNING: StructMapper is not goroutine safe. 66 type StructMapper struct { 67 adapters map[reflect.Type]MapAdapter 68 iadapters map[reflect.Type]InverseMapAdapter 69 } 70 71 // NewStructMapper returns a new instance of StructMapper. 72 func NewStructMapper() *StructMapper { 73 return &StructMapper{ 74 adapters: make(map[reflect.Type]MapAdapter), 75 iadapters: make(map[reflect.Type]InverseMapAdapter), 76 } 77 } 78 79 // MapTo takes giving struct(target) and map of values which it attempts to map 80 // back into struct field types using tag. It returns error if operation fails. 81 // Ensure provided type is a pointer of giving struct type and is non-nil. 82 func (sm *StructMapper) MapTo(tag string, target interface{}, data map[string]interface{}) error { 83 fields, err := GetTagFields(target, tag, true) 84 if err != nil { 85 return err 86 } 87 88 // If no fields get pulled, just stop here. 89 if len(fields) == 0 { 90 return ErrNoFieldWithTagFound 91 } 92 93 targetValue := reflect.ValueOf(target) 94 if targetValue.Kind() == reflect.Ptr { 95 targetValue = targetValue.Elem() 96 } 97 98 for _, field := range fields { 99 // We do a 3 step checks, first with tag name, if non, then name as is, if not 100 // then use lowercase of name. 101 value, ok := data[field.Tag] 102 if !ok { 103 value, ok = data[field.Name] 104 if !ok { 105 value, ok = data[field.NameLC] 106 if !ok { 107 continue 108 } 109 } 110 } 111 112 fieldTarget := targetValue.Field(field.Index) 113 114 if !fieldTarget.CanSet() { 115 continue 116 } 117 118 if iadapter, ok := sm.iadapters[field.Type]; ok { 119 converted, err := iadapter(field, value) 120 if err != nil { 121 return err 122 } 123 124 fieldTarget.Set(reflect.ValueOf(converted)) 125 continue 126 } 127 128 // If it's a map and the type is a struct, attempt to 129 // map that struct fields with map. 130 if innerMap, ok := value.(map[string]interface{}); ok { 131 if field.Type.Kind() == reflect.Struct { 132 if err := sm.MapTo(tag, fieldTarget.Addr().Interface(), innerMap); err != nil { 133 return err 134 } 135 continue 136 } 137 } 138 139 if innerList, ok := value.([]interface{}); ok { 140 if field.Value.Kind() == reflect.Slice { 141 if len(innerList) == 0 { 142 itemsSlice := reflect.MakeSlice(field.Type, 0, 0) 143 fieldTarget.Set(itemsSlice) 144 continue 145 } 146 147 itemsLen := len(innerList) 148 itemsSlice := reflect.MakeSlice(field.Type, itemsLen, itemsLen*2) 149 reflect.Copy(itemsSlice, reflect.ValueOf(value)) 150 fieldTarget.Set(itemsSlice) 151 continue 152 } 153 } 154 155 fieldTarget.Set(reflect.ValueOf(value)) 156 } 157 158 return nil 159 } 160 161 // MapFrom returns a map which contains all values of provided struct returned as a map 162 // using giving tag name. 163 // Ensure provided type is non-nil. 164 func (sm *StructMapper) MapFrom(tag string, target interface{}) (map[string]interface{}, error) { 165 data := make(map[string]interface{}) 166 167 fields, err := GetTagFields(target, tag, true) 168 if err != nil { 169 return data, err 170 } 171 172 // If it has no fields, or non was extractable, then return empty map. 173 if len(fields) == 0 { 174 return data, nil 175 } 176 177 for _, field := range fields { 178 if !field.Value.CanInterface() { 179 continue 180 } 181 182 if adapter, ok := sm.adapters[field.Type]; ok { 183 res, err := adapter(field) 184 if err != nil { 185 return data, err 186 } 187 188 if field.Tag == "" { 189 data[field.Name] = res 190 } else { 191 data[field.Tag] = res 192 } 193 continue 194 } 195 196 if field.Type.Kind() == reflect.Struct { 197 mapped, err := sm.MapFrom(tag, field.Value.Interface()) 198 if err != nil { 199 return data, err 200 } 201 202 if field.Tag == "" { 203 data[field.Name] = mapped 204 } else { 205 data[field.Tag] = mapped 206 } 207 continue 208 } 209 210 if field.Tag == "" { 211 data[field.Name] = field.Value.Interface() 212 } else { 213 data[field.Tag] = field.Value.Interface() 214 } 215 } 216 217 return data, nil 218 } 219 220 // HasInverseAdapter returns true/false if giving type has inverse adapter registered. 221 func (sm *StructMapper) HasInverseAdapter(ty reflect.Type) bool { 222 if sm.iadapters == nil { 223 sm.iadapters = make(map[reflect.Type]InverseMapAdapter) 224 return false 225 } 226 if ty.Kind() == reflect.Ptr { 227 ty = ty.Elem() 228 } 229 _, exists := sm.adapters[ty] 230 return exists 231 } 232 233 // AddInverseAdapter adds giving inverse adapter to be responsible for generating go type 234 // for giving reflect type. 235 // It replaces any previous inverse adapter with new inverse adapter for type. 236 // WARNING: Ensure to use StructMapper.HasAdapter to validate if adapter 237 // exists for type. 238 func (sm *StructMapper) AddInverseAdapter(ty reflect.Type, adapter InverseMapAdapter) { 239 if sm.iadapters == nil { 240 sm.iadapters = make(map[reflect.Type]InverseMapAdapter) 241 } 242 if ty.Kind() == reflect.Ptr { 243 ty = ty.Elem() 244 } 245 sm.iadapters[ty] = adapter 246 } 247 248 // HasAdapter returns true/false if giving type has adapter registered. 249 func (sm *StructMapper) HasAdapter(ty reflect.Type) bool { 250 if sm.adapters == nil { 251 sm.adapters = make(map[reflect.Type]MapAdapter) 252 return false 253 } 254 if ty.Kind() == reflect.Ptr { 255 ty = ty.Elem() 256 } 257 _, exists := sm.adapters[ty] 258 return exists 259 } 260 261 // AddAdapter adds giving adapter to be responsible for giving type. 262 // It replaces any previous adapter with new adapter for type. 263 // WARNING: Ensure to use StructMapper.HasAdapter to validate if adapter 264 // exists for type. 265 func (sm *StructMapper) AddAdapter(ty reflect.Type, adapter MapAdapter) { 266 if sm.adapters == nil { 267 sm.adapters = make(map[reflect.Type]MapAdapter) 268 } 269 if ty.Kind() == reflect.Ptr { 270 ty = ty.Elem() 271 } 272 sm.adapters[ty] = adapter 273 }