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  }