From 7066a99115b1174156aba2d5061bef9139361c17 Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Thu, 11 May 2017 13:26:34 -0400 Subject: [PATCH] Fix unstructured marshaler to handle all JSON types Kubernetes-commit: 5b27f8b8f9fcfc8405f9b34bf584abd0d168abf7 --- pkg/conversion/unstructured/converter.go | 79 +++++++++++++------ pkg/conversion/unstructured/converter_test.go | 45 +++++++++++ pkg/util/json/json.go | 12 +++ 3 files changed, 112 insertions(+), 24 deletions(-) diff --git a/pkg/conversion/unstructured/converter.go b/pkg/conversion/unstructured/converter.go index 8dab1dbf..e7021f1b 100644 --- a/pkg/conversion/unstructured/converter.go +++ b/pkg/conversion/unstructured/converter.go @@ -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() @@ -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 } diff --git a/pkg/conversion/unstructured/converter_test.go b/pkg/conversion/unstructured/converter_test.go index e05679f9..4c5264e1 100644 --- a/pkg/conversion/unstructured/converter_test.go +++ b/pkg/conversion/unstructured/converter_test.go @@ -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 { @@ -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 + } + } +} diff --git a/pkg/util/json/json.go b/pkg/util/json/json.go index e8054a12..10c8cb83 100644 --- a/pkg/util/json/json.go +++ b/pkg/util/json/json.go @@ -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) }