Merge pull request #39418 from wojtek-t/reflection_based_conversion_to_unstructured_2

Automatic merge from submit-queue (batch tested with PRs 39418, 41175, 40355, 41114, 32325)

runtime.Object <-> Unstructured conversion

Ref #39017
This commit is contained in:
Kubernetes Submit Queue 2017-02-10 04:50:39 -08:00 committed by GitHub
commit 85882ade1d
6 changed files with 1021 additions and 24 deletions

View File

@ -93,6 +93,7 @@ go_test(
"//vendor:k8s.io/apimachinery/pkg/api/testing", "//vendor:k8s.io/apimachinery/pkg/api/testing",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/conversion", "//vendor:k8s.io/apimachinery/pkg/conversion",
"//vendor:k8s.io/apimachinery/pkg/conversion/unstructured",
"//vendor:k8s.io/apimachinery/pkg/runtime", "//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apimachinery/pkg/runtime/schema", "//vendor:k8s.io/apimachinery/pkg/runtime/schema",
"//vendor:k8s.io/apimachinery/pkg/runtime/serializer/protobuf", "//vendor:k8s.io/apimachinery/pkg/runtime/serializer/protobuf",

View File

@ -28,6 +28,8 @@ import (
kapitesting "k8s.io/kubernetes/pkg/api/testing" kapitesting "k8s.io/kubernetes/pkg/api/testing"
"k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/api/v1"
"k8s.io/apimachinery/pkg/conversion/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/json"
@ -95,27 +97,23 @@ func doRoundTrip(t *testing.T, group testapi.TestGroup, kind string) {
return return
} }
// TODO; Enable the following part of test once to/from unstructured newUnstr := make(map[string]interface{})
// format conversions are implemented. err = unstructured.NewConverter().ToUnstructured(item, &newUnstr)
/* if err != nil {
newUnstr := make(map[string]interface{}) t.Errorf("ToUnstructured failed: %v", err)
err = unstructured.NewConverter().ToUnstructured(item, &newUnstr) return
if err != nil { }
t.Errorf("ToUnstructured failed: %v", err)
return
}
newObj := reflect.New(reflect.TypeOf(item).Elem()).Interface().(runtime.Object) newObj := reflect.New(reflect.TypeOf(item).Elem()).Interface().(runtime.Object)
err = unstructured.NewConverter().FromUnstructured(newUnstr, newObj) err = unstructured.NewConverter().FromUnstructured(newUnstr, newObj)
if err != nil { if err != nil {
t.Errorf("FromUnstructured failed: %v", err) t.Errorf("FromUnstructured failed: %v", err)
return return
} }
if !apiequality.Semantic.DeepEqual(item, newObj) { if !apiequality.Semantic.DeepEqual(item, newObj) {
t.Errorf("Object changed, diff: %v", diff.ObjectReflectDiff(item, newObj)) t.Errorf("Object changed, diff: %v", diff.ObjectReflectDiff(item, newObj))
} }
*/
} }
func TestRoundTrip(t *testing.T) { func TestRoundTrip(t *testing.T) {
@ -135,11 +133,8 @@ func TestRoundTrip(t *testing.T) {
} }
} }
// TODO; Enable the following benchmark once to/from unstructured
// format conversions are implemented.
/*
func BenchmarkToFromUnstructured(b *testing.B) { func BenchmarkToFromUnstructured(b *testing.B) {
items := benchmarkItems() items := benchmarkItems(b)
size := len(items) size := len(items)
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
@ -154,7 +149,6 @@ func BenchmarkToFromUnstructured(b *testing.B) {
} }
b.StopTimer() b.StopTimer()
} }
*/
func BenchmarkToFromUnstructuredViaJSON(b *testing.B) { func BenchmarkToFromUnstructuredViaJSON(b *testing.B) {
items := benchmarkItems(b) items := benchmarkItems(b)

View File

@ -0,0 +1,517 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package unstructured
import (
"bytes"
encodingjson "encoding/json"
"fmt"
"reflect"
"strings"
"sync"
"sync/atomic"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/json"
)
type structField struct {
structType reflect.Type
field int
}
type fieldInfo struct {
name string
nameValue reflect.Value
}
type fieldsCacheMap map[structField]*fieldInfo
type fieldsCache struct {
sync.Mutex
value atomic.Value
}
func newFieldsCache() *fieldsCache {
cache := &fieldsCache{}
cache.value.Store(make(fieldsCacheMap))
return cache
}
var (
marshalerType = reflect.TypeOf(new(encodingjson.Marshaler)).Elem()
unmarshalerType = reflect.TypeOf(new(encodingjson.Unmarshaler)).Elem()
mapStringInterfaceType = reflect.TypeOf(map[string]interface{}{})
fieldCache = newFieldsCache()
)
// Converter knows how to convert betweek runtime.Object and
// Unstructured in both ways.
type Converter struct {
}
func NewConverter() *Converter {
return &Converter{}
}
func (c *Converter) FromUnstructured(u map[string]interface{}, obj runtime.Object) error {
return fromUnstructured(reflect.ValueOf(u), reflect.ValueOf(obj).Elem())
}
func fromUnstructured(sv, dv reflect.Value) error {
sv = unwrapInterface(sv)
if !sv.IsValid() {
dv.Set(reflect.Zero(dv.Type()))
return nil
}
st, dt := sv.Type(), dv.Type()
switch dt.Kind() {
case reflect.Map, reflect.Slice, reflect.Ptr, reflect.Struct, reflect.Interface:
// Those require non-trivial conversion.
default:
// This should handle all simple types.
if st.AssignableTo(dt) {
dv.Set(sv)
return nil
}
// We cannot simply use "ConvertibleTo", as JSON doesn't support conversions
// between those four groups: bools, integers, floats and string. We need to
// do the same.
if st.ConvertibleTo(dt) {
switch st.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
switch dt.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
dv.Set(sv.Convert(dt))
return nil
}
case reflect.Float32, reflect.Float64:
switch dt.Kind() {
case reflect.Float32, reflect.Float64:
dv.Set(sv.Convert(dt))
return nil
}
}
return fmt.Errorf("cannot convert %s to %d", st.String(), dt.String())
}
}
// Check if the object has a custom JSON marshaller/unmarshaller.
if reflect.PtrTo(dt).Implements(unmarshalerType) {
data, err := json.Marshal(sv.Interface())
if err != nil {
return fmt.Errorf("error encoding %s to json: %v", st.String(), err)
}
unmarshaler := dv.Addr().Interface().(encodingjson.Unmarshaler)
return unmarshaler.UnmarshalJSON(data)
}
switch dt.Kind() {
case reflect.Map:
return mapFromUnstructured(sv, dv)
case reflect.Slice:
return sliceFromUnstructured(sv, dv)
case reflect.Ptr:
return pointerFromUnstructured(sv, dv)
case reflect.Struct:
return structFromUnstructured(sv, dv)
case reflect.Interface:
return interfaceFromUnstructured(sv, dv)
default:
return fmt.Errorf("unrecognized type: %v", dt.Kind())
}
}
func fieldInfoFromField(structType reflect.Type, field int) *fieldInfo {
fieldCacheMap := fieldCache.value.Load().(fieldsCacheMap)
if info, ok := fieldCacheMap[structField{structType, field}]; ok {
return info
}
// Cache miss - we need to compute the field name.
info := &fieldInfo{}
typeField := structType.Field(field)
jsonTag := typeField.Tag.Get("json")
if len(jsonTag) == 0 {
// Make the first character lowercase.
if typeField.Name == "" {
info.name = typeField.Name
} else {
info.name = strings.ToLower(typeField.Name[:1]) + typeField.Name[1:]
}
} else {
info.name = strings.Split(jsonTag, ",")[0]
}
info.nameValue = reflect.ValueOf(info.name)
fieldCache.Lock()
defer fieldCache.Unlock()
fieldCacheMap = fieldCache.value.Load().(fieldsCacheMap)
newFieldCacheMap := make(fieldsCacheMap)
for k, v := range fieldCacheMap {
newFieldCacheMap[k] = v
}
newFieldCacheMap[structField{structType, field}] = info
fieldCache.value.Store(newFieldCacheMap)
return info
}
func unwrapInterface(v reflect.Value) reflect.Value {
for v.Kind() == reflect.Interface {
v = v.Elem()
}
return v
}
func mapFromUnstructured(sv, dv reflect.Value) error {
st, dt := sv.Type(), dv.Type()
if st.Kind() != reflect.Map {
return fmt.Errorf("cannot restore map from %v", st.Kind())
}
if !st.Key().AssignableTo(dt.Key()) && !st.Key().ConvertibleTo(dt.Key()) {
return fmt.Errorf("cannot copy map with non-assignable keys: %v %v", st.Key(), dt.Key())
}
if sv.IsNil() {
dv.Set(reflect.Zero(dt))
return nil
}
dv.Set(reflect.MakeMap(dt))
for _, key := range sv.MapKeys() {
value := reflect.New(dt.Elem()).Elem()
if val := unwrapInterface(sv.MapIndex(key)); val.IsValid() {
if err := fromUnstructured(val, value); err != nil {
return err
}
} else {
value.Set(reflect.Zero(dt.Elem()))
}
if st.Key().AssignableTo(dt.Key()) {
dv.SetMapIndex(key, value)
} else {
dv.SetMapIndex(key.Convert(dt.Key()), value)
}
}
return nil
}
func sliceFromUnstructured(sv, dv reflect.Value) error {
st, dt := sv.Type(), dv.Type()
if st.Kind() == reflect.String && dt.Elem().Kind() == reflect.Uint8 {
// We store original []byte representation as string.
// This conversion is allowed, but we need to be careful about
// marshaling data appropriately.
if len(sv.Interface().(string)) > 0 {
marshalled, err := json.Marshal(sv.Interface())
if err != nil {
return fmt.Errorf("error encoding %s to json: %v", st, err)
}
// TODO: Is this Unmarshal needed?
var data []byte
err = json.Unmarshal(marshalled, &data)
if err != nil {
return fmt.Errorf("error decoding from json: %v", err)
}
dv.SetBytes(data)
} else {
dv.Set(reflect.Zero(dt))
}
return nil
}
if st.Kind() != reflect.Slice {
return fmt.Errorf("cannot restore slice from %v", st.Kind())
}
if sv.IsNil() {
dv.Set(reflect.Zero(dt))
return nil
}
dv.Set(reflect.MakeSlice(dt, sv.Len(), sv.Cap()))
for i := 0; i < sv.Len(); i++ {
if err := fromUnstructured(sv.Index(i), dv.Index(i)); err != nil {
return err
}
}
return nil
}
func pointerFromUnstructured(sv, dv reflect.Value) error {
st, dt := sv.Type(), dv.Type()
if st.Kind() == reflect.Ptr && sv.IsNil() {
dv.Set(reflect.Zero(dt))
return nil
}
dv.Set(reflect.New(dt.Elem()))
switch st.Kind() {
case reflect.Ptr, reflect.Interface:
return fromUnstructured(sv.Elem(), dv.Elem())
default:
return fromUnstructured(sv, dv.Elem())
}
}
func structFromUnstructured(sv, dv reflect.Value) error {
st, dt := sv.Type(), dv.Type()
if st.Kind() != reflect.Map {
return fmt.Errorf("cannot restore struct from: %v", st.Kind())
}
for i := 0; i < dt.NumField(); i++ {
fieldInfo := fieldInfoFromField(dt, i)
fv := dv.Field(i)
if len(fieldInfo.name) == 0 {
// This field is inlined.
if err := fromUnstructured(sv, fv); err != nil {
return err
}
} else {
value := unwrapInterface(sv.MapIndex(fieldInfo.nameValue))
if value.IsValid() {
if err := fromUnstructured(value, fv); err != nil {
return err
}
} else {
fv.Set(reflect.Zero(fv.Type()))
}
}
}
return nil
}
func interfaceFromUnstructured(sv, dv reflect.Value) error {
// TODO: Is this conversion safe?
dv.Set(sv)
return nil
}
func (c *Converter) ToUnstructured(obj runtime.Object, u *map[string]interface{}) error {
return toUnstructured(reflect.ValueOf(obj).Elem(), reflect.ValueOf(u).Elem())
}
func toUnstructured(sv, dv reflect.Value) error {
st, dt := sv.Type(), dv.Type()
// Check if the object has a custom JSON marshaller/unmarshaller.
if st.Implements(marshalerType) {
if sv.Kind() == reflect.Ptr && sv.IsNil() {
// We're done - we don't need to store anything.
return nil
}
marshaler := sv.Interface().(encodingjson.Marshaler)
data, err := marshaler.MarshalJSON()
if err != nil {
return err
}
if bytes.Equal(data, []byte("null")) {
// 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))
}
}
return nil
}
switch st.Kind() {
case reflect.String, reflect.Bool,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Float32, reflect.Float64:
if dt.Kind() == reflect.Interface && dv.NumMethod() == 0 {
dv.Set(reflect.New(st))
}
dv.Set(sv)
return nil
case reflect.Map:
return mapToUnstructured(sv, dv)
case reflect.Slice:
return sliceToUnstructured(sv, dv)
case reflect.Ptr:
return pointerToUnstructured(sv, dv)
case reflect.Struct:
return structToUnstructured(sv, dv)
case reflect.Interface:
return interfaceToUnstructured(sv, dv)
default:
return fmt.Errorf("unrecognized type: %v", st.Kind())
}
}
func mapToUnstructured(sv, dv reflect.Value) error {
st, dt := sv.Type(), dv.Type()
if sv.IsNil() {
dv.Set(reflect.Zero(dt))
return nil
}
if dt.Kind() == reflect.Interface && dv.NumMethod() == 0 {
if st.Key().Kind() == reflect.String {
switch st.Elem().Kind() {
// TODO It should be possible to reuse the slice for primitive types.
// However, it is panicing in the following form.
// case reflect.String, reflect.Bool,
// reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
// reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
// sv.Set(sv)
// return nil
default:
// We need to do a proper conversion.
}
}
dv.Set(reflect.MakeMap(mapStringInterfaceType))
dv = dv.Elem()
dt = dv.Type()
}
if dt.Kind() != reflect.Map {
return fmt.Errorf("cannot convert struct to: %v", dt.Kind())
}
if !st.Key().AssignableTo(dt.Key()) && !st.Key().ConvertibleTo(dt.Key()) {
return fmt.Errorf("cannot copy map with non-assignable keys: %v %v", st.Key(), dt.Key())
}
for _, key := range sv.MapKeys() {
value := reflect.New(dt.Elem()).Elem()
if err := toUnstructured(sv.MapIndex(key), value); err != nil {
return err
}
if st.Key().AssignableTo(dt.Key()) {
dv.SetMapIndex(key, value)
} else {
dv.SetMapIndex(key.Convert(dt.Key()), value)
}
}
return nil
}
func sliceToUnstructured(sv, dv reflect.Value) error {
st, dt := sv.Type(), dv.Type()
if sv.IsNil() {
dv.Set(reflect.Zero(dt))
return nil
}
if dt.Kind() == reflect.Interface && dv.NumMethod() == 0 {
switch st.Elem().Kind() {
// TODO It should be possible to reuse the slice for primitive types.
// However, it is panicing in the following form.
// case reflect.String, reflect.Bool,
// reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
// reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
// sv.Set(sv)
// return nil
default:
// We need to do a proper conversion.
dv.Set(reflect.MakeSlice(reflect.SliceOf(dt), sv.Len(), sv.Cap()))
dv = dv.Elem()
dt = dv.Type()
}
}
if dt.Kind() != reflect.Slice {
return fmt.Errorf("cannot convert slice to: %v", dt.Kind())
}
for i := 0; i < sv.Len(); i++ {
if err := toUnstructured(sv.Index(i), dv.Index(i)); err != nil {
return err
}
}
return nil
}
func pointerToUnstructured(sv, dv reflect.Value) error {
if sv.IsNil() {
// We're done - we don't need to store anything.
return nil
}
return toUnstructured(sv.Elem(), dv)
}
func structToUnstructured(sv, dv reflect.Value) error {
st, dt := sv.Type(), dv.Type()
if dt.Kind() == reflect.Interface && dv.NumMethod() == 0 {
dv.Set(reflect.MakeMap(mapStringInterfaceType))
dv = dv.Elem()
dt = dv.Type()
}
if dt.Kind() != reflect.Map {
return fmt.Errorf("cannot convert struct to: %v", dt.Kind())
}
realMap := dv.Interface().(map[string]interface{})
for i := 0; i < st.NumField(); i++ {
fieldInfo := fieldInfoFromField(st, i)
fv := sv.Field(i)
if len(fieldInfo.name) == 0 {
// This field is inlined.
if err := toUnstructured(fv, dv); err != nil {
return err
}
continue
}
if !fv.IsValid() {
// No fource field, skip.
continue
}
switch fv.Type().Kind() {
case reflect.String, reflect.Bool,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
realMap[fieldInfo.name] = fv.Interface()
default:
subv := reflect.New(dt.Elem()).Elem()
if err := toUnstructured(fv, subv); err != nil {
return err
}
dv.SetMapIndex(fieldInfo.nameValue, subv)
}
}
return nil
}
func interfaceToUnstructured(sv, dv reflect.Value) error {
if !sv.IsValid() || sv.IsNil() {
dv.Set(reflect.Zero(dv.Type()))
return nil
}
return toUnstructured(sv.Elem(), dv)
}

