Coder Social home page Coder Social logo

francoispqt / gojay Goto Github PK

View Code? Open in Web Editor NEW
2.1K 38.0 113.0 5.74 MB

high performance JSON encoder/decoder with stream API for Golang

License: MIT License

Makefile 0.28% Go 99.72%
go golang decoder encoder perfomance json stream-decoder stream-processing

gojay's Introduction

Build Status codecov Go Report Card Go doc MIT License Sourcegraph stability-stable

GoJay

GoJay is a performant JSON encoder/decoder for Golang (currently the most performant, see benchmarks).

It has a simple API and doesn't use reflection. It relies on small interfaces to decode/encode structures and slices.

Gojay also comes with powerful stream decoding features and an even faster Unsafe API.

There is also a code generation tool to make usage easier and faster.

Why another JSON parser?

I looked at other fast decoder/encoder and realised it was mostly hardly readable static code generation or a lot of reflection, poor streaming features, and not so fast in the end.

Also, I wanted to build a decoder that could consume an io.Reader of line or comma delimited JSON, in a JIT way. To consume a flow of JSON objects from a TCP connection for example or from a standard output. Same way I wanted to build an encoder that could encode a flow of data to a io.Writer.

This is how GoJay aims to be a very fast, JIT stream parser with 0 reflection, low allocation with a friendly API.

Get started

go get github.com/francoispqt/gojay

Decoding

Decoding is done through two different API similar to standard encoding/json:

Example of basic stucture decoding with Unmarshal:

import "github.com/francoispqt/gojay"

type user struct {
    id int
    name string
    email string
}
// implement gojay.UnmarshalerJSONObject
func (u *user) UnmarshalJSONObject(dec *gojay.Decoder, key string) error {
    switch key {
    case "id":
        return dec.Int(&u.id)
    case "name":
        return dec.String(&u.name)
    case "email":
        return dec.String(&u.email)
    }
    return nil
}
func (u *user) NKeys() int {
    return 3
}

func main() {
    u := &user{}
    d := []byte(`{"id":1,"name":"gojay","email":"[email protected]"}`)
    err := gojay.UnmarshalJSONObject(d, u)
    if err != nil {
        log.Fatal(err)
    }
}

with Decode:

func main() {
    u := &user{}
    dec := gojay.NewDecoder(bytes.NewReader([]byte(`{"id":1,"name":"gojay","email":"[email protected]"}`)))
    err := dec.DecodeObject(d, u)
    if err != nil {
        log.Fatal(err)
    }
}

Unmarshal API

Unmarshal API decodes a []byte to a given pointer with a single function.

Behind the doors, Unmarshal API borrows a *gojay.Decoder resets its settings and decodes the data to the given pointer and releases the *gojay.Decoder to the pool when it finishes, whether it encounters an error or not.

If it cannot find the right Decoding strategy for the type of the given pointer, it returns an InvalidUnmarshalError. You can test the error returned by doing if ok := err.(InvalidUnmarshalError); ok {}.

Unmarshal API comes with three functions:

  • Unmarshal
func Unmarshal(data []byte, v interface{}) error
  • UnmarshalJSONObject
func UnmarshalJSONObject(data []byte, v gojay.UnmarshalerJSONObject) error
  • UnmarshalJSONArray
func UnmarshalJSONArray(data []byte, v gojay.UnmarshalerJSONArray) error

Decode API

Decode API decodes a []byte to a given pointer by creating or borrowing a *gojay.Decoder with an io.Reader and calling Decode methods.

Getting a *gojay.Decoder or Borrowing

You can either get a fresh *gojay.Decoder calling dec := gojay.NewDecoder(io.Reader) or borrow one from the pool by calling dec := gojay.BorrowDecoder(io.Reader).

After using a decoder, you can release it by calling dec.Release(). Beware, if you reuse the decoder after releasing it, it will panic with an error of type InvalidUsagePooledDecoderError. If you want to fully benefit from the pooling, you must release your decoders after using.

Example getting a fresh an releasing:

str := ""
dec := gojay.NewDecoder(strings.NewReader(`"test"`))
defer dec.Release()
if err := dec.Decode(&str); err != nil {
    log.Fatal(err)
}

Example borrowing a decoder and releasing:

str := ""
dec := gojay.BorrowDecoder(strings.NewReader(`"test"`))
defer dec.Release()
if err := dec.Decode(&str); err != nil {
    log.Fatal(err)
}

*gojay.Decoder has multiple methods to decode to specific types:

  • Decode
func (dec *gojay.Decoder) Decode(v interface{}) error
  • DecodeObject
func (dec *gojay.Decoder) DecodeObject(v gojay.UnmarshalerJSONObject) error
  • DecodeArray
func (dec *gojay.Decoder) DecodeArray(v gojay.UnmarshalerJSONArray) error
  • DecodeInt
func (dec *gojay.Decoder) DecodeInt(v *int) error
  • DecodeBool
func (dec *gojay.Decoder) DecodeBool(v *bool) error
  • DecodeString
func (dec *gojay.Decoder) DecodeString(v *string) error

All DecodeXxx methods are used to decode top level JSON values. If you are decoding keys or items of a JSON object or array, don't use the Decode methods.

Example:

reader := strings.NewReader(`"John Doe"`)
dec := NewDecoder(reader)

var str string
err := dec.DecodeString(&str)
if err != nil {
    log.Fatal(err)
}

fmt.Println(str) // John Doe

Structs and Maps

UnmarshalerJSONObject Interface

To unmarshal a JSON object to a structure, the structure must implement the UnmarshalerJSONObject interface:

type UnmarshalerJSONObject interface {
	UnmarshalJSONObject(*gojay.Decoder, string) error
	NKeys() int
}

UnmarshalJSONObject method takes two arguments, the first one is a pointer to the Decoder (*gojay.Decoder) and the second one is the string value of the current key being parsed. If the JSON data is not an object, the UnmarshalJSONObject method will never be called.

NKeys method must return the number of keys to Unmarshal in the JSON object or 0. If zero is returned, all keys will be parsed.

Example of implementation for a struct:

type user struct {
    id int
    name string
    email string
}
// implement UnmarshalerJSONObject
func (u *user) UnmarshalJSONObject(dec *gojay.Decoder, key string) error {
    switch key {
    case "id":
        return dec.Int(&u.id)
    case "name":
        return dec.String(&u.name)
    case "email":
        return dec.String(&u.email)
    }
    return nil
}
func (u *user) NKeys() int {
    return 3
}

Example of implementation for a map[string]string:

// define our custom map type implementing UnmarshalerJSONObject
type message map[string]string

// Implementing Unmarshaler
func (m message) UnmarshalJSONObject(dec *gojay.Decoder, k string) error {
	str := ""
	err := dec.String(&str)
	if err != nil {
		return err
	}
	m[k] = str
	return nil
}

// we return 0, it tells the Decoder to decode all keys
func (m message) NKeys() int {
	return 0
}

Arrays, Slices and Channels

To unmarshal a JSON object to a slice an array or a channel, it must implement the UnmarshalerJSONArray interface:

type UnmarshalerJSONArray interface {
	UnmarshalJSONArray(*gojay.Decoder) error
}

UnmarshalJSONArray method takes one argument, a pointer to the Decoder (*gojay.Decoder). If the JSON data is not an array, the Unmarshal method will never be called.

Example of implementation with a slice:

type testSlice []string
// implement UnmarshalerJSONArray
func (t *testSlice) UnmarshalJSONArray(dec *gojay.Decoder) error {
	str := ""
	if err := dec.String(&str); err != nil {
		return err
	}
	*t = append(*t, str)
	return nil
}

func main() {
	dec := gojay.BorrowDecoder(strings.NewReader(`["Tom", "Jim"]`))
	var slice testSlice
	err := dec.DecodeArray(&slice)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(slice) // [Tom Jim]
	dec.Release()
}

Example of implementation with a channel:

type testChannel chan string
// implement UnmarshalerJSONArray
func (c testChannel) UnmarshalJSONArray(dec *gojay.Decoder) error {
	str := ""
	if err := dec.String(&str); err != nil {
		return err
	}
	c <- str
	return nil
}

func main() {
	dec := gojay.BorrowDecoder(strings.NewReader(`["Tom", "Jim"]`))
	c := make(testChannel, 2)
	err := dec.DecodeArray(c)
	if err != nil {
		log.Fatal(err)
	}
	for i := 0; i < 2; i++ {
		fmt.Println(<-c)
	}
	close(c)
	dec.Release()
}

Example of implementation with an array:

type testArray [3]string
// implement UnmarshalerJSONArray
func (a *testArray) UnmarshalJSONArray(dec *Decoder) error {
	var str string
	if err := dec.String(&str); err != nil {
		return err
	}
	a[dec.Index()] = str
	return nil
}

func main() {
	dec := gojay.BorrowDecoder(strings.NewReader(`["Tom", "Jim", "Bob"]`))
	var a testArray
	err := dec.DecodeArray(&a)
	fmt.Println(a) // [Tom Jim Bob]
	dec.Release()
}

Other types

To decode other types (string, int, int32, int64, uint32, uint64, float, booleans), you don't need to implement any interface.

Example of encoding strings:

func main() {
    json := []byte(`"Jay"`)
    var v string
    err := gojay.Unmarshal(json, &v)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(v) // Jay
}

Decode values methods

When decoding a JSON object of a JSON array using UnmarshalerJSONObject or UnmarshalerJSONArray interface, the gojay.Decoder provides dozens of methods to Decode multiple types.

Non exhaustive list of methods available (to see all methods, check the godoc):

dec.Int
dec.Int8
dec.Int16
dec.Int32
dec.Int64
dec.Uint8
dec.Uint16
dec.Uint32
dec.Uint64
dec.String
dec.Time
dec.Bool
dec.SQLNullString
dec.SQLNullInt64

Encoding

Encoding is done through two different API similar to standard encoding/json:

Example of basic structure encoding with Marshal:

import "github.com/francoispqt/gojay"

type user struct {
	id    int
	name  string
	email string
}

// implement MarshalerJSONObject
func (u *user) MarshalJSONObject(enc *gojay.Encoder) {
	enc.IntKey("id", u.id)
	enc.StringKey("name", u.name)
	enc.StringKey("email", u.email)
}
func (u *user) IsNil() bool {
	return u == nil
}

