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 }