github.com/aldelo/common@v1.5.1/helper-struct.go (about)

     1  package helper
     2  
     3  import (
     4  	"database/sql"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"net/url"
     9  	"reflect"
    10  	"strings"
    11  	"time"
    12  )
    13  
    14  /*
    15   * Copyright 2020-2023 Aldelo, LP
    16   *
    17   * Licensed under the Apache License, Version 2.0 (the "License");
    18   * you may not use this file except in compliance with the License.
    19   * You may obtain a copy of the License at
    20   *
    21   *     http://www.apache.org/licenses/LICENSE-2.0
    22   *
    23   * Unless required by applicable law or agreed to in writing, software
    24   * distributed under the License is distributed on an "AS IS" BASIS,
    25   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    26   * See the License for the specific language governing permissions and
    27   * limitations under the License.
    28   */
    29  
    30  // Fill copies the src struct with same tag name to dst struct tag pointer,
    31  // src and dst both must be struct,and dst must be pointer
    32  func Fill(src interface{}, dst interface{}) error {
    33  	srcType := reflect.TypeOf(src)
    34  	srcValue := reflect.ValueOf(src)
    35  	dstValue := reflect.ValueOf(dst)
    36  
    37  	if srcType.Kind() != reflect.Struct {
    38  		return errors.New("src must be struct")
    39  	}
    40  	if dstValue.Kind() != reflect.Ptr {
    41  		return errors.New("dst must be point")
    42  	}
    43  
    44  	for i := 0; i < srcType.NumField(); i++ {
    45  		dstField := dstValue.Elem().FieldByName(srcType.Field(i).Name)
    46  		if dstField.CanSet() {
    47  			dstField.Set(srcValue.Field(i))
    48  		}
    49  	}
    50  
    51  	return nil
    52  }
    53  
    54  // MarshalStructToQueryParams marshals a struct pointer's fields to query params string,
    55  // output query param names are based on values given in tagName,
    56  // to exclude certain struct fields from being marshaled, use - as value in struct tag defined by tagName,
    57  // if there is a need to name the value of tagName, but still need to exclude from output, use the excludeTagName with -, such as `x:"-"`
    58  //
    59  // special struct tags:
    60  //  1. `getter:"Key"`			// if field type is custom struct or enum,
    61  //     specify the custom method getter (no parameters allowed) that returns the expected value in first ordinal result position
    62  //     NOTE: if the method to invoke resides at struct level, precede the method name with 'base.', for example, 'base.XYZ' where XYZ is method name to invoke
    63  //     NOTE: if the method is to receive a parameter value, always in string data type, add '(x)' after the method name, such as 'XYZ(x)' or 'base.XYZ(x)'
    64  //  2. `booltrue:"1"` 			// if field is defined, contains bool literal for true condition, such as 1 or true, that overrides default system bool literal value,
    65  //     if bool literal value is determined by existence of outprefix and itself is blank, place a space in both booltrue and boolfalse (setting blank will negate literal override)
    66  //  3. `boolfalse:"0"`			// if field is defined, contains bool literal for false condition, such as 0 or false, that overrides default system bool literal value
    67  //     if bool literal value is determined by existence of outprefix and itself is blank, place a space in both booltrue and boolfalse (setting blank will negate literal override)
    68  //  4. `uniqueid:"xyz"`			// if two or more struct field is set with the same uniqueid, then only the first encountered field with the same uniqueid will be used in marshal
    69  //  5. `skipblank:"false"`		// if true, then any fields that is blank string will be excluded from marshal (this only affects fields that are string)
    70  //  6. `skipzero:"false"`		// if true, then any fields that are 0, 0.00, time.Zero(), false, nil will be excluded from marshal (this only affects fields that are number, bool, time, pointer)
    71  //  7. `timeformat:"20060102"`	// for time.Time field, optional date time format, specified as:
    72  //     2006, 06 = year,
    73  //     01, 1, Jan, January = month,
    74  //     02, 2, _2 = day (_2 = width two, right justified)
    75  //     03, 3, 15 = hour (15 = 24 hour format)
    76  //     04, 4 = minute
    77  //     05, 5 = second
    78  //     PM pm = AM PM
    79  //  8. `outprefix:""`			// for marshal method, if field value is to precede with an output prefix, such as XYZ= (affects marshal queryParams / csv methods only)
    80  //  9. `zeroblank:"false"`		// set true to set blank to data when value is 0, 0.00, or time.IsZero
    81  func MarshalStructToQueryParams(inputStructPtr interface{}, tagName string, excludeTagName string) (string, error) {
    82  	if inputStructPtr == nil {
    83  		return "", fmt.Errorf("MarshalStructToQueryParams Requires Input Struct Variable Pointer")
    84  	}
    85  
    86  	if LenTrim(tagName) == 0 {
    87  		return "", fmt.Errorf("MarshalStructToQueryParams Requires TagName (Tag Name defines query parameter name)")
    88  	}
    89  
    90  	s := reflect.ValueOf(inputStructPtr)
    91  
    92  	if s.Kind() != reflect.Ptr {
    93  		return "", fmt.Errorf("MarshalStructToQueryParams Expects inputStructPtr To Be a Pointer")
    94  	} else {
    95  		s = s.Elem()
    96  	}
    97  
    98  	if s.Kind() != reflect.Struct {
    99  		return "", fmt.Errorf("MarshalStructToQueryParams Requires Struct Object")
   100  	}
   101  
   102  	output := ""
   103  	uniqueMap := make(map[string]string)
   104  
   105  	for i := 0; i < s.NumField(); i++ {
   106  		field := s.Type().Field(i)
   107  
   108  		if o := s.FieldByName(field.Name); o.IsValid() {
   109  			tag := field.Tag.Get(tagName)
   110  
   111  			if LenTrim(tag) == 0 {
   112  				tag = field.Name
   113  			}
   114  
   115  			if tag != "-" {
   116  				if LenTrim(excludeTagName) > 0 {
   117  					if Trim(field.Tag.Get(excludeTagName)) == "-" {
   118  						continue
   119  					}
   120  				}
   121  
   122  				if tagUniqueId := Trim(field.Tag.Get("uniqueid")); len(tagUniqueId) > 0 {
   123  					if _, ok := uniqueMap[strings.ToLower(tagUniqueId)]; ok {
   124  						continue
   125  					} else {
   126  						uniqueMap[strings.ToLower(tagUniqueId)] = field.Name
   127  					}
   128  				}
   129  
   130  				var boolTrue, boolFalse, timeFormat, outPrefix string
   131  				var skipBlank, skipZero, zeroblank bool
   132  
   133  				if vs := GetStructTagsValueSlice(field, "booltrue", "boolfalse", "skipblank", "skipzero", "timeformat", "outprefix", "zeroblank"); len(vs) == 7 {
   134  					boolTrue = vs[0]
   135  					boolFalse = vs[1]
   136  					skipBlank, _ = ParseBool(vs[2])
   137  					skipZero, _ = ParseBool(vs[3])
   138  					timeFormat = vs[4]
   139  					outPrefix = vs[5]
   140  					zeroblank, _ = ParseBool(vs[6])
   141  				}
   142  
   143  				oldVal := o
   144  
   145  				if tagGetter := Trim(field.Tag.Get("getter")); len(tagGetter) > 0 {
   146  					isBase := false
   147  					useParam := false
   148  					paramVal := ""
   149  					var paramSlice interface{}
   150  
   151  					if strings.ToLower(Left(tagGetter, 5)) == "base." {
   152  						isBase = true
   153  						tagGetter = Right(tagGetter, len(tagGetter)-5)
   154  					}
   155  
   156  					if strings.ToLower(Right(tagGetter, 3)) == "(x)" {
   157  						useParam = true
   158  
   159  						if o.Kind() != reflect.Slice {
   160  							paramVal, _, _ = ReflectValueToString(o, boolTrue, boolFalse, skipBlank, skipZero, timeFormat, zeroblank)
   161  						} else {
   162  							if o.Len() > 0 {
   163  								paramSlice = o.Slice(0, o.Len()).Interface()
   164  							}
   165  						}
   166  
   167  						tagGetter = Left(tagGetter, len(tagGetter)-3)
   168  					}
   169  
   170  					var ov []reflect.Value
   171  					var notFound bool
   172  
   173  					if isBase {
   174  						if useParam {
   175  							if paramSlice == nil {
   176  								ov, notFound = ReflectCall(s.Addr(), tagGetter, paramVal)
   177  							} else {
   178  								ov, notFound = ReflectCall(s.Addr(), tagGetter, paramSlice)
   179  							}
   180  						} else {
   181  							ov, notFound = ReflectCall(s.Addr(), tagGetter)
   182  						}
   183  					} else {
   184  						if useParam {
   185  							if paramSlice == nil {
   186  								ov, notFound = ReflectCall(o, tagGetter, paramVal)
   187  							} else {
   188  								ov, notFound = ReflectCall(o, tagGetter, paramSlice)
   189  							}
   190  						} else {
   191  							ov, notFound = ReflectCall(o, tagGetter)
   192  						}
   193  					}
   194  
   195  					if !notFound {
   196  						if len(ov) > 0 {
   197  							o = ov[0]
   198  						}
   199  					}
   200  				}
   201  
   202  				if buf, skip, err := ReflectValueToString(o, boolTrue, boolFalse, skipBlank, skipZero, timeFormat, zeroblank); err != nil || skip {
   203  					if tagUniqueId := Trim(field.Tag.Get("uniqueid")); len(tagUniqueId) > 0 {
   204  						if _, ok := uniqueMap[strings.ToLower(tagUniqueId)]; ok {
   205  							delete(uniqueMap, strings.ToLower(tagUniqueId))
   206  						}
   207  					}
   208  
   209  					continue
   210  				} else {
   211  					defVal := field.Tag.Get("def")
   212  
   213  					if oldVal.Kind() == reflect.Int && oldVal.Int() == 0 && strings.ToLower(buf) == "unknown" {
   214  						// unknown enum value will be serialized as blank
   215  						buf = ""
   216  
   217  						if len(defVal) > 0 {
   218  							buf = defVal
   219  						} else {
   220  							if tagUniqueId := Trim(field.Tag.Get("uniqueid")); len(tagUniqueId) > 0 {
   221  								if _, ok := uniqueMap[strings.ToLower(tagUniqueId)]; ok {
   222  									// remove uniqueid if skip
   223  									delete(uniqueMap, strings.ToLower(tagUniqueId))
   224  									continue
   225  								}
   226  							}
   227  						}
   228  					}
   229  
   230  					if boolFalse == " " && len(outPrefix) > 0 && buf == "false" {
   231  						buf = ""
   232  					} else {
   233  						if len(buf) == 0 && len(defVal) > 0 {
   234  							buf = defVal
   235  						}
   236  
   237  						if skipBlank && LenTrim(buf) == 0 {
   238  							buf = ""
   239  						} else if skipZero && buf == "0" {
   240  							buf = ""
   241  						} else {
   242  							buf = outPrefix + buf
   243  						}
   244  					}
   245  
   246  					if LenTrim(output) > 0 {
   247  						output += "&"
   248  					}
   249  
   250  					output += fmt.Sprintf("%s=%s", tag, url.PathEscape(buf))
   251  				}
   252  			}
   253  		}
   254  	}
   255  
   256  	if LenTrim(output) == 0 {
   257  		return "", fmt.Errorf("MarshalStructToQueryParams Yielded Blank Output")
   258  	} else {
   259  		return output, nil
   260  	}
   261  }
   262  
   263  // MarshalStructToJson marshals a struct pointer's fields to json string,
   264  // output json names are based on values given in tagName,
   265  // to exclude certain struct fields from being marshaled, include - as value in struct tag defined by tagName,
   266  // if there is a need to name the value of tagName, but still need to exclude from output, use the excludeTagName with -, such as `x:"-"`
   267  //
   268  // special struct tags:
   269  //  1. `getter:"Key"`			// if field type is custom struct or enum,
   270  //     specify the custom method getter (no parameters allowed) that returns the expected value in first ordinal result position
   271  //     NOTE: if the method to invoke resides at struct level, precede the method name with 'base.', for example, 'base.XYZ' where XYZ is method name to invoke
   272  //     NOTE: if the method is to receive a parameter value, always in string data type, add '(x)' after the method name, such as 'XYZ(x)' or 'base.XYZ(x)'
   273  //  2. `booltrue:"1"` 			// if field is defined, contains bool literal for true condition, such as 1 or true, that overrides default system bool literal value
   274  //     if bool literal value is determined by existence of outprefix and itself is blank, place a space in both booltrue and boolfalse (setting blank will negate literal override)
   275  //  3. `boolfalse:"0"`			// if field is defined, contains bool literal for false condition, such as 0 or false, that overrides default system bool literal value
   276  //     if bool literal value is determined by existence of outprefix and itself is blank, place a space in both booltrue and boolfalse (setting blank will negate literal override)
   277  //  4. `uniqueid:"xyz"`			// if two or more struct field is set with the same uniqueid, then only the first encountered field with the same uniqueid will be used in marshal
   278  //  5. `skipblank:"false"`		// if true, then any fields that is blank string will be excluded from marshal (this only affects fields that are string)
   279  //  6. `skipzero:"false"`		// if true, then any fields that are 0, 0.00, time.Zero(), false, nil will be excluded from marshal (this only affects fields that are number, bool, time, pointer)
   280  //  7. `timeformat:"20060102"`	// for time.Time field, optional date time format, specified as:
   281  //     2006, 06 = year,
   282  //     01, 1, Jan, January = month,
   283  //     02, 2, _2 = day (_2 = width two, right justified)
   284  //     03, 3, 15 = hour (15 = 24 hour format)
   285  //     04, 4 = minute
   286  //     05, 5 = second
   287  //     PM pm = AM PM
   288  //  8. `zeroblank:"false"`		// set true to set blank to data when value is 0, 0.00, or time.IsZero
   289  func MarshalStructToJson(inputStructPtr interface{}, tagName string, excludeTagName string) (string, error) {
   290  	if inputStructPtr == nil {
   291  		return "", fmt.Errorf("MarshalStructToJson Requires Input Struct Variable Pointer")
   292  	}
   293  
   294  	if LenTrim(tagName) == 0 {
   295  		return "", fmt.Errorf("MarshalStructToJson Requires TagName (Tag Name defines Json name)")
   296  	}
   297  
   298  	s := reflect.ValueOf(inputStructPtr)
   299  
   300  	if s.Kind() != reflect.Ptr {
   301  		return "", fmt.Errorf("MarshalStructToJson Expects inputStructPtr To Be a Pointer")
   302  	} else {
   303  		s = s.Elem()
   304  	}
   305  
   306  	if s.Kind() != reflect.Struct {
   307  		return "", fmt.Errorf("MarshalStructToJson Requires Struct Object")
   308  	}
   309  
   310  	output := ""
   311  	uniqueMap := make(map[string]string)
   312  
   313  	for i := 0; i < s.NumField(); i++ {
   314  		field := s.Type().Field(i)
   315  
   316  		if o := s.FieldByName(field.Name); o.IsValid() {
   317  			tag := field.Tag.Get(tagName)
   318  
   319  			if LenTrim(tag) == 0 {
   320  				tag = field.Name
   321  			}
   322  
   323  			if tag != "-" {
   324  				if LenTrim(excludeTagName) > 0 {
   325  					if Trim(field.Tag.Get(excludeTagName)) == "-" {
   326  						continue
   327  					}
   328  				}
   329  
   330  				if tagUniqueId := Trim(field.Tag.Get("uniqueid")); len(tagUniqueId) > 0 {
   331  					if _, ok := uniqueMap[strings.ToLower(tagUniqueId)]; ok {
   332  						continue
   333  					} else {
   334  						uniqueMap[strings.ToLower(tagUniqueId)] = field.Name
   335  					}
   336  				}
   337  
   338  				var boolTrue, boolFalse, timeFormat string
   339  				var skipBlank, skipZero, zeroBlank bool
   340  
   341  				if vs := GetStructTagsValueSlice(field, "booltrue", "boolfalse", "skipblank", "skipzero", "timeformat", "zeroblank"); len(vs) == 6 {
   342  					boolTrue = vs[0]
   343  					boolFalse = vs[1]
   344  					skipBlank, _ = ParseBool(vs[2])
   345  					skipZero, _ = ParseBool(vs[3])
   346  					timeFormat = vs[4]
   347  					zeroBlank, _ = ParseBool(vs[5])
   348  				}
   349  
   350  				oldVal := o
   351  
   352  				if tagGetter := Trim(field.Tag.Get("getter")); len(tagGetter) > 0 {
   353  					isBase := false
   354  					useParam := false
   355  					paramVal := ""
   356  					var paramSlice interface{}
   357  
   358  					if strings.ToLower(Left(tagGetter, 5)) == "base." {
   359  						isBase = true
   360  						tagGetter = Right(tagGetter, len(tagGetter)-5)
   361  					}
   362  
   363  					if strings.ToLower(Right(tagGetter, 3)) == "(x)" {
   364  						useParam = true
   365  
   366  						if o.Kind() != reflect.Slice {
   367  							paramVal, _, _ = ReflectValueToString(o, boolTrue, boolFalse, skipBlank, skipZero, timeFormat, zeroBlank)
   368  						} else {
   369  							if o.Len() > 0 {
   370  								paramSlice = o.Slice(0, o.Len()).Interface()
   371  							}
   372  						}
   373  
   374  						tagGetter = Left(tagGetter, len(tagGetter)-3)
   375  					}
   376  
   377  					var ov []reflect.Value
   378  					var notFound bool
   379  
   380  					if isBase {
   381  						if useParam {
   382  							if paramSlice == nil {
   383  								ov, notFound = ReflectCall(s.Addr(), tagGetter, paramVal)
   384  							} else {
   385  								ov, notFound = ReflectCall(s.Addr(), tagGetter, paramSlice)
   386  							}
   387  						} else {
   388  							ov, notFound = ReflectCall(s.Addr(), tagGetter)
   389  						}
   390  					} else {
   391  						if useParam {
   392  							if paramSlice == nil {
   393  								ov, notFound = ReflectCall(o, tagGetter, paramVal)
   394  							} else {
   395  								ov, notFound = ReflectCall(o, tagGetter, paramSlice)
   396  							}
   397  						} else {
   398  							ov, notFound = ReflectCall(o, tagGetter)
   399  						}
   400  					}
   401  
   402  					if !notFound {
   403  						if len(ov) > 0 {
   404  							o = ov[0]
   405  						}
   406  					}
   407  				}
   408  
   409  				buf, skip, err := ReflectValueToString(o, boolTrue, boolFalse, skipBlank, skipZero, timeFormat, zeroBlank)
   410  
   411  				if err != nil || skip {
   412  					if tagUniqueId := Trim(field.Tag.Get("uniqueid")); len(tagUniqueId) > 0 {
   413  						if _, ok := uniqueMap[strings.ToLower(tagUniqueId)]; ok {
   414  							delete(uniqueMap, strings.ToLower(tagUniqueId))
   415  						}
   416  					}
   417  
   418  					continue
   419  				}
   420  
   421  				defVal := field.Tag.Get("def")
   422  
   423  				if oldVal.Kind() == reflect.Int && oldVal.Int() == 0 && strings.ToLower(buf) == "unknown" {
   424  					// unknown enum value will be serialized as blank
   425  					buf = ""
   426  
   427  					if len(defVal) > 0 {
   428  						buf = defVal
   429  					} else {
   430  						if tagUniqueId := Trim(field.Tag.Get("uniqueid")); len(tagUniqueId) > 0 {
   431  							if _, ok := uniqueMap[strings.ToLower(tagUniqueId)]; ok {
   432  								// remove uniqueid if skip
   433  								delete(uniqueMap, strings.ToLower(tagUniqueId))
   434  								continue
   435  							}
   436  						}
   437  					}
   438  				}
   439  
   440  				outPrefix := field.Tag.Get("outprefix")
   441  
   442  				if boolTrue == " " && len(buf) == 0 && len(outPrefix) > 0 {
   443  					buf = outPrefix + defVal
   444  				} else if boolFalse == " " && buf == "false" && len(outPrefix) > 0 {
   445  					buf = ""
   446  				} else if len(defVal) > 0 && len(buf) == 0 {
   447  					buf = outPrefix + defVal
   448  				}
   449  
   450  				buf = strings.Replace(buf, `"`, `\"`, -1)
   451  				buf = strings.Replace(buf, `'`, `\'`, -1)
   452  
   453  				if LenTrim(output) > 0 {
   454  					output += ", "
   455  				}
   456  
   457  				output += fmt.Sprintf(`"%s":"%s"`, tag, JsonToEscaped(buf))
   458  			}
   459  		}
   460  	}
   461  
   462  	if LenTrim(output) == 0 {
   463  		return "", fmt.Errorf("MarshalStructToJson Yielded Blank Output")
   464  	} else {
   465  		return fmt.Sprintf("{%s}", output), nil
   466  	}
   467  }
   468  
   469  // UnmarshalJsonToStruct will parse jsonPayload string,
   470  // and set parsed json element value into struct fields based on struct tag named by tagName,
   471  // any tagName value with - will be ignored, any excludeTagName defined with value of - will also cause parser to ignore the field
   472  //
   473  // note: this method expects simple json in key value pairs only, not json containing slices or more complex json structs within existing json field
   474  //
   475  // Predefined Struct Tags Usable:
   476  //  1. `setter:"ParseByKey`		// if field type is custom struct or enum,
   477  //     specify the custom method (only 1 lookup parameter value allowed) setter that sets value(s) into the field
   478  //     NOTE: if the method to invoke resides at struct level, precede the method name with 'base.', for example, 'base.XYZ' where XYZ is method name to invoke
   479  //     NOTE: setter method always intake a string parameter
   480  //  2. `def:""`					// default value to set into struct field in case unmarshal doesn't set the struct field value
   481  //  3. `timeformat:"20060102"`	// for time.Time field, optional date time format, specified as:
   482  //     2006, 06 = year,
   483  //     01, 1, Jan, January = month,
   484  //     02, 2, _2 = day (_2 = width two, right justified)
   485  //     03, 3, 15 = hour (15 = 24 hour format)
   486  //     04, 4 = minute
   487  //     05, 5 = second
   488  //     PM pm = AM PM
   489  //  4. `booltrue:"1"` 			// if field is defined, contains bool literal for true condition, such as 1 or true, that overrides default system bool literal value,
   490  //     if bool literal value is determined by existence of outprefix and itself is blank, place a space in both booltrue and boolfalse (setting blank will negate literal override)
   491  //  5. `boolfalse:"0"`			// if field is defined, contains bool literal for false condition, such as 0 or false, that overrides default system bool literal value
   492  //     if bool literal value is determined by existence of outprefix and itself is blank, place a space in both booltrue and boolfalse (setting blank will negate literal override)
   493  func UnmarshalJsonToStruct(inputStructPtr interface{}, jsonPayload string, tagName string, excludeTagName string) error {
   494  	if inputStructPtr == nil {
   495  		return fmt.Errorf("InputStructPtr is Required")
   496  	}
   497  
   498  	if LenTrim(jsonPayload) == 0 {
   499  		return fmt.Errorf("JsonPayload is Required")
   500  	}
   501  
   502  	if LenTrim(tagName) == 0 {
   503  		return fmt.Errorf("TagName is Required")
   504  	}
   505  
   506  	s := reflect.ValueOf(inputStructPtr)
   507  
   508  	if s.Kind() != reflect.Ptr {
   509  		return fmt.Errorf("InputStructPtr Must Be Pointer")
   510  	} else {
   511  		s = s.Elem()
   512  	}
   513  
   514  	if s.Kind() != reflect.Struct {
   515  		return fmt.Errorf("InputStructPtr Must Be Struct")
   516  	}
   517  
   518  	// unmarshal json to map
   519  	jsonMap := make(map[string]json.RawMessage)
   520  
   521  	if err := json.Unmarshal([]byte(jsonPayload), &jsonMap); err != nil {
   522  		return fmt.Errorf("Unmarshal Json Failed: %s", err)
   523  	}
   524  
   525  	if jsonMap == nil {
   526  		return fmt.Errorf("Unmarshaled Json Map is Nil")
   527  	}
   528  
   529  	if len(jsonMap) == 0 {
   530  		return fmt.Errorf("Unmarshaled Json Map Has No Elements")
   531  	}
   532  
   533  	StructClearFields(inputStructPtr)
   534  	SetStructFieldDefaultValues(inputStructPtr)
   535  
   536  	for i := 0; i < s.NumField(); i++ {
   537  		field := s.Type().Field(i)
   538  
   539  		if o := s.FieldByName(field.Name); o.IsValid() && o.CanSet() {
   540  			// get json field name if defined
   541  			jName := Trim(field.Tag.Get(tagName))
   542  
   543  			if jName == "-" {
   544  				continue
   545  			}
   546  
   547  			if LenTrim(excludeTagName) > 0 {
   548  				if Trim(field.Tag.Get(excludeTagName)) == "-" {
   549  					continue
   550  				}
   551  			}
   552  
   553  			if LenTrim(jName) == 0 {
   554  				jName = field.Name
   555  			}
   556  
   557  			// get json field value based on jName from jsonMap
   558  			jValue := ""
   559  			timeFormat := Trim(field.Tag.Get("timeformat"))
   560  
   561  			if jRaw, ok := jsonMap[jName]; !ok {
   562  				continue
   563  			} else {
   564  				jValue = JsonFromEscaped(string(jRaw))
   565  
   566  				if len(jValue) > 0 {
   567  					if tagSetter := Trim(field.Tag.Get("setter")); len(tagSetter) > 0 {
   568  						isBase := false
   569  
   570  						if strings.ToLower(Left(tagSetter, 5)) == "base." {
   571  							isBase = true
   572  							tagSetter = Right(tagSetter, len(tagSetter)-5)
   573  						}
   574  
   575  						if o.Kind() != reflect.Ptr && o.Kind() != reflect.Interface && o.Kind() != reflect.Struct && o.Kind() != reflect.Slice {
   576  							// o is not ptr, interface, struct
   577  							var results []reflect.Value
   578  							var notFound bool
   579  
   580  							if isBase {
   581  								results, notFound = ReflectCall(s.Addr(), tagSetter, jValue)
   582  							} else {
   583  								results, notFound = ReflectCall(o, tagSetter, jValue)
   584  							}
   585  
   586  							if !notFound && len(results) > 0 {
   587  								if len(results) == 1 {
   588  									if jv, _, err := ReflectValueToString(results[0], "", "", false, false, timeFormat, false); err == nil {
   589  										jValue = jv
   590  									}
   591  								} else if len(results) > 1 {
   592  									getFirstVar := true
   593  
   594  									if e, ok := results[len(results)-1].Interface().(error); ok {
   595  										// last var is error, check if error exists
   596  										if e != nil {
   597  											getFirstVar = false
   598  										}
   599  									}
   600  
   601  									if getFirstVar {
   602  										if jv, _, err := ReflectValueToString(results[0], "", "", false, false, timeFormat, false); err == nil {
   603  											jValue = jv
   604  										}
   605  									}
   606  								}
   607  							}
   608  						} else {
   609  							// o is ptr, interface, struct
   610  							// get base type
   611  							if o.Kind() != reflect.Slice {
   612  								if baseType, _, isNilPtr := DerefPointersZero(o); isNilPtr {
   613  									// create new struct pointer
   614  									o.Set(reflect.New(baseType.Type()))
   615  								} else {
   616  									if o.Kind() == reflect.Interface && o.Interface() == nil {
   617  										customType := ReflectTypeRegistryGet(o.Type().String())
   618  
   619  										if customType == nil {
   620  											return fmt.Errorf("%s Struct Field %s is Interface Without Actual Object Assignment", s.Type(), o.Type())
   621  										} else {
   622  											o.Set(reflect.New(customType))
   623  										}
   624  									}
   625  								}
   626  							}
   627  
   628  							var ov []reflect.Value
   629  							var notFound bool
   630  
   631  							if isBase {
   632  								ov, notFound = ReflectCall(s.Addr(), tagSetter, jValue)
   633  							} else {
   634  								ov, notFound = ReflectCall(o, tagSetter, jValue)
   635  							}
   636  
   637  							if !notFound {
   638  								if len(ov) == 1 {
   639  									if ov[0].Kind() == reflect.Ptr || ov[0].Kind() == reflect.Slice {
   640  										o.Set(ov[0])
   641  									}
   642  								} else if len(ov) > 1 {
   643  									getFirstVar := true
   644  
   645  									if e := DerefError(ov[len(ov)-1]); e != nil {
   646  										getFirstVar = false
   647  									}
   648  
   649  									if getFirstVar {
   650  										if ov[0].Kind() == reflect.Ptr || ov[0].Kind() == reflect.Slice {
   651  											o.Set(ov[0])
   652  										}
   653  									}
   654  								}
   655  							}
   656  
   657  							// for o as ptr
   658  							// once complete, continue
   659  							continue
   660  						}
   661  					}
   662  				}
   663  			}
   664  
   665  			// set validated csv value into corresponding struct field
   666  			outPrefix := field.Tag.Get("outprefix")
   667  			boolTrue := field.Tag.Get("booltrue")
   668  			boolFalse := field.Tag.Get("boolfalse")
   669  
   670  			if boolTrue == " " && len(outPrefix) > 0 && jValue == outPrefix {
   671  				jValue = "true"
   672  			} else {
   673  				evalOk := false
   674  				if LenTrim(boolTrue) > 0 && len(jValue) > 0 && boolTrue == jValue {
   675  					jValue = "true"
   676  					evalOk = true
   677  				}
   678  
   679  				if !evalOk {
   680  					if LenTrim(boolFalse) > 0 && len(jValue) > 0 && boolFalse == jValue {
   681  						jValue = "false"
   682  					}
   683  				}
   684  			}
   685  
   686  			if err := ReflectStringToField(o, jValue, timeFormat); err != nil {
   687  				return err
   688  			}
   689  		}
   690  	}
   691  
   692  	return nil
   693  }
   694  
   695  // MarshalSliceStructToJson accepts a slice of struct pointer, then using tagName and excludeTagName to marshal to json array
   696  // To pass in inputSliceStructPtr, convert slice of actual objects at the calling code, using SliceObjectsToSliceInterface(),
   697  // if there is a need to name the value of tagName, but still need to exclude from output, use the excludeTagName with -, such as `x:"-"`
   698  func MarshalSliceStructToJson(inputSliceStructPtr []interface{}, tagName string, excludeTagName string) (jsonArrayOutput string, err error) {
   699  	if len(inputSliceStructPtr) == 0 {
   700  		return "", fmt.Errorf("Input Slice Struct Pointer Nil")
   701  	}
   702  
   703  	for _, v := range inputSliceStructPtr {
   704  		if s, e := MarshalStructToJson(v, tagName, excludeTagName); e != nil {
   705  			return "", fmt.Errorf("MarshalSliceStructToJson Failed: %s", e)
   706  		} else {
   707  			if LenTrim(jsonArrayOutput) > 0 {
   708  				jsonArrayOutput += ", "
   709  			}
   710  
   711  			jsonArrayOutput += s
   712  		}
   713  	}
   714  
   715  	if LenTrim(jsonArrayOutput) > 0 {
   716  		return fmt.Sprintf("[%s]", jsonArrayOutput), nil
   717  	} else {
   718  		return "", fmt.Errorf("MarshalSliceStructToJson Yielded Blank String")
   719  	}
   720  }
   721  
   722  // StructClearFields will clear all fields within struct with default value
   723  func StructClearFields(inputStructPtr interface{}) {
   724  	if inputStructPtr == nil {
   725  		return
   726  	}
   727  
   728  	s := reflect.ValueOf(inputStructPtr)
   729  
   730  	if s.Kind() != reflect.Ptr {
   731  		return
   732  	} else {
   733  		s = s.Elem()
   734  	}
   735  
   736  	if s.Kind() != reflect.Struct {
   737  		return
   738  	}
   739  
   740  	for i := 0; i < s.NumField(); i++ {
   741  		field := s.Type().Field(i)
   742  
   743  		if o := s.FieldByName(field.Name); o.IsValid() && o.CanSet() {
   744  			switch o.Kind() {
   745  			case reflect.String:
   746  				o.SetString("")
   747  			case reflect.Bool:
   748  				o.SetBool(false)
   749  			case reflect.Int8:
   750  				fallthrough
   751  			case reflect.Int16:
   752  				fallthrough
   753  			case reflect.Int:
   754  				fallthrough
   755  			case reflect.Int32:
   756  				fallthrough
   757  			case reflect.Int64:
   758  				o.SetInt(0)
   759  			case reflect.Float32:
   760  				fallthrough
   761  			case reflect.Float64:
   762  				o.SetFloat(0)
   763  			case reflect.Uint8:
   764  				fallthrough
   765  			case reflect.Uint16:
   766  				fallthrough
   767  			case reflect.Uint:
   768  				fallthrough
   769  			case reflect.Uint32:
   770  				fallthrough
   771  			case reflect.Uint64:
   772  				o.SetUint(0)
   773  			case reflect.Ptr:
   774  				o.Set(reflect.Zero(o.Type()))
   775  			case reflect.Slice:
   776  				o.Set(reflect.Zero(o.Type()))
   777  			default:
   778  				switch o.Interface().(type) {
   779  				case sql.NullString:
   780  					o.Set(reflect.ValueOf(sql.NullString{}))
   781  				case sql.NullBool:
   782  					o.Set(reflect.ValueOf(sql.NullBool{}))
   783  				case sql.NullFloat64:
   784  					o.Set(reflect.ValueOf(sql.NullFloat64{}))
   785  				case sql.NullInt32:
   786  					o.Set(reflect.ValueOf(sql.NullInt32{}))
   787  				case sql.NullInt64:
   788  					o.Set(reflect.ValueOf(sql.NullInt64{}))
   789  				case sql.NullTime:
   790  					o.Set(reflect.ValueOf(sql.NullTime{}))
   791  				case time.Time:
   792  					o.Set(reflect.ValueOf(time.Time{}))
   793  				default:
   794  					o.Set(reflect.Zero(o.Type()))
   795  				}
   796  			}
   797  		}
   798  	}
   799  }
   800  
   801  // StructNonDefaultRequiredFieldsCount returns count of struct fields that are tagged as required but not having any default values pre-set
   802  func StructNonDefaultRequiredFieldsCount(inputStructPtr interface{}) int {
   803  	if inputStructPtr == nil {
   804  		return 0
   805  	}
   806  
   807  	s := reflect.ValueOf(inputStructPtr)
   808  
   809  	if s.Kind() != reflect.Ptr {
   810  		return 0
   811  	} else {
   812  		s = s.Elem()
   813  	}
   814  
   815  	if s.Kind() != reflect.Struct {
   816  		return 0
   817  	}
   818  
   819  	count := 0
   820  
   821  	for i := 0; i < s.NumField(); i++ {
   822  		field := s.Type().Field(i)
   823  
   824  		if o := s.FieldByName(field.Name); o.IsValid() && o.CanSet() {
   825  			tagDef := field.Tag.Get("def")
   826  			tagReq := field.Tag.Get("req")
   827  
   828  			if len(tagDef) == 0 && strings.ToLower(tagReq) == "true" {
   829  				// required and no default value
   830  				count++
   831  			}
   832  		}
   833  	}
   834  
   835  	return count
   836  }
   837  
   838  // IsStructFieldSet checks if any field value is not default blank or zero
   839  func IsStructFieldSet(inputStructPtr interface{}) bool {
   840  	if inputStructPtr == nil {
   841  		return false
   842  	}
   843  
   844  	s := reflect.ValueOf(inputStructPtr)
   845  
   846  	if s.Kind() != reflect.Ptr {
   847  		return false
   848  	} else {
   849  		s = s.Elem()
   850  	}
   851  
   852  	if s.Kind() != reflect.Struct {
   853  		return false
   854  	}
   855  
   856  	for i := 0; i < s.NumField(); i++ {
   857  		field := s.Type().Field(i)
   858  
   859  		if o := s.FieldByName(field.Name); o.IsValid() && o.CanSet() {
   860  			tagDef := field.Tag.Get("def")
   861  
   862  			switch o.Kind() {
   863  			case reflect.String:
   864  				if LenTrim(o.String()) > 0 {
   865  					if o.String() != tagDef {
   866  						return true
   867  					}
   868  				}
   869  			case reflect.Bool:
   870  				if o.Bool() {
   871  					return true
   872  				}
   873  			case reflect.Int8:
   874  				fallthrough
   875  			case reflect.Int16:
   876  				fallthrough
   877  			case reflect.Int:
   878  				fallthrough
   879  			case reflect.Int32:
   880  				fallthrough
   881  			case reflect.Int64:
   882  				if o.Int() != 0 {
   883  					if Int64ToString(o.Int()) != tagDef {
   884  						return true
   885  					}
   886  				}
   887  			case reflect.Float32:
   888  				fallthrough
   889  			case reflect.Float64:
   890  				if o.Float() != 0 {
   891  					if Float64ToString(o.Float()) != tagDef {
   892  						return true
   893  					}
   894  				}
   895  			case reflect.Uint8:
   896  				fallthrough
   897  			case reflect.Uint16:
   898  				fallthrough
   899  			case reflect.Uint:
   900  				fallthrough
   901  			case reflect.Uint32:
   902  				fallthrough
   903  			case reflect.Uint64:
   904  				if o.Uint() > 0 {
   905  					if UInt64ToString(o.Uint()) != tagDef {
   906  						return true
   907  					}
   908  				}
   909  			case reflect.Ptr:
   910  				if !o.IsNil() {
   911  					return true
   912  				}
   913  			case reflect.Slice:
   914  				if o.Len() > 0 {
   915  					return true
   916  				}
   917  			default:
   918  				switch f := o.Interface().(type) {
   919  				case sql.NullString:
   920  					if f.Valid {
   921  						if len(tagDef) == 0 {
   922  							return true
   923  						} else {
   924  							if f.String != tagDef {
   925  								return true
   926  							}
   927  						}
   928  					}
   929  				case sql.NullBool:
   930  					if f.Valid {
   931  						if len(tagDef) == 0 {
   932  							return true
   933  						} else {
   934  							if f.Bool, _ = ParseBool(tagDef); f.Bool {
   935  								return true
   936  							}
   937  						}
   938  					}
   939  				case sql.NullFloat64:
   940  					if f.Valid {
   941  						if len(tagDef) == 0 {
   942  							return true
   943  						} else {
   944  							if Float64ToString(f.Float64) != tagDef {
   945  								return true
   946  							}
   947  						}
   948  					}
   949  				case sql.NullInt32:
   950  					if f.Valid {
   951  						if len(tagDef) == 0 {
   952  							return true
   953  						} else {
   954  							if Itoa(int(f.Int32)) != tagDef {
   955  								return true
   956  							}
   957  						}
   958  					}
   959  				case sql.NullInt64:
   960  					if f.Valid {
   961  						if len(tagDef) == 0 {
   962  							return true
   963  						} else {
   964  							if Int64ToString(f.Int64) != tagDef {
   965  								return true
   966  							}
   967  						}
   968  					}
   969  				case sql.NullTime:
   970  					if f.Valid {
   971  						if len(tagDef) == 0 {
   972  							return true
   973  						} else {
   974  							tagTimeFormat := Trim(field.Tag.Get("timeformat"))
   975  
   976  							if LenTrim(tagTimeFormat) == 0 {
   977  								tagTimeFormat = DateTimeFormatString()
   978  							}
   979  
   980  							if f.Time != ParseDateTimeCustom(tagDef, tagTimeFormat) {
   981  								return true
   982  							}
   983  						}
   984  					}
   985  				case time.Time:
   986  					if !f.IsZero() {
   987  						if len(tagDef) == 0 {
   988  							return true
   989  						} else {
   990  							tagTimeFormat := Trim(field.Tag.Get("timeformat"))
   991  
   992  							if LenTrim(tagTimeFormat) == 0 {
   993  								tagTimeFormat = DateTimeFormatString()
   994  							}
   995  
   996  							if f != ParseDateTimeCustom(tagDef, tagTimeFormat) {
   997  								return true
   998  							}
   999  						}
  1000  					}
  1001  				default:
  1002  					if o.Kind() == reflect.Interface && o.Interface() != nil {
  1003  						return true
  1004  					}
  1005  				}
  1006  			}
  1007  		}
  1008  	}
  1009  
  1010  	return false
  1011  }
  1012  
  1013  // SetStructFieldDefaultValues sets default value defined in struct tag `def:""` into given field,
  1014  // this method is used during unmarshal action only,
  1015  // default value setting is for value types and fields with `setter:""` defined only,
  1016  // time format is used if field is datetime, for overriding default format of ISO style
  1017  func SetStructFieldDefaultValues(inputStructPtr interface{}) bool {
  1018  	if inputStructPtr == nil {
  1019  		return false
  1020  	}
  1021  
  1022  	s := reflect.ValueOf(inputStructPtr)
  1023  
  1024  	if s.Kind() != reflect.Ptr {
  1025  		return false
  1026  	} else {
  1027  		s = s.Elem()
  1028  	}
  1029  
  1030  	if s.Kind() != reflect.Struct {
  1031  		return false
  1032  	}
  1033  
  1034  	for i := 0; i < s.NumField(); i++ {
  1035  		field := s.Type().Field(i)
  1036  
  1037  		if o := s.FieldByName(field.Name); o.IsValid() && o.CanSet() {
  1038  			tagDef := field.Tag.Get("def")
  1039  
  1040  			if len(tagDef) == 0 {
  1041  				continue
  1042  			}
  1043  
  1044  			switch o.Kind() {
  1045  			case reflect.String:
  1046  				if LenTrim(o.String()) == 0 {
  1047  					o.SetString(tagDef)
  1048  				}
  1049  			case reflect.Int8:
  1050  				fallthrough
  1051  			case reflect.Int16:
  1052  				fallthrough
  1053  			case reflect.Int:
  1054  				fallthrough
  1055  			case reflect.Int32:
  1056  				fallthrough
  1057  			case reflect.Int64:
  1058  				if o.Int() == 0 {
  1059  					tagSetter := Trim(field.Tag.Get("setter"))
  1060  
  1061  					if LenTrim(tagSetter) == 0 {
  1062  						if i64, ok := ParseInt64(tagDef); ok && i64 != 0 {
  1063  							if !o.OverflowInt(i64) {
  1064  								o.SetInt(i64)
  1065  							}
  1066  						}
  1067  					} else {
  1068  						if res, notFound := ReflectCall(o, tagSetter, tagDef); !notFound {
  1069  							if len(res) == 1 {
  1070  								if val, skip, err := ReflectValueToString(res[0], "", "", false, false, "", false); err == nil && !skip {
  1071  									tagDef = val
  1072  								} else {
  1073  									continue
  1074  								}
  1075  							} else if len(res) > 1 {
  1076  								if err := DerefError(res[len(res)-1:][0]); err == nil {
  1077  									if val, skip, err := ReflectValueToString(res[0], "", "", false, false, "", false); err == nil && !skip {
  1078  										tagDef = val
  1079  									} else {
  1080  										continue
  1081  									}
  1082  								}
  1083  							}
  1084  
  1085  							if i64, ok := ParseInt64(tagDef); ok && i64 != 0 {
  1086  								if !o.OverflowInt(i64) {
  1087  									o.SetInt(i64)
  1088  								}
  1089  							}
  1090  						}
  1091  					}
  1092  				}
  1093  			case reflect.Float32:
  1094  				fallthrough
  1095  			case reflect.Float64:
  1096  				if o.Float() == 0 {
  1097  					if f64, ok := ParseFloat64(tagDef); ok && f64 != 0 {
  1098  						if !o.OverflowFloat(f64) {
  1099  							o.SetFloat(f64)
  1100  						}
  1101  					}
  1102  				}
  1103  			case reflect.Uint8:
  1104  				fallthrough
  1105  			case reflect.Uint16:
  1106  				fallthrough
  1107  			case reflect.Uint:
  1108  				fallthrough
  1109  			case reflect.Uint32:
  1110  				fallthrough
  1111  			case reflect.Uint64:
  1112  				if o.Uint() == 0 {
  1113  					if u64 := StrToUint64(tagDef); u64 != 0 {
  1114  						if !o.OverflowUint(u64) {
  1115  							o.SetUint(u64)
  1116  						}
  1117  					}
  1118  				}
  1119  			default:
  1120  				switch f := o.Interface().(type) {
  1121  				case sql.NullString:
  1122  					if !f.Valid {
  1123  						o.Set(reflect.ValueOf(sql.NullString{String: tagDef, Valid: true}))
  1124  					}
  1125  				case sql.NullBool:
  1126  					if !f.Valid {
  1127  						b, _ := ParseBool(tagDef)
  1128  						o.Set(reflect.ValueOf(sql.NullBool{Bool: b, Valid: true}))
  1129  					}
  1130  				case sql.NullFloat64:
  1131  					if !f.Valid {
  1132  						if f64, ok := ParseFloat64(tagDef); ok && f64 != 0 {
  1133  							o.Set(reflect.ValueOf(sql.NullFloat64{Float64: f64, Valid: true}))
  1134  						}
  1135  					}
  1136  				case sql.NullInt32:
  1137  					if !f.Valid {
  1138  						if i32, ok := ParseInt32(tagDef); ok && i32 != 0 {
  1139  							o.Set(reflect.ValueOf(sql.NullInt32{Int32: int32(i32), Valid: true}))
  1140  						}
  1141  					}
  1142  				case sql.NullInt64:
  1143  					if !f.Valid {
  1144  						if i64, ok := ParseInt64(tagDef); ok && i64 != 0 {
  1145  							o.Set(reflect.ValueOf(sql.NullInt64{Int64: i64, Valid: true}))
  1146  						}
  1147  					}
  1148  				case sql.NullTime:
  1149  					if !f.Valid {
  1150  						tagTimeFormat := Trim(field.Tag.Get("timeformat"))
  1151  
  1152  						if LenTrim(tagTimeFormat) == 0 {
  1153  							tagTimeFormat = DateTimeFormatString()
  1154  						}
  1155  
  1156  						if t := ParseDateTimeCustom(tagDef, tagTimeFormat); !t.IsZero() {
  1157  							o.Set(reflect.ValueOf(sql.NullTime{Time: t, Valid: true}))
  1158  						}
  1159  					}
  1160  				case time.Time:
  1161  					if f.IsZero() {
  1162  						tagTimeFormat := Trim(field.Tag.Get("timeformat"))
  1163  
  1164  						if LenTrim(tagTimeFormat) == 0 {
  1165  							tagTimeFormat = DateTimeFormatString()
  1166  						}
  1167  
  1168  						if t := ParseDateTimeCustom(tagDef, tagTimeFormat); !t.IsZero() {
  1169  							o.Set(reflect.ValueOf(t))
  1170  						}
  1171  					}
  1172  				}
  1173  			}
  1174  		}
  1175  	}
  1176  
  1177  	return true
  1178  }
  1179  
  1180  // UnmarshalCSVToStruct will parse csvPayload string (one line of csv data) using csvDelimiter, (if csvDelimiter = "", then customDelimiterParserFunc is required)
  1181  // and set parsed csv element value into struct fields based on Ordinal Position defined via struct tag,
  1182  // additionally processes struct tag data validation and length / range (if not valid, will set to data type default)
  1183  //
  1184  // Predefined Struct Tags Usable:
  1185  //  1. `pos:"1"`				// ordinal position of the field in relation to the csv parsed output expected (Zero-Based Index)
  1186  //     NOTE: if field is mutually exclusive with one or more uniqueId, then pos # should be named the same for all uniqueIds,
  1187  //     if multiple fields are in exclusive condition, and skipBlank or skipZero, always include a blank default field as the last of unique field list
  1188  //     if value is '-', this means position value is calculated from other fields and set via `setter:"base.Xyz"` during unmarshal csv, there is no marshal to csv for this field
  1189  //  2. `type:"xyz"`				// data type expected:
  1190  //     A = AlphabeticOnly, N = NumericOnly 0-9, AN = AlphaNumeric, ANS = AN + PrintableSymbols,
  1191  //     H = Hex, B64 = Base64, B = true/false, REGEX = Regular Expression, Blank = Any,
  1192  //  3. `regex:"xyz"`			// if Type = REGEX, this struct tag contains the regular expression string,
  1193  //     regex express such as [^A-Za-z0-9_-]+
  1194  //     method will replace any regex matched string to blank
  1195  //  4. `size:"x..y"`			// data type size rule:
  1196  //     x = Exact size match
  1197  //     x.. = From x and up
  1198  //     ..y = From 0 up to y
  1199  //     x..y = From x to y
  1200  //     +%z = Append to x, x.., ..y, x..y; adds additional constraint that the result size must equate to 0 from modulo of z
  1201  //  5. `range:"x..y"`			// data type range value when Type is N, if underlying data type is string, method will convert first before testing
  1202  //  6. `req:"true"`				// indicates data value is required or not, true or false
  1203  //  7. `getter:"Key"`			// if field type is custom struct or enum, specify the custom method getter (no parameters allowed) that returns the expected value in first ordinal result position
  1204  //     NOTE: if the method to invoke resides at struct level, precede the method name with 'base.', for example, 'base.XYZ' where XYZ is method name to invoke
  1205  //     NOTE: if the method is to receive a parameter value, always in string data type, add '(x)' after the method name, such as 'XYZ(x)' or 'base.XYZ(x)'
  1206  //  8. `setter:"ParseByKey`		// if field type is custom struct or enum, specify the custom method (only 1 lookup parameter value allowed) setter that sets value(s) into the field
  1207  //     NOTE: if the method to invoke resides at struct level, precede the method name with 'base.', for example, 'base.XYZ' where XYZ is method name to invoke
  1208  //     NOTE: setter method always intake a string parameter value
  1209  //  9. `outprefix:""`			// for marshal method, if field value is to precede with an output prefix, such as XYZ= (affects marshal queryParams / csv methods only)
  1210  //     WARNING: if csv is variable elements count, rather than fixed count ordinal, then csv MUST include outprefix for all fields in order to properly identify target struct field
  1211  //  10. `def:""`				// default value to set into struct field in case unmarshal doesn't set the struct field value
  1212  //  11. `timeformat:"20060102"`	// for time.Time field, optional date time format, specified as:
  1213  //     2006, 06 = year,
  1214  //     01, 1, Jan, January = month,
  1215  //     02, 2, _2 = day (_2 = width two, right justified)
  1216  //     03, 3, 15 = hour (15 = 24 hour format)
  1217  //     04, 4 = minute
  1218  //     05, 5 = second
  1219  //     PM pm = AM PM
  1220  //  12. `booltrue:"1"` 			// if field is defined, contains bool literal for true condition, such as 1 or true, that overrides default system bool literal value,
  1221  //     if bool literal value is determined by existence of outprefix and itself is blank, place a space in both booltrue and boolfalse (setting blank will negate literal override)
  1222  //  13. `boolfalse:"0"`			// if field is defined, contains bool literal for false condition, such as 0 or false, that overrides default system bool literal value
  1223  //     if bool literal value is determined by existence of outprefix and itself is blank, place a space in both booltrue and boolfalse (setting blank will negate literal override)
  1224  //  14. `validate:"==x"`		// if field has to match a specific value or the entire method call will fail, match data format as:
  1225  //     ==xyz (== refers to equal, for numbers and string match, xyz is data to match, case insensitive)
  1226  //     [if == validate against one or more values, use ||]
  1227  //     !=xyz (!= refers to not equal)
  1228  //     [if != validate against one or more values, use &&]
  1229  //     >=xyz >>xyz <<xyz <=xyz (greater equal, greater, less than, less equal; xyz must be int or float)
  1230  //     :=Xyz where Xyz is a parameterless function defined at struct level, that performs validation, returns bool or error where true or nil indicates validation success
  1231  //     note: expected source data type for validate to be effective is string, int, float64; if field is blank and req = false, then validate will be skipped
  1232  func UnmarshalCSVToStruct(inputStructPtr interface{}, csvPayload string, csvDelimiter string, customDelimiterParserFunc func(string) []string) error {
  1233  	if inputStructPtr == nil {
  1234  		return fmt.Errorf("InputStructPtr is Required")
  1235  	}
  1236  
  1237  	if LenTrim(csvPayload) == 0 {
  1238  		return fmt.Errorf("CSV Payload is Required")
  1239  	}
  1240  
  1241  	if len(csvDelimiter) == 0 && customDelimiterParserFunc == nil {
  1242  		return fmt.Errorf("CSV Delimiter or Custom Delimiter Func is Required")
  1243  	}
  1244  
  1245  	s := reflect.ValueOf(inputStructPtr)
  1246  
  1247  	if s.Kind() != reflect.Ptr {
  1248  		return fmt.Errorf("InputStructPtr Must Be Pointer")
  1249  	} else {
  1250  		s = s.Elem()
  1251  	}
  1252  
  1253  	if s.Kind() != reflect.Struct {
  1254  		return fmt.Errorf("InputStructPtr Must Be Struct")
  1255  	}
  1256  
  1257  	trueList := []string{"true", "yes", "on", "1", "enabled"}
  1258  
  1259  	var csvElements []string
  1260  
  1261  	if len(csvDelimiter) > 0 {
  1262  		csvElements = strings.Split(csvPayload, csvDelimiter)
  1263  	} else {
  1264  		csvElements = customDelimiterParserFunc(csvPayload)
  1265  	}
  1266  
  1267  	csvLen := len(csvElements)
  1268  
  1269  	if csvLen == 0 {
  1270  		return fmt.Errorf("CSV Payload Contains Zero Elements")
  1271  	}
  1272  
  1273  	StructClearFields(inputStructPtr)
  1274  	SetStructFieldDefaultValues(inputStructPtr)
  1275  	prefixProcessedMap := make(map[string]string)
  1276  
  1277  	for i := 0; i < s.NumField(); i++ {
  1278  		field := s.Type().Field(i)
  1279  
  1280  		if o := s.FieldByName(field.Name); o.IsValid() && o.CanSet() {
  1281  			// extract struct tag values
  1282  			tagPosBuf := field.Tag.Get("pos")
  1283  			tagPos, ok := ParseInt32(tagPosBuf)
  1284  			if !ok {
  1285  				if tagPosBuf != "-" || LenTrim(field.Tag.Get("setter")) == 0 {
  1286  					continue
  1287  				}
  1288  			} else if tagPos < 0 {
  1289  				continue
  1290  			}
  1291  
  1292  			tagType := Trim(strings.ToLower(field.Tag.Get("type")))
  1293  			switch tagType {
  1294  			case "a":
  1295  				fallthrough
  1296  			case "n":
  1297  				fallthrough
  1298  			case "an":
  1299  				fallthrough
  1300  			case "ans":
  1301  				fallthrough
  1302  			case "b":
  1303  				fallthrough
  1304  			case "b64":
  1305  				fallthrough
  1306  			case "regex":
  1307  				fallthrough
  1308  			case "h":
  1309  				// valid type
  1310  			default:
  1311  				tagType = ""
  1312  			}
  1313  
  1314  			tagRegEx := Trim(field.Tag.Get("regex"))
  1315  			if tagType != "regex" {
  1316  				tagRegEx = ""
  1317  			} else {
  1318  				if LenTrim(tagRegEx) == 0 {
  1319  					tagType = ""
  1320  				}
  1321  			}
  1322  
  1323  			// unmarshal only validates max
  1324  			tagSize := Trim(strings.ToLower(field.Tag.Get("size")))
  1325  			arModulo := strings.Split(tagSize, "+%")
  1326  			tagModulo := 0
  1327  			if len(arModulo) == 2 {
  1328  				tagSize = arModulo[0]
  1329  				if tagModulo, _ = ParseInt32(arModulo[1]); tagModulo < 0 {
  1330  					tagModulo = 0
  1331  				}
  1332  			}
  1333  			arSize := strings.Split(tagSize, "..")
  1334  			sizeMin := 0
  1335  			sizeMax := 0
  1336  			if len(arSize) == 2 {
  1337  				sizeMin, _ = ParseInt32(arSize[0])
  1338  				sizeMax, _ = ParseInt32(arSize[1])
  1339  			} else {
  1340  				sizeMin, _ = ParseInt32(tagSize)
  1341  				sizeMax = sizeMin
  1342  			}
  1343  
  1344  			/*
  1345  				// tagRange not used in unmarshal
  1346  				tagRange := Trim(strings.ToLower(field.Tag.Get("range")))
  1347  				arRange := strings.Split(tagRange, "..")
  1348  				rangeMin := 0
  1349  				rangeMax := 0
  1350  				if len(arRange) == 2 {
  1351  					rangeMin, _ = ParseInt32(arRange[0])
  1352  					rangeMax, _ = ParseInt32(arRange[1])
  1353  				} else {
  1354  					rangeMin, _ = ParseInt32(tagRange)
  1355  					rangeMax = rangeMin
  1356  				}
  1357  			*/
  1358  
  1359  			// tagReq not used in unmarshal
  1360  			tagReq := Trim(strings.ToLower(field.Tag.Get("req")))
  1361  			if tagReq != "true" && tagReq != "false" {
  1362  				tagReq = ""
  1363  			}
  1364  
  1365  			// if outPrefix exists, remove from csvValue
  1366  			outPrefix := Trim(field.Tag.Get("outprefix"))
  1367  
  1368  			// get csv value by ordinal position
  1369  			csvValue := ""
  1370  
  1371  			if tagPosBuf != "-" {
  1372  				if LenTrim(outPrefix) == 0 {
  1373  					// ordinal based csv parsing
  1374  					if csvElements != nil {
  1375  						if tagPos > csvLen-1 {
  1376  							// no more elements to unmarshal, rest of fields using default values
  1377  							return nil
  1378  						} else {
  1379  							csvValue = csvElements[tagPos]
  1380  
  1381  							evalOk := false
  1382  							if boolTrue := Trim(field.Tag.Get("booltrue")); len(boolTrue) > 0 {
  1383  								if boolTrue == csvValue {
  1384  									csvValue = "true"
  1385  									evalOk = true
  1386  								}
  1387  							}
  1388  
  1389  							if !evalOk {
  1390  								if boolFalse := Trim(field.Tag.Get("boolfalse")); len(boolFalse) > 0 {
  1391  									if boolFalse == csvValue {
  1392  										csvValue = "false"
  1393  									}
  1394  								}
  1395  							}
  1396  						}
  1397  					}
  1398  				} else {
  1399  					// variable element based csv, using outPrefix as the identifying key
  1400  					// instead of getting csv value from element position, acquire from outPrefix
  1401  					notFound := true
  1402  
  1403  					for _, v := range csvElements {
  1404  						if strings.ToLower(Left(v, len(outPrefix))) == strings.ToLower(outPrefix) {
  1405  							// match
  1406  							if _, ok := prefixProcessedMap[strings.ToLower(outPrefix)]; !ok {
  1407  								prefixProcessedMap[strings.ToLower(outPrefix)] = Itoa(tagPos)
  1408  
  1409  								if len(v)-len(outPrefix) == 0 {
  1410  									csvValue = ""
  1411  
  1412  									if field.Tag.Get("booltrue") == " " {
  1413  										// prefix found, since data is blank, and boolTrue is space, treat this as true
  1414  										csvValue = "true"
  1415  									}
  1416  								} else {
  1417  									csvValue = Right(v, len(v)-len(outPrefix))
  1418  
  1419  									evalOk := false
  1420  									if boolTrue := Trim(field.Tag.Get("booltrue")); len(boolTrue) > 0 {
  1421  										if boolTrue == csvValue {
  1422  											csvValue = "true"
  1423  											evalOk = true
  1424  										}
  1425  									}
  1426  
  1427  									if !evalOk {
  1428  										if boolFalse := Trim(field.Tag.Get("boolfalse")); len(boolFalse) > 0 {
  1429  											if boolFalse == csvValue {
  1430  												csvValue = "false"
  1431  											}
  1432  										}
  1433  									}
  1434  								}
  1435  
  1436  								notFound = false
  1437  								break
  1438  							}
  1439  						}
  1440  					}
  1441  
  1442  					if notFound {
  1443  						continue
  1444  					}
  1445  				}
  1446  			}
  1447  
  1448  			// pre-process csv value with validation
  1449  			tagSetter := Trim(field.Tag.Get("setter"))
  1450  			hasSetter := false
  1451  
  1452  			isBase := false
  1453  			if LenTrim(tagSetter) > 0 {
  1454  				hasSetter = true
  1455  
  1456  				if strings.ToLower(Left(tagSetter, 5)) == "base." {
  1457  					isBase = true
  1458  					tagSetter = Right(tagSetter, len(tagSetter)-5)
  1459  				}
  1460  			}
  1461  
  1462  			timeFormat := Trim(field.Tag.Get("timeformat"))
  1463  
  1464  			if o.Kind() != reflect.Ptr && o.Kind() != reflect.Interface && o.Kind() != reflect.Struct && o.Kind() != reflect.Slice {
  1465  				if tagPosBuf != "-" {
  1466  					switch tagType {
  1467  					case "a":
  1468  						csvValue, _ = ExtractAlpha(csvValue)
  1469  					case "n":
  1470  						csvValue, _ = ExtractNumeric(csvValue)
  1471  					case "an":
  1472  						csvValue, _ = ExtractAlphaNumeric(csvValue)
  1473  					case "ans":
  1474  						if !hasSetter {
  1475  							csvValue, _ = ExtractAlphaNumericPrintableSymbols(csvValue)
  1476  						}
  1477  					case "b":
  1478  						if StringSliceContains(&trueList, strings.ToLower(csvValue)) {
  1479  							csvValue = "true"
  1480  						} else {
  1481  							csvValue = "false"
  1482  						}
  1483  					case "regex":
  1484  						csvValue, _ = ExtractByRegex(csvValue, tagRegEx)
  1485  					case "h":
  1486  						csvValue, _ = ExtractHex(csvValue)
  1487  					case "b64":
  1488  						csvValue, _ = ExtractAlphaNumericPrintableSymbols(csvValue)
  1489  					}
  1490  
  1491  					if tagType == "a" || tagType == "an" || tagType == "ans" || tagType == "n" || tagType == "regex" || tagType == "h" || tagType == "b64" {
  1492  						if sizeMax > 0 {
  1493  							if len(csvValue) > sizeMax {
  1494  								csvValue = Left(csvValue, sizeMax)
  1495  							}
  1496  						}
  1497  
  1498  						if tagModulo > 0 {
  1499  							if len(csvValue)%tagModulo != 0 {
  1500  								return fmt.Errorf("Struct Field %s Expects Value In Blocks of %d Characters", field.Name, tagModulo)
  1501  							}
  1502  						}
  1503  					}
  1504  				}
  1505  
  1506  				if LenTrim(tagSetter) > 0 {
  1507  					var ov []reflect.Value
  1508  					var notFound bool
  1509  
  1510  					if isBase {
  1511  						ov, notFound = ReflectCall(s.Addr(), tagSetter, csvValue)
  1512  					} else {
  1513  						ov, notFound = ReflectCall(o, tagSetter, csvValue)
  1514  					}
  1515  
  1516  					if !notFound {
  1517  						if len(ov) == 1 {
  1518  							csvValue, _, _ = ReflectValueToString(ov[0], "", "", false, false, timeFormat, false)
  1519  						} else if len(ov) > 1 {
  1520  							getFirstVar := true
  1521  
  1522  							if e, ok := ov[len(ov)-1].Interface().(error); ok {
  1523  								// last var is error, check if error exists
  1524  								if e != nil {
  1525  									getFirstVar = false
  1526  								}
  1527  							}
  1528  
  1529  							if getFirstVar {
  1530  								csvValue, _, _ = ReflectValueToString(ov[0], "", "", false, false, timeFormat, false)
  1531  							}
  1532  						}
  1533  					}
  1534  				}
  1535  
  1536  				// validate if applicable
  1537  				skipFieldSet := false
  1538  
  1539  				if valData := Trim(field.Tag.Get("validate")); len(valData) >= 3 {
  1540  					valComp := Left(valData, 2)
  1541  					valData = Right(valData, len(valData)-2)
  1542  
  1543  					switch valComp {
  1544  					case "==":
  1545  						valAr := strings.Split(valData, "||")
  1546  
  1547  						if len(valAr) <= 1 {
  1548  							if strings.ToLower(csvValue) != strings.ToLower(valData) {
  1549  								if len(csvValue) > 0 || tagReq == "true" {
  1550  									StructClearFields(inputStructPtr)
  1551  									return fmt.Errorf("%s Validation Failed: Expected To Match '%s', But Received '%s'", field.Name, valData, csvValue)
  1552  								}
  1553  							}
  1554  						} else {
  1555  							found := false
  1556  
  1557  							for _, va := range valAr {
  1558  								if strings.ToLower(csvValue) == strings.ToLower(va) {
  1559  									found = true
  1560  									break
  1561  								}
  1562  							}
  1563  
  1564  							if !found && (len(csvValue) > 0 || tagReq == "true") {
  1565  								return fmt.Errorf("%s Validation Failed: Expected To Match '%s', But Received '%s'", field.Name, strings.ReplaceAll(valData, "||", " or "), csvValue)
  1566  							}
  1567  						}
  1568  					case "!=":
  1569  						valAr := strings.Split(valData, "&&")
  1570  
  1571  						if len(valAr) <= 1 {
  1572  							if strings.ToLower(csvValue) == strings.ToLower(valData) {
  1573  								if len(csvValue) > 0 || tagReq == "true" {
  1574  									StructClearFields(inputStructPtr)
  1575  									return fmt.Errorf("%s Validation Failed: Expected To Not Match '%s', But Received '%s'", field.Name, valData, csvValue)
  1576  								}
  1577  							}
  1578  						} else {
  1579  							found := false
  1580  
  1581  							for _, va := range valAr {
  1582  								if strings.ToLower(csvValue) == strings.ToLower(va) {
  1583  									found = true
  1584  									break
  1585  								}
  1586  							}
  1587  
  1588  							if found && (len(csvValue) > 0 || tagReq == "true") {
  1589  								return fmt.Errorf("%s Validation Failed: Expected To Not Match '%s', But Received '%s'", field.Name, strings.ReplaceAll(valData, "&&", " and "), csvValue)
  1590  							}
  1591  						}
  1592  					case "<=":
  1593  						if valNum, valOk := ParseFloat64(valData); valOk {
  1594  							if srcNum, _ := ParseFloat64(csvValue); srcNum > valNum {
  1595  								if len(csvValue) > 0 || tagReq == "true" {
  1596  									StructClearFields(inputStructPtr)
  1597  									return fmt.Errorf("%s Validation Failed: Expected To Be Less or Equal To '%s', But Received '%s'", field.Name, valData, csvValue)
  1598  								}
  1599  							}
  1600  						}
  1601  					case "<<":
  1602  						if valNum, valOk := ParseFloat64(valData); valOk {
  1603  							if srcNum, _ := ParseFloat64(csvValue); srcNum >= valNum {
  1604  								if len(csvValue) > 0 || tagReq == "true" {
  1605  									StructClearFields(inputStructPtr)
  1606  									return fmt.Errorf("%s Validation Failed: Expected To Be Less Than '%s', But Received '%s'", field.Name, valData, csvValue)
  1607  								}
  1608  							}
  1609  						}
  1610  					case ">=":
  1611  						if valNum, valOk := ParseFloat64(valData); valOk {
  1612  							if srcNum, _ := ParseFloat64(csvValue); srcNum < valNum {
  1613  								if len(csvValue) > 0 || tagReq == "true" {
  1614  									StructClearFields(inputStructPtr)
  1615  									return fmt.Errorf("%s Validation Failed: Expected To Be Greater or Equal To '%s', But Received '%s'", field.Name, valData, csvValue)
  1616  								}
  1617  							}
  1618  						}
  1619  					case ">>":
  1620  						if valNum, valOk := ParseFloat64(valData); valOk {
  1621  							if srcNum, _ := ParseFloat64(csvValue); srcNum <= valNum {
  1622  								if len(csvValue) > 0 || tagReq == "true" {
  1623  									StructClearFields(inputStructPtr)
  1624  									return fmt.Errorf("%s Validation Failed: Expected To Be Greater Than '%s', But Received '%s'", field.Name, valData, csvValue)
  1625  								}
  1626  							}
  1627  						}
  1628  					case ":=":
  1629  						if len(valData) > 0 {
  1630  							skipFieldSet = true
  1631  
  1632  							if err := ReflectStringToField(o, csvValue, timeFormat); err != nil {
  1633  								return err
  1634  							}
  1635  
  1636  							if retV, nf := ReflectCall(s.Addr(), valData); !nf {
  1637  								if len(retV) > 0 {
  1638  									if retV[0].Kind() == reflect.Bool && !retV[0].Bool() {
  1639  										// validation failed with bool false
  1640  										StructClearFields(inputStructPtr)
  1641  										return fmt.Errorf("%s Validation Failed: %s() Returned Result is False", field.Name, valData)
  1642  									} else if retErr := DerefError(retV[0]); retErr != nil {
  1643  										// validation failed with error
  1644  										StructClearFields(inputStructPtr)
  1645  										return fmt.Errorf("%s Validation On %s() Failed: %s", field.Name, valData, retErr.Error())
  1646  									}
  1647  								}
  1648  							}
  1649  						}
  1650  					}
  1651  				}
  1652  
  1653  				// set validated csv value into corresponding struct field
  1654  				if !skipFieldSet {
  1655  					if err := ReflectStringToField(o, csvValue, timeFormat); err != nil {
  1656  						return err
  1657  					}
  1658  				}
  1659  			} else {
  1660  				if LenTrim(tagSetter) > 0 {
  1661  					if o.Kind() != reflect.Slice {
  1662  						// get base type
  1663  						if baseType, _, isNilPtr := DerefPointersZero(o); isNilPtr {
  1664  							// create new struct pointer
  1665  							o.Set(reflect.New(baseType.Type()))
  1666  						} else {
  1667  							if o.Kind() == reflect.Interface && o.Interface() == nil {
  1668  								customType := ReflectTypeRegistryGet(o.Type().String())
  1669  
  1670  								if customType == nil {
  1671  									return fmt.Errorf("%s Struct Field %s is Interface Without Actual Object Assignment", s.Type(), o.Type())
  1672  								} else {
  1673  									o.Set(reflect.New(customType))
  1674  								}
  1675  							}
  1676  						}
  1677  					}
  1678  
  1679  					var ov []reflect.Value
  1680  					var notFound bool
  1681  
  1682  					if isBase {
  1683  						ov, notFound = ReflectCall(s.Addr(), tagSetter, csvValue)
  1684  					} else {
  1685  						ov, notFound = ReflectCall(o, tagSetter, csvValue)
  1686  					}
  1687  
  1688  					if !notFound {
  1689  						if len(ov) == 1 {
  1690  							if ov[0].Kind() == reflect.Ptr || ov[0].Kind() == reflect.Slice {
  1691  								o.Set(ov[0])
  1692  							}
  1693  						} else if len(ov) > 1 {
  1694  							getFirstVar := true
  1695  
  1696  							if e := DerefError(ov[len(ov)-1]); e != nil {
  1697  								getFirstVar = false
  1698  							}
  1699  
  1700  							if getFirstVar {
  1701  								if ov[0].Kind() == reflect.Ptr || ov[0].Kind() == reflect.Slice {
  1702  									o.Set(ov[0])
  1703  								}
  1704  							}
  1705  						}
  1706  					}
  1707  				} else {
  1708  					// set validated csv value into corresponding struct pointer field
  1709  					if err := ReflectStringToField(o, csvValue, timeFormat); err != nil {
  1710  						return err
  1711  					}
  1712  				}
  1713  			}
  1714  		}
  1715  	}
  1716  
  1717  	return nil
  1718  }
  1719  
  1720  // MarshalStructToCSV will serialize struct fields defined with strug tags below, to csvPayload string (one line of csv data) using csvDelimiter,
  1721  // the csv payload ordinal position is based on the struct tag pos defined for each struct field,
  1722  // additionally processes struct tag data validation and length / range (if not valid, will set to data type default),
  1723  // this method provides data validation and if fails, will return error (for string if size exceeds max, it will truncate)
  1724  //
  1725  // Predefined Struct Tags Usable:
  1726  //  1. `pos:"1"`				// ordinal position of the field in relation to the csv parsed output expected (Zero-Based Index)
  1727  //     NOTE: if field is mutually exclusive with one or more uniqueId, then pos # should be named the same for all uniqueIds
  1728  //     if multiple fields are in exclusive condition, and skipBlank or skipZero, always include a blank default field as the last of unique field list
  1729  //     if value is '-', this means position value is calculated from other fields and set via `setter:"base.Xyz"` during unmarshal csv, there is no marshal to csv for this field
  1730  //  2. `type:"xyz"`				// data type expected:
  1731  //     A = AlphabeticOnly, N = NumericOnly 0-9, AN = AlphaNumeric, ANS = AN + PrintableSymbols,
  1732  //     H = Hex, B64 = Base64, B = true/false, REGEX = Regular Expression, Blank = Any,
  1733  //  3. `regex:"xyz"`			// if Type = REGEX, this struct tag contains the regular expression string,
  1734  //     regex express such as [^A-Za-z0-9_-]+
  1735  //     method will replace any regex matched string to blank
  1736  //  4. `size:"x..y"`			// data type size rule:
  1737  //     x = Exact size match
  1738  //     x.. = From x and up
  1739  //     ..y = From 0 up to y
  1740  //     x..y = From x to y
  1741  //     +%z = Append to x, x.., ..y, x..y; adds additional constraint that the result size must equate to 0 from modulo of z
  1742  //  5. `range:"x..y"`			// data type range value when Type is N, if underlying data type is string, method will convert first before testing
  1743  //  6. `req:"true"`				// indicates data value is required or not, true or false
  1744  //  7. `getter:"Key"`			// if field type is custom struct or enum, specify the custom method getter (no parameters allowed) that returns the expected value in first ordinal result position
  1745  //     NOTE: if the method to invoke resides at struct level, precede the method name with 'base.', for example, 'base.XYZ' where XYZ is method name to invoke
  1746  //     NOTE: if the method is to receive a parameter value, always in string data type, add '(x)' after the method name, such as 'XYZ(x)' or 'base.XYZ(x)'
  1747  //  8. `setter:"ParseByKey`		// if field type is custom struct or enum, specify the custom method (only 1 lookup parameter value allowed) setter that sets value(s) into the field
  1748  //     NOTE: if the method to invoke resides at struct level, precede the method name with 'base.', for example, 'base.XYZ' where XYZ is method name to invoke
  1749  //     NOTE: setter method always intake a string parameter value
  1750  //  9. `booltrue:"1"` 			// if field is defined, contains bool literal for true condition, such as 1 or true, that overrides default system bool literal value,
  1751  //     if bool literal value is determined by existence of outprefix and itself is blank, place a space in both booltrue and boolfalse (setting blank will negate literal override)
  1752  //  10. `boolfalse:"0"`			// if field is defined, contains bool literal for false condition, such as 0 or false, that overrides default system bool literal value
  1753  //     if bool literal value is determined by existence of outprefix and itself is blank, place a space in both booltrue and boolfalse (setting blank will negate literal override)
  1754  //  11. `uniqueid:"xyz"`		// if two or more struct field is set with the same uniqueid, then only the first encountered field with the same uniqueid will be used in marshal,
  1755  //     NOTE: if field is mutually exclusive with one or more uniqueId, then pos # should be named the same for all uniqueIds
  1756  //  12. `skipblank:"false"`		// if true, then any fields that is blank string will be excluded from marshal (this only affects fields that are string)
  1757  //  13. `skipzero:"false"`		// if true, then any fields that are 0, 0.00, time.Zero(), false, nil will be excluded from marshal (this only affects fields that are number, bool, time, pointer)
  1758  //  14. `timeformat:"20060102"`	// for time.Time field, optional date time format, specified as:
  1759  //     2006, 06 = year,
  1760  //     01, 1, Jan, January = month,
  1761  //     02, 2, _2 = day (_2 = width two, right justified)
  1762  //     03, 3, 15 = hour (15 = 24 hour format)
  1763  //     04, 4 = minute
  1764  //     05, 5 = second
  1765  //     PM pm = AM PM
  1766  //  15. `outprefix:""`			// for marshal method, if field value is to precede with an output prefix, such as XYZ= (affects marshal queryParams / csv methods only)
  1767  //     WARNING: if csv is variable elements count, rather than fixed count ordinal, then csv MUST include outprefix for all fields in order to properly identify target struct field
  1768  //  16. `zeroblank:"false"`		// set true to set blank to data when value is 0, 0.00, or time.IsZero
  1769  //  17. `validate:"==x"`		// if field has to match a specific value or the entire method call will fail, match data format as:
  1770  //     ==xyz (== refers to equal, for numbers and string match, xyz is data to match, case insensitive)
  1771  //     [if == validate against one or more values, use ||]
  1772  //     !=xyz (!= refers to not equal)
  1773  //     [if != validate against one or more values, use &&]
  1774  //     >=xyz >>xyz <<xyz <=xyz (greater equal, greater, less than, less equal; xyz must be int or float)
  1775  //     :=Xyz where Xyz is a parameterless function defined at struct level, that performs validation, returns bool or error where true or nil indicates validation success
  1776  //     note: expected source data type for validate to be effective is string, int, float64; if field is blank and req = false, then validate will be skipped
  1777  func MarshalStructToCSV(inputStructPtr interface{}, csvDelimiter string) (csvPayload string, err error) {
  1778  	if inputStructPtr == nil {
  1779  		return "", fmt.Errorf("InputStructPtr is Required")
  1780  	}
  1781  
  1782  	s := reflect.ValueOf(inputStructPtr)
  1783  
  1784  	if s.Kind() != reflect.Ptr {
  1785  		return "", fmt.Errorf("InputStructPtr Must Be Pointer")
  1786  	} else {
  1787  		s = s.Elem()
  1788  	}
  1789  
  1790  	if s.Kind() != reflect.Struct {
  1791  		return "", fmt.Errorf("InputStructPtr Must Be Struct")
  1792  	}
  1793  
  1794  	if !IsStructFieldSet(inputStructPtr) && StructNonDefaultRequiredFieldsCount(inputStructPtr) > 0 {
  1795  		return "", nil
  1796  	}
  1797  
  1798  	trueList := []string{"true", "yes", "on", "1", "enabled"}
  1799  
  1800  	csvList := make([]string, s.NumField())
  1801  	csvLen := len(csvList)
  1802  
  1803  	for i := 0; i < csvLen; i++ {
  1804  		csvList[i] = "{?}" // indicates value not set, to be excluded
  1805  	}
  1806  
  1807  	excludePlaceholders := true
  1808  
  1809  	uniqueMap := make(map[string]string)
  1810  
  1811  	for i := 0; i < s.NumField(); i++ {
  1812  		field := s.Type().Field(i)
  1813  
  1814  		if o := s.FieldByName(field.Name); o.IsValid() && o.CanSet() {
  1815  			// extract struct tag values
  1816  			tagPos, ok := ParseInt32(field.Tag.Get("pos"))
  1817  			if !ok {
  1818  				continue
  1819  			} else if tagPos < 0 {
  1820  				continue
  1821  			} else if tagPos > csvLen-1 {
  1822  				continue
  1823  			}
  1824  
  1825  			if tagUniqueId := Trim(field.Tag.Get("uniqueid")); len(tagUniqueId) > 0 {
  1826  				if _, ok := uniqueMap[strings.ToLower(tagUniqueId)]; ok {
  1827  					continue
  1828  				} else {
  1829  					uniqueMap[strings.ToLower(tagUniqueId)] = field.Name
  1830  				}
  1831  			}
  1832  
  1833  			tagType := Trim(strings.ToLower(field.Tag.Get("type")))
  1834  			switch tagType {
  1835  			case "a":
  1836  				fallthrough
  1837  			case "n":
  1838  				fallthrough
  1839  			case "an":
  1840  				fallthrough
  1841  			case "ans":
  1842  				fallthrough
  1843  			case "b":
  1844  				fallthrough
  1845  			case "b64":
  1846  				fallthrough
  1847  			case "regex":
  1848  				fallthrough
  1849  			case "h":
  1850  				// valid type
  1851  			default:
  1852  				tagType = ""
  1853  			}
  1854  
  1855  			tagRegEx := Trim(field.Tag.Get("regex"))
  1856  			if tagType != "regex" {
  1857  				tagRegEx = ""
  1858  			} else {
  1859  				if LenTrim(tagRegEx) == 0 {
  1860  					tagType = ""
  1861  				}
  1862  			}
  1863  
  1864  			tagSize := Trim(strings.ToLower(field.Tag.Get("size")))
  1865  			arModulo := strings.Split(tagSize, "+%")
  1866  			tagModulo := 0
  1867  			if len(arModulo) == 2 {
  1868  				tagSize = arModulo[0]
  1869  				if tagModulo, _ = ParseInt32(arModulo[1]); tagModulo < 0 {
  1870  					tagModulo = 0
  1871  				}
  1872  			}
  1873  			arSize := strings.Split(tagSize, "..")
  1874  			sizeMin := 0
  1875  			sizeMax := 0
  1876  			if len(arSize) == 2 {
  1877  				sizeMin, _ = ParseInt32(arSize[0])
  1878  				sizeMax, _ = ParseInt32(arSize[1])
  1879  			} else {
  1880  				sizeMin, _ = ParseInt32(tagSize)
  1881  				sizeMax = sizeMin
  1882  			}
  1883  
  1884  			tagRange := Trim(strings.ToLower(field.Tag.Get("range")))
  1885  			arRange := strings.Split(tagRange, "..")
  1886  			rangeMin := 0
  1887  			rangeMax := 0
  1888  			if len(arRange) == 2 {
  1889  				rangeMin, _ = ParseInt32(arRange[0])
  1890  				rangeMax, _ = ParseInt32(arRange[1])
  1891  			} else {
  1892  				rangeMin, _ = ParseInt32(tagRange)
  1893  				rangeMax = rangeMin
  1894  			}
  1895  
  1896  			tagReq := Trim(strings.ToLower(field.Tag.Get("req")))
  1897  			if tagReq != "true" && tagReq != "false" {
  1898  				tagReq = ""
  1899  			}
  1900  
  1901  			// get csv value from current struct field
  1902  			var boolTrue, boolFalse, timeFormat, outPrefix string
  1903  			var skipBlank, skipZero, zeroBlank bool
  1904  
  1905  			if vs := GetStructTagsValueSlice(field, "booltrue", "boolfalse", "skipblank", "skipzero", "timeformat", "outprefix", "zeroblank"); len(vs) == 7 {
  1906  				boolTrue = vs[0]
  1907  				boolFalse = vs[1]
  1908  				skipBlank, _ = ParseBool(vs[2])
  1909  				skipZero, _ = ParseBool(vs[3])
  1910  				timeFormat = vs[4]
  1911  				outPrefix = vs[5]
  1912  				zeroBlank, _ = ParseBool(vs[6])
  1913  			}
  1914  
  1915  			if excludePlaceholders {
  1916  				excludePlaceholders = LenTrim(outPrefix) > 0
  1917  			}
  1918  
  1919  			// cache old value prior to getter invoke
  1920  			oldVal := o
  1921  			hasGetter := false
  1922  
  1923  			if tagGetter := Trim(field.Tag.Get("getter")); len(tagGetter) > 0 {
  1924  				hasGetter = true
  1925  
  1926  				isBase := false
  1927  				useParam := false
  1928  				paramVal := ""
  1929  				var paramSlice interface{}
  1930  
  1931  				if strings.ToLower(Left(tagGetter, 5)) == "base." {
  1932  					isBase = true
  1933  					tagGetter = Right(tagGetter, len(tagGetter)-5)
  1934  				}
  1935  
  1936  				if strings.ToLower(Right(tagGetter, 3)) == "(x)" {
  1937  					useParam = true
  1938  
  1939  					if o.Kind() != reflect.Slice {
  1940  						paramVal, _, _ = ReflectValueToString(o, boolTrue, boolFalse, skipBlank, skipZero, timeFormat, zeroBlank)
  1941  					} else {
  1942  						if o.Len() > 0 {
  1943  							paramSlice = o.Slice(0, o.Len()).Interface()
  1944  						}
  1945  					}
  1946  
  1947  					tagGetter = Left(tagGetter, len(tagGetter)-3)
  1948  				}
  1949  
  1950  				var ov []reflect.Value
  1951  				var notFound bool
  1952  
  1953  				if isBase {
  1954  					if useParam {
  1955  						if paramSlice == nil {
  1956  							ov, notFound = ReflectCall(s.Addr(), tagGetter, paramVal)
  1957  						} else {
  1958  							ov, notFound = ReflectCall(s.Addr(), tagGetter, paramSlice)
  1959  						}
  1960  					} else {
  1961  						ov, notFound = ReflectCall(s.Addr(), tagGetter)
  1962  					}
  1963  				} else {
  1964  					if useParam {
  1965  						if paramSlice == nil {
  1966  							ov, notFound = ReflectCall(o, tagGetter, paramVal)
  1967  						} else {
  1968  							ov, notFound = ReflectCall(o, tagGetter, paramSlice)
  1969  						}
  1970  					} else {
  1971  						ov, notFound = ReflectCall(o, tagGetter)
  1972  					}
  1973  				}
  1974  
  1975  				if !notFound {
  1976  					if len(ov) > 0 {
  1977  						o = ov[0]
  1978  					}
  1979  				}
  1980  			}
  1981  
  1982  			fv, skip, e := ReflectValueToString(o, boolTrue, boolFalse, skipBlank, skipZero, timeFormat, zeroBlank)
  1983  
  1984  			if e != nil {
  1985  				if tagUniqueId := Trim(field.Tag.Get("uniqueid")); len(tagUniqueId) > 0 {
  1986  					if _, ok := uniqueMap[strings.ToLower(tagUniqueId)]; ok {
  1987  						// remove uniqueid if skip
  1988  						delete(uniqueMap, strings.ToLower(tagUniqueId))
  1989  					}
  1990  				}
  1991  
  1992  				return "", e
  1993  			}
  1994  
  1995  			if skip {
  1996  				if tagUniqueId := Trim(field.Tag.Get("uniqueid")); len(tagUniqueId) > 0 {
  1997  					if _, ok := uniqueMap[strings.ToLower(tagUniqueId)]; ok {
  1998  						// remove uniqueid if skip
  1999  						delete(uniqueMap, strings.ToLower(tagUniqueId))
  2000  					}
  2001  				}
  2002  
  2003  				continue
  2004  			}
  2005  
  2006  			defVal := field.Tag.Get("def")
  2007  
  2008  			if oldVal.Kind() == reflect.Int && oldVal.Int() == 0 && strings.ToLower(fv) == "unknown" {
  2009  				// unknown enum value will be serialized as blank
  2010  				fv = ""
  2011  
  2012  				if len(defVal) > 0 {
  2013  					fv = defVal
  2014  				} else {
  2015  					if tagUniqueId := Trim(field.Tag.Get("uniqueid")); len(tagUniqueId) > 0 {
  2016  						if _, ok := uniqueMap[strings.ToLower(tagUniqueId)]; ok {
  2017  							// remove uniqueid if skip
  2018  							delete(uniqueMap, strings.ToLower(tagUniqueId))
  2019  							continue
  2020  						}
  2021  					}
  2022  				}
  2023  			}
  2024  
  2025  			// validate output csv value
  2026  			if oldVal.Kind() != reflect.Slice {
  2027  				origFv := fv
  2028  
  2029  				switch tagType {
  2030  				case "a":
  2031  					fv, _ = ExtractAlpha(fv)
  2032  				case "n":
  2033  					fv, _ = ExtractNumeric(fv)
  2034  				case "an":
  2035  					fv, _ = ExtractAlphaNumeric(fv)
  2036  				case "ans":
  2037  					if !hasGetter {
  2038  						fv, _ = ExtractAlphaNumericPrintableSymbols(fv)
  2039  					}
  2040  				case "b":
  2041  					if len(boolTrue) == 0 && len(boolFalse) == 0 {
  2042  						if StringSliceContains(&trueList, strings.ToLower(fv)) {
  2043  							fv = "true"
  2044  						} else {
  2045  							fv = "false"
  2046  						}
  2047  					} else {
  2048  						if Trim(boolTrue) == Trim(boolFalse) {
  2049  							if fv == "false" {
  2050  								fv = ""
  2051  								csvList[tagPos] = fv
  2052  								continue
  2053  							}
  2054  						}
  2055  					}
  2056  				case "regex":
  2057  					fv, _ = ExtractByRegex(fv, tagRegEx)
  2058  				case "h":
  2059  					fv, _ = ExtractHex(fv)
  2060  				case "b64":
  2061  					fv, _ = ExtractAlphaNumericPrintableSymbols(fv)
  2062  				}
  2063  
  2064  				if boolFalse == " " && origFv == "false" && len(outPrefix) > 0 {
  2065  					// just in case fv is not defined type type b
  2066  					fv = ""
  2067  					csvList[tagPos] = fv
  2068  					continue
  2069  				}
  2070  
  2071  				if len(fv) == 0 && len(defVal) > 0 {
  2072  					fv = defVal
  2073  				}
  2074  
  2075  				if tagType == "a" || tagType == "an" || tagType == "ans" || tagType == "n" || tagType == "regex" || tagType == "h" || tagType == "b64" {
  2076  					if sizeMin > 0 && len(fv) > 0 {
  2077  						if len(fv) < sizeMin {
  2078  							return "", fmt.Errorf("%s Min Length is %d", field.Name, sizeMin)
  2079  						}
  2080  					}
  2081  
  2082  					if sizeMax > 0 && len(fv) > sizeMax {
  2083  						fv = Left(fv, sizeMax)
  2084  					}
  2085  
  2086  					if tagModulo > 0 {
  2087  						if len(fv)%tagModulo != 0 {
  2088  							return "", fmt.Errorf("Struct Field %s Expects Value In Blocks of %d Characters", field.Name, tagModulo)
  2089  						}
  2090  					}
  2091  				}
  2092  
  2093  				if tagType == "n" {
  2094  					n, ok := ParseInt32(fv)
  2095  
  2096  					if ok {
  2097  						if rangeMin > 0 {
  2098  							if n < rangeMin {
  2099  								if !(n == 0 && tagReq != "true") {
  2100  									return "", fmt.Errorf("%s Range Minimum is %d", field.Name, rangeMin)
  2101  								}
  2102  							}
  2103  						}
  2104  
  2105  						if rangeMax > 0 {
  2106  							if n > rangeMax {
  2107  								return "", fmt.Errorf("%s Range Maximum is %d", field.Name, rangeMax)
  2108  							}
  2109  						}
  2110  					}
  2111  				}
  2112  
  2113  				if tagReq == "true" && len(fv) == 0 {
  2114  					return "", fmt.Errorf("%s is a Required Field", field.Name)
  2115  				}
  2116  			}
  2117  
  2118  			// validate if applicable
  2119  			if valData := Trim(field.Tag.Get("validate")); len(valData) >= 3 {
  2120  				valComp := Left(valData, 2)
  2121  				valData = Right(valData, len(valData)-2)
  2122  
  2123  				switch valComp {
  2124  				case "==":
  2125  					valAr := strings.Split(valData, "||")
  2126  
  2127  					if len(valAr) <= 1 {
  2128  						if strings.ToLower(fv) != strings.ToLower(valData) {
  2129  							if len(fv) > 0 || tagReq == "true" {
  2130  								return "", fmt.Errorf("%s Validation Failed: Expected To Match '%s', But Received '%s'", field.Name, valData, fv)
  2131  							}
  2132  						}
  2133  					} else {
  2134  						found := false
  2135  
  2136  						for _, va := range valAr {
  2137  							if strings.ToLower(fv) == strings.ToLower(va) {
  2138  								found = true
  2139  								break
  2140  							}
  2141  						}
  2142  
  2143  						if !found && (len(fv) > 0 || tagReq == "true") {
  2144  							return "", fmt.Errorf("%s Validation Failed: Expected To Match '%s', But Received '%s'", field.Name, strings.ReplaceAll(valData, "||", " or "), fv)
  2145  						}
  2146  					}
  2147  				case "!=":
  2148  					valAr := strings.Split(valData, "&&")
  2149  
  2150  					if len(valAr) <= 1 {
  2151  						if strings.ToLower(fv) == strings.ToLower(valData) {
  2152  							if len(fv) > 0 || tagReq == "true" {
  2153  								return "", fmt.Errorf("%s Validation Failed: Expected To Not Match '%s', But Received '%s'", field.Name, valData, fv)
  2154  							}
  2155  						}
  2156  					} else {
  2157  						found := false
  2158  
  2159  						for _, va := range valAr {
  2160  							if strings.ToLower(fv) == strings.ToLower(va) {
  2161  								found = true
  2162  								break
  2163  							}
  2164  						}
  2165  
  2166  						if found && (len(fv) > 0 || tagReq == "true") {
  2167  							return "", fmt.Errorf("%s Validation Failed: Expected To Not Match '%s', But Received '%s'", field.Name, strings.ReplaceAll(valData, "&&", " and "), fv)
  2168  						}
  2169  					}
  2170  				case "<=":
  2171  					if valNum, valOk := ParseFloat64(valData); valOk {
  2172  						if srcNum, _ := ParseFloat64(fv); srcNum > valNum {
  2173  							if len(fv) > 0 || tagReq == "true" {
  2174  								return "", fmt.Errorf("%s Validation Failed: Expected To Be Less or Equal To '%s', But Received '%s'", field.Name, valData, fv)
  2175  							}
  2176  						}
  2177  					}
  2178  				case "<<":
  2179  					if valNum, valOk := ParseFloat64(valData); valOk {
  2180  						if srcNum, _ := ParseFloat64(fv); srcNum >= valNum {
  2181  							if len(fv) > 0 || tagReq == "true" {
  2182  								return "", fmt.Errorf("%s Validation Failed: Expected To Be Less Than '%s', But Received '%s'", field.Name, valData, fv)
  2183  							}
  2184  						}
  2185  					}
  2186  				case ">=":
  2187  					if valNum, valOk := ParseFloat64(valData); valOk {
  2188  						if srcNum, _ := ParseFloat64(fv); srcNum < valNum {
  2189  							if len(fv) > 0 || tagReq == "true" {
  2190  								return "", fmt.Errorf("%s Validation Failed: Expected To Be Greater or Equal To '%s', But Received '%s'", field.Name, valData, fv)
  2191  							}
  2192  						}
  2193  					}
  2194  				case ">>":
  2195  					if valNum, valOk := ParseFloat64(valData); valOk {
  2196  						if srcNum, _ := ParseFloat64(fv); srcNum <= valNum {
  2197  							if len(fv) > 0 || tagReq == "true" {
  2198  								return "", fmt.Errorf("%s Validation Failed: Expected To Be Greater Than '%s', But Received '%s'", field.Name, valData, fv)
  2199  							}
  2200  						}
  2201  					}
  2202  				case ":=":
  2203  					if len(valData) > 0 {
  2204  						if retV, nf := ReflectCall(s.Addr(), valData); !nf {
  2205  							if len(retV) > 0 {
  2206  								if retV[0].Kind() == reflect.Bool && !retV[0].Bool() {
  2207  									// validation failed with bool false
  2208  									return "", fmt.Errorf("%s Validation Failed: %s() Returned Result is False", field.Name, valData)
  2209  								} else if retErr := DerefError(retV[0]); retErr != nil {
  2210  									// validation failed with error
  2211  									return "", fmt.Errorf("%s Validation On %s() Failed: %s", field.Name, valData, retErr.Error())
  2212  								}
  2213  							}
  2214  						}
  2215  					}
  2216  				}
  2217  			}
  2218  
  2219  			// store fv into sorted slice
  2220  			if skipBlank && LenTrim(fv) == 0 {
  2221  				csvList[tagPos] = ""
  2222  			} else if skipZero && fv == "0" {
  2223  				csvList[tagPos] = ""
  2224  			} else {
  2225  				csvList[tagPos] = outPrefix + fv
  2226  			}
  2227  		}
  2228  	}
  2229  
  2230  	firstCsvElement := true
  2231  
  2232  	for _, v := range csvList {
  2233  		if excludePlaceholders {
  2234  			if v != "{?}" && LenTrim(v) > 0 {
  2235  				if LenTrim(csvPayload) > 0 {
  2236  					csvPayload += csvDelimiter
  2237  				}
  2238  
  2239  				csvPayload += v
  2240  			}
  2241  		} else {
  2242  			if !firstCsvElement {
  2243  				csvPayload += csvDelimiter
  2244  			}
  2245  
  2246  			if v != "{?}" {
  2247  				csvPayload += v
  2248  			}
  2249  
  2250  			if firstCsvElement {
  2251  				firstCsvElement = false
  2252  			}
  2253  		}
  2254  	}
  2255  
  2256  	return csvPayload, nil
  2257  }