Skip to content

Commit

Permalink
feat(json): remove json-iterator for marshaling
Browse files Browse the repository at this point in the history
Towards removing json-iter and avoiding reflect.

Signed-off-by: Christian Stewart <[email protected]>
  • Loading branch information
paralin committed Apr 24, 2024
1 parent a56c2d3 commit 59856dc
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 27 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

Expand Down
8 changes: 2 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand All @@ -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=
Expand Down
32 changes: 14 additions & 18 deletions json/marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package json

import (
"bytes"
"encoding/base64"
"fmt"
"math"
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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{},
Expand All @@ -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,
Expand All @@ -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
}
Expand All @@ -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{
Expand Down
7 changes: 5 additions & 2 deletions json/marshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package json

import (
"bytes"
"math"
"testing"
"time"
Expand All @@ -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)
}
Expand Down
131 changes: 131 additions & 0 deletions json/stream.go
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 59856dc

Please sign in to comment.