Skip to content

Commit 43e0223

Browse files
committed
Use "jsonv2"/go-json-experimental to marshal OpenAPI v2
Performance boost is also pretty impressive: ``` name old time/op new time/op delta SwaggerSpec_ExperimentalMarshal/json-8 115ms ± 2% 33ms ± 1% -70.93% (p=0.000 n=9+9) name old alloc/op new alloc/op delta SwaggerSpec_ExperimentalMarshal/json-8 59.0MB ± 1% 22.5MB ± 0% -61.82% (p=0.001 n=7+7) name old allocs/op new allocs/op delta SwaggerSpec_ExperimentalMarshal/json-8 258k ± 0% 98k ± 0% -61.94% (p=0.000 n=10+10) ``` Mostly because this removes the need to deserialize json into buffers that are then thrown away when the jsons are concatenated together (due to lots of embedded objects).
1 parent 3c35167 commit 43e0223

File tree

15 files changed

+457
-3
lines changed

15 files changed

+457
-3
lines changed

pkg/util/jsontesting/json_roundtrip.go

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,28 @@ import (
2222
"reflect"
2323
"strings"
2424

25+
jsonv2 "k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
26+
27+
"github.com/go-openapi/jsonreference"
2528
"github.com/google/go-cmp/cmp"
29+
"github.com/google/go-cmp/cmp/cmpopts"
2630
)
2731

