Skip to content

Commit

Permalink
Merge pull request #49407 from liggitt/unstructured-marshaler
Browse files Browse the repository at this point in the history
Automatic merge from submit-queue (batch tested with PRs 45345, 49470, 49407, 49448, 49486)

Fix unstructured marshaler to handle all JSON types

Split from kubernetes/kubernetes#45294

/assign @wojtek-t

Kubernetes-commit: 17b9c21a6aca1da03ce0ca1eb6e79eeebf133e3a
  • Loading branch information
k8s-publishing-bot committed Jul 24, 2017
2 parents 7ed3e0a + 7066a99 commit 76395d1
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 24 deletions.
79 changes: 55 additions & 24 deletions pkg/conversion/unstructured/converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,12 @@ func toUnstructuredViaJSON(obj interface{}, u *map[string]interface{}) error {
return json.Unmarshal(data, u)
}

var (
nullBytes = []byte("null")
trueBytes = []byte("true")
falseBytes = []byte("false")
)

func toUnstructured(sv, dv reflect.Value) error {
st, dt := sv.Type(), dv.Type()

Expand All @@ -435,33 +441,58 @@ func toUnstructured(sv, dv reflect.Value) error {
if err != nil {
return err
}
if bytes.Equal(data, []byte("null")) {
switch {
case len(data) == 0:
return fmt.Errorf("error decoding from json: empty value")

case bytes.Equal(data, nullBytes):
// We're done - we don't need to store anything.
} else {
switch {
case len(data) > 0 && data[0] == '"':
var result string
err := json.Unmarshal(data, &result)
if err != nil {
return fmt.Errorf("error decoding from json: %v", err)
}
dv.Set(reflect.ValueOf(result))
case len(data) > 0 && data[0] == '{':
result := make(map[string]interface{})
err := json.Unmarshal(data, &result)
if err != nil {
return fmt.Errorf("error decoding from json: %v", err)
}
dv.Set(reflect.ValueOf(result))
default:
var result int64
err := json.Unmarshal(data, &result)
if err != nil {
return fmt.Errorf("error decoding from json: %v", err)
}
dv.Set(reflect.ValueOf(result))

case bytes.Equal(data, trueBytes):
dv.Set(reflect.ValueOf(true))

case bytes.Equal(data, falseBytes):
dv.Set(reflect.ValueOf(false))

case data[0] == '"':
var result string
err := json.Unmarshal(data, &result)
if err != nil {
return fmt.Errorf("error decoding string from json: %v", err)
}
dv.Set(reflect.ValueOf(result))

case data[0] == '{':
result := make(map[string]interface{})
err := json.Unmarshal(data, &result)
if err != nil {
return fmt.Errorf("error decoding object from json: %v", err)
}
dv.Set(reflect.ValueOf(result))

case data[0] == '[':
result := make([]interface{}, 0)
err := json.Unmarshal(data, &result)
if err != nil {
return fmt.Errorf("error decoding array from json: %v", err)
}
dv.Set(reflect.ValueOf(result))

default:
var (
resultInt int64
resultFloat float64
err error
)
if err = json.Unmarshal(data, &resultInt); err == nil {
dv.Set(reflect.ValueOf(resultInt))
} else if err = json.Unmarshal(data, &resultFloat); err == nil {
dv.Set(reflect.ValueOf(resultFloat))
} else {
return fmt.Errorf("error decoding number from json: %v", err)
}
}

return nil
}

Expand Down
45 changes: 45 additions & 0 deletions pkg/conversion/unstructured/converter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,18 @@ type F struct {
I []float32 `json:"fi"`
}

type G struct {
Custom Custom `json:"custom"`
}

type Custom struct {
data []byte
}

func (c Custom) MarshalJSON() ([]byte, error) {
return c.data, nil
}

func doRoundTrip(t *testing.T, item interface{}) {
data, err := json.Marshal(item)
if err != nil {
Expand Down Expand Up @@ -439,3 +451,36 @@ func TestFloatIntConversion(t *testing.T) {
t.Errorf("Incorrect conversion, diff: %v", diff.ObjectReflectDiff(obj, unmarshalled))
}
}

func TestCustomToUnstructured(t *testing.T) {
testcases := []struct {
Data string
Expected interface{}
}{
{Data: `null`, Expected: nil},
{Data: `true`, Expected: true},
{Data: `false`, Expected: false},
{Data: `[]`, Expected: []interface{}{}},
{Data: `[1]`, Expected: []interface{}{int64(1)}},
{Data: `{}`, Expected: map[string]interface{}{}},
{Data: `{"a":1}`, Expected: map[string]interface{}{"a": int64(1)}},
{Data: `0`, Expected: int64(0)},
{Data: `0.0`, Expected: float64(0)},
}

for _, tc := range testcases {
result, err := DefaultConverter.ToUnstructured(&G{Custom: Custom{data: []byte(tc.Data)}})
if err != nil {
t.Errorf("%s: %v", tc.Data, err)
continue
}

fieldResult := result["custom"]
if !reflect.DeepEqual(fieldResult, tc.Expected) {
t.Errorf("%s: expected %v, got %v", tc.Data, tc.Expected, fieldResult)
// t.Log("expected", spew.Sdump(tc.Expected))
// t.Log("actual", spew.Sdump(fieldResult))
continue
}
}
}
12 changes: 12 additions & 0 deletions pkg/util/json/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,18 @@ func Unmarshal(data []byte, v interface{}) error {
// If the decode succeeds, post-process the map to convert json.Number objects to int64 or float64
return convertMapNumbers(*v)

case *[]interface{}:
// Build a decoder from the given data
decoder := json.NewDecoder(bytes.NewBuffer(data))
// Preserve numbers, rather than casting to float64 automatically
decoder.UseNumber()
// Run the decode
if err := decoder.Decode(v); err != nil {
return err
}
// If the decode succeeds, post-process the map to convert json.Number objects to int64 or float64
return convertSliceNumbers(*v)

default:
return json.Unmarshal(data, v)
}
Expand Down

0 comments on commit 76395d1

Please sign in to comment.