func main() {
	u := &user{1, "gojay", "[email protected]"}
	b, err := gojay.MarshalJSONObject(u)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(string(b)) // {"id":1,"name":"gojay","email":"[email protected]"}
}

with Encode:

func main() {
	u := &user{1, "gojay", "[email protected]"}
	b := strings.Builder{}
	enc := gojay.NewEncoder(&b)
	if err := enc.Encode(u); err != nil {
		log.Fatal(err)
	}
	fmt.Println(b.String()) // {"id":1,"name":"gojay","email":"[email protected]"}
}

Marshal API

Marshal API encodes a value to a JSON []byte with a single function.

Behind the doors, Marshal API borrows a *gojay.Encoder resets its settings and encodes the data to an internal byte buffer and releases the *gojay.Encoder to the pool when it finishes, whether it encounters an error or not.

If it cannot find the right Encoding strategy for the type of the given value, it returns an InvalidMarshalError. You can test the error returned by doing if ok := err.(InvalidMarshalError); ok {}.

Marshal API comes with three functions:

  • Marshal
func Marshal(v interface{}) ([]byte, error)
  • MarshalJSONObject
func MarshalJSONObject(v gojay.MarshalerJSONObject) ([]byte, error)
  • MarshalJSONArray
func MarshalJSONArray(v gojay.MarshalerJSONArray) ([]byte, error)

Encode API

Encode API decodes a value to JSON by creating or borrowing a *gojay.Encoder sending it to an io.Writer and calling Encode methods.

Getting a *gojay.Encoder or Borrowing

You can either get a fresh *gojay.Encoder calling enc := gojay.NewEncoder(io.Writer) or borrow one from the pool by calling enc := gojay.BorrowEncoder(io.Writer).

After using an encoder, you can release it by calling enc.Release(). Beware, if you reuse the encoder after releasing it, it will panic with an error of type InvalidUsagePooledEncoderError. If you want to fully benefit from the pooling, you must release your encoders after using.

Example getting a fresh encoder an releasing:

str := "test"
b := strings.Builder{}
enc := gojay.NewEncoder(&b)
defer enc.Release()
if err := enc.Encode(str); err != nil {
    log.Fatal(err)
}

Example borrowing an encoder and releasing:

str := "test"
b := strings.Builder{}
enc := gojay.BorrowEncoder(b)
defer enc.Release()
if err := enc.Encode(str); err != nil {
    log.Fatal(err)
}

*gojay.Encoder has multiple methods to encoder specific types to JSON:

  • Encode
func (enc *gojay.Encoder) Encode(v interface{}) error
  • EncodeObject
func (enc *gojay.Encoder) EncodeObject(v gojay.MarshalerJSONObject) error
  • EncodeArray
func (enc *gojay.Encoder) EncodeArray(v gojay.MarshalerJSONArray) error
  • EncodeInt
func (enc *gojay.Encoder) EncodeInt(n int) error
  • EncodeInt64
func (enc *gojay.Encoder) EncodeInt64(n int64) error
  • EncodeFloat
func (enc *gojay.Encoder) EncodeFloat(n float64) error
  • EncodeBool
func (enc *gojay.Encoder) EncodeBool(v bool) error
  • EncodeString
func (enc *gojay.Encoder) EncodeString(s string) error

Structs and Maps

To encode a structure, the structure must implement the MarshalerJSONObject interface:

type MarshalerJSONObject interface {
	MarshalJSONObject(enc *gojay.Encoder)
	IsNil() bool
}

MarshalJSONObject method takes one argument, a pointer to the Encoder (*gojay.Encoder). The method must add all the keys in the JSON Object by calling Decoder's methods.

IsNil method returns a boolean indicating if the interface underlying value is nil or not. It is used to safely ensure that the underlying value is not nil without using Reflection.

Example of implementation for a struct:

type user struct {
	id    int
	name  string
	email string
}

// implement MarshalerJSONObject
func (u *user) MarshalJSONObject(enc *gojay.Encoder) {
	enc.IntKey("id", u.id)
	enc.StringKey("name", u.name)
	enc.StringKey("email", u.email)
}
func (u *user) IsNil() bool {
	return u == nil
}

Example of implementation for a map[string]string:

// define our custom map type implementing MarshalerJSONObject
type message map[string]string

// Implementing Marshaler
func (m message) MarshalJSONObject(enc *gojay.Encoder) {
	for k, v := range m {
		enc.StringKey(k, v)
	}
}

func (m message) IsNil() bool {
	return m == nil
}

Arrays and Slices

To encode an array or a slice, the slice/array must implement the MarshalerJSONArray interface:

type MarshalerJSONArray interface {
	MarshalJSONArray(enc *gojay.Encoder)
	IsNil() bool
}

MarshalJSONArray method takes one argument, a pointer to the Encoder (*gojay.Encoder). The method must add all element in the JSON Array by calling Decoder's methods.

IsNil method returns a boolean indicating if the interface underlying value is nil(empty) or not. It is used to safely ensure that the underlying value is not nil without using Reflection and also to in OmitEmpty feature.

Example of implementation:

type users []*user
// implement MarshalerJSONArray
func (u *users) MarshalJSONArray(enc *gojay.Encoder) {
	for _, e := range u {
		enc.Object(e)
	}
}
func (u *users) IsNil() bool {
	return len(u) == 0
}

Other types

To encode other types (string, int, float, booleans), you don't need to implement any interface.

Example of encoding strings:

func main() {
	name := "Jay"
	b, err := gojay.Marshal(name)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(string(b)) // "Jay"
}

Stream API

Stream Decoding

GoJay ships with a powerful stream decoder.

It allows to read continuously from an io.Reader stream and do JIT decoding writing unmarshalled JSON to a channel to allow async consuming.

When using the Stream API, the Decoder implements context.Context to provide graceful cancellation.

To decode a stream of JSON, you must call gojay.Stream.DecodeStream and pass it a UnmarshalerStream implementation.

type UnmarshalerStream interface {
	UnmarshalStream(*StreamDecoder) error
}

Example of implementation of stream reading from a WebSocket connection:

// implement UnmarshalerStream
type ChannelStream chan *user

func (c ChannelStream) UnmarshalStream(dec *gojay.StreamDecoder) error {
	u := &user{}
	if err := dec.Object(u); err != nil {
		return err
	}
	c <- u
	return nil
}

func main() {
	// get our websocket connection
	origin := "http://localhost/"
	url := "ws://localhost:12345/ws"
	ws, err := websocket.Dial(url, "", origin)
	if err != nil {
		log.Fatal(err)
	}
	// create our channel which will receive our objects
	streamChan := ChannelStream(make(chan *user))
	// borrow a decoder
	dec := gojay.Stream.BorrowDecoder(ws)
	// start decoding, it will block until a JSON message is decoded from the WebSocket
	// or until Done channel is closed
	go dec.DecodeStream(streamChan)
	for {
		select {
		case v := <-streamChan:
			// Got something from my websocket!
			log.Println(v)
		case <-dec.Done():
			log.Println("finished reading from WebSocket")
			os.Exit(0)
		}
	}
}

Stream Encoding

GoJay ships with a powerful stream encoder part of the Stream API.

It allows to write continuously to an io.Writer and do JIT encoding of data fed to a channel to allow async consuming. You can set multiple consumers on the channel to be as performant as possible. Consumers are non blocking and are scheduled individually in their own go routine.

When using the Stream API, the Encoder implements context.Context to provide graceful cancellation.

To encode a stream of data, you must call EncodeStream and pass it a MarshalerStream implementation.

type MarshalerStream interface {
	MarshalStream(enc *gojay.StreamEncoder)
}

Example of implementation of stream writing to a WebSocket:

// Our structure which will be pushed to our stream
type user struct {
	id    int
	name  string
	email string
}

func (u *user) MarshalJSONObject(enc *gojay.Encoder) {
	enc.IntKey("id", u.id)
	enc.StringKey("name", u.name)
	enc.StringKey("email", u.email)
}
func (u *user) IsNil() bool {
	return u == nil
}

// Our MarshalerStream implementation
type StreamChan chan *user

func (s StreamChan) MarshalStream(enc *gojay.StreamEncoder) {
	select {
	case <-enc.Done():
		return
	case o := <-s:
		enc.Object(o)
	}
}

// Our main function
func main() {
	// get our websocket connection
	origin := "http://localhost/"
	url := "ws://localhost:12345/ws"
	ws, err := websocket.Dial(url, "", origin)
	if err != nil {
		log.Fatal(err)
	}
	// we borrow an encoder set stdout as the writer,
	// set the number of consumer to 10
	// and tell the encoder to separate each encoded element
	// added to the channel by a new line character
	enc := gojay.Stream.BorrowEncoder(ws).NConsumer(10).LineDelimited()
	// instantiate our MarshalerStream
	s := StreamChan(make(chan *user))
	// start the stream encoder
	// will block its goroutine until enc.Cancel(error) is called
	// or until something is written to the channel
	go enc.EncodeStream(s)
	// write to our MarshalerStream
	for i := 0; i < 1000; i++ {
		s <- &user{i, "username", "[email protected]"}
	}
	// Wait
	<-enc.Done()
}

Unsafe API

Unsafe API has the same functions than the regular API, it only has Unmarshal API for now. It is unsafe because it makes assumptions on the quality of the given JSON.

If you are not sure if your JSON is valid, don't use the Unsafe API.

Also, the Unsafe API does not copy the buffer when using Unmarshal API, which, in case of string decoding, can lead to data corruption if a byte buffer is reused. Using the Decode API makes Unsafe API safer as the io.Reader relies on copy builtin method and Decoder will have its own internal buffer :)

Access the Unsafe API this way:

gojay.Unsafe.Unmarshal(b, v)

Benchmarks

Benchmarks encode and decode three different data based on size (small, medium, large).

To run benchmark for decoder:

cd $GOPATH/src/github.com/francoispqt/gojay/benchmarks/decoder && make bench

To run benchmark for encoder:

cd $GOPATH/src/github.com/francoispqt/gojay/benchmarks/encoder && make bench

Benchmark Results

Decode

Small Payload

benchmark code is here

benchmark data is here

ns/op bytes/op allocs/op
Std Library 2547 496 4
JsonIter 2046 312 12
JsonParser 1408 0 0
EasyJson 929 240 2
GoJay 807 256 2
GoJay-unsafe 712 112 1