32+
func JsonCompare(got, want []byte) error {
33+
if d := cmp.Diff(got, want, cmp.Transformer("JSONBytes", func(in []byte) (out interface{}) {
34+
// Note that integers larger than 2^53 will lose precision
35+
// since they will be represented using a Go float64.
36+
// There are workarounds for this, but calling it out.
37+
if err := json.Unmarshal(in, &out); err != nil {
38+
return in
39+
}
40+
return out
41+
})); d != "" {
42+
return fmt.Errorf("JSON mismatch (-got +want):\n%s", d)
43+
}
44+
return nil
45+
}
46+
2847
type RoundTripTestCase struct {
2948
Name string
3049
JSON string
@@ -79,14 +98,19 @@ func (t RoundTripTestCase) RoundTripTest(example json.Unmarshaler) error {
7998
}
8099

81100
if t.Object != nil && !reflect.DeepEqual(t.Object, example) {
82-
return fmt.Errorf("test case expected to unmarshal to specific value: %v", cmp.Diff(t.Object, example))
101+
return fmt.Errorf("test case expected to unmarshal to specific value: %v", cmp.Diff(t.Object, example, cmpopts.IgnoreUnexported(jsonreference.Ref{})))
83102
}
84103

85-
reEncoded, err := json.MarshalIndent(example, "", " ")
104+
reEncoded, err := json.Marshal(example)
86105
if err != nil {
87106
return fmt.Errorf("failed to marshal decoded value: %w", err)
88107
}
89108

109+
reEncodedV2, err := jsonv2.Marshal(example)
110+
if err != nil {
111+
return fmt.Errorf("failed to marshal decoded value with v2: %w", err)
112+
}
113+
90114
// Check expected marshal error if it has not yet been checked
91115
// (for case where JSON is provided, and object is not)
92116
if testFinished, err := expectError(err, "marshal", t.ExpectedMarshalError); testFinished {
@@ -109,5 +133,9 @@ func (t RoundTripTestCase) RoundTripTest(example json.Unmarshaler) error {
109133
return fmt.Errorf("expected equal values: %v", cmp.Diff(expected, actual))
110134
}
111135

136+
if err := JsonCompare(reEncoded, reEncodedV2); err != nil {
137+
return err
138+
}
139+
112140
return nil
113141
}

pkg/validation/spec/header.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,43 @@ func (h Header) MarshalJSON() ([]byte, error) {
6262
return swag.ConcatJSON(b1, b2, b3, b4), nil
6363
}
6464

65+
func (h Header) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
66+
type SimpleSchemaOmitZero struct {
67+
Type string `json:"type,omitempty"`
68+
Nullable bool `json:"nullable,omitzero"`
69+
Format string `json:"format,omitempty"`
70+
Items *Items `json:"items,omitzero"`
71+
CollectionFormat string `json:"collectionFormat,omitempty"`
72+
Default interface{} `json:"default,omitempty"`
73+
Example interface{} `json:"example,omitempty"`
74+
}
75+
type CommonValidationsOmitZero struct {
76+
Maximum *float64 `json:"maximum,omitempty"`
77+
ExclusiveMaximum bool `json:"exclusiveMaximum,omitzero"`
78+
Minimum *float64 `json:"minimum,omitempty"`
79+
ExclusiveMinimum bool `json:"exclusiveMinimum,omitzero"`
80+
MaxLength *int64 `json:"maxLength,omitempty"`
81+
MinLength *int64 `json:"minLength,omitempty"`
82+
Pattern string `json:"pattern,omitempty"`
83+
MaxItems *int64 `json:"maxItems,omitempty"`
84+
MinItems *int64 `json:"minItems,omitempty"`
85+
UniqueItems bool `json:"uniqueItems,omitzero"`
86+
MultipleOf *float64 `json:"multipleOf,omitempty"`
87+
Enum []interface{} `json:"enum,omitempty"`
88+
}
89+
var x struct {
90+
CommonValidationsOmitZero
91+
SimpleSchemaOmitZero
92+
Extensions
93+
HeaderProps
94+
}
95+
x.CommonValidationsOmitZero = CommonValidationsOmitZero(h.CommonValidations)
96+
x.SimpleSchemaOmitZero = SimpleSchemaOmitZero(h.SimpleSchema)
97+
x.Extensions = h.Extensions
98+
x.HeaderProps = h.HeaderProps
99+
return opts.MarshalNext(enc, x)
100+
}
101+
65102
// UnmarshalJSON unmarshals this header from JSON
66103
func (h *Header) UnmarshalJSON(data []byte) error {
67104
if internal.UseOptimizedJSONUnmarshaling {

pkg/validation/spec/info.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,16 @@ func (i Info) MarshalJSON() ([]byte, error) {
192192
return swag.ConcatJSON(b1, b2), nil
193193
}
194194

195+
func (i Info) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
196+
var x struct {
197+
Extensions
198+
InfoProps
199+
}
200+
x.Extensions = i.Extensions
201+
x.InfoProps = i.InfoProps
202+
return opts.MarshalNext(enc, x)
203+
}
204+
195205
// UnmarshalJSON marshal this from JSON
196206
func (i *Info) UnmarshalJSON(data []byte) error {
197207
if internal.UseOptimizedJSONUnmarshaling {

pkg/validation/spec/items.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,40 @@ func (i Items) MarshalJSON() ([]byte, error) {
135135
}
136136
return swag.ConcatJSON(b4, b3, b1, b2), nil
137137
}
138+
139+
func (i Items) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
140+
type SimpleSchemaOmitZero struct {
141+
Type string `json:"type,omitempty"`
142+
Nullable bool `json:"nullable,omitzero"`
143+
Format string `json:"format,omitempty"`
144+
Items *Items `json:"items,omitzero"`
145+
CollectionFormat string `json:"collectionFormat,omitempty"`
146+
Default interface{} `json:"default,omitempty"`
147+
Example interface{} `json:"example,omitempty"`
148+
}
149+
type CommonValidationsOmitZero struct {
150+
Maximum *float64 `json:"maximum,omitempty"`
151+
ExclusiveMaximum bool `json:"exclusiveMaximum,omitzero"`
152+
Minimum *float64 `json:"minimum,omitempty"`
153+
ExclusiveMinimum bool `json:"exclusiveMinimum,omitzero"`
154+
MaxLength *int64 `json:"maxLength,omitempty"`
155+
MinLength *int64 `json:"minLength,omitempty"`
156+
Pattern string `json:"pattern,omitempty"`
157+
MaxItems *int64 `json:"maxItems,omitempty"`
158+
MinItems *int64 `json:"minItems,omitempty"`
159+
UniqueItems bool `json:"uniqueItems,omitzero"`
160+
MultipleOf *float64 `json:"multipleOf,omitempty"`
161+
Enum []interface{} `json:"enum,omitempty"`
162+
}
163+
var x struct {
164+
CommonValidationsOmitZero
165+
SimpleSchemaOmitZero
166+
Ref string `json:"$ref,omitempty"`
167+
Extensions
168+
}
169+
x.CommonValidationsOmitZero = CommonValidationsOmitZero(i.CommonValidations)
170+
x.SimpleSchemaOmitZero = SimpleSchemaOmitZero(i.SimpleSchema)
171+
x.Ref = i.Refable.Ref.String()
172+
x.Extensions = i.Extensions
173+
return opts.MarshalNext(enc, x)
174+
}

pkg/validation/spec/operation.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ type OperationProps struct {
3636
Summary string `json:"summary,omitempty"`
3737
ExternalDocs *ExternalDocumentation `json:"externalDocs,omitempty"`
3838
ID string `json:"operationId,omitempty"`
39-
Deprecated bool `json:"deprecated,omitempty"`
39+
Deprecated bool `json:"deprecated,omitempty,omitzero"`
4040
Security []map[string][]string `json:"security,omitempty"`
4141
Parameters []Parameter `json:"parameters,omitempty"`
4242
Responses *Responses `json:"responses,omitempty"`
@@ -118,3 +118,27 @@ func (o Operation) MarshalJSON() ([]byte, error) {
118118
concated := swag.ConcatJSON(b1, b2)
119119
return concated, nil
120120
}
121+
122+
func (o Operation) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
123+
type OperationPropsOmitZero struct {
124+
Description string `json:"description,omitempty"`
125+
Consumes []string `json:"consumes,omitempty"`
126+
Produces []string `json:"produces,omitempty"`
127+
Schemes []string `json:"schemes,omitempty"`
128+
Tags []string `json:"tags,omitempty"`
129+
Summary string `json:"summary,omitempty"`
130+
ExternalDocs *ExternalDocumentation `json:"externalDocs,omitzero"`
131+
ID string `json:"operationId,omitempty"`
132+
Deprecated bool `json:"deprecated,omitempty,omitzero"`
133+
Security []map[string][]string `json:"security,omitempty"`
134+
Parameters []Parameter `json:"parameters,omitempty"`
135+
Responses *Responses `json:"responses,omitzero"`
136+
}
137+
var x struct {
138+
Extensions
139+
OperationPropsOmitZero
140+
}
141+
x.Extensions = o.Extensions
142+
x.OperationPropsOmitZero = OperationPropsOmitZero(o.OperationProps)
143+
return opts.MarshalNext(enc, x)
144+
}

pkg/validation/spec/parameter.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,3 +144,50 @@ func (p Parameter) MarshalJSON() ([]byte, error) {
144144
}
145145
return swag.ConcatJSON(b3, b1, b2, b4, b5), nil
146146
}
147+
148+
func (p Parameter) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
149+
type ParamPropsOmitZero struct {
150+
Description string `json:"description,omitempty"`
151+
Name string `json:"name,omitempty"`
152+
In string `json:"in,omitempty"`
153+
Required bool `json:"required,omitzero"`
154+
Schema *Schema `json:"schema,omitzero"`
155+
AllowEmptyValue bool `json:"allowEmptyValue,omitzero"`
156+
}
157+
type SimpleSchemaOmitZero struct {
158+
Type string `json:"type,omitempty"`
159+
Nullable bool `json:"nullable,omitzero"`
160+
Format string `json:"format,omitempty"`
161+
Items *Items `json:"items,omitzero"`
162+
CollectionFormat string `json:"collectionFormat,omitempty"`
163+
Default interface{} `json:"default,omitempty"`
164+
Example interface{} `json:"example,omitempty"`
165+
}
166+
type CommonValidationsOmitZero struct {
167+
Maximum *float64 `json:"maximum,omitempty"`
168+
ExclusiveMaximum bool `json:"exclusiveMaximum,omitzero"`
169+
Minimum *float64 `json:"minimum,omitempty"`
170+
ExclusiveMinimum bool `json:"exclusiveMinimum,omitzero"`
171+
MaxLength *int64 `json:"maxLength,omitempty"`
172+
MinLength *int64 `json:"minLength,omitempty"`
173+
Pattern string `json:"pattern,omitempty"`
174+
MaxItems *int64 `json:"maxItems,omitempty"`
175+
MinItems *int64 `json:"minItems,omitempty"`
176+
UniqueItems bool `json:"uniqueItems,omitzero"`
177+
MultipleOf *float64 `json:"multipleOf,omitempty"`
178+
Enum []interface{} `json:"enum,omitempty"`
179+
}
180+
var x struct {
181+
CommonValidationsOmitZero
182+
SimpleSchemaOmitZero
183+
Extensions
184+
ParamPropsOmitZero
185+
Ref string `json:"$ref,omitempty"`
186+
}
187+
x.CommonValidationsOmitZero = CommonValidationsOmitZero(p.CommonValidations)
188+
x.SimpleSchemaOmitZero = SimpleSchemaOmitZero(p.SimpleSchema)
189+
x.Extensions = p.Extensions
190+
x.ParamPropsOmitZero = ParamPropsOmitZero(p.ParamProps)
191+
x.Ref = p.Refable.Ref.String()
192+
return opts.MarshalNext(enc, x)
193+
}

