github.com/aavshr/aws-sdk-go@v1.41.3/service/dynamodb/dynamodbattribute/converter.go (about)

     1  package dynamodbattribute
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"reflect"
     8  	"runtime"
     9  	"strconv"
    10  
    11  	"github.com/aavshr/aws-sdk-go/aws/awserr"
    12  	"github.com/aavshr/aws-sdk-go/service/dynamodb"
    13  )
    14  
    15  // ConvertToMap accepts a map[string]interface{} or struct and converts it to a
    16  // map[string]*dynamodb.AttributeValue.
    17  //
    18  // If in contains any structs, it is first JSON encoded/decoded it to convert it
    19  // to a map[string]interface{}, so `json` struct tags are respected.
    20  //
    21  // Deprecated: Use MarshalMap instead
    22  func ConvertToMap(in interface{}) (item map[string]*dynamodb.AttributeValue, err error) {
    23  	defer func() {
    24  		if r := recover(); r != nil {
    25  			if e, ok := r.(runtime.Error); ok {
    26  				err = e
    27  			} else if s, ok := r.(string); ok {
    28  				err = fmt.Errorf(s)
    29  			} else {
    30  				err = r.(error)
    31  			}
    32  			item = nil
    33  		}
    34  	}()
    35  
    36  	if in == nil {
    37  		return nil, awserr.New("SerializationError",
    38  			"in must be a map[string]interface{} or struct, got <nil>", nil)
    39  	}
    40  
    41  	v := reflect.ValueOf(in)
    42  	if v.Kind() != reflect.Struct && !(v.Kind() == reflect.Map && v.Type().Key().Kind() == reflect.String) {
    43  		return nil, awserr.New("SerializationError",
    44  			fmt.Sprintf("in must be a map[string]interface{} or struct, got %s",
    45  				v.Type().String()),
    46  			nil)
    47  	}
    48  
    49  	if isTyped(reflect.TypeOf(in)) {
    50  		var out map[string]interface{}
    51  		in = convertToUntyped(in, out)
    52  	}
    53  
    54  	item = make(map[string]*dynamodb.AttributeValue)
    55  	for k, v := range in.(map[string]interface{}) {
    56  		item[k] = convertTo(v)
    57  	}
    58  
    59  	return item, nil
    60  }
    61  
    62  // ConvertFromMap accepts a map[string]*dynamodb.AttributeValue and converts it to a
    63  // map[string]interface{} or struct.
    64  //
    65  // If v points to a struct, the result is first converted it to a
    66  // map[string]interface{}, then JSON encoded/decoded it to convert to a struct,
    67  // so `json` struct tags are respected.
    68  //
    69  // Deprecated: Use UnmarshalMap instead
    70  func ConvertFromMap(item map[string]*dynamodb.AttributeValue, v interface{}) (err error) {
    71  	defer func() {
    72  		if r := recover(); r != nil {
    73  			if e, ok := r.(runtime.Error); ok {
    74  				err = e
    75  			} else if s, ok := r.(string); ok {
    76  				err = fmt.Errorf(s)
    77  			} else {
    78  				err = r.(error)
    79  			}
    80  			item = nil
    81  		}
    82  	}()
    83  
    84  	rv := reflect.ValueOf(v)
    85  	if rv.Kind() != reflect.Ptr || rv.IsNil() {
    86  		return awserr.New("SerializationError",
    87  			fmt.Sprintf("v must be a non-nil pointer to a map[string]interface{} or struct, got %s",
    88  				rv.Type()),
    89  			nil)
    90  	}
    91  	if rv.Elem().Kind() != reflect.Struct && !(rv.Elem().Kind() == reflect.Map && rv.Elem().Type().Key().Kind() == reflect.String) {
    92  		return awserr.New("SerializationError",
    93  			fmt.Sprintf("v must be a non-nil pointer to a map[string]interface{} or struct, got %s",
    94  				rv.Type()),
    95  			nil)
    96  	}
    97  
    98  	m := make(map[string]interface{})
    99  	for k, v := range item {
   100  		m[k] = convertFrom(v)
   101  	}
   102  
   103  	if isTyped(reflect.TypeOf(v)) {
   104  		err = convertToTyped(m, v)
   105  	} else {
   106  		rv.Elem().Set(reflect.ValueOf(m))
   107  	}
   108  
   109  	return err
   110  }
   111  
   112  // ConvertToList accepts an array or slice and converts it to a
   113  // []*dynamodb.AttributeValue.
   114  //
   115  // Converting []byte fields to dynamodb.AttributeValue are only currently supported
   116  // if the input is a map[string]interface{} type. []byte within typed structs are not
   117  // converted correctly and are converted into base64 strings. This is a known bug,
   118  // and will be fixed in a later release.
   119  //
   120  // If in contains any structs, it is first JSON encoded/decoded it to convert it
   121  // to a []interface{}, so `json` struct tags are respected.
   122  //
   123  // Deprecated: Use MarshalList instead
   124  func ConvertToList(in interface{}) (item []*dynamodb.AttributeValue, err error) {
   125  	defer func() {
   126  		if r := recover(); r != nil {
   127  			if e, ok := r.(runtime.Error); ok {
   128  				err = e
   129  			} else if s, ok := r.(string); ok {
   130  				err = fmt.Errorf(s)
   131  			} else {
   132  				err = r.(error)
   133  			}
   134  			item = nil
   135  		}
   136  	}()
   137  
   138  	if in == nil {
   139  		return nil, awserr.New("SerializationError",
   140  			"in must be an array or slice, got <nil>",
   141  			nil)
   142  	}
   143  
   144  	v := reflect.ValueOf(in)
   145  	if v.Kind() != reflect.Array && v.Kind() != reflect.Slice {
   146  		return nil, awserr.New("SerializationError",
   147  			fmt.Sprintf("in must be an array or slice, got %s",
   148  				v.Type().String()),
   149  			nil)
   150  	}
   151  
   152  	if isTyped(reflect.TypeOf(in)) {
   153  		var out []interface{}
   154  		in = convertToUntyped(in, out)
   155  	}
   156  
   157  	item = make([]*dynamodb.AttributeValue, 0, len(in.([]interface{})))
   158  	for _, v := range in.([]interface{}) {
   159  		item = append(item, convertTo(v))
   160  	}
   161  
   162  	return item, nil
   163  }
   164  
   165  // ConvertFromList accepts a []*dynamodb.AttributeValue and converts it to an array or
   166  // slice.
   167  //
   168  // If v contains any structs, the result is first converted it to a
   169  // []interface{}, then JSON encoded/decoded it to convert to a typed array or
   170  // slice, so `json` struct tags are respected.
   171  //
   172  // Deprecated: Use UnmarshalList instead
   173  func ConvertFromList(item []*dynamodb.AttributeValue, v interface{}) (err error) {
   174  	defer func() {
   175  		if r := recover(); r != nil {
   176  			if e, ok := r.(runtime.Error); ok {
   177  				err = e
   178  			} else if s, ok := r.(string); ok {
   179  				err = fmt.Errorf(s)
   180  			} else {
   181  				err = r.(error)
   182  			}
   183  			item = nil
   184  		}
   185  	}()
   186  
   187  	rv := reflect.ValueOf(v)
   188  	if rv.Kind() != reflect.Ptr || rv.IsNil() {
   189  		return awserr.New("SerializationError",
   190  			fmt.Sprintf("v must be a non-nil pointer to an array or slice, got %s",
   191  				rv.Type()),
   192  			nil)
   193  	}
   194  	if rv.Elem().Kind() != reflect.Array && rv.Elem().Kind() != reflect.Slice {
   195  		return awserr.New("SerializationError",
   196  			fmt.Sprintf("v must be a non-nil pointer to an array or slice, got %s",
   197  				rv.Type()),
   198  			nil)
   199  	}
   200  
   201  	l := make([]interface{}, 0, len(item))
   202  	for _, v := range item {
   203  		l = append(l, convertFrom(v))
   204  	}
   205  
   206  	if isTyped(reflect.TypeOf(v)) {
   207  		err = convertToTyped(l, v)
   208  	} else {
   209  		rv.Elem().Set(reflect.ValueOf(l))
   210  	}
   211  
   212  	return err
   213  }
   214  
   215  // ConvertTo accepts any interface{} and converts it to a *dynamodb.AttributeValue.
   216  //
   217  // If in contains any structs, it is first JSON encoded/decoded it to convert it
   218  // to a interface{}, so `json` struct tags are respected.
   219  //
   220  // Deprecated: Use Marshal instead
   221  func ConvertTo(in interface{}) (item *dynamodb.AttributeValue, err error) {
   222  	defer func() {
   223  		if r := recover(); r != nil {
   224  			if e, ok := r.(runtime.Error); ok {
   225  				err = e
   226  			} else if s, ok := r.(string); ok {
   227  				err = fmt.Errorf(s)
   228  			} else {
   229  				err = r.(error)
   230  			}
   231  			item = nil
   232  		}
   233  	}()
   234  
   235  	if in != nil && isTyped(reflect.TypeOf(in)) {
   236  		var out interface{}
   237  		in = convertToUntyped(in, out)
   238  	}
   239  
   240  	item = convertTo(in)
   241  	return item, nil
   242  }
   243  
   244  // ConvertFrom accepts a *dynamodb.AttributeValue and converts it to any interface{}.
   245  //
   246  // If v contains any structs, the result is first converted it to a interface{},
   247  // then JSON encoded/decoded it to convert to a struct, so `json` struct tags
   248  // are respected.
   249  //
   250  // Deprecated: Use Unmarshal instead
   251  func ConvertFrom(item *dynamodb.AttributeValue, v interface{}) (err error) {
   252  	defer func() {
   253  		if r := recover(); r != nil {
   254  			if e, ok := r.(runtime.Error); ok {
   255  				err = e
   256  			} else if s, ok := r.(string); ok {
   257  				err = fmt.Errorf(s)
   258  			} else {
   259  				err = r.(error)
   260  			}
   261  			item = nil
   262  		}
   263  	}()
   264  
   265  	rv := reflect.ValueOf(v)
   266  	if rv.Kind() != reflect.Ptr || rv.IsNil() {
   267  		return awserr.New("SerializationError",
   268  			fmt.Sprintf("v must be a non-nil pointer to an interface{} or struct, got %s",
   269  				rv.Type()),
   270  			nil)
   271  	}
   272  	if rv.Elem().Kind() != reflect.Interface && rv.Elem().Kind() != reflect.Struct {
   273  		return awserr.New("SerializationError",
   274  			fmt.Sprintf("v must be a non-nil pointer to an interface{} or struct, got %s",
   275  				rv.Type()),
   276  			nil)
   277  	}
   278  
   279  	res := convertFrom(item)
   280  
   281  	if isTyped(reflect.TypeOf(v)) {
   282  		err = convertToTyped(res, v)
   283  	} else if res != nil {
   284  		rv.Elem().Set(reflect.ValueOf(res))
   285  	}
   286  
   287  	return err
   288  }
   289  
   290  func isTyped(v reflect.Type) bool {
   291  	switch v.Kind() {
   292  	case reflect.Struct:
   293  		return true
   294  	case reflect.Array, reflect.Slice:
   295  		if isTyped(v.Elem()) {
   296  			return true
   297  		}
   298  	case reflect.Map:
   299  		if isTyped(v.Key()) {
   300  			return true
   301  		}
   302  		if isTyped(v.Elem()) {
   303  			return true
   304  		}
   305  	case reflect.Ptr:
   306  		return isTyped(v.Elem())
   307  	}
   308  	return false
   309  }
   310  
   311  func convertToUntyped(in, out interface{}) interface{} {
   312  	b, err := json.Marshal(in)
   313  	if err != nil {
   314  		panic(err)
   315  	}
   316  
   317  	decoder := json.NewDecoder(bytes.NewReader(b))
   318  	decoder.UseNumber()
   319  	err = decoder.Decode(&out)
   320  	if err != nil {
   321  		panic(err)
   322  	}
   323  
   324  	return out
   325  }
   326  
   327  func convertToTyped(in, out interface{}) error {
   328  	b, err := json.Marshal(in)
   329  	if err != nil {
   330  		return err
   331  	}
   332  
   333  	decoder := json.NewDecoder(bytes.NewReader(b))
   334  	return decoder.Decode(&out)
   335  }
   336  
   337  func convertTo(in interface{}) *dynamodb.AttributeValue {
   338  	a := &dynamodb.AttributeValue{}
   339  
   340  	if in == nil {
   341  		a.NULL = new(bool)
   342  		*a.NULL = true
   343  		return a
   344  	}
   345  
   346  	if m, ok := in.(map[string]interface{}); ok {
   347  		a.M = make(map[string]*dynamodb.AttributeValue)
   348  		for k, v := range m {
   349  			a.M[k] = convertTo(v)
   350  		}
   351  		return a
   352  	}
   353  
   354  	v := reflect.ValueOf(in)
   355  	switch v.Kind() {
   356  	case reflect.Bool:
   357  		a.BOOL = new(bool)
   358  		*a.BOOL = v.Bool()
   359  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   360  		a.N = new(string)
   361  		*a.N = strconv.FormatInt(v.Int(), 10)
   362  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
   363  		a.N = new(string)
   364  		*a.N = strconv.FormatUint(v.Uint(), 10)
   365  	case reflect.Float32, reflect.Float64:
   366  		a.N = new(string)
   367  		*a.N = strconv.FormatFloat(v.Float(), 'f', -1, 64)
   368  	case reflect.String:
   369  		if n, ok := in.(json.Number); ok {
   370  			a.N = new(string)
   371  			*a.N = n.String()
   372  		} else {
   373  			a.S = new(string)
   374  			*a.S = v.String()
   375  		}
   376  	case reflect.Slice:
   377  		switch v.Type() {
   378  		case reflect.TypeOf(([]byte)(nil)):
   379  			a.B = v.Bytes()
   380  		default:
   381  			a.L = make([]*dynamodb.AttributeValue, v.Len())
   382  			for i := 0; i < v.Len(); i++ {
   383  				a.L[i] = convertTo(v.Index(i).Interface())
   384  			}
   385  		}
   386  	default:
   387  		panic(fmt.Sprintf("the type %s is not supported", v.Type().String()))
   388  	}
   389  
   390  	return a
   391  }
   392  
   393  func convertFrom(a *dynamodb.AttributeValue) interface{} {
   394  	if a.S != nil {
   395  		return *a.S
   396  	}
   397  
   398  	if a.N != nil {
   399  		// Number is tricky b/c we don't know which numeric type to use. Here we
   400  		// simply try the different types from most to least restrictive.
   401  		if n, err := strconv.ParseInt(*a.N, 10, 64); err == nil {
   402  			return int(n)
   403  		}
   404  		if n, err := strconv.ParseUint(*a.N, 10, 64); err == nil {
   405  			return uint(n)
   406  		}
   407  		n, err := strconv.ParseFloat(*a.N, 64)
   408  		if err != nil {
   409  			panic(err)
   410  		}
   411  		return n
   412  	}
   413  
   414  	if a.BOOL != nil {
   415  		return *a.BOOL
   416  	}
   417  
   418  	if a.NULL != nil {
   419  		return nil
   420  	}
   421  
   422  	if a.M != nil {
   423  		m := make(map[string]interface{})
   424  		for k, v := range a.M {
   425  			m[k] = convertFrom(v)
   426  		}
   427  		return m
   428  	}
   429  
   430  	if a.L != nil {
   431  		l := make([]interface{}, len(a.L))
   432  		for index, v := range a.L {
   433  			l[index] = convertFrom(v)
   434  		}
   435  		return l
   436  	}
   437  
   438  	if a.B != nil {
   439  		return a.B
   440  	}
   441  
   442  	panic(fmt.Sprintf("%#v is not a supported dynamodb.AttributeValue", a))
   443  }