From c5bc6bc56e72d7a1d69f9d6dc1c6a35980df514c Mon Sep 17 00:00:00 2001
From: a <a@tuxpa.in>
Date: Mon, 14 Nov 2022 17:29:46 -0600
Subject: [PATCH] remove extra json handlers, add new json library to fill in
 gaps

---
 benchmark_test.go |  4 ++++
 client.go         | 15 ++++-----------
 go.mod            |  1 +
 go.sum            |  2 ++
 http.go           |  4 +++-
 http_client.go    |  5 ++++-
 json.go           | 41 +++++++++++++++++++----------------------
 protocol.go       | 22 +++++++---------------
 subscription.go   | 11 ++++++-----
 wire.go           | 37 +++++++++++++------------------------
 wsjson/wsjson.go  | 25 +++++--------------------
 11 files changed, 68 insertions(+), 99 deletions(-)

diff --git a/benchmark_test.go b/benchmark_test.go
index 49fa35c..d7ed9d5 100644
--- a/benchmark_test.go
+++ b/benchmark_test.go
@@ -24,6 +24,7 @@ func BenchmarkClientHTTPEcho(b *testing.B) {
 	b.StartTimer()
 	for n := 0; n < b.N; n++ {
 		eg := &errgroup.Group{}
+		eg.SetLimit(4)
 		for i := 0; i < 1000; i++ {
 			eg.Go(func() error {
 				return client.Call(nil, "test_echoAny", []any{1, 2, 3, 4, 56, 6, wantBack, wantBack, wantBack})
@@ -44,6 +45,7 @@ func BenchmarkClientHTTPEchoEmpty(b *testing.B) {
 	b.StartTimer()
 	for n := 0; n < b.N; n++ {
 		eg := &errgroup.Group{}
+		eg.SetLimit(4)
 		for i := 0; i < 1000; i++ {
 			eg.Go(func() error {
 				return client.Call(nil, "test_echoAny", 0)
@@ -71,6 +73,7 @@ func BenchmarkClientWebsocketEcho(b *testing.B) {
 	b.StartTimer()
 	for n := 0; n < b.N; n++ {
 		eg := &errgroup.Group{}
+		eg.SetLimit(4)
 		for i := 0; i < 1000; i++ {
 			eg.Go(func() error {
 				return client.Call(nil, "test_echoAny", payload)
@@ -92,6 +95,7 @@ func BenchmarkClientWebsocketEchoEmpty(b *testing.B) {
 
 	for n := 0; n < b.N; n++ {
 		eg := &errgroup.Group{}
+		eg.SetLimit(4)
 		for i := 0; i < 1000; i++ {
 			eg.Go(func() error {
 				return client.Call(nil, "test_echoAny", 0)
diff --git a/client.go b/client.go
index 8de58cc..efc7e1a 100644
--- a/client.go
+++ b/client.go
@@ -283,7 +283,7 @@ func (c *Client) call(ctx context.Context, result any, msg *jsonrpcMessage) erro
 	case result == nil:
 		return nil
 	default:
-		return jzon.Unmarshal(resp.Result, &result)
+		return json.Unmarshal(resp.Result, &result)
 	}
 }
 
@@ -383,7 +383,7 @@ func (c *Client) BatchCallContext(ctx context.Context, b []BatchElem) error {
 			elem.Error = ErrNoResult
 			continue
 		}
-		elem.Error = jzon.Unmarshal(resp.Result, elem.Result)
+		elem.Error = json.Unmarshal(resp.Result, elem.Result)
 	}
 
 	return err
@@ -442,20 +442,13 @@ func (c *Client) Subscribe(ctx context.Context, namespace string, channel interf
 }
 
 func (c *Client) newMessage(method string, paramsIn ...any) (*jsonrpcMessage, error) {
-	msg := &jsonrpcMessage{ID: c.nextID(), Method: method}
-	if paramsIn != nil { // prevent sending "params":null
-		var err error
-		if msg.Params, err = jzon.Marshal(paramsIn); err != nil {
-			return nil, err
-		}
-	}
-	return msg, nil
+	return c.newMessageP(method, paramsIn)
 }
 func (c *Client) newMessageP(method string, paramIn any) (*jsonrpcMessage, error) {
 	msg := &jsonrpcMessage{ID: c.nextID(), Method: method}
 	if paramIn != nil { // prevent sending "params":null
 		var err error
-		if msg.Params, err = jzon.Marshal(paramIn); err != nil {
+		if msg.Params, err = json.Marshal(paramIn); err != nil {
 			return nil, err
 		}
 	}
diff --git a/go.mod b/go.mod
index ed4a32b..3f40282 100644
--- a/go.mod
+++ b/go.mod
@@ -11,6 +11,7 @@ require (
 	github.com/deckarep/golang-set v1.8.0
 	github.com/ethereum/go-ethereum v1.10.25
 	github.com/gobuffalo/packr/v2 v2.8.3
+	github.com/goccy/go-json v0.9.11
 	github.com/iancoleman/strcase v0.2.0
 	github.com/json-iterator/go v1.1.12
 	golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0
diff --git a/go.sum b/go.sum
index 572f2a7..d43c659 100644
--- a/go.sum
+++ b/go.sum
@@ -119,6 +119,8 @@ github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
 github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
 github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
 github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
+github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
+github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
diff --git a/http.go b/http.go
index 0e4d784..212c893 100644
--- a/http.go
+++ b/http.go
@@ -29,6 +29,8 @@ import (
 	"time"
 
 	"gfx.cafe/util/go/bufpool"
+
+	json "github.com/goccy/go-json"
 )
 
 const (
@@ -108,7 +110,7 @@ func newHTTPServerConn(r *http.Request, w http.ResponseWriter, pi PeerInfo) Serv
 			param = pb
 		}
 		buf := bufpool.GetStd()
-		jzon.NewEncoder(buf).Encode(jsonrpcMessage{
+		json.NewEncoder(buf).Encode(jsonrpcMessage{
 			ID:     NewStringIDPtr(id),
 			Method: method_up,
 			Params: param,
diff --git a/http_client.go b/http_client.go
index 2693f34..605155d 100644
--- a/http_client.go
+++ b/http_client.go
@@ -3,14 +3,17 @@ package jrpc
 import (
 	"bytes"
 	"context"
-	"encoding/json"
 	"io"
 	"net/http"
 	"net/url"
 	"sync"
+
+	"github.com/goccy/go-json"
 )
 
 func (hc *httpConn) doRequest(ctx context.Context, msg any) (io.ReadCloser, error) {
+	// TODO:
+	// the jsoniter encoder performs a lot better here, not sure why. (nearly 10%? maybe more)
 	body, err := jzon.Marshal(msg)
 	if err != nil {
 		return nil, err
diff --git a/json.go b/json.go
index e982dfd..a07782d 100644
--- a/json.go
+++ b/json.go
@@ -3,7 +3,6 @@ package jrpc
 import (
 	"bytes"
 	"context"
-	"encoding/json"
 	"errors"
 	"fmt"
 	"io"
@@ -13,8 +12,10 @@ import (
 	"sync"
 	"time"
 
+	stdjson "encoding/json"
+
 	"gfx.cafe/open/jrpc/wsjson"
-	jsoniter "github.com/json-iterator/go"
+	"github.com/goccy/go-json"
 )
 
 var jzon = wsjson.JZON
@@ -35,8 +36,6 @@ type jsonrpcMessage struct {
 	Result  json.RawMessage `json:"result,omitempty"`
 
 	Error *jsonError `json:"error,omitempty"`
-
-	sortKeys bool
 }
 
 func MakeCall(id int, method string, params []any) *JsonRpcMessage {
@@ -76,7 +75,7 @@ func (msg *jsonrpcMessage) namespace() string {
 }
 
 func (msg *jsonrpcMessage) String() string {
-	b, _ := jzon.Marshal(msg)
+	b, _ := json.Marshal(msg)
 	return string(b)
 }
 
@@ -90,11 +89,7 @@ func (msg *jsonrpcMessage) errorResponse(err error) *jsonrpcMessage {
 
 func (msg *jsonrpcMessage) response(result any) *jsonrpcMessage {
 	// do a funny marshaling
-	jz := jzon
-	if msg.sortKeys {
-		jz = wsjson.JSON
-	}
-	enc, err := jz.Marshal(result)
+	enc, err := jzon.Marshal(result)
 	if err != nil {
 		return msg.errorResponse(err)
 	}
@@ -200,11 +195,7 @@ func NewFuncCodec(
 // NewCodec creates a codec on the given connection. If conn implements ConnRemoteAddr, log
 // messages will use it to include the remote address of the connection.
 func NewCodec(conn Conn) ServerCodec {
-	// for some reason other json decoders are incompatible with our test suite
-	// pretty sure its how we handle EOFs and stuff
-	dec := json.NewDecoder(conn)
-	dec.UseNumber()
-	return NewFuncCodec(conn, func(v any) error {
+	encr := func(v any) error {
 		enc := jzon.BorrowStream(conn)
 		defer jzon.ReturnStream(enc)
 		enc.WriteVal(v)
@@ -214,8 +205,13 @@ func NewCodec(conn Conn) ServerCodec {
 			return enc.Error
 		}
 		return nil
-		//	return jzon.NewEncoder(conn).Encode(v)
-	}, dec.Decode, func() error {
+	}
+	// TODO:
+	// for some reason other json decoders are incompatible with our test suite
+	// pretty sure its how we handle EOFs and stuff
+	dec := stdjson.NewDecoder(conn)
+	dec.UseNumber()
+	return NewFuncCodec(conn, encr, dec.Decode, func() error {
 		return nil
 	})
 }
@@ -285,7 +281,10 @@ func parseMessage(raw json.RawMessage) ([]*jsonrpcMessage, bool) {
 		json.Unmarshal(raw, &msgs[0])
 		return msgs, false
 	}
-	dec := json.NewDecoder(bytes.NewReader(raw))
+	// TODO:
+	// for some reason other json decoders are incompatible with our test suite
+	// pretty sure its how we handle EOFs and stuff
+	dec := stdjson.NewDecoder(bytes.NewReader(raw))
 	dec.Token() // skip '['
 	var msgs []*jsonrpcMessage
 	for dec.More() {
@@ -336,11 +335,9 @@ func parsePositionalArguments(rawArgs json.RawMessage, types []reflect.Type) ([]
 	return args, nil
 }
 
-var jzpool = jsoniter.NewIterator(jzon).Pool()
-
 func parseArgumentArray(p json.RawMessage, types []reflect.Type) ([]reflect.Value, error) {
-	dec := jzpool.BorrowIterator(p)
-	defer jzpool.ReturnIterator(dec)
+	dec := jzon.BorrowIterator(p)
+	defer jzon.ReturnIterator(dec)
 	args := make([]reflect.Value, 0, len(types))
 	for i := 0; dec.ReadArray(); i++ {
 		if i >= len(types) {
diff --git a/protocol.go b/protocol.go
index c27ef3a..635a783 100644
--- a/protocol.go
+++ b/protocol.go
@@ -2,10 +2,10 @@ package jrpc
 
 import (
 	"context"
-	"encoding/json"
 	"errors"
 	"net/http"
 
+	json "github.com/goccy/go-json"
 	jsoniter "github.com/json-iterator/go"
 )
 
@@ -36,7 +36,7 @@ type Request struct {
 
 func NewRequest(ctx context.Context, id string, method string, params any) *Request {
 	r := &Request{ctx: ctx}
-	pms, _ := jzon.Marshal(params)
+	pms, _ := json.Marshal(params)
 	r.msg = jsonrpcMessage{
 		ID:     NewStringIDPtr(id),
 		Method: method,
@@ -55,7 +55,7 @@ func (r *Request) Params() json.RawMessage {
 
 func (r *Request) ParamSlice() []any {
 	var params []any
-	jzon.Unmarshal(r.msg.Params, &params)
+	json.Unmarshal(r.msg.Params, &params)
 	return params
 }
 
@@ -69,10 +69,10 @@ func (r *Request) Iter(fn func(j *jsoniter.Iterator) error) error {
 
 func (r *Request) ParamArray(a ...any) error {
 	var params []json.RawMessage
-	jzon.Unmarshal(r.msg.Params, &params)
+	json.Unmarshal(r.msg.Params, &params)
 	for idx, v := range params {
 		if len(v) > idx {
-			err := jzon.Unmarshal(v, &a[idx])
+			err := json.Unmarshal(v, &a[idx])
 			if err != nil {
 				return err
 			}
@@ -84,7 +84,7 @@ func (r *Request) ParamArray(a ...any) error {
 }
 
 func (r *Request) ParamInto(v any) error {
-	return jzon.Unmarshal(r.msg.Params, &v)
+	return json.Unmarshal(r.msg.Params, &v)
 }
 
 func (r *Request) Context() context.Context {
@@ -126,7 +126,6 @@ type ResponseWriterMsg struct {
 }
 
 type options struct {
-	sorted bool
 }
 
 func UpgradeToSubscription(w ResponseWriter, r *Request) (*Subscription, error) {
@@ -151,12 +150,6 @@ func (w *ResponseWriterMsg) Header() http.Header {
 }
 
 func (w *ResponseWriterMsg) Option(k string, v any) {
-	switch k {
-	case "sorted":
-		w.options.sorted = true
-	case "unsorted":
-		w.options.sorted = false
-	}
 }
 
 func (w *ResponseWriterMsg) Send(args any, e error) (err error) {
@@ -171,7 +164,6 @@ func (w *ResponseWriterMsg) Send(args any, e error) (err error) {
 	default:
 	}
 	w.msg = cm.response(args)
-	w.msg.sortKeys = w.options.sorted
 	return nil
 }
 
@@ -179,7 +171,7 @@ func (w *ResponseWriterMsg) Notify(args any) (err error) {
 	if w.s == nil || w.n == nil {
 		return ErrSubscriptionNotFound
 	}
-	bts, _ := jzon.Marshal(args)
+	bts, _ := json.Marshal(args)
 	err = w.n.send(w.s, bts)
 	if err != nil {
 		return err
diff --git a/subscription.go b/subscription.go
index 313cb26..52d0900 100644
--- a/subscription.go
+++ b/subscription.go
@@ -4,13 +4,14 @@ import (
 	"container/list"
 	"context"
 	"encoding/hex"
-	"encoding/json"
 	"errors"
 	"reflect"
 	"strings"
 	"sync"
 
 	"gfx.cafe/util/go/frand"
+
+	json "github.com/goccy/go-json"
 )
 
 const (
@@ -107,7 +108,7 @@ func (n *Notifier) CreateSubscription() *Subscription {
 // Notify sends a notification to the client with the given data as payload.
 // If an error occurs the RPC connection is closed and the error is returned.
 func (n *Notifier) Notify(id SubID, data interface{}) error {
-	enc, err := jzon.Marshal(data)
+	enc, err := json.Marshal(data)
 	if err != nil {
 		return err
 	}
@@ -157,7 +158,7 @@ func (n *Notifier) activate() error {
 }
 
 func (n *Notifier) send(sub *Subscription, data json.RawMessage) error {
-	params, _ := jzon.Marshal(&subscriptionResult{ID: string(sub.ID), Result: data})
+	params, _ := json.Marshal(&subscriptionResult{ID: string(sub.ID), Result: data})
 	ctx := context.Background()
 	return n.h.conn.WriteJSON(ctx, &jsonrpcMessage{
 		Method: n.namespace + notificationMethodSuffix,
@@ -180,7 +181,7 @@ func (s *Subscription) Err() <-chan error {
 
 // MarshalJSON marshals a subscription as its ID.
 func (s *Subscription) MarshalJSON() ([]byte, error) {
-	return jzon.Marshal(s.ID)
+	return json.Marshal(s.ID)
 }
 
 // ClientSubscription is a subscription established through the Client's Subscribe or
@@ -303,7 +304,7 @@ func (sub *ClientSubscription) forward() (unsubscribeServer bool, err error) {
 
 func (sub *ClientSubscription) unmarshal(result json.RawMessage) (interface{}, error) {
 	val := reflect.New(sub.etype)
-	err := jzon.Unmarshal(result, val.Interface())
+	err := json.Unmarshal(result, val.Interface())
 	return val.Elem().Interface(), err
 }
 
diff --git a/wire.go b/wire.go
index 4a13cae..0489515 100644
--- a/wire.go
+++ b/wire.go
@@ -1,8 +1,10 @@
 package jrpc
 
 import (
-	"encoding/json"
 	"fmt"
+	"strconv"
+
+	json "github.com/goccy/go-json"
 )
 
 // Version represents a JSON-RPC version.
@@ -21,13 +23,13 @@ var (
 
 // MarshalJSON implements json.Marshaler.
 func (version) MarshalJSON() ([]byte, error) {
-	return jzon.Marshal(Version)
+	return []byte(`"` + Version + `"`), nil
 }
 
 // UnmarshalJSON implements json.Unmarshaler.
 func (version) UnmarshalJSON(data []byte) error {
 	version := ""
-	if err := jzon.Unmarshal(data, &version); err != nil {
+	if err := json.Unmarshal(data, &version); err != nil {
 		return fmt.Errorf("failed to Unmarshal: %w", err)
 	}
 	if version != Version {
@@ -89,43 +91,30 @@ func (id *ID) Format(f fmt.State, r rune) {
 
 // get the raw message
 func (id *ID) RawMessage() json.RawMessage {
+	if id == nil {
+		return null
+	}
 	if id.null {
 		return null
 	}
 	if id.name != "" {
-		ans, err := jzon.Marshal(id.name)
-		if err == nil {
-			return ans
-		}
-	}
-	ans, err := jzon.Marshal(id.number)
-	if err == nil {
-		return ans
+		return json.RawMessage(`"` + id.name + `"`)
 	}
-	return nil
+	return strconv.AppendInt(make([]byte, 0, 8), id.number, 10)
 }
 
 // MarshalJSON implements json.Marshaler.
 func (id *ID) MarshalJSON() ([]byte, error) {
-	if id == nil {
-		return null, nil
-	}
-	if id.null {
-		return null, nil
-	}
-	if id.name != "" {
-		return jzon.Marshal(id.name)
-	}
-	return jzon.Marshal(id.number)
+	return id.RawMessage(), nil
 }
 
 // UnmarshalJSON implements json.Unmarshaler.
 func (id *ID) UnmarshalJSON(data []byte) error {
 	*id = ID{}
-	if err := jzon.Unmarshal(data, &id.number); err == nil {
+	if err := json.Unmarshal(data, &id.number); err == nil {
 		return nil
 	}
-	if err := jzon.Unmarshal(data, &id.name); err == nil {
+	if err := json.Unmarshal(data, &id.name); err == nil {
 		return nil
 	}
 	id.null = true
diff --git a/wsjson/wsjson.go b/wsjson/wsjson.go
index 01f607e..8862811 100644
--- a/wsjson/wsjson.go
+++ b/wsjson/wsjson.go
@@ -5,26 +5,12 @@ import (
 	"fmt"
 
 	"gfx.cafe/util/go/bufpool"
+	json "github.com/goccy/go-json"
 	jsoniter "github.com/json-iterator/go"
 	"nhooyr.io/websocket"
 )
 
-var jzon = jsoniter.Config{
-	IndentionStep:                 0,
-	MarshalFloatWith6Digits:       false,
-	EscapeHTML:                    true,
-	SortMapKeys:                   true,
-	UseNumber:                     false,
-	DisallowUnknownFields:         false,
-	TagKey:                        "",
-	OnlyTaggedField:               false,
-	ValidateJsonRawMessage:        false,
-	ObjectFieldMustBeSimpleString: false,
-	CaseSensitive:                 false,
-}.Froze()
-
-var JZON = jzon
-var JSON = jsoniter.Config{
+var JZON = jsoniter.Config{
 	IndentionStep:                 0,
 	MarshalFloatWith6Digits:       false,
 	EscapeHTML:                    true,
@@ -55,11 +41,10 @@ func read(ctx context.Context, c *websocket.Conn, v interface{}) (err error) {
 	if err != nil {
 		return err
 	}
-	err = jzon.NewDecoder(b).Decode(v)
+	err = json.NewDecoder(b).Decode(v)
 	if err != nil {
 		return fmt.Errorf("failed to unmarshal JSON: %w", err)
 	}
-
 	return nil
 }
 
@@ -74,8 +59,8 @@ func write(ctx context.Context, c *websocket.Conn, v interface{}) (err error) {
 	if err != nil {
 		return err
 	}
-	st := jzon.BorrowStream(w)
-	defer jzon.ReturnStream(st)
+	st := JZON.BorrowStream(w)
+	defer JZON.ReturnStream(st)
 	st.WriteVal(v)
 	err = st.Flush()
 	if err != nil {
-- 
GitLab