mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-07-18 04:54:54 +00:00
Make ToUnstructured match stdlib omitempty and anonymous behavior
This commit is contained in:
@@ -376,17 +376,20 @@ func fieldInfoFromField(structType reflect.Type, field int) *fieldInfo {
|
||||
typeField := structType.Field(field)
|
||||
jsonTag := typeField.Tag.Get("json")
|
||||
if len(jsonTag) == 0 {
|
||||
// Make the first character lowercase.
|
||||
if typeField.Name == "" {
|
||||
if !typeField.Anonymous {
|
||||
// match stdlib behavior for naming fields that don't specify a json tag name
|
||||
info.name = typeField.Name
|
||||
} else {
|
||||
info.name = strings.ToLower(typeField.Name[:1]) + typeField.Name[1:]
|
||||
}
|
||||
} else {
|
||||
items := strings.Split(jsonTag, ",")
|
||||
info.name = items[0]
|
||||
if len(info.name) == 0 && !typeField.Anonymous {
|
||||
// match stdlib behavior for naming fields that don't specify a json tag name
|
||||
info.name = typeField.Name
|
||||
}
|
||||
|
||||
for i := range items {
|
||||
if items[i] == "omitempty" {
|
||||
if i > 0 && items[i] == "omitempty" {
|
||||
info.omitempty = true
|
||||
break
|
||||
}
|
||||
|
||||
@@ -998,3 +998,179 @@ func TestCustomToUnstructuredTopLevel(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type OmitemptyNameField struct {
|
||||
I int `json:"omitempty"`
|
||||
}
|
||||
|
||||
func TestOmitempty(t *testing.T) {
|
||||
expected := `{"omitempty":0}`
|
||||
|
||||
o := &OmitemptyNameField{}
|
||||
jsonData, err := json.Marshal(o)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if e, a := expected, string(jsonData); e != a {
|
||||
t.Fatalf("expected\n%s\ngot\n%s", e, a)
|
||||
}
|
||||
|
||||
unstr, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&o)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
jsonUnstrData, err := json.Marshal(unstr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if e, a := expected, string(jsonUnstrData); e != a {
|
||||
t.Fatalf("expected\n%s\ngot\n%s", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
type InlineTestPrimitive struct {
|
||||
NoNameTagPrimitive int64 `json:""`
|
||||
NoNameTagInlinePrimitive int64 `json:",inline"`
|
||||
NoNameTagOmitemptyPrimitive int64 `json:",omitempty"`
|
||||
}
|
||||
type InlineTestAnonymous struct {
|
||||
NoTag
|
||||
NoNameTag `json:""`
|
||||
NameTag `json:"nameTagEmbedded"`
|
||||
NoNameTagInline `json:",inline"`
|
||||
NoNameTagOmitempty `json:",omitempty"`
|
||||
}
|
||||
type InlineTestNamed struct {
|
||||
NoTag NoTag
|
||||
NoNameTag NoNameTag `json:""`
|
||||
NameTag NameTag `json:"nameTagEmbedded"`
|
||||
NoNameTagInline NoNameTagInline `json:",inline"`
|
||||
NoNameTagOmitempty NoNameTagOmitempty `json:",omitempty"`
|
||||
}
|
||||
type NoTag struct {
|
||||
Data0 int `json:"data0"`
|
||||
}
|
||||
type NameTag struct {
|
||||
Data1 int `json:"data1"`
|
||||
}
|
||||
type NoNameTag struct {
|
||||
Data2 int `json:"data2"`
|
||||
}
|
||||
type NoNameTagInline struct {
|
||||
Data3 int `json:"data3"`
|
||||
}
|
||||
type NoNameTagOmitempty struct {
|
||||
Data4 int `json:"data4"`
|
||||
}
|
||||
|
||||
func TestInline(t *testing.T) {
|
||||
testcases := []struct {
|
||||
name string
|
||||
obj any
|
||||
expect map[string]any
|
||||
}{
|
||||
{
|
||||
name: "primitive-zero",
|
||||
obj: &InlineTestPrimitive{},
|
||||
expect: map[string]any{
|
||||
"NoNameTagPrimitive": int64(0),
|
||||
"NoNameTagInlinePrimitive": int64(0),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "primitive-set",
|
||||
obj: &InlineTestPrimitive{
|
||||
NoNameTagPrimitive: 1,
|
||||
NoNameTagInlinePrimitive: 2,
|
||||
NoNameTagOmitemptyPrimitive: 3,
|
||||
},
|
||||
expect: map[string]any{
|
||||
"NoNameTagPrimitive": int64(1),
|
||||
"NoNameTagInlinePrimitive": int64(2),
|
||||
"NoNameTagOmitemptyPrimitive": int64(3),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "anonymous-zero",
|
||||
obj: &InlineTestAnonymous{},
|
||||
expect: map[string]any{
|
||||
"data0": int64(0),
|
||||
"data2": int64(0),
|
||||
"data3": int64(0),
|
||||
"data4": int64(0),
|
||||
"nameTagEmbedded": map[string]any{"data1": int64(0)},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "anonymous-set",
|
||||
obj: &InlineTestAnonymous{},
|
||||
expect: map[string]any{
|
||||
"data0": int64(0),
|
||||
"data2": int64(0),
|
||||
"data3": int64(0),
|
||||
"data4": int64(0),
|
||||
"nameTagEmbedded": map[string]any{"data1": int64(0)},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "named-zero",
|
||||
obj: &InlineTestNamed{},
|
||||
expect: map[string]any{
|
||||
"NoTag": map[string]any{"data0": int64(0)},
|
||||
"nameTagEmbedded": map[string]any{"data1": int64(0)},
|
||||
"NoNameTag": map[string]any{"data2": int64(0)},
|
||||
"NoNameTagInline": map[string]any{"data3": int64(0)},
|
||||
"NoNameTagOmitempty": map[string]any{"data4": int64(0)},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "named-set",
|
||||
obj: &InlineTestNamed{
|
||||
NoTag: NoTag{Data0: 10},
|
||||
NameTag: NameTag{Data1: 11},
|
||||
NoNameTag: NoNameTag{Data2: 12},
|
||||
NoNameTagInline: NoNameTagInline{Data3: 13},
|
||||
NoNameTagOmitempty: NoNameTagOmitempty{Data4: 14},
|
||||
},
|
||||
expect: map[string]any{
|
||||
"NoTag": map[string]any{"data0": int64(10)},
|
||||
"nameTagEmbedded": map[string]any{"data1": int64(11)},
|
||||
"NoNameTag": map[string]any{"data2": int64(12)},
|
||||
"NoNameTagInline": map[string]any{"data3": int64(13)},
|
||||
"NoNameTagOmitempty": map[string]any{"data4": int64(14)},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
defer func() {
|
||||
// handle panics
|
||||
if err := recover(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Check the expectation against stdlib
|
||||
jsonData, err := json.Marshal(tc.obj)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
jsonUnstr := map[string]any{}
|
||||
if err := json.Unmarshal(jsonData, &jsonUnstr); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(tc.expect, jsonUnstr) {
|
||||
t.Fatal(cmp.Diff(tc.expect, jsonUnstr))
|
||||
}
|
||||
|
||||
// Check the expectation against DefaultUnstructuredConverter.ToUnstructured
|
||||
unstr, err := runtime.DefaultUnstructuredConverter.ToUnstructured(tc.obj)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(tc.expect, unstr) {
|
||||
t.Fatal(cmp.Diff(tc.expect, unstr))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
@@ -338,7 +339,10 @@ func TestGoldenResponse(t *testing.T) {
|
||||
|
||||
var got []interface{}
|
||||
for e := range w.ResultChan() {
|
||||
u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&e)
|
||||
u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&metav1.WatchEvent{
|
||||
Type: string(e.Type),
|
||||
Object: runtime.RawExtension{Object: e.Object},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to convert watch event to unstructured content: %v", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user