Medium Payload

benchmark code is here

benchmark data is here

ns/op bytes/op allocs/op
Std Library 30148 2152 496
JsonIter 16309 2976 80
JsonParser 7793 0 0
EasyJson 7957 232 6
GoJay 4984 2448 8
GoJay-unsafe 4809 144 7

Large Payload

benchmark code is here

benchmark data is here

ns/op bytes/op allocs/op
JsonIter 210078 41712 1136
EasyJson 106626 160 2
JsonParser 66813 0 0
GoJay 52153 31241 77
GoJay-unsafe 48277 2561 76

Encode

Small Struct

benchmark code is here

benchmark data is here

ns/op bytes/op allocs/op
Std Library 1280 464 3
EasyJson 871 944 6
JsonIter 866 272 3
GoJay 543 112 1
GoJay-func 347 0 0

Medium Struct

benchmark code is here

benchmark data is here

ns/op bytes/op allocs/op
Std Library 5006 1496 25
JsonIter 2232 1544 20
EasyJson 1997 1544 19
GoJay 1522 312 14

Large Struct

benchmark code is here

benchmark data is here

ns/op bytes/op allocs/op
Std Library 66441 20576 332
JsonIter 35247 20255 328
EasyJson 32053 15474 327
GoJay 27847 9802 318

Contributing

Contributions are welcome :)

If you encounter issues please report it in Github and/or send an email at [email protected]

gojay's People

Contributors

adranwit avatar archdx avatar aseptianto avatar francoispqt avatar hyperjiang avatar lalajt avatar lorenzo-stoakes avatar m1ome avatar marcopaganini avatar nemosupremo avatar ogkevin avatar opb avatar thehamsta avatar verytable avatar y-f-u avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gojay's Issues

Code generation tool not working at all

go version
go version go1.10 windows/amd64

When I run this: gojay -s ./services/xxxxx/models.go -o models_gojay.go
I get a file that looks like this:

// Code generated by GoJay. DO NOT EDIT.

package pixel_common 

import "github.com/francoispqt/gojay"

No other content in the file.

When I run this: gojay -s ./services/xxxxx/models.go -t Stuff -o models_gojay.go
I get this:

2018/07/23 01:12:20 tag does not exist
2018/07/23 01:12:20 tag does not exist
2018/07/23 01:12:20 tag does not exist
...

Same situation with the package flag:
gojay github.com/xxxxx/ecm/services/xxxxx Stuff models_gojay.go gives the empty file.
gojay -p github.com/xxxxx/ecm/services/xxxxx -t Stuff -o models_gojay.go gives the tag does not exist errors.

Here is my models.go file:

package xxxxx

type Stuff struct {
	Thing string
}

Single-decimal float parse error

This diff produces a test error:

