Skip to content

Commit 4dd8d93

Browse files
Merge pull request #51526 from atlassian/optimize-unstructured-converter
Automatic merge from submit-queue (batch tested with PRs 50392, 52108, 52083, 52134, 51526). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.. Do deep copy instead of to and from JSON encoding **What this PR does / why we need it**: Unstructured converter encodes to JSON and then parses the result into a new object. For `Unstructured` this can be avoided by directly doing a deep copy. It is an optimization. **Special notes for your reviewer**: #47889 is somewhat related. **Release note**: ```release-note NONE ``` /sig api-machinery Kubernetes-commit: 8fe960e71018f0e1bf1c427539f180c16147a646
2 parents 3b05bbf + 4287c6f commit 4dd8d93

File tree

4 files changed

+70
-32
lines changed

4 files changed

+70
-32
lines changed

pkg/apis/meta/v1/unstructured/unstructured.go

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -145,33 +145,13 @@ func (u *Unstructured) UnmarshalJSON(b []byte) error {
145145
return err
146146
}
147147

148-
func deepCopyJSON(x interface{}) interface{} {
149-
switch x := x.(type) {
150-
case map[string]interface{}:
151-
clone := make(map[string]interface{}, len(x))
152-
for k, v := range x {
153-
clone[k] = deepCopyJSON(v)
154-
}
155-
return clone
156-
case []interface{}:
157-
clone := make([]interface{}, len(x))
158-
for i := range x {
159-
clone[i] = deepCopyJSON(x[i])
160-
}
161-
return clone
162-
default:
163-
// only non-pointer values (float64, int64, bool, string) are left. These can be copied by-value.
164-
return x
165-
}
166-
}
167-
168148
func (in *Unstructured) DeepCopy() *Unstructured {
169149
if in == nil {
170150
return nil
171151
}
172152
out := new(Unstructured)
173153
*out = *in
174-
out.Object = deepCopyJSON(in.Object).(map[string]interface{})
154+
out.Object = unstructured.DeepCopyJSON(in.Object)
175155
return out
176156
}
177157

