Skip to content

Commit 5443b57

Browse files
committed
Add json marshal conversion cache
1 parent 5382ed4 commit 5443b57

File tree

7 files changed

+439
-259
lines changed

7 files changed

+439
-259
lines changed

value/fields.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ type Field struct {
2727
Value Value
2828
}
2929

30-
// FieldList is a list of key-value pairs. Each field is expectUpdated to
30+
// FieldList is a list of key-value pairs. Each field is expected to
3131
// have a different name.
3232
type FieldList []Field
3333

value/listreflect.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func (r listReflect) Length() int {
3232

3333
func (r listReflect) At(i int) Value {
3434
val := r.Value
35-
return mustWrapValueReflect(val.Index(i))
35+
return mustWrapValueReflect(val.Index(i), nil, nil)
3636
}
3737

3838
func (r listReflect) Unstructured() interface{} {
@@ -79,7 +79,7 @@ func (r *listReflectRange) Item() (index int, value Value) {
7979
if r.i >= r.list.Len() {
8080
panic("Item() called on ListRange with no more items")
8181
}
82-
return r.i, r.vr.reuse(r.list.Index(r.i))
82+
return r.i, r.vr.mustReuse(r.list.Index(r.i), nil, nil)
8383
}
8484

8585
func (r *listReflectRange) Recycle() {

value/mapreflect.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func (r mapReflect) Get(key string) (Value, bool) {
3434
if !ok {
3535
return nil, false
3636
}
37-
return mustWrapValueReflectMapItem(&r.Value, &k, v), true
37+
return mustWrapValueReflect(v, &r.Value, &k), true
3838
}
3939

4040
func (r mapReflect) get(k string) (key, value reflect.Value, ok bool) {
@@ -70,19 +70,19 @@ func (r mapReflect) toMapKey(key string) reflect.Value {
7070
func (r mapReflect) Iterate(fn func(string, Value) bool) bool {
7171
vr := reflectPool.Get().(*valueReflect)
7272
defer vr.Recycle()
73-
return eachMapEntry(r.Value, func(s string, value reflect.Value) bool {
74-
return fn(s, vr.reuse(value))
73+
return eachMapEntry(r.Value, func(key reflect.Value, value reflect.Value) bool {
74+
return fn(key.String(), vr.mustReuse(value, &r.Value, &key))
7575
})
7676
}
7777

78-
func eachMapEntry(val reflect.Value, fn func(string, reflect.Value) bool) bool {
78+
func eachMapEntry(val reflect.Value, fn func(reflect.Value, reflect.Value) bool) bool {
7979
iter := val.MapRange()
8080
for iter.Next() {
8181
next := iter.Value()
8282
if !next.IsValid() {
8383
continue
8484
}
85-
if !fn(iter.Key().String(), next) {
85+
if !fn(iter.Key(), next) {
8686
return false
8787
}
8888
}
@@ -114,6 +114,6 @@ func (r mapReflect) Equals(m Map) bool {
114114
if !ok {
115115
return false
116116
}
117-
return Equals(vr.reuse(lhsVal), value)
117+
return Equals(vr.mustReuse(lhsVal, nil, nil), value)
118118
})
119119
}

value/reflectcache.go

Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
/*
2+
Copyright 2020 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package value
18+
19+
import (
20+
"bytes"
21+
"encoding/json"
22+
"fmt"
23+
"reflect"
24+
"sync"
25+
"sync/atomic"
26+
)
27+
28+
// UnstructuredConverter defines how a type can be converted directly to unstructured.
29+
// Types that implement json.Marshaler may also optionally implement this interface to provide a more
30+
// direct and more efficient conversion. All types that choose to implement this interface must still
31+
// implement this same conversion via json.Marshaler.
32+
type UnstructuredConverter interface {
33+
json.Marshaler // require that json.Marshaler is implemented
34+
35+
// ToUnstructured returns the unstructured representation.
36+
ToUnstructured() interface{}
37+
}
38+
39+
// TypeReflectCacheEntry keeps data gathered using reflection about how a type is converted to/from unstructured.
40+
type TypeReflectCacheEntry struct {
41+
isJsonMarshaler bool
42+
ptrIsJsonMarshaler bool
43+
isJsonUnmarshaler bool
44+
ptrIsJsonUnmarshaler bool
45+
isStringConvertable bool
46+
ptrIsStringConvertable bool
47+
48+
structFields map[string]*FieldCacheEntry
49+
}
50+
51+
// FieldCacheEntry keeps data gathered using reflection about how the field of a struct is converted to/from
52+
// unstructured.
53+
type FieldCacheEntry struct {
54+
// isOmitEmpty is true if the field has the json 'omitempty' tag.
55+
isOmitEmpty bool
56+
// fieldPath is a list of field indices (see FieldByIndex) to lookup the value of
57+
// a field in a reflect.Value struct. The field indices in the list form a path used
58+
// to traverse through intermediary 'inline' fields.
59+
fieldPath [][]int
60+
}
61+
62+
// GetFrom returns the field identified by this FieldCacheEntry from the provided struct.
63+
func (f *FieldCacheEntry) GetFrom(structVal reflect.Value) reflect.Value {
64+
// field might be nested within 'inline' structs
65+
for _, elem := range f.fieldPath {
66+
structVal = structVal.FieldByIndex(elem)
67+
}
68+
return structVal
69+
}
70+
71+
var marshalerType = reflect.TypeOf(new(json.Marshaler)).Elem()
72+
var unmarshalerType = reflect.TypeOf(new(json.Unmarshaler)).Elem()
73+
var unstructuredConvertableType = reflect.TypeOf(new(UnstructuredConverter)).Elem()
74+
var defaultReflectCache = newReflectCache()
75+
76+
// TypeReflectEntryOf returns the TypeReflectCacheEntry of the provided reflect.Type.
77+
func TypeReflectEntryOf(t reflect.Type) TypeReflectCacheEntry {
78+
if record, ok := defaultReflectCache.get(t); ok {
79+
return record
80+
}
81+
record := TypeReflectCacheEntry{
82+
isJsonMarshaler: t.Implements(marshalerType),
83+
ptrIsJsonMarshaler: reflect.PtrTo(t).Implements(marshalerType),
84+
isJsonUnmarshaler: reflect.PtrTo(t).Implements(unmarshalerType),
85+
isStringConvertable: t.Implements(unstructuredConvertableType),
86+
ptrIsStringConvertable: reflect.PtrTo(t).Implements(unstructuredConvertableType),
87+
}
88+
if t.Kind() == reflect.Struct {
89+
hints := map[string]*FieldCacheEntry{}
90+
buildStructCacheEntry(t, hints, nil)
91+
record.structFields = hints
92+
}
93+
94+
defaultReflectCache.update(t, record)
95+
return record
96+
}
97+
98+
func buildStructCacheEntry(t reflect.Type, infos map[string]*FieldCacheEntry, fieldPath [][]int) {
99+
for i := 0; i < t.NumField(); i++ {
100+
field := t.Field(i)
101+
jsonName, omit, isInline, isOmitempty := lookupJsonTags(field)
102+
if omit {
103+
continue
104+
}
105+
if isInline {
106+
buildStructCacheEntry(field.Type, infos, append(fieldPath, field.Index))
107+
continue
108+
}
109+
info := &FieldCacheEntry{isOmitEmpty: isOmitempty, fieldPath: append(fieldPath, field.Index)}
110+
infos[jsonName] = info
111+
}
112+
}
113+
114+
// Fields returns a map of JSON field name to FieldCacheEntry for structs, or nil for non-structs.
115+
func (e TypeReflectCacheEntry) Fields() map[string]*FieldCacheEntry {
116+
return e.structFields
117+
}
118+
119+
// CanConvertToUnstructured returns true if this TypeReflectCacheEntry can convert values of its type to unstructured.
120+
func (e TypeReflectCacheEntry) CanConvertToUnstructured() bool {
121+
return e.isJsonMarshaler || e.ptrIsJsonMarshaler || e.isStringConvertable || e.ptrIsStringConvertable
122+
}
123+
124+
// ToUnstructured converts the provided value to unstructured and returns it.
125+
func (e TypeReflectCacheEntry) ToUnstructured(sv reflect.Value) (interface{}, error) {
126+
// This is based on https://github.com/kubernetes/kubernetes/blob/82c9e5c814eb7acc6cc0a090c057294d0667ad66/staging/src/k8s.io/apimachinery/pkg/runtime/converter.go#L505
127+
// and is intended to replace it.
128+
129+
// Check if the object has a custom string converter and use it if available, since it is much more efficient
130+
// than round tripping through json.
131+
if converter, ok := e.getUnstructuredConverter(sv); ok {
132+
return converter.ToUnstructured(), nil
133+
}
134+
// Check if the object has a custom JSON marshaller/unmarshaller.
135+
if marshaler, ok := e.getJsonMarshaler(sv); ok {
136+
if sv.Kind() == reflect.Ptr && sv.IsNil() {
137+
// We're done - we don't need to store anything.
138+
return nil, nil
139+
}
140+
141+
data, err := marshaler.MarshalJSON()
142+
if err != nil {
143+
return nil, err
144+
}
145+
switch {
146+
case len(data) == 0:
147+
return nil, fmt.Errorf("error decoding from json: empty value")
148+
149+
case bytes.Equal(data, nullBytes):
150+
// We're done - we don't need to store anything.
151+
return nil, nil
152+
153+
case bytes.Equal(data, trueBytes):
154+
return true, nil
155+
156+
case bytes.Equal(data, falseBytes):
157+
return false, nil
158+
159+
case data[0] == '"':
160+
var result string
161+
err := json.Unmarshal(data, &result)
162+
if err != nil {
163+
return nil, fmt.Errorf("error decoding string from json: %v", err)
164+
}
165+
return result, nil
166+
167+
case data[0] == '{':
168+
result := make(map[string]interface{})
169+
err := json.Unmarshal(data, &result)
170+
if err != nil {
171+
return nil, fmt.Errorf("error decoding object from json: %v", err)
172+
}
173+
return result, nil
174+
175+
case data[0] == '[':
176+
result := make([]interface{}, 0)
177+
err := json.Unmarshal(data, &result)
178+
if err != nil {
179+
return nil, fmt.Errorf("error decoding array from json: %v", err)
180+
}
181+
return result, nil
182+
183+
default:
184+
var (
185+
resultInt int64
186+
resultFloat float64
187+
err error
188+
)
189+
if err = json.Unmarshal(data, &resultInt); err == nil {
190+
return resultInt, nil
191+
} else if err = json.Unmarshal(data, &resultFloat); err == nil {
192+
return resultFloat, nil
193+
} else {
194+
return nil, fmt.Errorf("error decoding number from json: %v", err)
195+
}
196+
}
197+
}
198+
199+
return nil, fmt.Errorf("provided type cannot be converted: %v", sv.Type())
200+
}
201+
202+
// CanConvertFromUnstructured returns true if this TypeReflectCacheEntry can convert objects of the type from unstructured.
203+
func (e TypeReflectCacheEntry) CanConvertFromUnstructured() bool {
204+
return e.isJsonUnmarshaler
205+
}
206+
207+
// FromUnstructured converts the provided source value from unstructured into the provided destination value.
208+
func (e TypeReflectCacheEntry) FromUnstructured(sv, dv reflect.Value) error {
209+
// TODO: this could be made much more efficient using direct conversions like
210+
// UnstructuredConverter.ToUnstructured provides.
211+
st := dv.Type()
212+
data, err := json.Marshal(sv.Interface())
213+
if err != nil {
214+
return fmt.Errorf("error encoding %s to json: %v", st.String(), err)
215+
}
216+
if unmarshaler, ok := e.getJsonUnmarshaler(dv); ok {
217+
return unmarshaler.UnmarshalJSON(data)
218+
}
219+
return fmt.Errorf("unable to unmarshal %v into %v", sv.Type(), dv.Type())
220+
}
221+
222+
var (
223+
nullBytes = []byte("null")
224+
trueBytes = []byte("true")
225+
falseBytes = []byte("false")
226+
)
227+
228+
func (e TypeReflectCacheEntry) getJsonMarshaler(v reflect.Value) (json.Marshaler, bool) {
229+
if e.isJsonMarshaler {
230+
return v.Interface().(json.Marshaler), true
231+
}
232+
if e.ptrIsJsonMarshaler {
233+
// Check pointer receivers if v is not a pointer
234+
if v.Kind() != reflect.Ptr && v.CanAddr() {
235+
v = v.Addr()
236+
return v.Interface().(json.Marshaler), true
237+
}
238+
}
239+
return nil, false
240+
}
241+
242+
func (e TypeReflectCacheEntry) getJsonUnmarshaler(v reflect.Value) (json.Unmarshaler, bool) {
243+
if !e.isJsonUnmarshaler {
244+
return nil, false
245+
}
246+
return v.Addr().Interface().(json.Unmarshaler), true
247+
}
248+
249+
func (e TypeReflectCacheEntry) getUnstructuredConverter(v reflect.Value) (UnstructuredConverter, bool) {
250+
if e.isStringConvertable {
251+
return v.Interface().(UnstructuredConverter), true
252+
}
253+
if e.ptrIsStringConvertable {
254+
// Check pointer receivers if v is not a pointer
255+
if v.CanAddr() {
256+
v = v.Addr()
257+
return v.Interface().(UnstructuredConverter), true
258+
}
259+
}
260+
return nil, false
261+
}
262+
263+
type typeReflectCache struct {
264+
// use an atomic and copy-on-write since there are a fixed (typically very small) number of structs compiled into any
265+
// go program using this cache
266+
value atomic.Value
267+
// mu is held by writers when performing load/modify/store operations on the cache, readers do not need to hold a
268+
// read-lock since the atomic value is always read-only
269+
mu sync.Mutex
270+
}
271+
272+
func newReflectCache() *typeReflectCache {
273+
cache := &typeReflectCache{}
274+
cache.value.Store(make(reflectCacheMap))
275+
return cache
276+
}
277+
278+
type reflectCacheMap map[reflect.Type]TypeReflectCacheEntry
279+
280+
// get returns true and TypeReflectCacheEntry for the given type if the type is in the cache. Otherwise get returns false.
281+
func (c *typeReflectCache) get(t reflect.Type) (TypeReflectCacheEntry, bool) {
282+
entry, ok := c.value.Load().(reflectCacheMap)[t]
283+
return entry, ok
284+
}
285+
286+
// update sets the TypeReflectCacheEntry for the given type via a copy-on-write update to the struct cache.
287+
func (c *typeReflectCache) update(t reflect.Type, m TypeReflectCacheEntry) {
288+
c.mu.Lock()
289+
defer c.mu.Unlock()
290+
291+
currentCacheMap := c.value.Load().(reflectCacheMap)
292+
if _, ok := currentCacheMap[t]; ok {
293+
// Bail if the entry has been set while waiting for lock acquisition.
294+
// This is safe since setting entries is idempotent.
295+
return
296+
}
297+
298+
newCacheMap := make(reflectCacheMap, len(currentCacheMap)+1)
299+
for k, v := range currentCacheMap {
300+
newCacheMap[k] = v
301+
}
302+
newCacheMap[t] = m
303+
c.value.Store(newCacheMap)
304+
}

0 commit comments

Comments
 (0)