View File

@ -0,0 +1,440 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package unstructured
import (
"fmt"
"reflect"
"testing"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/json"
)
// Definte a number of test types.
type A struct {
A int `json:"aa,omitempty"`
B string `json:"ab,omitempty"`
C bool `json:"ac,omitempty"`
}
type B struct {
A A `json:"ba"`
B string `json:"bb"`
C map[string]string `json:"bc"`
D []string `json:"bd"`
}
type C struct {
A []A `json:"ca"`
B B `json:",inline"`
C string `json:"cc"`
D *int64 `json:"cd"`
E map[string]int `json:"ce"`
F []bool `json:"cf"`
G []int `json"cg"`
H float32 `json:ch"`
I []interface{} `json:"ci"`
}
type D struct {
A []interface{} `json:"da"`
}
type E struct {
A interface{} `json:"ea"`
}
type F struct {
A string `json:"fa"`
B map[string]string `json:"fb"`
C []A `json:"fc"`
D int `json:"fd"`
E float32 `json:"fe"`
F []string `json:"ff"`
G []int `json:"fg"`
H []bool `json:"fh"`
I []float32 `json:"fi"`
}
// Implement runtime.Object to make types usable for tests.
func (c *C) GetObjectKind() schema.ObjectKind {
return schema.EmptyObjectKind
}
func (d *D) GetObjectKind() schema.ObjectKind {
return schema.EmptyObjectKind
}
func (e *E) GetObjectKind() schema.ObjectKind {
return schema.EmptyObjectKind
}
func (f *F) GetObjectKind() schema.ObjectKind {
return schema.EmptyObjectKind
}
func doRoundTrip(t *testing.T, item runtime.Object) {
data, err := json.Marshal(item)
if err != nil {
t.Errorf("Error when marshaling object: %v", err)
return
}
unstr := make(map[string]interface{})
err = json.Unmarshal(data, &unstr)
if err != nil {
t.Errorf("Error when unmarshaling to unstructured: %v", err)
return
}
data, err = json.Marshal(unstr)
if err != nil {
t.Errorf("Error when marshaling unstructured: %v", err)
return
}
unmarshalledObj := reflect.New(reflect.TypeOf(item).Elem()).Interface()
err = json.Unmarshal(data, &unmarshalledObj)
if err != nil {
t.Errorf("Error when unmarshaling to object: %v", err)
return
}
if !reflect.DeepEqual(item, unmarshalledObj) {
t.Errorf("Object changed during JSON operations, diff: %v", diff.ObjectReflectDiff(item, unmarshalledObj))
return
}
newUnstr := make(map[string]interface{})
err = NewConverter().ToUnstructured(item, &newUnstr)
if err != nil {
t.Errorf("ToUnstructured failed: %v", err)
return
}
newObj := reflect.New(reflect.TypeOf(item).Elem()).Interface().(runtime.Object)
err = NewConverter().FromUnstructured(newUnstr, newObj)
if err != nil {
t.Errorf("FromUnstructured failed: %v", err)
return
}
if !reflect.DeepEqual(item, newObj) {
t.Errorf("Object changed, diff: %v", diff.ObjectReflectDiff(item, newObj))
}
}
func TestRoundTrip(t *testing.T) {
intVal := int64(42)
testCases := []struct{
obj runtime.Object
}{
{
// This (among others) tests nil map, slice and pointer.
obj: &C{
C: "ccc",
},
},
{
// This (among others) tests empty map and slice.
obj: &C{
A: []A{},
C: "ccc",
E: map[string]int{},
I: []interface{}{},
},
},
{
obj: &C{
A: []A{
{
A: 1,
B: "11",
C: true,
},
{
A: 2,
B: "22",
C: false,
},
},
B: B{
A: A{
A: 3,
B: "33",
},
B: "bbb",
C: map[string]string{
"k1": "v1",
"k2": "v2",
},
D: []string{"s1", "s2"},
},
C: "ccc",
D: &intVal,
E: map[string]int{
"k1": 1,
"k2": 2,
},
F: []bool{true, false, false},
G: []int{1, 2, 5},
H: 3.3,
I: []interface{}{nil, nil, nil},
},
},
{
// Test slice of interface{} with empty slices.
obj: &D{
A: []interface{}{[]interface{}{}, []interface{}{}},
},
},
{
// Test slice of interface{} with different values.
obj: &D{
A: []interface{}{3.0, "3.0", nil},
},
},
}
for i := range testCases {
doRoundTrip(t, testCases[i].obj)
if t.Failed() {
break
}
}
}
// Verifies that:
// 1) serialized json -> object
// 2) serialized json -> map[string]interface{} -> object
// produces the same object.
func doUnrecognized(t *testing.T, jsonData string, item runtime.Object, expectedErr error) {
unmarshalledObj := reflect.New(reflect.TypeOf(item).Elem()).Interface()
err := json.Unmarshal([]byte(jsonData), &unmarshalledObj)
if (err != nil) != (expectedErr != nil) {
t.Errorf("Unexpected error when unmarshaling to object: %v, expected: %v", err, expectedErr)
return
}
unstr := make(map[string]interface{})
err = json.Unmarshal([]byte(jsonData), &unstr)
if err != nil {
t.Errorf("Error when unmarshaling to unstructured: %v", err)
return
}
newObj := reflect.New(reflect.TypeOf(item).Elem()).Interface().(runtime.Object)
err = NewConverter().FromUnstructured(unstr, newObj)
if (err != nil) != (expectedErr != nil) {
t.Errorf("Unexpected error in FromUnstructured: %v, expected: %v", err, expectedErr)
}
if expectedErr == nil && !reflect.DeepEqual(unmarshalledObj, newObj) {
t.Errorf("Object changed, diff: %v", diff.ObjectReflectDiff(unmarshalledObj, newObj))
}
}
func TestUnrecognized(t *testing.T) {
testCases := []struct{
data string
obj runtime.Object
err error
}{
{
data: "{\"da\":[3.0,\"3.0\",null]}",
obj: &D{},
},
{
data: "{\"ea\":[3.0,\"3.0\",null]}",
obj: &E{},
},
{
data: "{\"ea\":[null,null,null]}",
obj: &E{},
},
{
data: "{\"ea\":[[],[null]]}",
obj: &E{},
},
{
data: "{\"ea\":{\"a\":[],\"b\":null}}",
obj: &E{},
},
{
data: "{\"fa\":\"fa\",\"fb\":{\"a\":\"a\"}}",
obj: &F{},
},
{
data: "{\"fa\":\"fa\",\"fb\":{\"a\":null}}",
obj: &F{},
},
{
data: "{\"fc\":[null]}",
obj: &F{},
},
{
data: "{\"fc\":[{\"aa\":123,\"ab\":\"bbb\"}]}",
obj: &F{},
},
{
// Only unknown fields
data: "{\"fx\":[{\"aa\":123,\"ab\":\"bbb\"}],\"fz\":123}",
obj: &F{},
},
{
data: "{\"fc\":[{\"aa\":\"aaa\",\"ab\":\"bbb\"}]}",
obj: &F{},
err: fmt.Errorf("json: cannot unmarshal string into Go value of type int"),
},
{
data: "{\"fd\":123,\"fe\":3.5}",
obj: &F{},
},
{
data: "{\"ff\":[\"abc\"],\"fg\":[123],\"fh\":[true,false]}",
obj: &F{},
},
{
// Invalid string data
data: "{\"fa\":123}",
obj: &F{},
err: fmt.Errorf("json: cannot unmarshal number into Go value of type string"),
},
{
// Invalid string data
data: "{\"fa\":13.5}",
obj: &F{},
err: fmt.Errorf("json: cannot unmarshal number into Go value of type string"),
},
{
// Invalid string data
data: "{\"fa\":true}",
obj: &F{},
err: fmt.Errorf("json: cannot unmarshal bool into Go value of type string"),
},
{
// Invalid []string data
data: "{\"ff\":123}",
obj: &F{},
err: fmt.Errorf("json: cannot unmarshal number into Go value of type []string"),
},
{
// Invalid []string data
data: "{\"ff\":3.5}",
obj: &F{},
err: fmt.Errorf("json: cannot unmarshal number into Go value of type []string"),
},
{
// Invalid []string data
data: "{\"ff\":[123,345]}",
obj: &F{},
err: fmt.Errorf("json: cannot unmarshal number into Go value of type string"),
},
{
// Invalid []int data
data: "{\"fg\":123}",
obj: &F{},
err: fmt.Errorf("json: cannot unmarshal number into Go value of type []int"),
},
{
// Invalid []int data
data: "{\"fg\":\"abc\"}",
obj: &F{},
err: fmt.Errorf("json: cannot unmarshal string into Go value of type []int"),
},
{
// Invalid []int data
data: "{\"fg\":[\"abc\"]}",
obj: &F{},
err: fmt.Errorf("json: cannot unmarshal string into Go value of type int"),
},
{
// Invalid []int data
data: "{\"fg\":[3.5]}",
obj: &F{},
err: fmt.Errorf("json: cannot unmarshal number 3.5 into Go value of type int"),
},
{
// Invalid []int data
data: "{\"fg\":[true,false]}",
obj: &F{},
err: fmt.Errorf("json: cannot unmarshal number 3.5 into Go value of type int"),
},
{
// Invalid []bool data
data: "{\"fh\":123}",
obj: &F{},
err: fmt.Errorf("json: cannot unmarshal number into Go value of type []bool"),
},
{
// Invalid []bool data
data: "{\"fh\":\"abc\"}",
obj: &F{},
err: fmt.Errorf("json: cannot unmarshal string into Go value of type []bool"),
},
{
// Invalid []bool data
data: "{\"fh\":[\"abc\"]}",
obj: &F{},
err: fmt.Errorf("json: cannot unmarshal string into Go value of type bool"),
},
{
// Invalid []bool data
data: "{\"fh\":[3.5]}",
obj: &F{},
err: fmt.Errorf("json: cannot unmarshal number into Go value of type bool"),
},
{
// Invalid []bool data
data: "{\"fh\":[123]}",
obj: &F{},
err: fmt.Errorf("json: cannot unmarshal number into Go value of type bool"),
},
{
// Invalid []float data
data: "{\"fi\":123}",
obj: &F{},
err: fmt.Errorf("json: cannot unmarshal number into Go value of type []float32"),
},
{
// Invalid []float data
data: "{\"fi\":\"abc\"}",
obj: &F{},
err: fmt.Errorf("json: cannot unmarshal string into Go value of type []float32"),
},
{
// Invalid []float data
data: "{\"fi\":[\"abc\"]}",
obj: &F{},
err: fmt.Errorf("json: cannot unmarshal string into Go value of type float32"),
},
{
// Invalid []float data
data: "{\"fi\":[true]}",
obj: &F{},
err: fmt.Errorf("json: cannot unmarshal bool into Go value of type float32"),
},
}
for i := range testCases {
doUnrecognized(t, testCases[i].data, testCases[i].obj, testCases[i].err)
if t.Failed() {
break
}
}
}

