Skip to content

Commit

Permalink
Use "jsonv2"/go-json-experimental to marshal OpenAPI v2
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
apelisse committed Nov 6, 2022
1 parent 3c35167 commit dd694fc
Show file tree
Hide file tree
Showing 15 changed files with 455 additions and 3 deletions.
32 changes: 30 additions & 2 deletions pkg/util/jsontesting/json_roundtrip.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,28 @@ import (
"reflect"
"strings"

jsonv2 "k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"

"github.com/go-openapi/jsonreference"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
)

func JsonCompare(got, want []byte) error {
if d := cmp.Diff(got, want, cmp.Transformer("JSONBytes", func(in []byte) (out interface{}) {
// Note that integers larger than 2^53 will lose precision
// since they will be represented using a Go float64.
// There are workarounds for this, but calling it out.
if err := json.Unmarshal(in, &out); err != nil {
return in
}
return out
})); d != "" {
return fmt.Errorf("JSON mismatch (-got +want):\n%s", d)
}
return nil
}

type RoundTripTestCase struct {
Name string
JSON string
Expand Down Expand Up @@ -79,14 +98,19 @@ func (t RoundTripTestCase) RoundTripTest(example json.Unmarshaler) error {
}

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

reEncoded, err := json.MarshalIndent(example, "", " ")
reEncoded, err := json.Marshal(example)
if err != nil {
return fmt.Errorf("failed to marshal decoded value: %w", err)
}

reEncodedV2, err := jsonv2.Marshal(example)
if err != nil {
return fmt.Errorf("failed to marshal decoded value with v2: %w", err)
}

// Check expected marshal error if it has not yet been checked
// (for case where JSON is provided, and object is not)
if testFinished, err := expectError(err, "marshal", t.ExpectedMarshalError); testFinished {
Expand All @@ -109,5 +133,9 @@ func (t RoundTripTestCase) RoundTripTest(example json.Unmarshaler) error {
return fmt.Errorf("expected equal values: %v", cmp.Diff(expected, actual))
}

if err := JsonCompare(reEncoded, reEncodedV2); err != nil {
return err
}

return nil
}
37 changes: 37 additions & 0 deletions pkg/validation/spec/header.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,43 @@ func (h Header) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2, b3, b4), nil
}

func (h Header) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
type SimpleSchemaOmitZero struct {
Type string `json:"type,omitempty"`
Nullable bool `json:"nullable,omitzero"`
Format string `json:"format,omitempty"`
Items *Items `json:"items,omitzero"`
CollectionFormat string `json:"collectionFormat,omitempty"`
Default interface{} `json:"default,omitempty"`
Example interface{} `json:"example,omitempty"`
}
type CommonValidationsOmitZero struct {
Maximum *float64 `json:"maximum,omitempty"`
ExclusiveMaximum bool `json:"exclusiveMaximum,omitzero"`
Minimum *float64 `json:"minimum,omitempty"`
ExclusiveMinimum bool `json:"exclusiveMinimum,omitzero"`
MaxLength *int64 `json:"maxLength,omitempty"`
MinLength *int64 `json:"minLength,omitempty"`
Pattern string `json:"pattern,omitempty"`
MaxItems *int64 `json:"maxItems,omitempty"`
MinItems *int64 `json:"minItems,omitempty"`
UniqueItems bool `json:"uniqueItems,omitzero"`
MultipleOf *float64 `json:"multipleOf,omitempty"`
Enum []interface{} `json:"enum,omitempty"`
}
var x struct {
CommonValidationsOmitZero
SimpleSchemaOmitZero
Extensions
HeaderProps
}
x.CommonValidationsOmitZero = CommonValidationsOmitZero(h.CommonValidations)
x.SimpleSchemaOmitZero = SimpleSchemaOmitZero(h.SimpleSchema)
x.Extensions = h.Extensions
x.HeaderProps = h.HeaderProps
return opts.MarshalNext(enc, x)
}

// UnmarshalJSON unmarshals this header from JSON
func (h *Header) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshaling {
Expand Down
10 changes: 10 additions & 0 deletions pkg/validation/spec/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,16 @@ func (i Info) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2), nil
}

func (i Info) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
Extensions
InfoProps
}
x.Extensions = i.Extensions
x.InfoProps = i.InfoProps
return opts.MarshalNext(enc, x)
}

