Skip to content

Commit 7a2849f

Browse files
committed
Convert numbers like Kubernetes does for json.Marshaler types
1 parent 1791273 commit 7a2849f

File tree

1 file changed

+103
-5
lines changed

1 file changed

+103
-5
lines changed

value/reflectcache.go

Lines changed: 103 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -210,23 +210,23 @@ func (e TypeReflectCacheEntry) ToUnstructured(sv reflect.Value) (interface{}, er
210210

211211
case data[0] == '"':
212212
var result string
213-
err := json.Unmarshal(data, &result)
213+
err := Unmarshal(data, &result)
214214
if err != nil {
215215
return nil, fmt.Errorf("error decoding string from json: %v", err)
216216
}
217217
return result, nil
218218

219219
case data[0] == '{':
220220
result := make(map[string]interface{})
221-
err := json.Unmarshal(data, &result)
221+
err := Unmarshal(data, &result)
222222
if err != nil {
223223
return nil, fmt.Errorf("error decoding object from json: %v", err)
224224
}
225225
return result, nil
226226

227227
case data[0] == '[':
228228
result := make([]interface{}, 0)
229-
err := json.Unmarshal(data, &result)
229+
err := Unmarshal(data, &result)
230230
if err != nil {
231231
return nil, fmt.Errorf("error decoding array from json: %v", err)
232232
}
@@ -238,9 +238,9 @@ func (e TypeReflectCacheEntry) ToUnstructured(sv reflect.Value) (interface{}, er
238238
resultFloat float64
239239
err error
240240
)
241-
if err = json.Unmarshal(data, &resultInt); err == nil {
241+
if err = Unmarshal(data, &resultInt); err == nil {
242242
return resultInt, nil
243-
} else if err = json.Unmarshal(data, &resultFloat); err == nil {
243+
} else if err = Unmarshal(data, &resultFloat); err == nil {
244244
return resultFloat, nil
245245
} else {
246246
return nil, fmt.Errorf("error decoding number from json: %v", err)
@@ -363,3 +363,101 @@ func (c *typeReflectCache) update(updates reflectCacheMap) {
363363
}
364364
c.value.Store(newCacheMap)
365365
}
366+
367+
// Below json Unmarshal is fromk8s.io/apimachinery/pkg/util/json
368+
// to handle number conversions as expected by Kubernetes
369+
370+
// limit recursive depth to prevent stack overflow errors
371+
const maxDepth = 10000
372+
373+
// Unmarshal unmarshals the given data
374+
// If v is a *map[string]interface{}, numbers are converted to int64 or float64
375+
func Unmarshal(data []byte, v interface{}) error {
376+
switch v := v.(type) {
377+
case *map[string]interface{}:
378+
// Build a decoder from the given data
379+
decoder := json.NewDecoder(bytes.NewBuffer(data))
380+
// Preserve numbers, rather than casting to float64 automatically
381+
decoder.UseNumber()
382+
// Run the decode
383+
if err := decoder.Decode(v); err != nil {
384+
return err
385+
}
386+
// If the decode succeeds, post-process the map to convert json.Number objects to int64 or float64
387+
return convertMapNumbers(*v, 0)
388+
389+
case *[]interface{}:
390+
// Build a decoder from the given data
391+
decoder := json.NewDecoder(bytes.NewBuffer(data))
392+
// Preserve numbers, rather than casting to float64 automatically
393+
decoder.UseNumber()
394+
// Run the decode
395+
if err := decoder.Decode(v); err != nil {
396+
return err
397+
}
398+
// If the decode succeeds, post-process the map to convert json.Number objects to int64 or float64
399+
return convertSliceNumbers(*v, 0)
400+
401+
default:
402+
return json.Unmarshal(data, v)
403+
}
404+
}
405+
406+
// convertMapNumbers traverses the map, converting any json.Number values to int64 or float64.
407+
// values which are map[string]interface{} or []interface{} are recursively visited
408+
func convertMapNumbers(m map[string]interface{}, depth int) error {
409+
if depth > maxDepth {
410+
return fmt.Errorf("exceeded max depth of %d", maxDepth)
411+
}
412+
413+
var err error
414+
for k, v := range m {
415+
switch v := v.(type) {
416+
case json.Number:
417+
m[k], err = convertNumber(v)
418+
case map[string]interface{}:
419+
err = convertMapNumbers(v, depth+1)
420+
case []interface{}:
421+
err = convertSliceNumbers(v, depth+1)
422+
}
423+
if err != nil {
424+
return err
425+
}
426+
}
427+
return nil
428+
}
429+
430+
// convertSliceNumbers traverses the slice, converting any json.Number values to int64 or float64.
431+
// values which are map[string]interface{} or []interface{} are recursively visited
432+
func convertSliceNumbers(s []interface{}, depth int) error {
433+
if depth > maxDepth {
434+
return fmt.Errorf("exceeded max depth of %d", maxDepth)
435+
}
436+
437+
var err error
438+
for i, v := range s {
439+
switch v := v.(type) {
440+
case json.Number:
441+
s[i], err = convertNumber(v)
442+
case map[string]interface{}:
443+
err = convertMapNumbers(v, depth+1)
444+
case []interface{}:
445+
err = convertSliceNumbers(v, depth+1)
446+
}
447+
if err != nil {
448+
return err
449+
}
450+
}
451+
return nil
452+
}
453+
454+
// convertNumber converts a json.Number to an int64 or float64, or returns an error
455+
func convertNumber(n json.Number) (interface{}, error) {
456+
// Attempt to convert to an int64 first
457+
if i, err := n.Int64(); err == nil {
458+
return i, nil
459+
}
460+
// Return a float64 (default json.Decode() behavior)
461+
// An overflow will return an error
462+
return n.Float64()
463+
}

0 commit comments

Comments
 (0)