View File

@ -0,0 +1,19 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package unstructured provides conversion from runtime objects
// to map[string]interface{} representation.
package unstructured // import "k8s.io/apimachinery/pkg/conversion/unstructured"

26
vendor/BUILD vendored
View File

@ -15414,3 +15414,29 @@ go_library(
"//vendor:k8s.io/sample-apiserver/pkg/apis/wardle", "//vendor:k8s.io/sample-apiserver/pkg/apis/wardle",
], ],
) )
go_test(
name = "k8s.io/apimachinery/pkg/conversion/unstructured_test",
srcs = ["k8s.io/apimachinery/pkg/conversion/unstructured/converter_test.go"],
library = ":k8s.io/apimachinery/pkg/conversion/unstructured",
tags = ["automanaged"],
deps = [
"//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
"//vendor:k8s.io/apimachinery/pkg/util/diff",
"//vendor:k8s.io/apimachinery/pkg/util/json",
],
)
go_library(
name = "k8s.io/apimachinery/pkg/conversion/unstructured",
srcs = [
"k8s.io/apimachinery/pkg/conversion/unstructured/converter.go",
"k8s.io/apimachinery/pkg/conversion/unstructured/doc.go",
],
tags = ["automanaged"],
deps = [
"//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apimachinery/pkg/util/json",
],
)