// UnmarshalJSON marshal this from JSON
func (i *Info) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshaling {
Expand Down
37 changes: 37 additions & 0 deletions pkg/validation/spec/items.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,40 @@ func (i Items) MarshalJSON() ([]byte, error) {
}
return swag.ConcatJSON(b4, b3, b1, b2), nil
}

func (i Items) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
type SimpleSchemaOmitZero struct {
Type string `json:"type,omitempty"`
Nullable bool `json:"nullable,omitzero"`
Format string `json:"format,omitempty"`
Items *Items `json:"items,omitzero"`
CollectionFormat string `json:"collectionFormat,omitempty"`
Default interface{} `json:"default,omitempty"`
Example interface{} `json:"example,omitempty"`
}
type CommonValidationsOmitZero struct {
Maximum *float64 `json:"maximum,omitempty"`
ExclusiveMaximum bool `json:"exclusiveMaximum,omitzero"`
Minimum *float64 `json:"minimum,omitempty"`
ExclusiveMinimum bool `json:"exclusiveMinimum,omitzero"`
MaxLength *int64 `json:"maxLength,omitempty"`
MinLength *int64 `json:"minLength,omitempty"`
Pattern string `json:"pattern,omitempty"`
MaxItems *int64 `json:"maxItems,omitempty"`
MinItems *int64 `json:"minItems,omitempty"`
UniqueItems bool `json:"uniqueItems,omitzero"`
MultipleOf *float64 `json:"multipleOf,omitempty"`
Enum []interface{} `json:"enum,omitempty"`
}
var x struct {
CommonValidationsOmitZero
SimpleSchemaOmitZero
Ref string `json:"$ref,omitempty"`
Extensions
}
x.CommonValidationsOmitZero = CommonValidationsOmitZero(i.CommonValidations)
x.SimpleSchemaOmitZero = SimpleSchemaOmitZero(i.SimpleSchema)
x.Ref = i.Refable.Ref.String()
x.Extensions = i.Extensions
return opts.MarshalNext(enc, x)
}
26 changes: 25 additions & 1 deletion pkg/validation/spec/operation.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ type OperationProps struct {
Summary string `json:"summary,omitempty"`
ExternalDocs *ExternalDocumentation `json:"externalDocs,omitempty"`
ID string `json:"operationId,omitempty"`
Deprecated bool `json:"deprecated,omitempty"`
Deprecated bool `json:"deprecated,omitempty,omitzero"`
Security []map[string][]string `json:"security,omitempty"`
Parameters []Parameter `json:"parameters,omitempty"`
Responses *Responses `json:"responses,omitempty"`
Expand Down Expand Up @@ -118,3 +118,27 @@ func (o Operation) MarshalJSON() ([]byte, error) {
concated := swag.ConcatJSON(b1, b2)
return concated, nil
}

func (o Operation) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
type OperationPropsOmitZero struct {
Description string `json:"description,omitempty"`
Consumes []string `json:"consumes,omitempty"`
Produces []string `json:"produces,omitempty"`
Schemes []string `json:"schemes,omitempty"`
Tags []string `json:"tags,omitempty"`
Summary string `json:"summary,omitempty"`
ExternalDocs *ExternalDocumentation `json:"externalDocs,omitzero"`
ID string `json:"operationId,omitempty"`
Deprecated bool `json:"deprecated,omitempty,omitzero"`
Security []map[string][]string `json:"security,omitempty"`
Parameters []Parameter `json:"parameters,omitempty"`
Responses *Responses `json:"responses,omitzero"`
}
var x struct {
Extensions
OperationPropsOmitZero
}
x.Extensions = o.Extensions
x.OperationPropsOmitZero = OperationPropsOmitZero(o.OperationProps)
return opts.MarshalNext(enc, x)
}
47 changes: 47 additions & 0 deletions pkg/validation/spec/parameter.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,50 @@ func (p Parameter) MarshalJSON() ([]byte, error) {
}
return swag.ConcatJSON(b3, b1, b2, b4, b5), nil
}

