mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-30 23:15:14 +00:00
Add NestedNumberAsFloat64 unstructured field accessor.
Go float64 values that have no fractional component and can be accurately represented as an int64, when present in an unstructured object and roundtripped through JSON, appear in the resulting object with the concrete type int64. For code that processes unstructured objects and expects to find float64 values, this is a surprising edge case. NestedNumberAsFloat64 behaves the same as NestedFloat64 when accessing a float64 value, but will additionally convert to float64 and return an int64 value at the requested path. Errors are returned on encountering an int64 that cannot be precisely represented as a float64.
This commit is contained in:
parent
d99d3f7eb7
commit
30c35a5618
@ -125,6 +125,28 @@ func NestedInt64(obj map[string]interface{}, fields ...string) (int64, bool, err
|
||||
return i, true, nil
|
||||
}
|
||||
|
||||
// NestedNumberAsFloat64 returns the float64 value of a nested field. If the field's value is a
|
||||
// float64, it is returned. If the field's value is an int64 that can be losslessly converted to
|
||||
// float64, it will be converted and returned. Returns false if value is not found and an error if
|
||||
// not a float64 or an int64 that can be accurately represented as a float64.
|
||||
func NestedNumberAsFloat64(obj map[string]interface{}, fields ...string) (float64, bool, error) {
|
||||
val, found, err := NestedFieldNoCopy(obj, fields...)
|
||||
if !found || err != nil {
|
||||
return 0, found, err
|
||||
}
|
||||
switch x := val.(type) {
|
||||
case int64:
|
||||
if x != int64(float64(x)) {
|
||||
return 0, false, fmt.Errorf("%v accessor error: int64 value %v cannot be losslessly converted to float64", jsonPath(fields), x)
|
||||
}
|
||||
return float64(x), true, nil
|
||||
case float64:
|
||||
return x, true, nil
|
||||
default:
|
||||
return 0, false, fmt.Errorf("%v accessor error: %v is of the type %T, expected float64 or int64", jsonPath(fields), val, val)
|
||||
}
|
||||
}
|
||||
|
||||
// NestedStringSlice returns a copy of []string value of a nested field.
|
||||
// Returns false if value is not found and an error if not a []interface{} or contains non-string items in the slice.
|
||||
func NestedStringSlice(obj map[string]interface{}, fields ...string) ([]string, bool, error) {
|
||||
|
@ -18,6 +18,7 @@ package unstructured
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
@ -225,3 +226,74 @@ func TestSetNestedMap(t *testing.T) {
|
||||
assert.Len(t, obj["x"].(map[string]interface{})["z"], 1)
|
||||
assert.Equal(t, "bar", obj["x"].(map[string]interface{})["z"].(map[string]interface{})["b"])
|
||||
}
|
||||
|
||||
func TestNestedNumberAsFloat64(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
obj map[string]interface{}
|
||||
path []string
|
||||
wantFloat64 float64
|
||||
wantBool bool
|
||||
wantErrMessage string
|
||||
}{
|
||||
{
|
||||
name: "not found",
|
||||
obj: nil,
|
||||
path: []string{"missing"},
|
||||
wantFloat64: 0,
|
||||
wantBool: false,
|
||||
wantErrMessage: "",
|
||||
},
|
||||
{
|
||||
name: "found float64",
|
||||
obj: map[string]interface{}{"value": float64(42)},
|
||||
path: []string{"value"},
|
||||
wantFloat64: 42,
|
||||
wantBool: true,
|
||||
wantErrMessage: "",
|
||||
},
|
||||
{
|
||||
name: "found unexpected type bool",
|
||||
obj: map[string]interface{}{"value": true},
|
||||
path: []string{"value"},
|
||||
wantFloat64: 0,
|
||||
wantBool: false,
|
||||
wantErrMessage: ".value accessor error: true is of the type bool, expected float64 or int64",
|
||||
},
|
||||
{
|
||||
name: "found int64",
|
||||
obj: map[string]interface{}{"value": int64(42)},
|
||||
path: []string{"value"},
|
||||
wantFloat64: 42,
|
||||
wantBool: true,
|
||||
wantErrMessage: "",
|
||||
},
|
||||
{
|
||||
name: "found int64 not representable as float64",
|
||||
obj: map[string]interface{}{"value": int64(math.MaxInt64)},
|
||||
path: []string{"value"},
|
||||
wantFloat64: 0,
|
||||
wantBool: false,
|
||||
wantErrMessage: ".value accessor error: int64 value 9223372036854775807 cannot be losslessly converted to float64",
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
gotFloat64, gotBool, gotErr := NestedNumberAsFloat64(tc.obj, tc.path...)
|
||||
if gotFloat64 != tc.wantFloat64 {
|
||||
t.Errorf("got %v, wanted %v", gotFloat64, tc.wantFloat64)
|
||||
}
|
||||
if gotBool != tc.wantBool {
|
||||
t.Errorf("got %t, wanted %t", gotBool, tc.wantBool)
|
||||
}
|
||||
if tc.wantErrMessage != "" {
|
||||
if gotErr == nil {
|
||||
t.Errorf("got nil error, wanted %s", tc.wantErrMessage)
|
||||
} else if gotErrMessage := gotErr.Error(); gotErrMessage != tc.wantErrMessage {
|
||||
t.Errorf("wanted error %q, got: %v", gotErrMessage, tc.wantErrMessage)
|
||||
}
|
||||
} else if gotErr != nil {
|
||||
t.Errorf("wanted nil error, got %v", gotErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user