From 59856dc2034a77906468e9002838ffee41d42884 Mon Sep 17 00:00:00 2001 From: Christian Stewart Date: Wed, 24 Apr 2024 00:41:09 -0700 Subject: [PATCH] feat(json): remove json-iterator for marshaling Towards removing json-iter and avoiding reflect. Signed-off-by: Christian Stewart --- go.mod | 2 +- go.sum | 8 +-- json/marshal.go | 32 +++++------ json/marshal_test.go | 7 ++- json/stream.go | 131 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 153 insertions(+), 27 deletions(-) create mode 100644 json/stream.go diff --git a/go.mod b/go.mod index ffacdeca..c96c6bc6 100644 --- a/go.mod +++ b/go.mod @@ -7,8 +7,8 @@ require ( github.com/google/go-cmp v0.6.0 github.com/json-iterator/go v1.1.12 github.com/pkg/errors v0.9.1 - github.com/protocolbuffers/txtpbfmt v0.0.0-20240416193709-1e18ef0a7fdc github.com/stretchr/testify v1.9.0 + github.com/valyala/fastjson v1.6.4 google.golang.org/protobuf v1.33.0 ) diff --git a/go.sum b/go.sum index bafb75fa..5cbfb666 100644 --- a/go.sum +++ b/go.sum @@ -8,10 +8,6 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= -github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= @@ -20,12 +16,12 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/protocolbuffers/txtpbfmt v0.0.0-20240416193709-1e18ef0a7fdc h1:DRZwH75/E4a2SOr7+gKZ99OEhmjzBzAhgyTnzo1TepY= -github.com/protocolbuffers/txtpbfmt v0.0.0-20240416193709-1e18ef0a7fdc/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ= +github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= diff --git a/json/marshal.go b/json/marshal.go index 0a109240..27d9f841 100644 --- a/json/marshal.go +++ b/json/marshal.go @@ -4,6 +4,7 @@ package json import ( + "bytes" "encoding/base64" "fmt" "math" @@ -12,7 +13,6 @@ import ( "time" anypb_resolver "github.com/aperturerobotics/protobuf-go-lite/types/known/anypb/resolver" - jsoniter "github.com/json-iterator/go" ) // Marshaler is the interface implemented by types that are supported by this plugin. @@ -51,14 +51,18 @@ var DefaultMarshalerConfig = MarshalerConfig{ // Marshal marshals a message. func (c MarshalerConfig) Marshal(m Marshaler) ([]byte, error) { - s := NewMarshalState(c) + var buf bytes.Buffer + s := NewMarshalState(c, NewJsonStream(&buf)) m.MarshalProtoJSON(s) - return s.Bytes() + if err := s.Err(); err != nil { + return nil, err + } + return buf.Bytes(), nil } // MarshalState is the internal state of the Marshaler. type MarshalState struct { - inner *jsoniter.Stream + inner *JsonStream config *MarshalerConfig err *marshalError @@ -67,9 +71,9 @@ type MarshalState struct { } // NewMarshalState creates a new MarshalState. -func NewMarshalState(config MarshalerConfig) *MarshalState { +func NewMarshalState(config MarshalerConfig, stream *JsonStream) *MarshalState { return &MarshalState{ - inner: jsoniter.NewStream(jsoniterConfig, nil, 1024), + inner: stream, config: &config, err: &marshalError{}, @@ -92,9 +96,9 @@ func (s *MarshalState) AnyTypeResolver() anypb_resolver.AnyTypeResolver { } // Sub returns a sub-marshaler with a new buffer, but with the same configuration, error and path info. -func (s *MarshalState) Sub() *MarshalState { +func (s *MarshalState) Sub(js *JsonStream) *MarshalState { return &MarshalState{ - inner: jsoniter.NewStream(jsoniterConfig, nil, 1024), + inner: js, config: s.config, err: s.err, @@ -108,8 +112,8 @@ func (s *MarshalState) Err() error { if s.err.Err != nil { return s.err } - if s.inner.Error != nil { - return s.inner.Error + if err := s.inner.Error(); err != nil { + return err } return nil } @@ -129,14 +133,6 @@ func (s *MarshalState) SetErrorf(format string, a ...interface{}) { s.SetError(fmt.Errorf(format, a...)) } -// Bytes returns the buffer of the marshaler. -func (s *MarshalState) Bytes() ([]byte, error) { - if err := s.Err(); err != nil { - return nil, err - } - return s.inner.Buffer(), nil -} - // WithFieldMask returns a MarshalState for the given field mask. func (s *MarshalState) WithFieldMask(paths ...string) *MarshalState { return &MarshalState{ diff --git a/json/marshal_test.go b/json/marshal_test.go index 766a7a0b..d2949169 100644 --- a/json/marshal_test.go +++ b/json/marshal_test.go @@ -4,6 +4,7 @@ package json import ( + "bytes" "math" "testing" "time" @@ -19,12 +20,14 @@ var ( func testMarshal(t *testing.T, f func(s *MarshalState), expected string) { t.Helper() - s := NewMarshalState(DefaultMarshalerConfig) + var buf bytes.Buffer + s := NewMarshalState(DefaultMarshalerConfig, NewJsonStream(&buf)) f(s) - data, err := s.Bytes() + err := s.Err() if err != nil { t.Error(err) } + data := buf.Bytes() if diff := cmp.Diff(expected, string(data)); diff != "" { t.Errorf("diff: %s", diff) } diff --git a/json/stream.go b/json/stream.go new file mode 100644 index 00000000..827858c9 --- /dev/null +++ b/json/stream.go @@ -0,0 +1,131 @@ +package json + +import ( + "io" + "strconv" +) + +// JsonStream is an outgoing stream of json. +type JsonStream struct { + wr io.Writer + err error +} + +// NewJsonStream creates a new JsonStream that writes to wr. +func NewJsonStream(wr io.Writer) *JsonStream { + return &JsonStream{wr: wr} +} + +// Write writes the contents of p into the stream. +// It returns the number of bytes written and any write error encountered. +func (s *JsonStream) Write(p []byte) (n int, err error) { + if s.err != nil { + return 0, s.err + } + n, err = s.wr.Write(p) + if err != nil { + s.err = err + } + return n, err +} + +// WriteString writes a quoted string into the stream. +func (s *JsonStream) WriteString(str string) { + if s.err == nil { + _, s.err = io.WriteString(s.wr, strconv.Quote(str)) + } +} + +// WriteFloat32 writes a float32 value into the stream. +func (s *JsonStream) WriteFloat32(f float32) { + if s.err == nil { + _, s.err = io.WriteString(s.wr, strconv.FormatFloat(float64(f), 'f', -1, 32)) + } +} + +// WriteFloat64 writes a float64 value into the stream. +func (s *JsonStream) WriteFloat64(f float64) { + if s.err == nil { + _, s.err = io.WriteString(s.wr, strconv.FormatFloat(f, 'f', -1, 64)) + } +} + +// WriteInt32 writes an int32 value into the stream. +func (s *JsonStream) WriteInt32(i int32) { + if s.err == nil { + _, s.err = io.WriteString(s.wr, strconv.FormatInt(int64(i), 10)) + } +} + +// WriteUint32 writes a uint32 value into the stream. +func (s *JsonStream) WriteUint32(u uint32) { + if s.err == nil { + _, s.err = io.WriteString(s.wr, strconv.FormatUint(uint64(u), 10)) + } +} + +// WriteBool writes a boolean value into the stream. +func (s *JsonStream) WriteBool(b bool) { + if s.err == nil { + _, s.err = io.WriteString(s.wr, strconv.FormatBool(b)) + } +} + +// WriteNil writes a null value into the stream. +func (s *JsonStream) WriteNil() { + if s.err == nil { + _, s.err = io.WriteString(s.wr, "null") + } +} + +// WriteObjectStart writes the start of a JSON object into the stream. +func (s *JsonStream) WriteObjectStart() { + if s.err == nil { + _, s.err = io.WriteString(s.wr, "{") + } +} + +// WriteObjectField writes a field name into the stream. +func (s *JsonStream) WriteObjectField(field string) { + if s.err != nil { + return + } + s.WriteString(field) + if s.err != nil { + return + } + _, s.err = io.WriteString(s.wr, ":") +} + +// WriteObjectEnd writes the end of a JSON object into the stream. +func (s *JsonStream) WriteObjectEnd() { + if s.err == nil { + _, s.err = io.WriteString(s.wr, "}") + } +} + +// WriteArrayStart writes the start of a JSON array into the stream. +func (s *JsonStream) WriteArrayStart() { + if s.err == nil { + _, s.err = io.WriteString(s.wr, "[") + } +} + +// WriteArrayEnd writes the end of a JSON array into the stream. +func (s *JsonStream) WriteArrayEnd() { + if s.err == nil { + _, s.err = io.WriteString(s.wr, "]") + } +} + +// WriteMore writes a comma to separate elements in the stream. +func (s *JsonStream) WriteMore() { + if s.err == nil { + _, s.err = io.WriteString(s.wr, ",") + } +} + +// Error returns any error that has occurred on the stream. +func (s *JsonStream) Error() error { + return s.err +}