From 2a122d1880d16ec5c5e34fc9adff482ebb029ca5 Mon Sep 17 00:00:00 2001 From: Evan Phoenix Date: Fri, 12 Jan 2024 13:40:36 -0800 Subject: [PATCH] More optimizations * Avoid checkValid and compact as much as possible * Keep decodeState's in a pool --- v5/internal/json/decode.go | 48 +++++++++++++++- v5/internal/json/encode.go | 60 +++++++++++++++++++- v5/merge.go | 14 +++-- v5/patch.go | 110 +++++++++++++++++-------------------- 4 files changed, 161 insertions(+), 71 deletions(-) diff --git a/v5/internal/json/decode.go b/v5/internal/json/decode.go index 6f526b1..e9bb0ef 100644 --- a/v5/internal/json/decode.go +++ b/v5/internal/json/decode.go @@ -14,6 +14,7 @@ import ( "reflect" "strconv" "strings" + "sync" "unicode" "unicode/utf16" "unicode/utf8" @@ -98,7 +99,9 @@ func Unmarshal(data []byte, v any) error { // Check for well-formedness. // Avoids filling out half a data structure // before discovering a JSON syntax error. - var d decodeState + d := ds.Get().(*decodeState) + defer ds.Put(d) + //var d decodeState d.useNumber = true err := checkValid(data, &d.scan) if err != nil { @@ -109,11 +112,20 @@ func Unmarshal(data []byte, v any) error { return d.unmarshal(v) } +var ds = sync.Pool{ + New: func() any { + return new(decodeState) + }, +} + func UnmarshalWithKeys(data []byte, v any) ([]string, error) { // Check for well-formedness. // Avoids filling out half a data structure // before discovering a JSON syntax error. - var d decodeState + + d := ds.Get().(*decodeState) + defer ds.Put(d) + //var d decodeState d.useNumber = true err := checkValid(data, &d.scan) if err != nil { @@ -129,6 +141,38 @@ func UnmarshalWithKeys(data []byte, v any) ([]string, error) { return d.lastKeys, nil } +func UnmarshalValid(data []byte, v any) error { + // Check for well-formedness. + // Avoids filling out half a data structure + // before discovering a JSON syntax error. + d := ds.Get().(*decodeState) + defer ds.Put(d) + //var d decodeState + d.useNumber = true + + d.init(data) + return d.unmarshal(v) +} + +func UnmarshalValidWithKeys(data []byte, v any) ([]string, error) { + // Check for well-formedness. + // Avoids filling out half a data structure + // before discovering a JSON syntax error. + + d := ds.Get().(*decodeState) + defer ds.Put(d) + //var d decodeState + d.useNumber = true + + d.init(data) + err := d.unmarshal(v) + if err != nil { + return nil, err + } + + return d.lastKeys, nil +} + // Unmarshaler is the interface implemented by types // that can unmarshal a JSON description of themselves. // The input can be assumed to be a valid encoding of diff --git a/v5/internal/json/encode.go b/v5/internal/json/encode.go index 9d59b0f..a1819b1 100644 --- a/v5/internal/json/encode.go +++ b/v5/internal/json/encode.go @@ -224,6 +224,14 @@ type Marshaler interface { MarshalJSON() ([]byte, error) } +type RedirectMarshaler interface { + RedirectMarshalJSON() (any, error) +} + +type TrustMarshaler interface { + TrustMarshalJSON(b *bytes.Buffer) error +} + // An UnsupportedTypeError is returned by Marshal when attempting // to encode an unsupported value type. type UnsupportedTypeError struct { @@ -406,13 +414,21 @@ func typeEncoder(t reflect.Type) encoderFunc { } var ( - marshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem() - textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() + marshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem() + redirMarshalerType = reflect.TypeOf((*RedirectMarshaler)(nil)).Elem() + trustMarshalerType = reflect.TypeOf((*TrustMarshaler)(nil)).Elem() + textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() ) // newTypeEncoder constructs an encoderFunc for a type. // The returned encoder only checks CanAddr when allowAddr is true. func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc { + if t.Implements(redirMarshalerType) { + return redirMarshalerEncoder + } + if t.Implements(trustMarshalerType) { + return marshalerTrustEncoder + } // If we have a non-pointer value whose type implements // Marshaler with a value receiver, then we're better off taking // the address of the value - otherwise we end up with an @@ -464,6 +480,46 @@ func invalidValueEncoder(e *encodeState, v reflect.Value, _ encOpts) { e.WriteString("null") } +func redirMarshalerEncoder(e *encodeState, v reflect.Value, opts encOpts) { + if v.Kind() == reflect.Pointer && v.IsNil() { + e.WriteString("null") + return + } + m, ok := v.Interface().(RedirectMarshaler) + if !ok { + e.WriteString("null") + return + } + + iv, err := m.RedirectMarshalJSON() + if err != nil { + e.error(&MarshalerError{v.Type(), err, "RedirectMarshalJSON"}) + return + } + + e.marshal(iv, opts) +} + +func marshalerTrustEncoder(e *encodeState, v reflect.Value, opts encOpts) { + if v.Kind() == reflect.Pointer && v.IsNil() { + e.WriteString("null") + return + } + m, ok := v.Interface().(TrustMarshaler) + if !ok { + e.WriteString("null") + return + } + err := m.TrustMarshalJSON(&e.Buffer) + if err == nil { + //_, err = e.Buffer.Write(b) + // copy JSON into buffer, checking validity. + //err = compact(&e.Buffer, b, opts.escapeHTML) + } + if err != nil { + e.error(&MarshalerError{v.Type(), err, "MarshalJSON"}) + } +} func marshalerEncoder(e *encodeState, v reflect.Value, opts encOpts) { if v.Kind() == reflect.Pointer && v.IsNil() { e.WriteString("null") diff --git a/v5/merge.go b/v5/merge.go index fe71070..1a03f86 100644 --- a/v5/merge.go +++ b/v5/merge.go @@ -122,13 +122,15 @@ func MergePatch(docData, patchData []byte) ([]byte, error) { func doMergePatch(docData, patchData []byte, mergeMerge bool) ([]byte, error) { doc := &partialDoc{} - docErr := unmarshal(docData, doc) - - patch := &partialDoc{ - fastKeys: true, + if !json.Valid(docData) || !json.Valid(patchData) { + return nil, ErrInvalid } - patchErr := unmarshal(patchData, patch) + docErr := doc.UnmarshalJSON(docData) + + patch := &partialDoc{} + + patchErr := patch.UnmarshalJSON(patchData) if isSyntaxError(docErr) { return nil, errBadJSONDoc @@ -268,7 +270,7 @@ func createObjectMergePatch(originalJSON, modifiedJSON []byte) ([]byte, error) { } func unmarshal(data []byte, into interface{}) error { - return json.Unmarshal(data, into) + return json.UnmarshalValid(data, into) } // createArrayMergePatch will return an array of merge-patch documents capable diff --git a/v5/patch.go b/v5/patch.go index 6c3c3a6..a3bbf1c 100644 --- a/v5/patch.go +++ b/v5/patch.go @@ -5,6 +5,7 @@ import ( "fmt" "strconv" "strings" + "unicode" "github.com/evanphx/json-patch/v5/internal/json" "github.com/pkg/errors" @@ -59,8 +60,6 @@ type partialDoc struct { self *lazyNode keys []string obj map[string]*lazyNode - - fastKeys bool } type partialArray struct { @@ -113,14 +112,14 @@ func newRawMessage(buf []byte) *json.RawMessage { return &ra } -func (n *lazyNode) MarshalJSON() ([]byte, error) { +func (n *lazyNode) RedirectMarshalJSON() (any, error) { switch n.which { case eRaw: - return json.Marshal(n.raw) + return n.raw, nil case eDoc: - return json.Marshal(n.doc) + return n.doc, nil case eAry: - return json.Marshal(n.ary.nodes) + return n.ary.nodes, nil default: return nil, ErrUnknownType } @@ -134,39 +133,38 @@ func (n *lazyNode) UnmarshalJSON(data []byte) error { return nil } -func (n *partialDoc) MarshalJSON() ([]byte, error) { - var buf strings.Builder - if _, err := buf.WriteString("{"); err != nil { - return nil, err +func (n *partialDoc) TrustMarshalJSON(buf *bytes.Buffer) error { + if err := buf.WriteByte('{'); err != nil { + return err } for i, k := range n.keys { if i > 0 { - if _, err := buf.WriteString(", "); err != nil { - return nil, err + if err := buf.WriteByte(','); err != nil { + return err } } key, err := json.Marshal(k) if err != nil { - return nil, err + return err } if _, err := buf.Write(key); err != nil { - return nil, err + return err } - if _, err := buf.WriteString(": "); err != nil { - return nil, err + if err := buf.WriteByte(':'); err != nil { + return err } value, err := json.Marshal(n.obj[k]) if err != nil { - return nil, err + return err } if _, err := buf.Write(value); err != nil { - return nil, err + return err } } - if _, err := buf.WriteString("}"); err != nil { - return nil, err + if err := buf.WriteByte('}'); err != nil { + return err } - return []byte(buf.String()), nil + return nil } type syntaxError struct { @@ -178,7 +176,7 @@ func (err *syntaxError) Error() string { } func (n *partialDoc) UnmarshalJSON(data []byte) error { - keys, err := json.UnmarshalWithKeys(data, &n.obj) + keys, err := json.UnmarshalValidWithKeys(data, &n.obj) if err != nil { return err } @@ -189,50 +187,18 @@ func (n *partialDoc) UnmarshalJSON(data []byte) error { } func (n *partialArray) UnmarshalJSON(data []byte) error { - return json.Unmarshal(data, &n.nodes) + return json.UnmarshalValid(data, &n.nodes) } -func (n *partialArray) MarshalJSON() ([]byte, error) { - return json.Marshal(n.nodes) -} - -func skipValue(d *json.Decoder) error { - t, err := d.Token() - if err != nil { - return err - } - if t != startObject && t != startArray { - return nil - } - for d.More() { - if t == startObject { - // consume key token - if _, err := d.Token(); err != nil { - return err - } - } - if err := skipValue(d); err != nil { - return err - } - } - end, err := d.Token() - if err != nil { - return err - } - if t == startObject && end != endObject { - return &syntaxError{msg: "expected close object token"} - } - if t == startArray && end != endArray { - return &syntaxError{msg: "expected close object token"} - } - return nil +func (n *partialArray) RedirectMarshalJSON() (interface{}, error) { + return n.nodes, nil } func deepCopy(src *lazyNode) (*lazyNode, int, error) { if src == nil { return nil, 0, nil } - a, err := src.MarshalJSON() + a, err := json.Marshal(src) if err != nil { return nil, 0, err } @@ -240,6 +206,16 @@ func deepCopy(src *lazyNode) (*lazyNode, int, error) { return newLazyNode(newRawMessage(a)), sz, nil } +func (n *lazyNode) nextByte() byte { + s := []byte(*n.raw) + + for unicode.IsSpace(rune(s[0])) { + s = s[1:] + } + + return s[0] +} + func (n *lazyNode) intoDoc() (*partialDoc, error) { if n.which == eDoc { return n.doc, nil @@ -249,6 +225,10 @@ func (n *lazyNode) intoDoc() (*partialDoc, error) { return nil, ErrInvalid } + if n.nextByte() != '{' { + return nil, ErrInvalid + } + err := unmarshal(*n.raw, &n.doc) if n.doc == nil { @@ -359,11 +339,11 @@ func (n *lazyNode) equal(o *lazyNode) bool { var ns, os string - err := json.Unmarshal(nc, &ns) + err := json.UnmarshalValid(nc, &ns) if err != nil { return false } - err = json.Unmarshal(oc, &os) + err = json.UnmarshalValid(oc, &os) if err != nil { return false } @@ -773,7 +753,7 @@ func (p Patch) add(doc *container, op Operation, options *ApplyOptions) error { } } - err := json.Unmarshal(*val.raw, pd) + err := json.UnmarshalValid(*val.raw, pd) if err != nil { return err @@ -1163,6 +1143,10 @@ func Equal(a, b []byte) bool { // DecodePatch decodes the passed JSON document as an RFC 6902 patch. func DecodePatch(buf []byte) (Patch, error) { + if !json.Valid(buf) { + return nil, ErrInvalid + } + var p Patch err := unmarshal(buf, &p) @@ -1203,6 +1187,10 @@ func (p Patch) ApplyIndentWithOptions(doc []byte, indent string, options *ApplyO return doc, nil } + if !json.Valid(doc) { + return nil, ErrInvalid + } + raw := json.RawMessage(doc) self := newLazyNode(&raw)