@@ -181,7 +161,7 @@ func (in *UnstructuredList) DeepCopy() *UnstructuredList {
181161
}
182162
out := new(UnstructuredList)
183163
*out = *in
184-
out.Object = deepCopyJSON(in.Object).(map[string]interface{})
164+
out.Object = unstructured.DeepCopyJSON(in.Object)
185165
out.Items = make([]Unstructured, len(in.Items))
186166
for i := range in.Items {
187167
in.Items[i].DeepCopyInto(&out.Items[i])

pkg/conversion/unstructured/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ go_library(
1414
deps = [
1515
"//vendor/github.com/golang/glog:go_default_library",
1616
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
17+
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
1718
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
1819
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
1920
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",

pkg/conversion/unstructured/converter.go

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"sync/atomic"
3030

3131
apiequality "k8s.io/apimachinery/pkg/api/equality"
32+
"k8s.io/apimachinery/pkg/runtime"
3233
"k8s.io/apimachinery/pkg/util/diff"
3334
"k8s.io/apimachinery/pkg/util/json"
3435
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
@@ -106,6 +107,8 @@ func NewConverter(mismatchDetection bool) Converter {
106107
}
107108
}
108109

110+
// FromUnstructured converts an object from map[string]interface{} representation into a concrete type.
111+
// It uses encoding/json/Unmarshaler if object implements it or reflection if not.
109112
func (c *converterImpl) FromUnstructured(u map[string]interface{}, obj interface{}) error {
110113
t := reflect.TypeOf(obj)
111114
value := reflect.ValueOf(obj)
@@ -388,19 +391,27 @@ func interfaceFromUnstructured(sv, dv reflect.Value) error {
388391
return nil
389392
}
390393

394+
// ToUnstructured converts an object into map[string]interface{} representation.
395+
// It uses encoding/json/Marshaler if object implements it or reflection if not.
391396
func (c *converterImpl) ToUnstructured(obj interface{}) (map[string]interface{}, error) {
392-
t := reflect.TypeOf(obj)
393-
value := reflect.ValueOf(obj)
394-
if t.Kind() != reflect.Ptr || value.IsNil() {
395-
return nil, fmt.Errorf("ToUnstructured requires a non-nil pointer to an object, got %v", t)
397+
var u map[string]interface{}
398+
var err error
399+
if unstr, ok := obj.(runtime.Unstructured); ok {
400+
u = DeepCopyJSON(unstr.UnstructuredContent())
401+
} else {
402+
t := reflect.TypeOf(obj)
403+
value := reflect.ValueOf(obj)
404+
if t.Kind() != reflect.Ptr || value.IsNil() {
405+
return nil, fmt.Errorf("ToUnstructured requires a non-nil pointer to an object, got %v", t)
406+
}
407+
u = map[string]interface{}{}
408+
err = toUnstructured(value.Elem(), reflect.ValueOf(&u).Elem())
396409
}
397-
u := &map[string]interface{}{}
398-
err := toUnstructured(value.Elem(), reflect.ValueOf(u).Elem())
399410
if c.mismatchDetection {
400-
newUnstr := &map[string]interface{}{}
401-
newErr := toUnstructuredViaJSON(obj, newUnstr)
411+
newUnstr := map[string]interface{}{}
412+
newErr := toUnstructuredViaJSON(obj, &newUnstr)
402413
if (err != nil) != (newErr != nil) {
403-
glog.Fatalf("ToUnstructured unexpected error for %v: error: %v", obj, err)
414+
glog.Fatalf("ToUnstructured unexpected error for %v: error: %v; newErr: %v", obj, err, newErr)
404415
}
405416
if err == nil && !apiequality.Semantic.DeepEqual(u, newUnstr) {
406417
glog.Fatalf("ToUnstructured mismatch for %#v, diff: %v", u, diff.ObjectReflectDiff(u, newUnstr))
@@ -409,7 +420,34 @@ func (c *converterImpl) ToUnstructured(obj interface{}) (map[string]interface{},
409420
if err != nil {
410421
return nil, err
411422
}
412-
return *u, nil
423+
return u, nil
424+
}
425+
426+
// DeepCopyJSON deep copies the passed value, assuming it is a valid JSON representation i.e. only contains
427+
// types produced by json.Unmarshal().
428+
func DeepCopyJSON(x map[string]interface{}) map[string]interface{} {
429+
return deepCopyJSON(x).(map[string]interface{})
430+
}
431+
432+
func deepCopyJSON(x interface{}) interface{} {
433+
switch x := x.(type) {
434+
case map[string]interface{}:
435+
clone := make(map[string]interface{}, len(x))
436+
for k, v := range x {
437+
clone[k] = deepCopyJSON(v)
438+
}
439+
return clone
440+
case []interface{}:
441+
clone := make([]interface{}, len(x))
442+
for i, v := range x {
443+
clone[i] = deepCopyJSON(v)
444+
}
445+
return clone
446+
case string, int64, bool, float64, nil, encodingjson.Number:
447+
return x
448+
default:
449+
panic(fmt.Errorf("cannot deep copy %T", x))
450+
}
413451
}
414452

415453
func toUnstructuredViaJSON(obj interface{}, u *map[string]interface{}) error {

pkg/conversion/unstructured/testing/converter_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ limitations under the License.
2121
package testing
2222

2323
import (
24+
encodingjson "encoding/json"
2425
"fmt"
2526
"reflect"
2627
"strconv"
@@ -464,6 +465,24 @@ func TestUnrecognized(t *testing.T) {
464465
}
465466
}
466467

468+
func TestDeepCopyJSON(t *testing.T) {
469+
src := map[string]interface{}{
470+
"a": nil,
471+
"b": int64(123),
472+
"c": map[string]interface{}{
473+
"a": "b",
474+
},
475+
"d": []interface{}{
476+
int64(1), int64(2),
477+
},
478+
"e": "estr",
479+
"f": true,
480+
"g": encodingjson.Number("123"),
481+
}
482+
deepCopy := conversionunstructured.DeepCopyJSON(src)
483+
assert.Equal(t, src, deepCopy)
484+
}
485+
467486
func TestFloatIntConversion(t *testing.T) {
468487
unstr := map[string]interface{}{"fd": float64(3)}
469488

0 commit comments

Comments
 (0)