pkg/validation/spec/path_item.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,15 @@ func (p PathItem) MarshalJSON() ([]byte, error) {
103103
concated := swag.ConcatJSON(b3, b4, b5)
104104
return concated, nil
105105
}
106+
107+
func (p PathItem) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
108+
var x struct {
109+
Ref string `json:"$ref,omitempty"`
110+
Extensions
111+
PathItemProps
112+
}
113+
x.Ref = p.Refable.Ref.String()
114+
x.Extensions = p.Extensions
115+
x.PathItemProps = p.PathItemProps
116+
return opts.MarshalNext(enc, x)
117+
}

pkg/validation/spec/paths.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,14 @@ func (p Paths) MarshalJSON() ([]byte, error) {
145145
concated := swag.ConcatJSON(b1, b2)
146146
return concated, nil
147147
}
148+
149+
func (p Paths) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
150+
m := make(map[string]interface{}, len(p.Extensions)+len(p.Paths))
151+
for k, v := range p.Extensions {
152+
m[k] = v
153+
}
154+
for k, v := range p.Paths {
155+
m[k] = v
156+
}
157+
return opts.MarshalNext(enc, m)
158+
}

pkg/validation/spec/response.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,24 @@ func (r Response) MarshalJSON() ([]byte, error) {
100100
return swag.ConcatJSON(b1, b2, b3), nil
101101
}
102102

