diff options
Diffstat (limited to 'util/jsonutil')
| -rw-r--r-- | util/jsonutil/types.go | 32 | ||||
| -rw-r--r-- | util/jsonutil/unmarshal.go | 178 |
2 files changed, 105 insertions, 105 deletions
diff --git a/util/jsonutil/types.go b/util/jsonutil/types.go index 057473249..2ee53f44a 100644 --- a/util/jsonutil/types.go +++ b/util/jsonutil/types.go @@ -1,16 +1,16 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package jsonutil - -// Bytes is a byte slice in a json-encoded struct. -// encoding/json assumes that []byte fields are hex-encoded. -// Bytes are not hex-encoded; they are treated the same as strings. -// This can avoid unnecessary allocations due to a round trip through strings. -type Bytes []byte - -func (b *Bytes) UnmarshalText(text []byte) error { - // Copy the contexts of text. - *b = append(*b, text...) - return nil -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package jsonutil
+
+// Bytes is a byte slice in a json-encoded struct.
+// encoding/json assumes that []byte fields are hex-encoded.
+// Bytes are not hex-encoded; they are treated the same as strings.
+// This can avoid unnecessary allocations due to a round trip through strings.
+type Bytes []byte
+
+func (b *Bytes) UnmarshalText(text []byte) error {
+ // Copy the contexts of text.
+ *b = append(*b, text...)
+ return nil
+}
diff --git a/util/jsonutil/unmarshal.go b/util/jsonutil/unmarshal.go index b1eb4ea87..13aea0c87 100644 --- a/util/jsonutil/unmarshal.go +++ b/util/jsonutil/unmarshal.go @@ -1,89 +1,89 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// Package jsonutil provides utilities to improve JSON performance. -// It includes an Unmarshal wrapper that amortizes allocated garbage over subsequent runs -// and a Bytes type to reduce allocations when unmarshalling a non-hex-encoded string into a []byte. -package jsonutil - -import ( - "bytes" - "encoding/json" - "sync" -) - -// decoder is a re-usable json decoder. -type decoder struct { - dec *json.Decoder - r *bytes.Reader -} - -var readerPool = sync.Pool{ - New: func() any { - return bytes.NewReader(nil) - }, -} - -var decoderPool = sync.Pool{ - New: func() any { - var d decoder - d.r = readerPool.Get().(*bytes.Reader) - d.dec = json.NewDecoder(d.r) - return &d - }, -} - -// Unmarshal is similar to encoding/json.Unmarshal. -// There are three major differences: -// -// On error, encoding/json.Unmarshal zeros v. -// This Unmarshal may leave partial data in v. -// Always check the error before using v! -// (Future improvements may remove this bug.) -// -// The errors they return don't always match perfectly. -// If you do error matching more precise than err != nil, -// don't use this Unmarshal. -// -// This Unmarshal allocates considerably less memory. -func Unmarshal(b []byte, v any) error { - d := decoderPool.Get().(*decoder) - d.r.Reset(b) - off := d.dec.InputOffset() - err := d.dec.Decode(v) - d.r.Reset(nil) // don't keep a reference to b - // In case of error, report the offset in this byte slice, - // instead of in the totality of all bytes this decoder has processed. - // It is not possible to make all errors match json.Unmarshal exactly, - // but we can at least try. - switch jsonerr := err.(type) { - case *json.SyntaxError: - jsonerr.Offset -= off - case *json.UnmarshalTypeError: - jsonerr.Offset -= off - case nil: - // json.Unmarshal fails if there's any extra junk in the input. - // json.Decoder does not; see https://github.com/golang/go/issues/36225. - // We need to check for anything left over in the buffer. - if d.dec.More() { - // TODO: Provide a better error message. - // Unfortunately, we can't set the msg field. - // The offset doesn't perfectly match json: - // Ours is at the end of the valid data, - // and theirs is at the beginning of the extra data after whitespace. - // Close enough, though. - err = &json.SyntaxError{Offset: d.dec.InputOffset() - off} - - // TODO: zero v. This is hard; see encoding/json.indirect. - } - } - if err == nil { - decoderPool.Put(d) - } else { - // There might be junk left in the decoder's buffer. - // There's no way to flush it, no Reset method. - // Abandoned the decoder but reuse the reader. - readerPool.Put(d.r) - } - return err -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package jsonutil provides utilities to improve JSON performance.
+// It includes an Unmarshal wrapper that amortizes allocated garbage over subsequent runs
+// and a Bytes type to reduce allocations when unmarshalling a non-hex-encoded string into a []byte.
+package jsonutil
+
+import (
+ "bytes"
+ "encoding/json"
+ "sync"
+)
+
+// decoder is a re-usable json decoder.
+type decoder struct {
+ dec *json.Decoder
+ r *bytes.Reader
+}
+
+var readerPool = sync.Pool{
+ New: func() any {
+ return bytes.NewReader(nil)
+ },
+}
+
+var decoderPool = sync.Pool{
+ New: func() any {
+ var d decoder
+ d.r = readerPool.Get().(*bytes.Reader)
+ d.dec = json.NewDecoder(d.r)
+ return &d
+ },
+}
+
+// Unmarshal is similar to encoding/json.Unmarshal.
+// There are three major differences:
+//
+// On error, encoding/json.Unmarshal zeros v.
+// This Unmarshal may leave partial data in v.
+// Always check the error before using v!
+// (Future improvements may remove this bug.)
+//
+// The errors they return don't always match perfectly.
+// If you do error matching more precise than err != nil,
+// don't use this Unmarshal.
+//
+// This Unmarshal allocates considerably less memory.
+func Unmarshal(b []byte, v any) error {
+ d := decoderPool.Get().(*decoder)
+ d.r.Reset(b)
+ off := d.dec.InputOffset()
+ err := d.dec.Decode(v)
+ d.r.Reset(nil) // don't keep a reference to b
+ // In case of error, report the offset in this byte slice,
+ // instead of in the totality of all bytes this decoder has processed.
+ // It is not possible to make all errors match json.Unmarshal exactly,
+ // but we can at least try.
+ switch jsonerr := err.(type) {
+ case *json.SyntaxError:
+ jsonerr.Offset -= off
+ case *json.UnmarshalTypeError:
+ jsonerr.Offset -= off
+ case nil:
+ // json.Unmarshal fails if there's any extra junk in the input.
+ // json.Decoder does not; see https://github.com/golang/go/issues/36225.
+ // We need to check for anything left over in the buffer.
+ if d.dec.More() {
+ // TODO: Provide a better error message.
+ // Unfortunately, we can't set the msg field.
+ // The offset doesn't perfectly match json:
+ // Ours is at the end of the valid data,
+ // and theirs is at the beginning of the extra data after whitespace.
+ // Close enough, though.
+ err = &json.SyntaxError{Offset: d.dec.InputOffset() - off}
+
+ // TODO: zero v. This is hard; see encoding/json.indirect.
+ }
+ }
+ if err == nil {
+ decoderPool.Put(d)
+ } else {
+ // There might be junk left in the decoder's buffer.
+ // There's no way to flush it, no Reset method.
+ // Abandoned the decoder but reuse the reader.
+ readerPool.Put(d.r)
+ }
+ return err
+}
|
