jsonutil.go 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. package ionet
  2. import (
  3. "encoding/json"
  4. "strings"
  5. "time"
  6. "github.com/samber/lo"
  7. )
  8. // decodeWithFlexibleTimes unmarshals API responses while tolerating timestamp strings
  9. // that omit timezone information by normalizing them to RFC3339Nano.
  10. func decodeWithFlexibleTimes(data []byte, target interface{}) error {
  11. var intermediate interface{}
  12. if err := json.Unmarshal(data, &intermediate); err != nil {
  13. return err
  14. }
  15. normalized := normalizeTimeValues(intermediate)
  16. reencoded, err := json.Marshal(normalized)
  17. if err != nil {
  18. return err
  19. }
  20. return json.Unmarshal(reencoded, target)
  21. }
  22. func decodeData[T any](data []byte, target *T) error {
  23. var wrapper struct {
  24. Data T `json:"data"`
  25. }
  26. if err := json.Unmarshal(data, &wrapper); err != nil {
  27. return err
  28. }
  29. *target = wrapper.Data
  30. return nil
  31. }
  32. func decodeDataWithFlexibleTimes[T any](data []byte, target *T) error {
  33. var wrapper struct {
  34. Data T `json:"data"`
  35. }
  36. if err := decodeWithFlexibleTimes(data, &wrapper); err != nil {
  37. return err
  38. }
  39. *target = wrapper.Data
  40. return nil
  41. }
  42. func normalizeTimeValues(value interface{}) interface{} {
  43. switch v := value.(type) {
  44. case map[string]interface{}:
  45. return lo.MapValues(v, func(val interface{}, _ string) interface{} {
  46. return normalizeTimeValues(val)
  47. })
  48. case []interface{}:
  49. return lo.Map(v, func(item interface{}, _ int) interface{} {
  50. return normalizeTimeValues(item)
  51. })
  52. case string:
  53. if normalized, changed := normalizeTimeString(v); changed {
  54. return normalized
  55. }
  56. return v
  57. default:
  58. return value
  59. }
  60. }
  61. func normalizeTimeString(input string) (string, bool) {
  62. trimmed := strings.TrimSpace(input)
  63. if trimmed == "" {
  64. return input, false
  65. }
  66. if _, err := time.Parse(time.RFC3339Nano, trimmed); err == nil {
  67. return trimmed, trimmed != input
  68. }
  69. if _, err := time.Parse(time.RFC3339, trimmed); err == nil {
  70. return trimmed, trimmed != input
  71. }
  72. layouts := []string{
  73. "2006-01-02T15:04:05.999999999",
  74. "2006-01-02T15:04:05.999999",
  75. "2006-01-02T15:04:05",
  76. }
  77. for _, layout := range layouts {
  78. if parsed, err := time.Parse(layout, trimmed); err == nil {
  79. return parsed.UTC().Format(time.RFC3339Nano), true
  80. }
  81. }
  82. return input, false
  83. }