103+
func (r Response) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
104+
type ResponsePropsOmitZero struct {
105+
Description string `json:"description,omitempty"`
106+
Schema *Schema `json:"schema,omitzero"`
107+
Headers map[string]Header `json:"headers,omitempty"`
108+
Examples map[string]interface{} `json:"examples,omitempty"`
109+
}
110+
var x struct {
111+
Ref string `json:"$ref,omitempty"`
112+
Extensions
113+
ResponsePropsOmitZero
114+
}
115+
x.Ref = r.Refable.Ref.String()
116+
x.Extensions = r.Extensions
117+
x.ResponsePropsOmitZero = ResponsePropsOmitZero(r.ResponseProps)
118+
return opts.MarshalNext(enc, x)
119+
}
120+
103121
// NewResponse creates a new response instance
104122
func NewResponse() *Response {
105123
return new(Response)

pkg/validation/spec/responses.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,23 @@ func (r Responses) MarshalJSON() ([]byte, error) {
7575
return concated, nil
7676
}
7777

78+
func (r Responses) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
79+
type ArbitraryKeys map[string]interface{}
80+
var x struct {
81+
ArbitraryKeys
82+
Default *Response `json:"default,omitempty"`
83+
}
84+
x.ArbitraryKeys = make(ArbitraryKeys, len(r.Extensions)+len(r.StatusCodeResponses))
85+
for k, v := range r.Extensions {
86+
x.ArbitraryKeys[k] = v
87+
}
88+
for k, v := range r.StatusCodeResponses {
89+
x.ArbitraryKeys[strconv.Itoa(k)] = v
90+
}
91+
x.Default = r.Default
92+
return opts.MarshalNext(enc, x)
93+
}
94+
7895
// ResponsesProps describes all responses for an operation.
7996
// It tells what is the default response and maps all responses with a
8097
// HTTP status code.

0 commit comments

Comments
 (0)