-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathnormalize.go
More file actions
163 lines (141 loc) · 3.38 KB
/
normalize.go
File metadata and controls
163 lines (141 loc) · 3.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package gotoon
import (
"math"
"reflect"
"time"
)
// normalizeValue converts any Go value to a JSON-compatible value
func normalizeValue(value interface{}) interface{} {
if value == nil {
return nil
}
v := reflect.ValueOf(value)
switch v.Kind() {
case reflect.Bool:
return v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return float64(v.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return float64(v.Uint())
case reflect.Float32, reflect.Float64:
f := v.Float()
// Handle special float values
if math.IsNaN(f) || math.IsInf(f, 0) {
return nil
}
// Normalize -0 to 0
if f == 0 {
return 0.0
}
return f
case reflect.String:
return v.String()
case reflect.Slice, reflect.Array:
arr := make([]interface{}, v.Len())
for i := 0; i < v.Len(); i++ {
arr[i] = normalizeValue(v.Index(i).Interface())
}
return arr
case reflect.Map:
if v.Type().Key().Kind() != reflect.String {
// Non-string keys not supported, return null
return nil
}
obj := make(map[string]interface{})
iter := v.MapRange()
for iter.Next() {
key := iter.Key().String()
obj[key] = normalizeValue(iter.Value().Interface())
}
return obj
case reflect.Struct:
// Handle time.Time specially
if t, ok := value.(time.Time); ok {
return t.Format(time.RFC3339Nano)
}
// Convert struct to map using exported fields
obj := make(map[string]interface{})
t := v.Type()
for i := 0; i < v.NumField(); i++ {
field := t.Field(i)
// Only include exported fields
if field.PkgPath == "" {
fieldValue := v.Field(i)
if fieldValue.CanInterface() {
// Use json tag if available, otherwise use field name
name := field.Name
if tag := field.Tag.Get("json"); tag != "" && tag != "-" {
name = tag
}
obj[name] = normalizeValue(fieldValue.Interface())
}
}
}
return obj
case reflect.Ptr, reflect.Interface:
if v.IsNil() {
return nil
}
return normalizeValue(v.Elem().Interface())
default:
// Unsupported types (func, chan, etc.) become null
return nil
}
}
// Type guard functions
// isPrimitive checks if a value is a JSON primitive (string, number, bool, null)
func isPrimitive(value interface{}) bool {
if value == nil {
return true
}
switch value.(type) {
case bool, float64, string:
return true
default:
return false
}
}
// isArray checks if a value is a slice/array
func isArray(value interface{}) bool {
if value == nil {
return false
}
v := reflect.ValueOf(value)
return v.Kind() == reflect.Slice || v.Kind() == reflect.Array
}
// isObject checks if a value is a map (after normalization)
func isObject(value interface{}) bool {
if value == nil {
return false
}
_, ok := value.(map[string]interface{})
return ok
}
// Array type detection helpers
// isArrayOfPrimitives checks if all elements are primitives
func isArrayOfPrimitives(arr []interface{}) bool {
for _, item := range arr {
if !isPrimitive(item) {
return false
}
}
return true
}
// isArrayOfArrays checks if all elements are arrays
func isArrayOfArrays(arr []interface{}) bool {
for _, item := range arr {
if !isArray(item) {
return false
}
}
return true
}
// isArrayOfObjects checks if all elements are objects
func isArrayOfObjects(arr []interface{}) bool {
for _, item := range arr {
if !isObject(item) {
return false
}
}
return true
}