func (p Parameter) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
type ParamPropsOmitZero struct {
Description string `json:"description,omitempty"`
Name string `json:"name,omitempty"`
In string `json:"in,omitempty"`
Required bool `json:"required,omitzero"`
Schema *Schema `json:"schema,omitzero"`
AllowEmptyValue bool `json:"allowEmptyValue,omitzero"`
}
type SimpleSchemaOmitZero struct {
Type string `json:"type,omitempty"`
Nullable bool `json:"nullable,omitzero"`
Format string `json:"format,omitempty"`
Items *Items `json:"items,omitzero"`
CollectionFormat string `json:"collectionFormat,omitempty"`
Default interface{} `json:"default,omitempty"`
Example interface{} `json:"example,omitempty"`
}
type CommonValidationsOmitZero struct {
Maximum *float64 `json:"maximum,omitempty"`
ExclusiveMaximum bool `json:"exclusiveMaximum,omitzero"`
Minimum *float64 `json:"minimum,omitempty"`
ExclusiveMinimum bool `json:"exclusiveMinimum,omitzero"`
MaxLength *int64 `json:"maxLength,omitempty"`
MinLength *int64 `json:"minLength,omitempty"`
Pattern string `json:"pattern,omitempty"`
MaxItems *int64 `json:"maxItems,omitempty"`
MinItems *int64 `json:"minItems,omitempty"`
UniqueItems bool `json:"uniqueItems,omitzero"`
MultipleOf *float64 `json:"multipleOf,omitempty"`
Enum []interface{} `json:"enum,omitempty"`
}
var x struct {
CommonValidationsOmitZero
SimpleSchemaOmitZero
Extensions
ParamPropsOmitZero
Ref string `json:"$ref,omitempty"`
}
x.CommonValidationsOmitZero = CommonValidationsOmitZero(p.CommonValidations)
x.SimpleSchemaOmitZero = SimpleSchemaOmitZero(p.SimpleSchema)
x.Extensions = p.Extensions
x.ParamPropsOmitZero = ParamPropsOmitZero(p.ParamProps)
x.Ref = p.Refable.Ref.String()
return opts.MarshalNext(enc, x)
}
12 changes: 12 additions & 0 deletions pkg/validation/spec/path_item.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,15 @@ func (p PathItem) MarshalJSON() ([]byte, error) {
concated := swag.ConcatJSON(b3, b4, b5)
return concated, nil
}

func (p PathItem) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
Ref string `json:"$ref,omitempty"`
Extensions
PathItemProps
}
x.Ref = p.Refable.Ref.String()
x.Extensions = p.Extensions
x.PathItemProps = p.PathItemProps
return opts.MarshalNext(enc, x)
}
11 changes: 11 additions & 0 deletions pkg/validation/spec/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,14 @@ func (p Paths) MarshalJSON() ([]byte, error) {
concated := swag.ConcatJSON(b1, b2)
return concated, nil
}

func (p Paths) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
m := make(map[string]interface{}, len(p.Extensions)+len(p.Paths))
for k, v := range p.Extensions {
m[k] = v
}
for k, v := range p.Paths {
m[k] = v
}
return opts.MarshalNext(enc, m)
}
18 changes: 18 additions & 0 deletions pkg/validation/spec/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,24 @@ func (r Response) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2, b3), nil
}

func (r Response) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
type ResponsePropsOmitZero struct {
Description string `json:"description,omitempty"`
Schema *Schema `json:"schema,omitzero"`
Headers map[string]Header `json:"headers,omitempty"`
Examples map[string]interface{} `json:"examples,omitempty"`
}
var x struct {
Ref string `json:"$ref,omitempty"`
Extensions
ResponsePropsOmitZero
}
x.Ref = r.Refable.Ref.String()
x.Extensions = r.Extensions
x.ResponsePropsOmitZero = ResponsePropsOmitZero(r.ResponseProps)
return opts.MarshalNext(enc, x)
}

// NewResponse creates a new response instance
func NewResponse() *Response {
return new(Response)
Expand Down
17 changes: 17 additions & 0 deletions pkg/validation/spec/responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,23 @@ func (r Responses) MarshalJSON() ([]byte, error) {
return concated, nil
}

func (r Responses) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
type ArbitraryKeys map[string]interface{}
var x struct {
ArbitraryKeys
Default *Response `json:"default,omitempty"`
}
x.ArbitraryKeys = make(ArbitraryKeys, len(r.Extensions)+len(r.StatusCodeResponses))
for k, v := range r.Extensions {
x.ArbitraryKeys[k] = v
}
for k, v := range r.StatusCodeResponses {
x.ArbitraryKeys[strconv.Itoa(k)] = v
}
x.Default = r.Default
return opts.MarshalNext(enc, x)
}

// ResponsesProps describes all responses for an operation.
// It tells what is the default response and maps all responses with a
// HTTP status code.
Expand Down
Loading

0 comments on commit dd694fc

Please sign in to comment.