diff --git a/decode_number_float_test.go b/decode_number_float_test.go
index fc9dd82..18d7bf9 100644
--- a/decode_number_float_test.go
+++ b/decode_number_float_test.go
@@ -20,6 +20,11 @@ func TestDecoderFloat64(t *testing.T) {
                errType        interface{}
        }{
                {
+                       name:           "basic-float",
+                       json:           "1.1",
+                       expectedResult: 1.1,
+               },
+               {
                        name:           "basic-exponent-positive-positive-exp",
                        json:           "1e2",
                        expectedResult: 100,

The test output:

-- FAIL: TestDecoderFloat64 (0.00s)
    --- FAIL: TestDecoderFloat64/basic-float (0.00s)
        decode_number_float_test.go:277:
                        Error Trace:    decode_number_float_test.go:277
                        Error:          Expected nil, but got: "Invalid JSON, wrong char '1' found at position 0"
                        Test:           TestDecoderFloat64/basic-float
                        Messages:       Err must be nil
        decode_number_float_test.go:280:
                        Error Trace:    decode_number_float_test.go:280
                        Error:          Not equal:
                                        expected: 1.1e+06
                                        actual  : 0
                        Test:           TestDecoderFloat64/basic-float
                        Messages:       v must be equal to 1.100000
FAIL

This diff works:

diff --git a/decode_number_float_test.go b/decode_number_float_test.go
index fc9dd82..afd8cd8 100644
--- a/decode_number_float_test.go
+++ b/decode_number_float_test.go
@@ -20,6 +20,11 @@ func TestDecoderFloat64(t *testing.T) {
                errType        interface{}
        }{
                {
+                       name:           "basic-float",
+                       json:           "1.12",
+                       expectedResult: 1.12,
+               },
+               {
                        name:           "basic-exponent-positive-positive-exp",
                        json:           "1e2",
                        expectedResult: 100,

codegen: Trick fields

Hi.

Might be a tiny tiny use case, but it'd be awesome to be able to pass say gojay:"fieldname|altfieldname" and have gojay generate code around using whichever field it finds

The actual fields I'm looking at are 'ip_prefix' and 'ipv6_prefix' they parse the same as far as I'm concerned.

Perhaps some way to make the code gen extensible?

Perhaps some inspiration can be drawn from https://github.com/clipperhouse/gen?

Is there a way to get the raw bytes with the decoder + unmarshal interface

I have a complex data structure, for which I have all the interfaces defined to satisfy the UnmarshalerJSONObject interface. There is one object within this complex structure which is currently being handled by the standard lib UnmarshalJSON interface, where I mutated/normalize the bytes before unmarshaling. I cannot use UnmarshalerJSONObject for this object because UnmarshalJSONObject(dec *gojay.Decoder, k string) does not expose the bytes

I need a way to see if the value for the k is a string or []string, if it's a string I need to change the byte structure to be a []string

Example of the UnmarshalJSON I use which does not work well with UnmarshalerJSONObject

func (a *Foo) UnmarshalJSON(b []byte) error {
	var rawData map[string]interface{}
	err := json.Unmarshal(b, &rawData)
	if err != nil {
		return nil
	}

	writeJSON := func(buf *bytes.Buffer, key string, value []interface{}) {
		buf.WriteString(`"` + key + `":[`)
		for i, v := range value {
			buf.WriteString(`"` + v.(string) + `"`)
			if i+1 < len(value) {
				buf.WriteByte(',')
			}
		}
		buf.WriteString(`]`)
	}

	// allocate the buffer upfront
	buf := bytes.NewBuffer(make([]byte, 0, len(b)))
	buf.WriteByte('{')

	i, keysN := 1, len(rawData)
	for key, value := range rawData {
		switch rawData[key].(type) {
		case []interface{}:
			writeJSON(buf, key, value.([]interface{}))
		case string:
			// handle the case where the SDK sends seperated values
			parts := strings.Split(value.(string), ",")
			if len(parts) == 1 && len(parts[0]) == 0 {
				parts = []string{}
			}

			// create an interface slice for the method, for the most part this will always be a slice of 1
			slice := make([]interface{}, len(parts))
			for i := 0; i < len(parts); i++ {
				slice[i] = parts[i]
			}
			writeJSON(buf, key, slice)
		}
		if i < keysN {
			buf.WriteByte(',')
			i++
		}
	}
	buf.WriteByte('}')

	
	// avoid infinite recursion, create a type alias
	type temp Foo
	var tempFoo temp
	err = json.Unmarshal(buf.Bytes(), &tempFoo)
	if err != nil {
		return nil
	}
	// mutate a
	*a = Foo(tempFoo)
	return nil
}

^ oh as an FYI on this example, nil error returns are on purpose. If this object fails to unmarshal, it shouldn't break all the other objects in the complex structure this belongs to.

Within the UnmarshalerJSONObject I use the dec.Array as the data structure foo contains fields that are all of type []string

However, the value of the data can either be a single string, or a comma separated string, or an array. My custom unmarshaler handles all those permutations and ensures everything is of type []string to avoid a structure where the value is of type interface{}.

within the context of (s *Foo) UnmarshalJSONObject(dec *gojay.Decoder, k string)
each value is defined to spec

switch k {
	case "bar":
		var aSlice = Strings{}
		err := dec.Array(&aSlice)
		if err == nil && len(aSlice) > 0 {
			s.Bar = []string(aSlice)
		}
		return err
....
}

however dec.Array(&aSlice) doesn't allow there to be the chance that the data is of type string.
I've tried calling dec.String() first and then following back if err != nil to dec.Array(), but calling String() moves the reader forward and skips "bad" data, therefore calling dec.Array() after fails. Calling dec.Array() on a string type also fails with a non catchable error invalidUnmarshalErrorMsg, which is not bubbled up to err := dec.Array(&aSlice), which means one can't simply call dec.String() after. And because I haven't found a way to work with the bytes or call UnmarshalJSON within UnmarshalJSONObject I can't get the performance boost from calling

decoder := gojay.BorrowDecoder(reader)
defer decoder.Release()
err = decoder.DecodeObject(&v)

Because the data will be invalid for object Foo as a result of not being able to handle string types.

That also means I don't gain a real performance boost when calling

decoder := gojay.BorrowDecoder(reader)
defer decoder.Release()
err = decoder.Decode(&v)

Which uses the std lib as the the code always hits the case

case *interface{}:
    err = dec.decodeInterface(vt)

which uses the std lib underneath

if err = json.Unmarshal(object, i); err != nil {
  return err
}

Any thoughts?

Enhancement Request: Support Slices and Arrays as builtin types

Right now, having to create custom types for every single slice or array in your structs is pretty annoying.
Additionally, it can be annoying to have to cast it back to the original type, though golang does a good job of not requiring this most of the time.

Would it be possible to support slices and arrays natively, perhaps by forcing the logic into the UnmarshalJSONObject/MarshalJSONObject functions?

For example, instead of:

type Record struct {
	Loc Location   `json:"loc,omitempty"`
}

type Location []float64

func (l Location) UnmarshalJSONArray(dec *gojay.Decoder) error {
	fl := 0.0
	if err := dec.Float64(&fl); err != nil {
		return err
	}
	l = append(l, fl)
	return nil
}

func (p *Record) UnmarshalJSONObject(dec *gojay.Decoder, key string) error {
	switch key {
	case "loc":
		return dec.Array(&p.Loc)
	}
	return nil
}

Maybe something like this (rough pseudocode):

type Record struct {
	Loc []float64  `json:"loc,omitempty"`
}

func (p *Record) UnmarshalJSONObject(dec *gojay.Decoder, key string) error {
	switch key {
	case "loc":
		return dec.SliceFloat64(&p.Loc)
	}
	return nil
}

or

func (p *Record) UnmarshalJSONObject(dec *gojay.Decoder, key string) error {
	switch key {
	case "loc":
		return dec.ArrayFunc(&p.Loc, func(dec *gojay.Decoder) error {
			fl := 0.0
			if err := dec.Float64(&fl); err != nil {
				return err
			}
			l = append(l, fl)
			return nil
		})
	}
	return nil
}

Decode map[string]interface{}?

Is there a way to decode arbitrary objects as a map[string]interface{}?

It should be possible to decode any object with any keys/value structure (including deeply nested objects) (as stdlib JSON package allows).

If it's currently possible, I wasn't able to find an example in the readme, could you please let me know how it's done

Decoder.Interface does not work for booleans

The following code should print:

&main.anyMap{"a":123, "b":"foo"}
&main.anyMap{"a":true}

However, currently it fails for boolean values as follows:

&main.anyMap{"a":123, "b":"foo"}
2018/09/05 09:57:40 invalid character ' ' in literal true (expecting 'e')
exit status 1
package main

import (
	"fmt"
	"log"

	"github.com/francoispqt/gojay"
)

type anyMap map[string]interface{}

func (a *anyMap) UnmarshalJSONObject(dec *gojay.Decoder, key string) error {
	var i interface{}
	err := dec.Interface(&i)
	if err != nil {
		return err
	}
	(*a)[key] = i
	return nil
}

func (a *anyMap) NKeys() int {
	return 0
}

func main() {
	m := &anyMap{}
	d := []byte(`{"a": 123, "b": "foo"}`)

	err := gojay.UnmarshalJSONObject(d, m)
	if err != nil {
		log.Fatal(err.Error())
	}
	fmt.Printf("%#v\n", m)

	m2 := &anyMap{}
	d2 := []byte(`{"a": true}`)
	err = gojay.UnmarshalJSONObject(d2, m2)
	if err != nil {
		log.Fatal(err.Error())
	}
	fmt.Printf("%#v\n", m)
}

Streaming decoder hung

First, thank you for your work on gojay. I am seeing a 2-3x performance improvement over the standard library.

I encountered what I thought was a deadlock in the streaming decoder, but after further debugging I found that the process was not hung. Instead, the buffer size had been reduced to zero, which brought the streaming decoder to a screeching halt. I was going to submit a pull request, but noticed e5b786c in the list of commits.

When using go modules, the broken version is the version that all new projects will pull in as a dependency. Any chance a new release could be built to prevent others from running into this issue?

Umarshal float64 with one too many decimal places

Unmarshaling float64 with one too many many decimal places i.e. 0.0080660999999999997 gives me an error.

Simple example:

type ExampleStruct struct {
	Price  float64 `json:"price"`
}
// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject
func (v *ExampleStruct) UnmarshalJSONObject(dec *gojay.Decoder, k string) error {
	switch k {
	case "price":
		return dec.Float64(&v.Price)
	}
	return nil
}
// NKeys returns the number of keys to unmarshal
func (v *ExampleStruct) NKeys() int { return 1 }

func TestGojay(t *testing.T) {
	data:= []byte(`{"price": 0.0080660999999999997}`)
	value := new(ExampleStruct)
	err := gojay.Unmarshal(data, value)
	if err != nil {
		t.Error(err)
	}
}

Suggested solution bellow fixed my issue. Just added one more element to pow10uint64 (10^10) at the end:

var pow10uint64 = [21]uint64{
	0,
	1,
	10,
	100,
	1000,
	10000,
	100000,
	1000000,
	10000000,
	100000000,
	1000000000,
	10000000000,
	100000000000,
	1000000000000,
	10000000000000,
	100000000000000,
	1000000000000000,
	10000000000000000,
	100000000000000000,
	1000000000000000000,
	10000000000000000000,
}

Panic on malformed integers

Ran go-fuzz with the following fuzzer (based on an example in the README):

package fuzz

import (
	"github.com/francoispqt/gojay"
)

type user struct {
	id    int
	name  string
	email string
}

// implement gojay.UnmarshalerJSONObject
func (u *user) UnmarshalJSONObject(dec *gojay.Decoder, key string) error {
	switch key {
	case "id":
		return dec.Int(&u.id)
	case "name":
		return dec.String(&u.name)
	case "email":
		return dec.String(&u.email)
	}
	return nil
}
func (u *user) NKeys() int {
	return 3
}

func Fuzz(input []byte) int {
	u := &user{}
	err := gojay.UnmarshalJSONObject(input, u)
	if err != nil {
		return 0
	}
	return 1
}

This is meant to reflect a deployment where the input JSON comes from an untrusted source.

After a few minutes, the fuzzer found issues like the following:
this

{\"id\":0.E----

causes

panic: runtime error: index out of range

goroutine 1 [running]:
github.com/francoispqt/gojay.(*Decoder).atoi64(0xc42005a060, 0xc, 0xc, 0x0)
	/tmp/go-fuzz-build108984608/gopath/src/github.com/francoispqt/gojay/decode_number_int.go:673 +0x485
github.com/francoispqt/gojay.(*Decoder).getExponent(0xc42005a060, 0x0)
	/tmp/go-fuzz-build108984608/gopath/src/github.com/francoispqt/gojay/decode_number.go:105 +0x260
github.com/francoispqt/gojay.(*Decoder).getExponent(0xc42005a060, 0x0)
	/tmp/go-fuzz-build108984608/gopath/src/github.com/francoispqt/gojay/decode_number.go:91 +0x1ab
github.com/francoispqt/gojay.(*Decoder).getExponent(0xc42005a060, 0x568880)
	/tmp/go-fuzz-build108984608/gopath/src/github.com/francoispqt/gojay/decode_number.go:91 +0x1ab
github.com/francoispqt/gojay.(*Decoder).getExponent(0xc42005a060, 0x8)
	/tmp/go-fuzz-build108984608/gopath/src/github.com/francoispqt/gojay/decode_number.go:91 +0x1ab
github.com/francoispqt/gojay.(*Decoder).getInt64(0xc42005a060, 0x300000030, 0xc420000180, 0xc420047d80, 0x42f089)
	/tmp/go-fuzz-build108984608/gopath/src/github.com/francoispqt/gojay/decode_number_int.go:611 +0x618
github.com/francoispqt/gojay.(*Decoder).decodeInt(0xc42005a060, 0xc42007a750, 0x5, 0x0)
	/tmp/go-fuzz-build108984608/gopath/src/github.com/francoispqt/gojay/decode_number_int.go:24 +0x4d1
github.com/francoispqt/gojay.(*Decoder).Int(0xc42005a060, 0xc42007a750, 0x2, 0xa)
	/tmp/go-fuzz-build108984608/gopath/src/github.com/francoispqt/gojay/decode.go:332 +0x51
github.com/francoispqt/gojay/fuzz.(*user).UnmarshalJSONObject(0xc42007a750, 0xc42005a060, 0xc4200140e2, 0x2, 0x0, 0x0)
	/tmp/go-fuzz-build108984608/gopath/src/github.com/francoispqt/gojay/fuzz/fuzz.go:17 +0x1a2
github.com/francoispqt/gojay.(*Decoder).decodeObject(0xc42005a060, 0x4f54a0, 0xc42007a750, 0xc4200140e0, 0xc, 0xc)
	/tmp/go-fuzz-build108984608/gopath/src/github.com/francoispqt/gojay/decode_object.go:58 +0x6df
github.com/francoispqt/gojay.UnmarshalJSONObject(0x7f54c5bc5000, 0xc, 0x200000, 0x4f54a0, 0xc42007a750, 0x0, 0x0)
	/tmp/go-fuzz-build108984608/gopath/src/github.com/francoispqt/gojay/decode.go:43 +0x128
github.com/francoispqt/gojay/fuzz.Fuzz(0x7f54c5bc5000, 0xc, 0x200000, 0x4ac3e7)
	/tmp/go-fuzz-build108984608/gopath/src/github.com/francoispqt/gojay/fuzz/fuzz.go:31 +0x81
go-fuzz-dep.Main(0x4e9598)
	/tmp/go-fuzz-build108984608/goroot/src/go-fuzz-dep/main.go:49 +0xad
main.main()
	/tmp/go-fuzz-build108984608/gopath/src/github.com/francoispqt/gojay/fuzz/go.fuzz.main/main.go:10 +0x2d

The problem here seems to be Decoder's Int() method isn't robust against this input? I'd expect the method to fail with a error indicating that the input isn't a well-formed string representation of an integer.

Usafe use of unsafe that leads to data corruption

This library main perfomance gain based in unsafe hack in decoding strings:

gojay/decode_string.go

Lines 23 to 25 in cce191b

// we do minus one to remove the last quote
d := dec.data[start : end-1]
*v = *(*string)(unsafe.Pointer(&d))

It's will lead to very hard debugging problems with data corruption in some use cases with reuse raw data source bytes.

This behaviour must be specified in the documentation, becuse it's limits library usege ( as general purpose decoder replacement ) and makes benchmarks not really fair.

Here is th demo:

package main

import (
	"bytes"
	"fmt"
	"github.com/francoispqt/gojay"
	"net/http"
	"net/http/httptest"
	"sync"
)

var (
	users   = []*user{}
	bufPool = sync.Pool{
		New: func() interface{} {
			// this line must be printed once
			// to be sure that we reuse previous buffer
			fmt.Println("make new buf")
			return bytes.NewBuffer(make([]byte, 0, 64))
		},
	}
)

type user struct {
	email string
}

func (u *user) UnmarshalObject(dec *gojay.Decoder, key string) error {
	switch key {
	case "email":
		return dec.AddString(&u.email)
	}
	return nil
}

func (u *user) NKeys() int {
	return 1
}

func main() {
	postData := bytes.NewBufferString(`{"id":1,"name":"gojay","email":"[email protected]"}`)
	req := httptest.NewRequest("POST", "http://example.com/", postData)
	w := httptest.NewRecorder()
	ProcessUser(w, req)

	postData = bytes.NewBufferString(`{"id":1,"name":"go","email":"[email protected]"}`)
	req = httptest.NewRequest("POST", "http://example.com/", postData)
	w = httptest.NewRecorder()
	ProcessUser(w, req)

	// ----------------------------------
	// expected equal users
	fmt.Println(users[0], users[1])
	// got
	// &{[email protected]"}m} &{[email protected]}
	// ----------------------------------
}

func ProcessUser(w http.ResponseWriter, r *http.Request) {
	buf := bufPool.Get().(*bytes.Buffer)
	buf.Reset() // now we will reuse same bytes buffer from previous request

	// users[0].email (previous user) uses memory from this buffer underliying bytes
	// overriding it will corrupt users[0].email
	// because now there will be different data
	buf.ReadFrom(r.Body)
	defer r.Body.Close()

	newUser := &user{}
	gojay.UnmarshalObject(buf.Bytes(), newUser)
	users = append(users, newUser)

	bufPool.Put(buf)
}

Bug: go install not working

When using the command
go install github.com/francoispqt/gojay/gojay

This error is returned

github.com/viant/toolbox

../../../viant/toolbox/yaml.go:13:10: undefined: yaml.NewEncoder

Mac go version
go version go1.12.5 darwin/amd64

Encoder SetEscapeHTML

gojay should support SetEscapeHTML on the Encoder, and should default to true to match the standard library.

For example:

type Stuff struct {
	Thing string
}

v := &Stuff{Thing: `hello <&,.='"'=.,&> world`}

buffer := &bytes.Buffer{}
encoder := gojay.NewEncoder(buffer)
encoder.SetEscapeHTML(false)
_ = encoder.Encode(v)

Should result in: {"Thing":"hello <&,.='\"'=.,&> world"}

While encoder.SetEscapeHTML(true) should result in: {"Thing":"hello \u003c\u0026,.='\"'=.,\u0026\u003e world"}
This should also be the default result when using gojay.Marshal

Panic on malformed floats

Did some fuzzing on ca0442d with:

package fuzz

import (
	"errors"

	"github.com/francoispqt/gojay"
)

type jsonFloat float32

var badKey = errors.New("bad key")

func (f *jsonFloat) UnmarshalJSONObject(dec *gojay.Decoder, key string) error {
	if key == "n" {
		return dec.Float32((*float32)(f))
	}
	return badKey
}
func (f *jsonFloat) NKeys() int {
	return 1
}

func Fuzz(input []byte) int {
	var f jsonFloat
	err := gojay.UnmarshalJSONObject(input, &f)
	if err != nil {
		return 0
	}
	return 1
}

Found cases like

{"n":-5e-80

caused panics in float parsing

panic: runtime error: index out of range

goroutine 1 [running]:
github.com/francoispqt/gojay.(*Decoder).getFloat32(0xc42005a060, 0x200000003, 0xc420000180, 0xc420020000)
	/tmp/go-fuzz-build454068747/gopath/src/github.com/francoispqt/gojay/decode_number_float.go:266 +0xb06
github.com/francoispqt/gojay.(*Decoder).getFloat32Negative(0xc42005a060, 0x40d989, 0x4, 0xc420047ed0)
	/tmp/go-fuzz-build454068747/gopath/src/github.com/francoispqt/gojay/decode_number_float.go:194 +0xa9
github.com/francoispqt/gojay.(*Decoder).decodeFloat32(0xc42005a060, 0xc420014108, 0x4, 0x0)
	/tmp/go-fuzz-build454068747/gopath/src/github.com/francoispqt/gojay/decode_number_float.go:164 +0x1e9
github.com/francoispqt/gojay.(*Decoder).Float32(0xc42005a060, 0xc420014108, 0x1, 0x9)
	/tmp/go-fuzz-build454068747/gopath/src/github.com/francoispqt/gojay/decode.go:442 +0x51
github.com/francoispqt/gojay/fuzz.(*jsonFloat).UnmarshalJSONObject(0xc420014108, 0xc42005a060, 0xc420014112, 0x1, 0x0, 0x0)
	/tmp/go-fuzz-build454068747/gopath/src/github.com/francoispqt/gojay/fuzz/fuzz.go:15 +0xd2
github.com/francoispqt/gojay.(*Decoder).decodeObject(0xc42005a060, 0x4f5460, 0xc420014108, 0xc420014110, 0xb, 0xb)
	/tmp/go-fuzz-build454068747/gopath/src/github.com/francoispqt/gojay/decode_object.go:58 +0x6df
github.com/francoispqt/gojay.UnmarshalJSONObject(0x7fd3e1e19000, 0xb, 0x200000, 0x4f5460, 0xc420014108, 0x0, 0x0)
	/tmp/go-fuzz-build454068747/gopath/src/github.com/francoispqt/gojay/decode.go:43 +0x128
github.com/francoispqt/gojay/fuzz.Fuzz(0x7fd3e1e19000, 0xb, 0x200000, 0x3)
	/tmp/go-fuzz-build454068747/gopath/src/github.com/francoispqt/gojay/fuzz/fuzz.go:25 +0x81
go-fuzz-dep.Main(0x4e9588)
	/tmp/go-fuzz-build454068747/goroot/src/go-fuzz-dep/main.go:49 +0xad
main.main()
	/tmp/go-fuzz-build454068747/gopath/src/github.com/francoispqt/gojay/fuzz/go.fuzz.main/main.go:10 +0x2d

Looks like the check https://github.com/francoispqt/gojay/blob/master/decode_number_float.go#L261 needs to use the magnitude of exp to handle the negative case?

Also, the following input

{"n":0.

gave

panic: runtime error: index out of range

goroutine 1 [running]:
github.com/francoispqt/gojay.(*Decoder).atoi32(0xc42005a060, 0x7, 0x5, 0xc400000000)
	/tmp/go-fuzz-build454068747/gopath/src/github.com/francoispqt/gojay/decode_number_int.go:812 +0x440
github.com/francoispqt/gojay.(*Decoder).getFloat32(0xc42005a060, 0x40d989, 0x4, 0xc420047ed0)
	/tmp/go-fuzz-build454068747/gopath/src/github.com/francoispqt/gojay/decode_number_float.go:248 +0x8d1
github.com/francoispqt/gojay.(*Decoder).decodeFloat32(0xc42005a060, 0xc4200b07fc, 0x4, 0x0)
	/tmp/go-fuzz-build454068747/gopath/src/github.com/francoispqt/gojay/decode_number_float.go:156 +0x359
github.com/francoispqt/gojay.(*Decoder).Float32(0xc42005a060, 0xc4200b07fc, 0x1, 0x5)
	/tmp/go-fuzz-build454068747/gopath/src/github.com/francoispqt/gojay/decode.go:442 +0x51
github.com/francoispqt/gojay/fuzz.(*jsonFloat).UnmarshalJSONObject(0xc4200b07fc, 0xc42005a060, 0xc4200b0802, 0x1, 0x0, 0x0)
	/tmp/go-fuzz-build454068747/gopath/src/github.com/francoispqt/gojay/fuzz/fuzz.go:15 +0xd2
github.com/francoispqt/gojay.(*Decoder).decodeObject(0xc42005a060, 0x4f5460, 0xc4200b07fc, 0xc4200b0800, 0x7, 0x7)
	/tmp/go-fuzz-build454068747/gopath/src/github.com/francoispqt/gojay/decode_object.go:58 +0x6df
github.com/francoispqt/gojay.UnmarshalJSONObject(0x7fc76d580000, 0x7, 0x200000, 0x4f5460, 0xc4200b07fc, 0x0, 0x0)
	/tmp/go-fuzz-build454068747/gopath/src/github.com/francoispqt/gojay/decode.go:43 +0x128
github.com/francoispqt/gojay/fuzz.Fuzz(0x7fc76d580000, 0x7, 0x200000, 0x3)
	/tmp/go-fuzz-build454068747/gopath/src/github.com/francoispqt/gojay/fuzz/fuzz.go:25 +0x81
go-fuzz-dep.Main(0x4e9588)
	/tmp/go-fuzz-build454068747/goroot/src/go-fuzz-dep/main.go:49 +0xad
main.main()
	/tmp/go-fuzz-build454068747/gopath/src/github.com/francoispqt/gojay/fuzz/go.fuzz.main/main.go:10 +0x2d

Gojay supports for sql.Nulls but does not generate them

Currently, one could use sql.Null.. to encode/decode structs.

e.g

	DB           sql.NullString `gojay:"db"`

	enc.SQLNullStringKey("db", &v.DB)

however, when a struct with such field gets generated via the Gojay gen tool, the bindings for these fields are not generated.

What is the difference between dec.String() and dec.DecodeString()

I've noticed that in many cases using dec.DecodeString() eventually leads to InvalidJson error, while parsing the same json and using dec.String() works perfectly. The only difference between them that I see is

dec.called |= 1

Same with DecodeInt vs Int, DecodeArray vs Array and etc.

Can you, please, help me understand why do you need them both.

Usage for structs that has interfaces as fields (`interface{}`)

There are some cases where the JSON data could have different shapes, in those cases we would use an interface{} to define a field within a struct.

Is it possible for gojay to Decode fields of a struct that are interfaces{}?

As for instance, this example of an error that has a description with a number of sections that could have different shapes:

type Error struct {
	Class       string               `json:"class"`
	Message     string               `json:"message"`
	Backtrace   []string             `json:"backtrace"`
	Description Description          `json:"description"`
}
type Description struct {
	Title    string                   `json:"title"`
	Sections []map[string]interface{} `json:"sections"`
}

How would you implement the UnmarshalJSONObject() function?

string unmarshal does not recognize esacpe sequences

Test:

import (
"testing"

"github.com/francoispqt/gojay"
)

func TestGC(t *testing.T) {
var a string = "d:\\test \\r go to next line" // also `d:\test`
var b string

t.Log(a)  // >> d:\test \r go to next line

a_json, _ := gojay.Marshal(a)

t.Log(string(a_json)) // ok >> "d:\\test \\r go to next line"  

gojay.Unmarshal(a_json, &b)

t.Log(b) // ERR >> d:        est
//go to next line
}

Please also check if
"this is a \t tab this is a path d:\\test"
works.
There seems to be en error in parseEscapedString.
Could not realy find it - dec.cursor is increased twice - maybe this helps.
If You remove this line, simple escape sequences are handled correct:

start := dec.cursor
for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { 
	if dec.data[dec.cursor] != '\\' {
		d := dec.data[dec.cursor]
// >>>>	dec.cursor = dec.cursor + 1            <<<< this may be to much
		nSlash := dec.cursor - start

Periodically flush the `buf` to the `Writer`.

Hi and thanks for the lib.

My goal is to start pushing JSON to an io.Writer while it's being JIT encoded. I noticed that EncodeObject calls enc.Write() and manages the error, that MarshalJSONObject doesn't return an error and that when calling enc.Write() myself (from within a MarshalJSONObject() func), there's no place to bubble up the error.

Surely I'm doing something a bit off here.. but I simply want to flush the buf between rows (say I have 200,000 such rows, which are objects). I also wonder how the buf would handle the getPreviousRune if I were to Write() before my Array is closed.

any tips would help, thanks a lot!

Example of encoding structs with nested array

There are no examples of how to encode/decode []string when it's nested in a struct. For example:

type MyStruct struct {
Field1 string
Field2 string
FieldArray []string
}

If I implement the interface for marshaling and object and encoding each field within that implementation it gives me an error when I try to use: enc.ArrayKey or AddArrayKey. Is there an example which could be provided as I'm a bit stuck at the moment

Encoding/Decoding time.Time

It would be nice if there was an example showing the best approach for encoding and decoding time.Time values. I have searched this repository but can't find any reference on how to accomplish this.

Say I have the following object that maps to a SQL database table, how would I go about encoding/decoding the timestamp fields?

type User struct {
	ID        uint64
	Username  string
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt *time.Time
}

No Marshal indent?

Looks like it's not possible to do the marshaling with indentation. Please consider adding this as a feature

How should I handle NaN in floating numbers?

If I have a struct with a floating point number field like:

type A struct {
    B float64
}

func (a *A) MarshalJSONObject(enc *gojay.Encoder) {
	enc.Float64Key("b", a.B)
}

func (a *A) IsNil() bool {
	return a == nil
}

And I assign it with NaN like:

a := &A{B: math.NaN()}

Running this through the standard library's Marshal method will fail with Error: json: unsupported value: NaN:

_, e := json.Marshal(a)
fmt.Println("Error:", e)

But using GoJay's Marshal method produces an invalid JSON {"b":NaN}:

b, e := gojay.Marshal(a)
fmt.Println(string(b))

How would you recommend handling this situation?

Use code generator only for unmarshal?

I would like to only generate code related to unmarshalling of a data type as I would not use the marshalling bits.

Is there a flag or do I need to delete manually the code?

What is the suggested way to decode into an array?

There is a section in docs https://github.com/francoispqt/gojay#arrays-slices-and-channels describing how to decode JSON object to a slice, an array or a channel with examples for a slice and a channel.

User has to implement UnmarshalerJSONArray interface consisting of a single function UnmarshalJSONArray which is called on each element of a collection.

Provided example for a slice (where user can simply append each new element to receiver):

type testSlice []string
// implement UnmarshalerJSONArray
func (t *testStringArr) UnmarshalJSONArray(dec *gojay.Decoder) error {
	str := ""
	if err := dec.String(&str); err != nil {
		return err
	}
	*t = append(*t, str)
	return nil
}

The problem with an array is that there is no (or is there?) way to get current index of unmarshalled array element.

Of course user can maintain this index himself:

type testArray struct {
	arr       [3]string
	decodeIdx int
}

func (a *testArray) UnmarshalJSONArray(dec *gojay.Decoder) error {
	str := ""
	if err := dec.String(&str); err != nil {
		return err
	}
	a.arr[a.decodeIdx] = str
	a.decodeIdx++
	// handle overflow
	return nil
}

Is this the suggested way or i'm missing more elegant/concise/idiomatic solution?

expected 'IDENT', found '/'

When generating a struct with 1 string field or any number of fields, i manage to get

4:9: expected 'IDENT', found '/'
error.

e.g.

type Show struct { 
 SomeString string
}

and then

//go:generate gojay -s $GOFILE -p true -t Show -o $GOFILE.gojay.go

On top of the file generates that error.

Any ideas what might be causing this?

Proposal: support for embedded / raw json

Some kind of embedded json saved handled as raw []byte is usefull.
If the type of value does not matter or is unknown at this point of computation. Taking a modified json rpc request as example for test, shows the benefits. A router is interested in id and method. The method itself is interested in params. So params should not be routers problem. I made the changes, so it works for me. It would be great, if the code could be reviewed and added in some way.

Proposed code for decoder.go:

// Decode reads the next JSON-encoded value from its input and stores it in the value pointed to by v.
//
// See the documentation for Unmarshal for details about the conversion of JSON into a Go value.
func (dec *Decoder) Decode(v interface{}) error {
	if dec.isPooled == 1 {
		panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder"))
	}
	switch vt := v.(type) {
	case *string:
		return dec.decodeString(vt)
	case *int:
		return dec.decodeInt(vt)
	case *int32:
		return dec.decodeInt32(vt)
	case *uint32:
		return dec.decodeUint32(vt)
	case *int64:
		return dec.decodeInt64(vt)
	case *uint64:
		return dec.decodeUint64(vt)
	case *float64:
		return dec.decodeFloat64(vt)
	case *bool:
		return dec.decodeBool(vt)
	case UnmarshalerObject:
		_, err := dec.decodeObject(vt)
		return err
	case UnmarshalerArray:
		_, err := dec.decodeArray(vt)
		return err
	case *EmbeddedJson:    // <-----------------------------------------  NEW
		return dec.decodeEmbeddedJson(vt)     // <------------  NEW
	default:
		return InvalidUnmarshalError(fmt.Sprintf(invalidUnmarshalErrorMsg, reflect.TypeOf(vt).String()))
	}
}


// >> NEW >>

type EmbeddedJson []byte

func (dec *Decoder) decodeEmbeddedJson(ej *EmbeddedJson) error {
	var err error
	if ej == nil {
		ej = &EmbeddedJson{}
	}
	beginOfEmbeddedJson := dec.cursor
	for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
		switch dec.data[dec.cursor] {
		case ' ', '\n', '\t', '\r', ',':
			continue
		// is null
		case 'n', 't':
			dec.cursor = dec.cursor + 4
		// is false
		case 'f':
			dec.cursor = dec.cursor + 5
		// is an object
		case '{':
			dec.cursor = dec.cursor + 1
			dec.cursor, err = dec.skipObject()
		// is string
		case '"':
			dec.cursor = dec.cursor + 1
			err = dec.skipString()                    // why no new dec.cursor in result?
		// is array
		case '[':
			dec.cursor = dec.cursor + 1
			dec.cursor, err = dec.skipArray()
		case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-':
			dec.cursor, err = dec.skipNumber()
		}
		break
	}
	if err == nil {
		if dec.cursor-1 > beginOfEmbeddedJson {
			*ej = append(*ej, dec.data[beginOfEmbeddedJson:dec.cursor]...)
		}
	}
	return err
}

func (dec *Decoder) AddEmbeddedJson(v *EmbeddedJson) error {
	err := dec.decodeEmbeddedJson(v)
	if err != nil {
		return err
	}
	dec.called |= 1
	return nil
}

// << NEW <<

QUICK TEST:

type Request struct {
	id     string
	method string
	params EmbeddedJson
	more   int
}

func (r *Request) UnmarshalObject(dec *Decoder, key string) error {
	switch key {
	case "id":
		return dec.AddString(&r.id)
	case "method":
		return dec.AddString(&r.method)
	case "params":
		return dec.AddEmbeddedJson(&r.params)
	case "more":
		dec.AddInt(&r.more)
	}
	return nil
}

func (r *Request) NKeys() int {
	return 4
}

func TestEmbeddedJson(t *testing.T) {
	json := []byte(`{"id":"someid","method":"getmydata","params":{"example":"of raw data"}, "more":123}`)
	request := &Request{}
	Unmarshal(json, request)
	t.Log(request)
	t.Log(string(request.params))
}

I know test is not complete - but shows, what the benefits are.
For json rpc the params are important inside of the method using them.
This change would make things simpler (simpler ist better ;-)


ENCODER:

func Marshal(v interface{}) ([]byte, error) {
	switch vt := v.(type) {
	case MarshalerObject:
		enc := BorrowEncoder(nil)
		defer enc.Release()
		return enc.encodeObject(vt)
	case MarshalerArray:
		enc := BorrowEncoder(nil)
		defer enc.Release()
		return enc.encodeArray(vt)
	case string:
		enc := BorrowEncoder(nil)
		defer enc.Release()
		return enc.encodeString(vt)
	case bool:
		enc := BorrowEncoder(nil)
		defer enc.Release()
		return enc.encodeBool(vt)
	case int:
		enc := BorrowEncoder(nil)
		defer enc.Release()
		return enc.encodeInt(vt)
	case int64:
		enc := BorrowEncoder(nil)
		defer enc.Release()
		return enc.encodeInt64(vt)
	case int32:
		enc := BorrowEncoder(nil)
		defer enc.Release()
		return enc.encodeInt(int(vt))
	case int16:
		enc := BorrowEncoder(nil)
		defer enc.Release()
		return enc.encodeInt(int(vt))
	case int8:
		enc := BorrowEncoder(nil)
		defer enc.Release()
		return enc.encodeInt(int(vt))
	case uint64:
		enc := BorrowEncoder(nil)
		defer enc.Release()
		return enc.encodeInt(int(vt))
	case uint32:
		enc := BorrowEncoder(nil)
		defer enc.Release()
		return enc.encodeInt(int(vt))
	case uint16:
		enc := BorrowEncoder(nil)
		defer enc.Release()
		return enc.encodeInt(int(vt))
	case uint8:
		enc := BorrowEncoder(nil)
		defer enc.Release()
		return enc.encodeInt(int(vt))
	case float64:
		enc := BorrowEncoder(nil)
		defer enc.Release()
		return enc.encodeFloat(vt)
	case float32:
		enc := BorrowEncoder(nil)
		defer enc.Release()
		return enc.encodeFloat32(vt)
	case *EmbeddedJson:                   // << ------------ NEW
		enc := BorrowEncoder(nil)         // << ------------ NEW
		defer enc.Release()               // << ------------ NEW
		return enc.encodeEmbeddedJson(vt) // << ------------ NEW
	default:
		return nil, InvalidMarshalError(fmt.Sprintf(invalidMarshalErrorMsg, reflect.TypeOf(vt).String()))
	}
}

// >> NEW >>

func (enc *Encoder) encodeEmbeddedJson(v *EmbeddedJson) ([]byte, error) {
	enc.writeBytes(*v)
	return enc.buf, nil
}

func (enc *Encoder) AddEmbeddedJson(v *EmbeddedJson) {
	enc.grow(len(*v) + 4)
	r, ok := enc.getPreviousRune()
	if ok && r != '[' {
		enc.writeByte(',')
	}
	enc.writeBytes(*v)
}

func (enc *Encoder) AddEmbeddedJsonOmitEmpty(v *EmbeddedJson) {
	if v == nil || len(*v) == 0 {
		return
	}
	r, ok := enc.getPreviousRune()
	if ok && r != '[' {
		enc.writeByte(',')
	}
	enc.writeBytes(*v)
}

func (enc *Encoder) AddEmbeddedJsonKey(key string, v *EmbeddedJson) {
	enc.grow(len(key) + len(*v) + 5)
	r, ok := enc.getPreviousRune()
	if ok && r != '{' {
		enc.writeByte(',')
	}
	enc.writeByte('"')
	enc.writeStringEscape(key)
	enc.writeBytes(objKey)
	enc.writeBytes(*v)
}

func (enc *Encoder) AddEmbeddedJsonKeyOmitEmpty(key string, v *EmbeddedJson) {
	if v == nil || len(*v) == 0 {
		return
	}
	enc.grow(len(key) + len(*v) + 5)
	r, ok := enc.getPreviousRune()
	if ok && r != '{' {
		enc.writeByte(',')
	}
	enc.writeByte('"')
	enc.writeStringEscape(key)
	enc.writeBytes(objKey)
	enc.writeBytes(*v)
}


// << NEW <<

Encoder, Decoder Test:

type Request struct {
	id     string
	method string
	params EmbeddedJson
	more   int
}

func (r *Request) UnmarshalObject(dec *Decoder, key string) error {
	switch key {
	case "id":
		return dec.AddString(&r.id)
	case "method":
		return dec.AddString(&r.method)
	case "params":
		return dec.AddEmbeddedJson(&r.params)

	case "more":
		dec.AddInt(&r.more)
	}
	return nil
}

func (r *Request) NKeys() int {
	return 4
}

func (r *Request) MarshalObject(enc *Encoder) {
	enc.AddStringKeyOmitEmpty("id", r.id)
	enc.AddStringKey("method", r.method)
	enc.AddEmbeddedJsonKey("params", &r.params)
	enc.AddIntKeyOmitEmpty("more", r.more)
}
func (r *Request) IsNil() bool {
	return r == nil
}

var request = &Request{}

func TestDecodeEmbeddedJson(t *testing.T) {
	json := []byte(`{"id":"someid","method":"getmydata","params":{"example":"of raw data"},"more":123}`)
	Unmarshal(json, request)
	t.Log(request)
	t.Log(string(request.params))
}

func TestEncodeEmbeddedJson(t *testing.T) {
	json := `{"id":"someid","method":"getmydata","params":{"example":"of raw data"},"more":123}`
	marshalled, err := MarshalObject(request)
	if err != nil {
		t.Error(err)
	}
	t.Log(string(marshalled))
	if string(marshalled) != json {
		t.Error("bad", string(marshalled), " != ", json)
	}
}

Code generation giving bad key strings

This example code:

type Record struct {
	TS   int64   `json:"ts,omitempty"`  // Timestamp in seconds
	RID  string  `json:"rid,omitempty"`  // R-ID
}

Gives this generated code:

func (v *Record) UnmarshalJSONObject(dec *gojay.Decoder, k string) error {
	switch k {
	case "tS":
		return dec.Int64(&v.TS)
	case "rID":
		return dec.String(&v.RID)
	}
	return nil
}

func (v *Record) MarshalJSONObject(enc *gojay.Encoder) {
	enc.Int64Key("tS", v.TS)
	enc.StringKey("rID", v.RID)
}

When it should give:

func (v *Record) UnmarshalJSONObject(dec *gojay.Decoder, k string) error {
	switch k {
	case "tS":
		return dec.Int64(&v.TS)
	case "rID":
		return dec.String(&v.RID)
	}
	return nil
}

func (v *Record) MarshalJSONObject(enc *gojay.Encoder) {
	enc.Int64Key("ts", v.TS)
	enc.StringKeyOmitEmpty("rid", v.RID)
}

The code generator should be pulling the json tag and parsing it to determine the correct key string, as well as other things like omitempty, or fields to skip (-).

Trouble Unmarshalling Array of JSON Objects

Hi there,

I'm trying to unmarshal/decode an array of objects, but I'm afraid it's either unimplemented or I'm doing something incorrectly.

Boilerplate for my struct:

type bucketAgg struct {
	IP         string
	Count      int
	ReportTime gojay.EmbeddedJSON
}

func (b *bucketAgg) UnmarshalJSONObject(dec *gojay.Decoder, key string) error {
	switch key {
	case "key":
		return dec.String(&b.IP)
	case "doc_count":
		return dec.Int(&b.Count)
	case "ReportTime":
		return dec.AddEmbeddedJSON(&b.ReportTime)
	}
	return nil
}

func (b *bucketAgg) NKeys() int {
	return 3
}

type bucketChan chan *bucketAgg

func (bc bucketChan) UnmarshalJSONArray(dec *gojay.Decoder) error {
	b := &bucketAgg{}
	err := dec.Decode(b)
	if err != nil {
		return err
	}
	bc <- b
	return nil
}

Test Code:

func TestUnmarshal(t *testing.T) {
	body := []byte(`{"key":"67.13X.XXX.XXX","doc_count":116,"ReportTime":{"value":1.528330747E12,"value_as_string":"2018-06-07T00:19:07.000Z"}}`)
	ba := &bucketAgg{}
	err := gojay.UnmarshalJSONObject(body, ba)
	if err != nil {
		t.Error(err)
	}
	fmt.Printf("%+v\n", ba)
	fmt.Println(string(ba.ReportTime))
}

func TestUnmarshalJsonArray(t *testing.T) {
	var wg = sync.WaitGroup{}
	body := []byte(
		`
[
  {"key":"67.13X.XXX.XXX","doc_count":116,"ReportTime":
    {"value":1.528330747E12,"value_as_string":"2018-06-07T00:19:07.000Z"}
  },
  {"key":"67.25X.XXX.XXX","doc_count":34,"ReportTime":"
    {"value":1.528335168E12,"value_as_string":"2018-06-07T01:32:48.000Z"}"
  }
]
`,
	)
	bc := make(bucketChan)
	wg.Add(1)
	go func() {
		defer wg.Done()
		for r := range bc {
			fmt.Println("Result: ", r)
		}
	}()
	err := gojay.UnmarshalJSONArray(body, bc)
	close(bc)
	wg.Wait()
	if err != nil {
		t.Error(err)
	}
}

The former passes, the latter fails:

=== RUN   TestUnmarshalJsonArray
Result:  &{67.13X.XXX.XXX 116 [123 34 118 97 108 117 101 34 58 49 46 53 50 56 51 51 48 55 52 55 69 49 50 44 34 118 97 108 117 101 95 97 115 95 115 116 114 105 110 103 34 58 34 50 48 49 56 45 48 54 45 48 55 84 48 48 58 49 57 58 48 55 46 48 48 48 90 34 125]}
--- FAIL: TestUnmarshalJsonArray (0.00s)
	.../api_test.go:52: Invalid JSON, wrong char '}' found at position 135
FAIL

I deviated from the docs since all of the array decoding was for standard types and used Decode. I also thought I had a possible issue using the undocumented EmbeddedJSON, but ran into the same problems when dropping that from my struct. I'm guessing that when the current chunk is finished decoding, it expects a ',', but gets the close bracket of the object instead.

Please let me know if I can provide any additional information or if I've made a glaring mistake.

Invalid JSON, wrong char ':' found at position xxxx

Hello,

I'm trying to parse a simple JSON file and somehow it always gives me an Invalid JSON message. Other parsers work fine. I'm unsure if this is a bug in gojay of if I'm doing something wrong.

I'm attaching a simple program that shows the problem:
gojay.go.gz

The source is also available here for immediate viewing:
https://repl.it/@marcopaganini/GojayUnableToParse

(Unfortunately, repl.it does not allow loading external libraries, so it's impossible to run it there).

Code generation tool should fail if it can't generate code for any fields

I ran the code generation tool against some of my structs, and discovered that it was missing several fields in the generated code.
The fields that were missing were Slices.

Yes, your documentation says those aren't supported.
But your code generation tool needs to at least output an error message for each non-supported public/exported field so that people don't assume everything is good.

10-decimal float32 parse error

There is a bug in parsing numbers with 10 decimal digits.
To reproduce, add next test case to TestDecoderFloat32

{
	name:           "10-decimal-digit-float",
	json:           "0.9833984375",
	expectedResult: 0.9833984,
},

output:

--- FAIL: TestDecoderFloat32 (0.00s)
    --- FAIL: TestDecoderFloat32/10-digit-float (0.00s)
        decode_number_float_test.go:925:
                Error Trace:    decode_number_float_test.go:925
                Error:          Expected nil, but got: "Cannot unmarshal JSON to type 'int32'"
                Test:           TestDecoderFloat32/10-decimal-digit-float
                Messages:       Err must be nil
        decode_number_float_test.go:928:
                Error Trace:    decode_number_float_test.go:928
                Error:          Not equal:
                                expected: 983398
                                actual  : 0
                Test:           TestDecoderFloat32/10-decimal-digit-float
                Messages:       v must be equal to 0.983398

First problem, it shouldn't fail. Second - error message "Cannot unmarshal JSON to type 'int32'" when parsing float32 is misleading (should it be moved to a separate issue?).

This particular error can be fixed by changing line 383 in decode_number_float.go (expI >= 11 instead of expI >= 12). I wanted to submit a pull request with the fix, however there is similar logic with numbers in exponential format (line 347) which probably should be changed too. IMO, there is not enough test cases for this part of the code to make such changes with confidence.

Infinite loop on decode stream

(Sorry for the empty post, i fat fingered the keyboard)
I've found an issue when I'm decoding an stream where gojay would go into an infinite loop on some chunks of the stream.
The code basically was this:

rawBytes, bufferErr := b.ReadBytes('\n')
entries := 1
breakOut := false
logger.Log.Debug("First line read, going into loop")
//TODO: Review loop code
for (bufferErr == io.EOF || bufferErr == nil) && !readCanceled && !breakOut {
    if len(rawBytes) > 0 {
        logger.Log.Debugf("Going to decode %d bytes", len(rawBytes))
        rawBuffer := bytes.NewBuffer(rawBytes)
        dec := gojay.Stream.BorrowDecoder(rawBuffer)
        
        err := dec.DecodeStream(streamChan)
        if err != nil {
            logger.Log.Errorf("error decoding \"%+v\" %+v", rawBytes, err)
        } else {
            streamChan <- m
        }
        dec.Release()
    }
    rawBytes, bufferErr = b.ReadBytes('\n')
    entries = entries + 1
    logger.Log.Debugf("Read %d bytes", len(rawBytes))
    if len(rawBytes) == 0 {
        if bufferErr == io.EOF {
            breakOut = true
        } else {
            logger.Log.Debug("No data but not at EOF. Error: %+v", bufferErr)
        }
    }
    logger.Log.Debugf("Exit? Err: %+v, RC: %+v, BO: %+v", bufferErr, readCanceled, breakOut)
}

Basically some chunks of "rawBuffer" would make the buffer end logic fail on https://github.com/francoispqt/gojay/blob/master/decode.go#L961-L990 and then the loop would get stuck.

I fixed by working with specific messages and putting the messages to a channel myself

gojay can not unmarsharing some valid json

JSON

{"table":"account","id":35,"type":"UPDATE"}

GOLANG

package publisher

import (
	"encoding/json"
	"github.com/francoispqt/gojay"
	"log"
	"strings"
	"testing"
)
type TableUpdateEvent struct {
	Table string `json:"table"`
	Id    int64 `json:"id"`
	Type  string `json:"type"`
}

func (t *TableUpdateEvent) UnmarshalJSONObject(dec *gojay.Decoder, key string) (err error) {
	log.Println("UnmarshalJSONObject",key)
	switch key {
	case "table":
		err := dec.DecodeString(&t.Table)
		if err != nil{
			log.Println("????")
		}
		return err
	case "id":
		return  dec.DecodeInt64(&t.Id)
	case "type":
		return dec.DecodeString(&t.Type)
	}
	return nil
}

func (t *TableUpdateEvent) NKeys() int {
	return 3
}

func TestA(t *testing.T){
	j := `{"table":"account","id":35,"type":"UPDATE"}`
	event := TableUpdateEvent{}
	err := gojay.UnmarshalJSONObject([]byte(j),&event)
	if err != nil{
		log.Println(err,strings.TrimSpace(j))
	}

	err = json.Unmarshal([]byte(j),&event)
	if err != nil{
		log.Println(err,strings.TrimSpace(j))
	}
	log.Println(event)
}

LOG

=== RUN   TestA
time="2019-08-18T07:34:27+09:00" level=info msg="UnmarshalJSONObject table"
2019/08/18 07:34:27 Invalid JSON, wrong char ':' found at position 23 {"table":"account","id":35,"type":"UPDATE"}
2019/08/18 07:34:27 {account 35 UPDATE}

Valid JSON throws error "Invalid JSON, wrong char ':' found"

Working with DecodeObject and UnmarshalerJSONObject interface, and I'm getting an error decoding JSON that I've validated with couple of different validators.

Using the attached issue.go and test.json, console reads, "Invalid JSON, wrong char ':' found at position 50". Using go version 1.10.3.

issue.zip

panic: close of closed channel

When i run

	enc := gojay.Stream.NewEncoder(buf).NConsumer(10).CommaDelimited()
	go enc.EncodeStream(processListCh)
	<- enc.Done()

in a loop of 1 sec interval, i manage to get

panic: close of closed channel

goroutine 591 [running]:
.../vendor/github.com/francoispqt/gojay.(*StreamEncoder).Cancel(0xc00014c570, 0x0, 0x0)
        .../vendor/github.com/francoispqt/gojay/encode_stream.go:133 +0xa3
...../vendor/github.com/francoispqt/gojay.consume(0xc00014c570, 0xc00014c720, 0x140f2a0, 0xc000122300)
        ..../vendor/github.com/francoispqt/gojay/encode_stream.go:199 +0xeb
created by .../vendor/github.com/francoispqt/gojay.(*StreamEncoder).EncodeStream
      ..../vendor/github.com/francoispqt/gojay/encode_stream.go:53 +0x1b3
exit status 2

This is caused by

gojay/encode_stream.go

Lines 124 to 136 in 90d9533

// Cancel cancels the consumers of the stream, interrupting the stream encoding.
//
// After calling cancel, Done() will return a closed channel.
func (s *StreamEncoder) Cancel(err error) {
s.mux.RLock()
select {
case <-s.done:
default:
s.err = err
close(s.done)
}
s.mux.RUnlock()
}

on line 133, however i've not managed to locate how it got closed right after the <-done check and before the close() call. However, i can constantly reproduce this ๐Ÿค” I have a feeling there should be a mutex with write lock to prevent this from happening.

concurrent unsafety of gojay.Marshal is not clearly documented

Hello,

I think that the fact that gojay.Marshal is not safe for concurrent usage from multiple goroutines should be explicitly mentioned both in the readme and godoc.

Now it is stated that Marshal borrows an encoder and releases it back no matter what. While an attentive reader will have realised that it introduces a race (because same encoder could be reused by another goroutine and the buffer will get corrupted), the race is easily overseen problem as everything seems to be working most of the time, so it should be mentioned (may be even in bold and with some exclamation marks).

Example program that shows the race exists:

package main

import (
	"log"
	"strconv"
	"time"

	"github.com/francoispqt/gojay"
)

func proeb(num int) {
	for {
		b, err := gojay.Marshal(num)
		if err != nil {
			log.Fatal(err)
		}

		s := string(b)
		if n, err := strconv.Atoi(s); err != nil || n != num {
			log.Printf("caught race: %v %v", s, num)
		}
	}
}

func main() {
	for i := 0; i < 100; i++ {
		go proeb(i)
	}
	time.Sleep(100 * time.Second)
}

Decoder is skipping validation for boolean values

Running the following code with the std decoder will return an error:

func main() {
	var b bool
	log.Println(json.Unmarshal([]byte(`tttt`), &b))
}

However, running the "same" code with gojay.Decoder will return no error. which is not good.

func main() {
	var b bool
	err := gojay.NewDecoder(strings.NewReader("tttt")).DecodeBool(&b)
	if err != nil {
		log.Fatal(err)
	}
	log.Println(b)
}

Looks like the problem is here.

Thanks for your work, let me know if you need any help to address that.

Decoder never releases any memory

I'm running into a issue where the decoder is never releasing any memory, and just contiously eats up more and more as you decode, with all the mentions of pooling and reusing decoders, i'm assuming this should not be the case?

code to reproduce (it eats up ram pretty fast so i wouldn't run it for more than a couple seconds):

package main

import (
	"bytes"
	"github.com/francoispqt/gojay"
	"log"
	"runtime"
	"time"
)

const encodedData = `{"a": 100, "b": "hello world"}`

type Data struct {
	A int64
	B string
}

// implement gojay.UnmarshalerJSONObject
func (d *Data) UnmarshalJSONObject(dec *gojay.Decoder, key string) error {
	switch key {
	case "a":
		return dec.Int64(&d.A)
	case "b":
		return dec.String(&d.B)
	}

	return nil
}

func (evt *Data) NKeys() int {
	return 0 // refuses to work without this being 0 for some reason
}

func main() {
	go printStats()

	RunWorker()
}

func RunWorker() {
	buf := &bytes.Buffer{}
	decoder := gojay.NewDecoder(buf)
	dst := &Data{}
	for {
		_, err := buf.WriteString(encodedData)
		if err != nil {
			log.Fatal(err)
                        return
		}

		err = decoder.Decode(dst)
		if err != nil {
			log.Fatal(err)
			return
		}

		buf.Reset()
	}
}

func printStats() {
	tc := time.NewTicker(time.Second)

	for {
		var memStats runtime.MemStats
		runtime.ReadMemStats(&memStats)
		log.Printf("Alloc: %5.1fMB, Sys: %5.1fMB", float64(memStats.Alloc)/1000000, float64(memStats.Sys)/1000000)
		<-tc.C
	}
}

Encoding/Decoding null values

Hello,

Currently, there is no way to encode or decode a NULL. Is there a reason for this?
Would you mind if it was added?

Thanks.

Problem with gojay generator

I'm trying to decode this structure

type MyVeryOwnMap map[string]string

type MyStruct struct {
	Properties   MyVeryOwnMap `gojay:"properties"`
}

In the end the gojay generator is trying to generate this code:

	enc.AnyKey("properties", v.Properties)

But that method is nowhere, How can I get gojay to encode (and decode) a map[string]string

Is the generator the right way of using this library?
Thanks!

How to Unmarshal to struct of uncertain type

If I have a stream of JSON messages coming in as []byte, with some messages having "foo" as a key and some with "bar" as key, how can I Unmarshal the "foo" messages into one type of struct and the "bar" messages into another?

type FooMessage struct {
    foo  int
    ... other fields ...
}

type BarMessage struct {
    bar  int
    ... other fields ...
}

Given the example in the README:

func main() {
    ...
    ws, err := websocket.Dial(url, "", origin)
    streamChan := ChannelStream(make(chan *user))
    dec := gojay.Stream.BorrowDecoder(ws)
    go dec.DecodeStream(streamChan)
}

I believe the logic needs to be in UnmarshalStream. However, I am not sure how to determine what value of what type should be passed to dec.Object.

Note: This is a simplified example, I need to solve this for >5 types of messages.

More compataibility with encoding/json

It'd be sweet to be more compatible with the encoding/json Decode interface

Specifically functions like

func (dec *Decoder) More() bool
func (dec *Decoder) Token() (Token, error)

Seem to be missing with no equivilent.

Useful for parsing giant streaming files (of a single object) and splitting them into multiple objects - or extracting an array.

eg: https://ip-ranges.amazonaws.com/ip-ranges.json

Would be parsed by skipping through tokens until we got to the prefixes array, then parsed as a stream of objects until done

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.