gojay

high performance JSON encoder/decoder with stream API for Golang
git clone git://git.lair.cx/gojay
Log | Files | Refs | README | LICENSE

commit 8cb756b5e68ee053e8bee7a987c5d7a300ce2667
parent 5585f956f2aab66fc16a51605f149308beaab032
Author: Francois Parquet <francois.parquet@gmail.com>
Date:   Sun, 20 May 2018 02:10:10 +0800

Merge pull request #26 from francoispqt/update/comply-rfc7159

add support of unicode parsing, refactor test suite, prevent data race in streams, ready for v1
Diffstat:
MREADME.md | 164++++++++++++++++++++++++++++++++++++++++----------------------------------------
Mbenchmarks/benchmarks_large.go | 24++++++++++++------------
Mbenchmarks/benchmarks_medium.go | 28++++++++++++++--------------
Mbenchmarks/benchmarks_small.go | 4++--
Mbenchmarks/decoder/decoder_bench_large_test.go | 4++--
Mbenchmarks/decoder/decoder_bench_medium_test.go | 4++--
Mbenchmarks/decoder/decoder_bench_small_test.go | 4++--
Mbenchmarks/decoder/decoder_large_test.go | 4+---
Mbenchmarks/encoder/encoder_bench_large_test.go | 4++--
Mbenchmarks/encoder/encoder_bench_medium_test.go | 4++--
Mbenchmarks/encoder/encoder_bench_small_test.go | 6+++---
Mdecode.go | 332++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Mdecode_array.go | 29++++++++++-------------------
Mdecode_array_test.go | 509+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Mdecode_bool.go | 44+++++++++++++++++++-------------------------
Mdecode_embedded_json_test.go | 2+-
Mdecode_number.go | 797++-----------------------------------------------------------------------------
Adecode_number_float.go | 223+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adecode_number_float_test.go | 417+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adecode_number_int.go | 793+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adecode_number_int_test.go | 1276+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdecode_number_test.go | 1146+------------------------------------------------------------------------------
Adecode_number_uint.go | 409+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adecode_number_uint_test.go | 686+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdecode_object.go | 54+++++++++++++++++++++++++++---------------------------
Mdecode_object_test.go | 955+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mdecode_pool.go | 7++++---
Mdecode_pool_test.go | 5+++++
Mdecode_stream_pool.go | 7++++---
Mdecode_stream_pool_test.go | 5+++++
Mdecode_stream_test.go | 4++--
Mdecode_string.go | 92+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Mdecode_string_test.go | 407+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Adecode_string_unicode.go | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdecode_test.go | 10+++++-----
Mdecode_unsafe.go | 57+++++++++++++++++++++++++++++++++++----------------------
Mdecode_unsafe_test.go | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mencode.go | 34+++++++++++++++++-----------------
Mencode_array.go | 56++++++++++++++++++++++++++++++++++++++++----------------
Mencode_array_test.go | 24++++++++++++------------
Mencode_bool.go | 33+++++++++++++++++++++++++++------
Mencode_embedded_json_test.go | 6+++---
Mencode_interface.go | 20++++++++++----------
Mencode_interface_test.go | 2+-
Mencode_number.go | 293-------------------------------------------------------------------------------
Aencode_number_float.go | 192+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aencode_number_int.go | 193+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mencode_number_test.go | 123+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aencode_number_uint.go | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mencode_object.go | 68+++++++++++++++++++++++++++++++++++++++++++++++---------------------
Mencode_object_test.go | 60++++++++++++------------------------------------------------
Mencode_stream.go | 34+++++++++++++++++++++++-----------
Mencode_stream_pool.go | 7+++++--
Mencode_stream_test.go | 18+++++++++++++++++-
Mencode_string.go | 34++++++++++++++++++++++++++++------
Merrors.go | 38+++++++++++++++++++++++++++++---------
Mexamples/encode-decode-map/main.go | 6+++---
Mexamples/http-json/main.go | 4++--
Mexamples/websocket/comm/comm.go | 10+++++-----
Agojay_test.go | 265+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
60 files changed, 7279 insertions(+), 3030 deletions(-)

diff --git a/README.md b/README.md @@ -50,15 +50,15 @@ type user struct { name string email string } -// implement UnmarshalerObject -func (u *user) UnmarshalObject(dec *gojay.Decoder, key string) error { +// implement UnmarshalerJSONObject +func (u *user) UnmarshalJSONObject(dec *gojay.Decoder, key string) error { switch key { case "id": - return dec.AddInt(&u.id) + return dec.Int(&u.id) case "name": - return dec.AddString(&u.name) + return dec.String(&u.name) case "email": - return dec.AddString(&u.email) + return dec.String(&u.email) } return nil } @@ -69,7 +69,7 @@ func (u *user) NKeys() int { func main() { u := &user{} d := []byte(`{"id":1,"name":"gojay","email":"gojay@email.com"}`) - err := gojay.UnmarshalObject(d, u) + err := gojay.UnmarshalJSONObject(d, u) if err != nil { log.Fatal(err) } @@ -102,14 +102,14 @@ Unmarshal API comes with three functions: func Unmarshal(data []byte, v interface{}) error ``` -* UnmarshalObject +* UnmarshalJSONObject ```go -func UnmarshalObject(data []byte, v UnmarshalerObject) error +func UnmarshalJSONObject(data []byte, v UnmarshalerJSONObject) error ``` -* UnmarshalArray +* UnmarshalJSONArray ```go -func UnmarshalArray(data []byte, v UnmarshalerArray) error +func UnmarshalJSONArray(data []byte, v UnmarshalerJSONArray) error ``` @@ -145,41 +145,41 @@ if err := dec.Decode(&str); err != nil { `*gojay.Decoder` has multiple methods to decode to specific types: * Decode ```go -func (dec *Decoder) Decode(v interface{}) error +func (dec *gojay.Decoder) Decode(v interface{}) error ``` * DecodeObject ```go -func (dec *Decoder) DecodeObject(v UnmarshalerObject) error +func (dec *gojay.Decoder) DecodeObject(v UnmarshalerJSONObject) error ``` * DecodeArray ```go -func (dec *Decoder) DecodeArray(v UnmarshalerArray) error +func (dec *gojay.Decoder) DecodeArray(v UnmarshalerJSONArray) error ``` * DecodeInt ```go -func (dec *Decoder) DecodeInt(v *int) error +func (dec *gojay.Decoder) DecodeInt(v *int) error ``` * DecodeBool ```go -func (dec *Decoder) DecodeBool(v *bool) error +func (dec *gojay.Decoder) DecodeBool(v *bool) error ``` * DecodeString ```go -func (dec *Decoder) DecodeString(v *string) error +func (dec *gojay.Decoder) DecodeString(v *string) error ``` ### Structs and Maps -#### UnmarshalerObject Interface +#### UnmarshalerJSONObject Interface -To unmarshal a JSON object to a structure, the structure must implement the UnmarshalerObject interface: +To unmarshal a JSON object to a structure, the structure must implement the UnmarshalerJSONObject interface: ```go -type UnmarshalerObject interface { - UnmarshalObject(*Decoder, string) error +type UnmarshalerJSONObject interface { + UnmarshalJSONObject(*gojay.Decoder, string) error NKeys() int } ``` -UnmarshalObject 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 UnmarshalObject method will never be called. +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. @@ -190,15 +190,15 @@ type user struct { name string email string } -// implement UnmarshalerObject -func (u *user) UnmarshalObject(dec *gojay.Decoder, key string) error { +// implement UnmarshalerJSONObject +func (u *user) UnmarshalJSONObject(dec *gojay.Decoder, key string) error { switch k { case "id": - return dec.AddInt(&u.id) + return dec.Int(&u.id) case "name": - return dec.AddString(&u.name) + return dec.String(&u.name) case "email": - return dec.AddString(&u.email) + return dec.String(&u.email) } return nil } @@ -209,13 +209,13 @@ func (u *user) NKeys() int { Example of implementation for a `map[string]string`: ```go -// define our custom map type implementing UnmarshalerObject +// define our custom map type implementing UnmarshalerJSONObject type message map[string]string // Implementing Unmarshaler -func (m message) UnmarshalObject(dec *gojay.Decoder, k string) error { +func (m message) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { str := "" - err := dec.AddString(&str) + err := dec.String(&str) if err != nil { return err } @@ -231,21 +231,21 @@ func (m message) NKeys() int { ### Arrays, Slices and Channels -To unmarshal a JSON object to a slice an array or a channel, it must implement the UnmarshalerArray interface: +To unmarshal a JSON object to a slice an array or a channel, it must implement the UnmarshalerJSONArray interface: ```go -type UnmarshalerArray interface { - UnmarshalArray(*Decoder) error +type UnmarshalerJSONArray interface { + UnmarshalJSONArray(*gojay.Decoder) error } ``` -UnmarshalArray 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. +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: ```go type testSlice []string -// implement UnmarshalerArray -func (t *testStringArr) UnmarshalArray(dec *gojay.Decoder) error { +// implement UnmarshalerJSONArray +func (t *testStringArr) UnmarshalJSONArray(dec *gojay.Decoder) error { str := "" - if err := dec.AddString(&str); err != nil { + if err := dec.String(&str); err != nil { return err } *t = append(*t, str) @@ -256,10 +256,10 @@ func (t *testStringArr) UnmarshalArray(dec *gojay.Decoder) error { Example of implementation with a channel: ```go type ChannelString chan string -// implement UnmarshalerArray -func (c ChannelArray) UnmarshalArray(dec *gojay.Decoder) error { +// implement UnmarshalerJSONArray +func (c ChannelArray) UnmarshalJSONArray(dec *gojay.Decoder) error { str := "" - if err := dec.AddString(&str); err != nil { + if err := dec.String(&str); err != nil { return err } c <- str @@ -298,11 +298,11 @@ type user struct { name string email string } -// implement MarshalerObject -func (u *user) MarshalObject(enc *gojay.Encoder) { - enc.AddIntKey("id", u.id) - enc.AddStringKey("name", u.name) - enc.AddStringKey("email", u.email) +// 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 @@ -310,7 +310,7 @@ func (u *user) IsNil() bool { func main() { u := &user{1, "gojay", "gojay@email.com"} - b, err := gojay.MarshalObject(u) + b, err := gojay.MarshalJSONObject(u) if err != nil { log.Fatal(err) } @@ -346,14 +346,14 @@ Marshal API comes with three functions: func Marshal(v interface{}) ([]byte, error) ``` -* MarshalObject +* MarshalJSONObject ```go -func MarshalObject(v MarshalerObject) ([]byte, error) +func MarshalJSONObject(v MarshalerJSONObject) ([]byte, error) ``` -* MarshalArray +* MarshalJSONArray ```go -func MarshalArray(v MarshalerArray) ([]byte, error) +func MarshalJSONArray(v MarshalerJSONArray) ([]byte, error) ``` ### Encode API @@ -390,47 +390,47 @@ if err := enc.Encode(str); err != nil { `*gojay.Encoder` has multiple methods to encoder specific types to JSON: * Encode ```go -func (enc *Encoder) Encode(v interface{}) error +func (enc *gojay.Encoder) Encode(v interface{}) error ``` * EncodeObject ```go -func (enc *Encoder) EncodeObject(v MarshalerObject) error +func (enc *gojay.Encoder) EncodeObject(v MarshalerJSONObject) error ``` * EncodeArray ```go -func (enc *Encoder) EncodeArray(v MarshalerArray) error +func (enc *gojay.Encoder) EncodeArray(v MarshalerJSONArray) error ``` * EncodeInt ```go -func (enc *Encoder) EncodeInt(n int) error +func (enc *gojay.Encoder) EncodeInt(n int) error ``` * EncodeInt64 ```go -func (enc *Encoder) EncodeInt64(n int64) error +func (enc *gojay.Encoder) EncodeInt64(n int64) error ``` * EncodeFloat ```go -func (enc *Encoder) EncodeFloat(n float64) error +func (enc *gojay.Encoder) EncodeFloat(n float64) error ``` * EncodeBool ```go -func (enc *Encoder) EncodeBool(v bool) error +func (enc *gojay.Encoder) EncodeBool(v bool) error ``` * EncodeString ```go -func (enc *Encoder) EncodeString(s string) error +func (enc *gojay.Encoder) EncodeString(s string) error ``` ### Structs and Maps -To encode a structure, the structure must implement the MarshalerObject interface: +To encode a structure, the structure must implement the MarshalerJSONObject interface: ```go -type MarshalerObject interface { - MarshalObject(enc *Encoder) +type MarshalerJSONObject interface { + MarshalJSONObject(enc *gojay.Encoder) IsNil() bool } ``` -MarshalObject 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. +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. @@ -441,11 +441,11 @@ type user struct { name string email string } -// implement MarshalerObject -func (u *user) MarshalObject(dec *gojay.Decoder, key string) { - dec.AddIntKey("id", u.id) - dec.AddStringKey("name", u.name) - dec.AddStringKey("email", u.email) +// implement MarshalerJSONObject +func (u *user) MarshalJSONObject(dec *gojay.Decoder, key string) { + dec.IntKey("id", u.id) + dec.StringKey("name", u.name) + dec.StringKey("email", u.email) } func (u *user) IsNil() bool { return u == nil @@ -454,13 +454,13 @@ func (u *user) IsNil() bool { Example of implementation for a `map[string]string`: ```go -// define our custom map type implementing MarshalerObject +// define our custom map type implementing MarshalerJSONObject type message map[string]string // Implementing Marshaler -func (m message) MarshalObject(enc *gojay.Encoder) { +func (m message) MarshalJSONObject(enc *gojay.Encoder) { for k, v := range m { - enc.AddStringKey(k, v) + enc.StringKey(k, v) } } @@ -470,24 +470,24 @@ func (m message) IsNil() bool { ``` ### Arrays and Slices -To encode an array or a slice, the slice/array must implement the MarshalerArray interface: +To encode an array or a slice, the slice/array must implement the MarshalerJSONArray interface: ```go -type MarshalerArray interface { - MarshalArray(enc *Encoder) +type MarshalerJSONArray interface { + MarshalJSONArray(enc *gojay.Encoder) IsNil() bool } ``` -MarshalArray 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. +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: ```go type users []*user -// implement MarshalerArray -func (u *users) MarshalArray(dec *Decoder) { +// implement MarshalerJSONArray +func (u *users) MarshalJSONArray(dec *gojay.Decoder) { for _, e := range u { - enc.AddObject(e) + enc.Object(e) } } func (u *users) IsNil() bool { @@ -534,7 +534,7 @@ type ChannelStream chan *user func (c ChannelStream) UnmarshalStream(dec *gojay.StreamDecoder) error { u := &user{} - if err := dec.AddObject(u); err != nil { + if err := dec.Object(u); err != nil { return err } c <- u @@ -591,10 +591,10 @@ type user struct { email string } -func (u *user) MarshalObject(enc *gojay.Encoder) { - enc.AddIntKey("id", u.id) - enc.AddStringKey("name", u.name) - enc.AddStringKey("email", u.email) +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 @@ -608,7 +608,7 @@ func (s StreamChan) MarshalStream(enc *gojay.StreamEncoder) { case <-enc.Done(): return case o := <-s: - enc.AddObject(o) + enc.Object(o) } } diff --git a/benchmarks/benchmarks_large.go b/benchmarks/benchmarks_large.go @@ -10,7 +10,7 @@ type DSUser struct { Username string } -func (m *DSUser) UnmarshalObject(dec *gojay.Decoder, key string) error { +func (m *DSUser) UnmarshalJSONObject(dec *gojay.Decoder, key string) error { switch key { case "username": return dec.AddString(&m.Username) @@ -23,7 +23,7 @@ func (m *DSUser) NKeys() int { func (m *DSUser) IsNil() bool { return m == nil } -func (m *DSUser) MarshalObject(enc *gojay.Encoder) { +func (m *DSUser) MarshalJSONObject(enc *gojay.Encoder) { enc.AddStringKey("username", m.Username) } @@ -32,7 +32,7 @@ type DSTopic struct { Slug string } -func (m *DSTopic) UnmarshalObject(dec *gojay.Decoder, key string) error { +func (m *DSTopic) UnmarshalJSONObject(dec *gojay.Decoder, key string) error { switch key { case "id": return dec.AddInt(&m.Id) @@ -47,20 +47,20 @@ func (m *DSTopic) NKeys() int { func (m *DSTopic) IsNil() bool { return m == nil } -func (m *DSTopic) MarshalObject(enc *gojay.Encoder) { +func (m *DSTopic) MarshalJSONObject(enc *gojay.Encoder) { enc.AddIntKey("id", m.Id) enc.AddStringKey("slug", m.Slug) } type DSTopics []*DSTopic -func (t *DSTopics) UnmarshalArray(dec *gojay.Decoder) error { +func (t *DSTopics) UnmarshalJSONArray(dec *gojay.Decoder) error { dsTopic := &DSTopic{} *t = append(*t, dsTopic) return dec.AddObject(dsTopic) } -func (m *DSTopics) MarshalArray(enc *gojay.Encoder) { +func (m *DSTopics) MarshalJSONArray(enc *gojay.Encoder) { for _, e := range *m { enc.AddObject(e) } @@ -74,7 +74,7 @@ type DSTopicsList struct { MoreTopicsUrl string } -func (m *DSTopicsList) UnmarshalObject(dec *gojay.Decoder, key string) error { +func (m *DSTopicsList) UnmarshalJSONObject(dec *gojay.Decoder, key string) error { switch key { case "topics": m.Topics = DSTopics{} @@ -92,20 +92,20 @@ func (m *DSTopicsList) IsNil() bool { return m == nil } -func (m *DSTopicsList) MarshalObject(enc *gojay.Encoder) { +func (m *DSTopicsList) MarshalJSONObject(enc *gojay.Encoder) { enc.AddArrayKey("users", &m.Topics) enc.AddStringKey("more_topics_url", m.MoreTopicsUrl) } type DSUsers []*DSUser -func (t *DSUsers) UnmarshalArray(dec *gojay.Decoder) error { +func (t *DSUsers) UnmarshalJSONArray(dec *gojay.Decoder) error { dsUser := DSUser{} *t = append(*t, &dsUser) return dec.AddObject(&dsUser) } -func (m *DSUsers) MarshalArray(enc *gojay.Encoder) { +func (m *DSUsers) MarshalJSONArray(enc *gojay.Encoder) { for _, e := range *m { enc.AddObject(e) } @@ -119,7 +119,7 @@ type LargePayload struct { Topics *DSTopicsList } -func (m *LargePayload) UnmarshalObject(dec *gojay.Decoder, key string) error { +func (m *LargePayload) UnmarshalJSONObject(dec *gojay.Decoder, key string) error { switch key { case "users": return dec.AddArray(&m.Users) @@ -135,7 +135,7 @@ func (m *LargePayload) NKeys() int { } //easyjson:json -func (m *LargePayload) MarshalObject(enc *gojay.Encoder) { +func (m *LargePayload) MarshalJSONObject(enc *gojay.Encoder) { enc.AddArrayKey("users", &m.Users) enc.AddObjectKey("topics", m.Topics) } diff --git a/benchmarks/benchmarks_medium.go b/benchmarks/benchmarks_medium.go @@ -101,7 +101,7 @@ type CBAvatar struct { Url string } -func (m *CBAvatar) UnmarshalObject(dec *gojay.Decoder, key string) error { +func (m *CBAvatar) UnmarshalJSONObject(dec *gojay.Decoder, key string) error { switch key { case "avatars": return dec.AddString(&m.Url) @@ -112,7 +112,7 @@ func (m *CBAvatar) NKeys() int { return 1 } -func (m *CBAvatar) MarshalObject(enc *gojay.Encoder) { +func (m *CBAvatar) MarshalJSONObject(enc *gojay.Encoder) { enc.AddStringKey("url", m.Url) } @@ -122,13 +122,13 @@ func (m *CBAvatar) IsNil() bool { type Avatars []*CBAvatar -func (t *Avatars) UnmarshalArray(dec *gojay.Decoder) error { +func (t *Avatars) UnmarshalJSONArray(dec *gojay.Decoder) error { avatar := CBAvatar{} *t = append(*t, &avatar) return dec.AddObject(&avatar) } -func (m *Avatars) MarshalArray(enc *gojay.Encoder) { +func (m *Avatars) MarshalJSONArray(enc *gojay.Encoder) { for _, e := range *m { enc.AddObject(e) } @@ -141,7 +141,7 @@ type CBGravatar struct { Avatars Avatars } -func (m *CBGravatar) UnmarshalObject(dec *gojay.Decoder, key string) error { +func (m *CBGravatar) UnmarshalJSONObject(dec *gojay.Decoder, key string) error { switch key { case "avatars": return dec.AddArray(&m.Avatars) @@ -152,7 +152,7 @@ func (m *CBGravatar) NKeys() int { return 1 } -func (m *CBGravatar) MarshalObject(enc *gojay.Encoder) { +func (m *CBGravatar) MarshalJSONObject(enc *gojay.Encoder) { enc.AddArrayKey("avatars", &m.Avatars) } @@ -164,7 +164,7 @@ type CBGithub struct { Followers int } -func (m *CBGithub) UnmarshalObject(dec *gojay.Decoder, key string) error { +func (m *CBGithub) UnmarshalJSONObject(dec *gojay.Decoder, key string) error { switch key { case "followers": return dec.AddInt(&m.Followers) @@ -176,7 +176,7 @@ func (m *CBGithub) NKeys() int { return 1 } -func (m *CBGithub) MarshalObject(enc *gojay.Encoder) { +func (m *CBGithub) MarshalJSONObject(enc *gojay.Encoder) { enc.AddIntKey("followers", m.Followers) } @@ -188,7 +188,7 @@ type CBName struct { FullName string `json:"fullName"` } -func (m *CBName) UnmarshalObject(dec *gojay.Decoder, key string) error { +func (m *CBName) UnmarshalJSONObject(dec *gojay.Decoder, key string) error { switch key { case "fullName": return dec.AddString(&m.FullName) @@ -200,7 +200,7 @@ func (m *CBName) NKeys() int { return 1 } -func (m *CBName) MarshalObject(enc *gojay.Encoder) { +func (m *CBName) MarshalJSONObject(enc *gojay.Encoder) { enc.AddStringKey("fullName", m.FullName) } @@ -214,7 +214,7 @@ type CBPerson struct { Gravatar *CBGravatar } -func (m *CBPerson) UnmarshalObject(dec *gojay.Decoder, key string) error { +func (m *CBPerson) UnmarshalJSONObject(dec *gojay.Decoder, key string) error { switch key { case "name": m.Name = &CBName{} @@ -233,7 +233,7 @@ func (m *CBPerson) NKeys() int { return 3 } -func (m *CBPerson) MarshalObject(enc *gojay.Encoder) { +func (m *CBPerson) MarshalJSONObject(enc *gojay.Encoder) { enc.AddObjectKey("name", m.Name) enc.AddObjectKey("github", m.Github) enc.AddObjectKey("gravatar", m.Gravatar) @@ -249,7 +249,7 @@ type MediumPayload struct { Company string `json:"company"` } -func (m *MediumPayload) UnmarshalObject(dec *gojay.Decoder, key string) error { +func (m *MediumPayload) UnmarshalJSONObject(dec *gojay.Decoder, key string) error { switch key { case "person": m.Person = &CBPerson{} @@ -264,7 +264,7 @@ func (m *MediumPayload) NKeys() int { return 2 } -func (m *MediumPayload) MarshalObject(enc *gojay.Encoder) { +func (m *MediumPayload) MarshalJSONObject(enc *gojay.Encoder) { enc.AddObjectKey("person", m.Person) // enc.AddStringKey("company", m.Company) } diff --git a/benchmarks/benchmarks_small.go b/benchmarks/benchmarks_small.go @@ -17,7 +17,7 @@ type SmallPayload struct { V int } -func (t *SmallPayload) MarshalObject(enc *gojay.Encoder) { +func (t *SmallPayload) MarshalJSONObject(enc *gojay.Encoder) { enc.AddIntKey("st", t.St) enc.AddIntKey("sid", t.Sid) enc.AddStringKey("tt", t.Tt) @@ -33,7 +33,7 @@ func (t *SmallPayload) IsNil() bool { return t == nil } -func (t *SmallPayload) UnmarshalObject(dec *gojay.Decoder, key string) error { +func (t *SmallPayload) UnmarshalJSONObject(dec *gojay.Decoder, key string) error { switch key { case "st": return dec.AddInt(&t.St) diff --git a/benchmarks/decoder/decoder_bench_large_test.go b/benchmarks/decoder/decoder_bench_large_test.go @@ -46,7 +46,7 @@ func BenchmarkGoJayDecodeObjLarge(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { result := benchmarks.LargePayload{} - gojay.UnmarshalObject(benchmarks.LargeFixture, &result) + gojay.UnmarshalJSONObject(benchmarks.LargeFixture, &result) } } @@ -54,6 +54,6 @@ func BenchmarkGoJayUnsafeDecodeObjLarge(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { result := benchmarks.LargePayload{} - gojay.Unsafe.UnmarshalObject(benchmarks.LargeFixture, &result) + gojay.Unsafe.UnmarshalJSONObject(benchmarks.LargeFixture, &result) } } diff --git a/benchmarks/decoder/decoder_bench_medium_test.go b/benchmarks/decoder/decoder_bench_medium_test.go @@ -54,7 +54,7 @@ func BenchmarkGoJayDecodeObjMedium(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { result := benchmarks.MediumPayload{} - err := gojay.UnmarshalObject(benchmarks.MediumFixture, &result) + err := gojay.UnmarshalJSONObject(benchmarks.MediumFixture, &result) if err != nil { b.Error(err) } @@ -64,7 +64,7 @@ func BenchmarkGoJayUnsafeDecodeObjMedium(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { result := benchmarks.MediumPayload{} - err := gojay.Unsafe.UnmarshalObject(benchmarks.MediumFixture, &result) + err := gojay.Unsafe.UnmarshalJSONObject(benchmarks.MediumFixture, &result) if err != nil { b.Error(err) } diff --git a/benchmarks/decoder/decoder_bench_small_test.go b/benchmarks/decoder/decoder_bench_small_test.go @@ -56,7 +56,7 @@ func BenchmarkGoJayDecodeObjSmall(b *testing.B) { b.ReportAllocs() for n := 0; n < b.N; n++ { result := benchmarks.SmallPayload{} - gojay.UnmarshalObject(benchmarks.SmallFixture, &result) + gojay.UnmarshalJSONObject(benchmarks.SmallFixture, &result) } } @@ -64,6 +64,6 @@ func BenchmarkGoJayUnsafeDecodeObjSmall(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { result := benchmarks.SmallPayload{} - gojay.Unsafe.UnmarshalObject(benchmarks.SmallFixture, &result) + gojay.Unsafe.UnmarshalJSONObject(benchmarks.SmallFixture, &result) } } diff --git a/benchmarks/decoder/decoder_large_test.go b/benchmarks/decoder/decoder_large_test.go @@ -1,7 +1,6 @@ package benchmarks import ( - "log" "testing" "github.com/francoispqt/gojay" @@ -11,11 +10,10 @@ import ( func TestGoJayDecodeObjLarge(t *testing.T) { result := benchmarks.LargePayload{} - err := gojay.UnmarshalObject(benchmarks.LargeFixture, &result) + err := gojay.UnmarshalJSONObject(benchmarks.LargeFixture, &result) assert.Nil(t, err, "err should be nil") assert.Len(t, result.Users, 32, "Len of users should be 32") for _, u := range result.Users { - log.Print(u) assert.True(t, len(u.Username) > 0, "User should have username") } assert.Len(t, result.Topics.Topics, 30, "Len of topics should be 30") diff --git a/benchmarks/encoder/encoder_bench_large_test.go b/benchmarks/encoder/encoder_bench_large_test.go @@ -41,14 +41,14 @@ func BenchmarkEasyJsonEncodeObjLarge(b *testing.B) { func BenchmarkGoJayEncodeLargeStruct(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - if _, err := gojay.MarshalObject(benchmarks.NewLargePayload()); err != nil { + if _, err := gojay.MarshalJSONObject(benchmarks.NewLargePayload()); err != nil { b.Fatal(err) } } } func TestGoJayEncodeLargeStruct(t *testing.T) { - if output, err := gojay.MarshalObject(benchmarks.NewLargePayload()); err != nil { + if output, err := gojay.MarshalJSONObject(benchmarks.NewLargePayload()); err != nil { t.Fatal(err) } else { log.Print(string(output)) diff --git a/benchmarks/encoder/encoder_bench_medium_test.go b/benchmarks/encoder/encoder_bench_medium_test.go @@ -42,14 +42,14 @@ func BenchmarkEasyJsonEncodeObjMedium(b *testing.B) { func BenchmarkGoJayEncodeMediumStruct(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - if _, err := gojay.MarshalObject(benchmarks.NewMediumPayload()); err != nil { + if _, err := gojay.MarshalJSONObject(benchmarks.NewMediumPayload()); err != nil { b.Fatal(err) } } } func TestGoJayEncodeMediumStruct(t *testing.T) { - if output, err := gojay.MarshalObject(benchmarks.NewMediumPayload()); err != nil { + if output, err := gojay.MarshalJSONObject(benchmarks.NewMediumPayload()); err != nil { t.Fatal(err) } else { log.Print(string(output)) diff --git a/benchmarks/encoder/encoder_bench_small_test.go b/benchmarks/encoder/encoder_bench_small_test.go @@ -42,7 +42,7 @@ func BenchmarkJsonIterEncodeSmallStruct(b *testing.B) { func BenchmarkGoJayEncodeSmallStruct(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - if _, err := gojay.MarshalObject(benchmarks.NewSmallPayload()); err != nil { + if _, err := gojay.MarshalJSONObject(benchmarks.NewSmallPayload()); err != nil { b.Fatal(err) } } @@ -51,7 +51,7 @@ func BenchmarkGoJayEncodeSmallStruct(b *testing.B) { func BenchmarkGoJayEncodeSmallFunc(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - if _, err := gojay.MarshalObject(gojay.EncodeObjectFunc(func(enc *gojay.Encoder) { + if _, err := gojay.MarshalJSONObject(gojay.EncodeObjectFunc(func(enc *gojay.Encoder) { enc.AddIntKey("st", 1) enc.AddIntKey("sid", 1) enc.AddStringKey("tt", "test") @@ -68,7 +68,7 @@ func BenchmarkGoJayEncodeSmallFunc(b *testing.B) { } func TestGoJayEncodeSmallStruct(t *testing.T) { - if output, err := gojay.MarshalObject(benchmarks.NewSmallPayload()); err != nil { + if output, err := gojay.MarshalJSONObject(benchmarks.NewSmallPayload()); err != nil { t.Fatal(err) } else { log.Print(output) diff --git a/decode.go b/decode.go @@ -6,13 +6,13 @@ import ( "reflect" ) -// UnmarshalArray parses the JSON-encoded data and stores the result in the value pointed to by v. +// UnmarshalJSONArray parses the JSON-encoded data and stores the result in the value pointed to by v. // -// v must implement UnmarshalerArray. +// v must implement UnmarshalerJSONArray. // // If a JSON value is not appropriate for a given target type, or if a JSON number -// overflows the target type, UnmarshalArray skips that field and completes the unmarshaling as best it can. -func UnmarshalArray(data []byte, v UnmarshalerArray) error { +// overflows the target type, UnmarshalJSONArray skips that field and completes the unmarshaling as best it can. +func UnmarshalJSONArray(data []byte, v UnmarshalerJSONArray) error { dec := borrowDecoder(nil, 0) defer dec.Release() dec.data = make([]byte, len(data)) @@ -28,13 +28,13 @@ func UnmarshalArray(data []byte, v UnmarshalerArray) error { return nil } -// UnmarshalObject parses the JSON-encoded data and stores the result in the value pointed to by v. +// UnmarshalJSONObject parses the JSON-encoded data and stores the result in the value pointed to by v. // -// v must implement UnmarshalerObject. +// v must implement UnmarshalerJSONObject. // // If a JSON value is not appropriate for a given target type, or if a JSON number -// overflows the target type, UnmarshalObject skips that field and completes the unmarshaling as best it can. -func UnmarshalObject(data []byte, v UnmarshalerObject) error { +// overflows the target type, UnmarshalJSONObject skips that field and completes the unmarshaling as best it can. +func UnmarshalJSONObject(data []byte, v UnmarshalerJSONObject) error { dec := borrowDecoder(nil, 0) defer dec.Release() dec.data = make([]byte, len(data)) @@ -51,7 +51,7 @@ func UnmarshalObject(data []byte, v UnmarshalerObject) error { } // Unmarshal parses the JSON-encoded data and stores the result in the value pointed to by v. -// If v is nil, not a pointer, or not an implementation of UnmarshalerObject or UnmarshalerArray +// If v is nil, not a pointer, or not an implementation of UnmarshalerJSONObject or UnmarshalerJSONArray // Unmarshal returns an InvalidUnmarshalError. // // Unmarshal uses the inverse of the encodings that Marshal uses, allocating maps, slices, and pointers as necessary, with the following additional rules: @@ -60,9 +60,9 @@ func UnmarshalObject(data []byte, v UnmarshalerObject) error { // Otherwise, Unmarshal unmarshals the JSON into the value pointed at by the pointer. // If the pointer is nil, Unmarshal allocates a new value for it to point to. // -// To Unmarshal JSON into a struct, Unmarshal requires the struct to implement UnmarshalerObject. +// To Unmarshal JSON into a struct, Unmarshal requires the struct to implement UnmarshalerJSONObject. // -// To unmarshal a JSON array into a slice, Unmarshal requires the slice to implement UnmarshalerArray. +// To unmarshal a JSON array into a slice, Unmarshal requires the slice to implement UnmarshalerJSONArray. // // Unmarshal JSON does not allow yet to unmarshall an interface value // If a JSON value is not appropriate for a given target type, or if a JSON number @@ -82,21 +82,41 @@ func Unmarshal(data []byte, v interface{}) error { dec.length = len(data) dec.data = data err = dec.decodeInt(vt) - case *int32: + case *int8: dec = borrowDecoder(nil, 0) dec.length = len(data) dec.data = data - err = dec.decodeInt32(vt) - case *uint32: + err = dec.decodeInt8(vt) + case *int16: dec = borrowDecoder(nil, 0) dec.length = len(data) dec.data = data - err = dec.decodeUint32(vt) + err = dec.decodeInt16(vt) + case *int32: + dec = borrowDecoder(nil, 0) + dec.length = len(data) + dec.data = data + err = dec.decodeInt32(vt) case *int64: dec = borrowDecoder(nil, 0) dec.length = len(data) dec.data = data err = dec.decodeInt64(vt) + case *uint8: + dec = borrowDecoder(nil, 0) + dec.length = len(data) + dec.data = data + err = dec.decodeUint8(vt) + case *uint16: + dec = borrowDecoder(nil, 0) + dec.length = len(data) + dec.data = data + err = dec.decodeUint16(vt) + case *uint32: + dec = borrowDecoder(nil, 0) + dec.length = len(data) + dec.data = data + err = dec.decodeUint32(vt) case *uint64: dec = borrowDecoder(nil, 0) dec.length = len(data) @@ -107,18 +127,23 @@ func Unmarshal(data []byte, v interface{}) error { dec.length = len(data) dec.data = data err = dec.decodeFloat64(vt) + case *float32: + dec = borrowDecoder(nil, 0) + dec.length = len(data) + dec.data = data + err = dec.decodeFloat32(vt) case *bool: dec = borrowDecoder(nil, 0) dec.length = len(data) dec.data = data err = dec.decodeBool(vt) - case UnmarshalerObject: + case UnmarshalerJSONObject: dec = borrowDecoder(nil, 0) dec.length = len(data) dec.data = make([]byte, len(data)) copy(dec.data, data) _, err = dec.decodeObject(vt) - case UnmarshalerArray: + case UnmarshalerJSONArray: dec = borrowDecoder(nil, 0) dec.length = len(data) dec.data = make([]byte, len(data)) @@ -134,17 +159,17 @@ func Unmarshal(data []byte, v interface{}) error { return dec.err } -// UnmarshalerObject is the interface to implement for a struct to be +// UnmarshalerJSONObject is the interface to implement for a struct to be // decoded -type UnmarshalerObject interface { - UnmarshalObject(*Decoder, string) error +type UnmarshalerJSONObject interface { + UnmarshalJSONObject(*Decoder, string) error NKeys() int } -// UnmarshalerArray is the interface to implement for a slice or an array to be +// UnmarshalerJSONArray is the interface to implement for a slice or an array to be // decoded -type UnmarshalerArray interface { - UnmarshalArray(*Decoder) error +type UnmarshalerJSONArray interface { + UnmarshalJSONArray(*Decoder) error } // A Decoder reads and decodes JSON values from an input stream. @@ -167,41 +192,143 @@ func (dec *Decoder) Decode(v interface{}) error { if dec.isPooled == 1 { panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) } + var err error switch vt := v.(type) { case *string: - return dec.decodeString(vt) + err = dec.decodeString(vt) case *int: - return dec.decodeInt(vt) + err = dec.decodeInt(vt) + case *int8: + err = dec.decodeInt8(vt) + case *int16: + err = dec.decodeInt16(vt) case *int32: - return dec.decodeInt32(vt) - case *uint32: - return dec.decodeUint32(vt) + err = dec.decodeInt32(vt) case *int64: - return dec.decodeInt64(vt) + err = dec.decodeInt64(vt) + case *uint8: + err = dec.decodeUint8(vt) + case *uint16: + err = dec.decodeUint16(vt) + case *uint32: + err = dec.decodeUint32(vt) case *uint64: - return dec.decodeUint64(vt) + err = dec.decodeUint64(vt) case *float64: - return dec.decodeFloat64(vt) + err = dec.decodeFloat64(vt) + case *float32: + err = dec.decodeFloat32(vt) case *bool: - return dec.decodeBool(vt) - case UnmarshalerObject: - _, err := dec.decodeObject(vt) - return err - case UnmarshalerArray: - _, err := dec.decodeArray(vt) - return err + err = dec.decodeBool(vt) + case UnmarshalerJSONObject: + _, err = dec.decodeObject(vt) + case UnmarshalerJSONArray: + _, err = dec.decodeArray(vt) case *EmbeddedJSON: - return dec.decodeEmbeddedJSON(vt) + err = dec.decodeEmbeddedJSON(vt) default: return InvalidUnmarshalError(fmt.Sprintf(invalidUnmarshalErrorMsg, reflect.TypeOf(vt).String())) } + if err != nil { + return err + } + return dec.err } // ADD VALUES FUNCTIONS // AddInt decodes the next key to an *int. -// If next key value overflows int, an InvalidTypeError error will be returned. +// If next key value overflows int, an InvalidUnmarshalError error will be returned. func (dec *Decoder) AddInt(v *int) error { + return dec.Int(v) +} + +// AddInt8 decodes the next key to an *int. +// If next key value overflows int8, an InvalidUnmarshalError error will be returned. +func (dec *Decoder) AddInt8(v *int8) error { + return dec.Int8(v) +} + +// AddInt16 decodes the next key to an *int. +// If next key value overflows int16, an InvalidUnmarshalError error will be returned. +func (dec *Decoder) AddInt16(v *int16) error { + return dec.Int16(v) +} + +// AddInt32 decodes the next key to an *int. +// If next key value overflows int32, an InvalidUnmarshalError error will be returned. +func (dec *Decoder) AddInt32(v *int32) error { + return dec.Int32(v) +} + +// AddInt64 decodes the next key to an *int. +// If next key value overflows int64, an InvalidUnmarshalError error will be returned. +func (dec *Decoder) AddInt64(v *int64) error { + return dec.Int64(v) +} + +// AddUint8 decodes the next key to an *int. +// If next key value overflows uint8, an InvalidUnmarshalError error will be returned. +func (dec *Decoder) AddUint8(v *uint8) error { + return dec.Uint8(v) +} + +// AddUint16 decodes the next key to an *int. +// If next key value overflows uint16, an InvalidUnmarshalError error will be returned. +func (dec *Decoder) AddUint16(v *uint16) error { + return dec.Uint16(v) +} + +// AddUint32 decodes the next key to an *int. +// If next key value overflows uint32, an InvalidUnmarshalError error will be returned. +func (dec *Decoder) AddUint32(v *uint32) error { + return dec.Uint32(v) +} + +// AddUint64 decodes the next key to an *int. +// If next key value overflows uint64, an InvalidUnmarshalError error will be returned. +func (dec *Decoder) AddUint64(v *uint64) error { + return dec.Uint64(v) +} + +// AddFloat decodes the next key to a *float64. +// If next key value overflows float64, an InvalidUnmarshalError error will be returned. +func (dec *Decoder) AddFloat(v *float64) error { + return dec.Float(v) +} + +// AddFloat32 decodes the next key to a *float64. +// If next key value overflows float64, an InvalidUnmarshalError error will be returned. +func (dec *Decoder) AddFloat32(v *float32) error { + return dec.Float32(v) +} + +// AddBool decodes the next key to a *bool. +// If next key is neither null nor a JSON boolean, an InvalidUnmarshalError will be returned. +// If next key is null, bool will be false. +func (dec *Decoder) AddBool(v *bool) error { + return dec.Bool(v) +} + +// AddString decodes the next key to a *string. +// If next key is not a JSON string nor null, InvalidUnmarshalError will be returned. +func (dec *Decoder) AddString(v *string) error { + return dec.String(v) +} + +// AddObject decodes the next key to a UnmarshalerJSONObject. +func (dec *Decoder) AddObject(v UnmarshalerJSONObject) error { + return dec.Object(v) +} + +// AddArray decodes the next key to a UnmarshalerJSONArray. +func (dec *Decoder) AddArray(v UnmarshalerJSONArray) error { + return dec.Array(v) +} + +// Int decodes the next key to an *int. +// If next key value overflows int, an InvalidUnmarshalError error will be returned. +func (dec *Decoder) Int(v *int) error { err := dec.decodeInt(v) if err != nil { return err @@ -210,9 +337,97 @@ func (dec *Decoder) AddInt(v *int) error { return nil } -// AddFloat decodes the next key to a *float64. -// If next key value overflows float64, an InvalidTypeError error will be returned. -func (dec *Decoder) AddFloat(v *float64) error { +// Int8 decodes the next key to an *int. +// If next key value overflows int8, an InvalidUnmarshalError error will be returned. +func (dec *Decoder) Int8(v *int8) error { + err := dec.decodeInt8(v) + if err != nil { + return err + } + dec.called |= 1 + return nil +} + +// Int16 decodes the next key to an *int. +// If next key value overflows int16, an InvalidUnmarshalError error will be returned. +func (dec *Decoder) Int16(v *int16) error { + err := dec.decodeInt16(v) + if err != nil { + return err + } + dec.called |= 1 + return nil +} + +// Int32 decodes the next key to an *int. +// If next key value overflows int32, an InvalidUnmarshalError error will be returned. +func (dec *Decoder) Int32(v *int32) error { + err := dec.decodeInt32(v) + if err != nil { + return err + } + dec.called |= 1 + return nil +} + +// Int64 decodes the next key to an *int. +// If next key value overflows int64, an InvalidUnmarshalError error will be returned. +func (dec *Decoder) Int64(v *int64) error { + err := dec.decodeInt64(v) + if err != nil { + return err + } + dec.called |= 1 + return nil +} + +// Uint8 decodes the next key to an *int. +// If next key value overflows uint8, an InvalidUnmarshalError error will be returned. +func (dec *Decoder) Uint8(v *uint8) error { + err := dec.decodeUint8(v) + if err != nil { + return err + } + dec.called |= 1 + return nil +} + +// Uint16 decodes the next key to an *int. +// If next key value overflows uint16, an InvalidUnmarshalError error will be returned. +func (dec *Decoder) Uint16(v *uint16) error { + err := dec.decodeUint16(v) + if err != nil { + return err + } + dec.called |= 1 + return nil +} + +// Uint32 decodes the next key to an *int. +// If next key value overflows uint32, an InvalidUnmarshalError error will be returned. +func (dec *Decoder) Uint32(v *uint32) error { + err := dec.decodeUint32(v) + if err != nil { + return err + } + dec.called |= 1 + return nil +} + +// Uint64 decodes the next key to an *int. +// If next key value overflows uint64, an InvalidUnmarshalError error will be returned. +func (dec *Decoder) Uint64(v *uint64) error { + err := dec.decodeUint64(v) + if err != nil { + return err + } + dec.called |= 1 + return nil +} + +// Float decodes the next key to a *float64. +// If next key value overflows float64, an InvalidUnmarshalError error will be returned. +func (dec *Decoder) Float(v *float64) error { err := dec.decodeFloat64(v) if err != nil { return err @@ -221,10 +436,21 @@ func (dec *Decoder) AddFloat(v *float64) error { return nil } -// AddBool decodes the next key to a *bool. -// If next key is neither null nor a JSON boolean, an InvalidTypeError will be returned. +// Float32 decodes the next key to a *float64. +// If next key value overflows float64, an InvalidUnmarshalError error will be returned. +func (dec *Decoder) Float32(v *float32) error { + err := dec.decodeFloat32(v) + if err != nil { + return err + } + dec.called |= 1 + return nil +} + +// Bool decodes the next key to a *bool. +// If next key is neither null nor a JSON boolean, an InvalidUnmarshalError will be returned. // If next key is null, bool will be false. -func (dec *Decoder) AddBool(v *bool) error { +func (dec *Decoder) Bool(v *bool) error { err := dec.decodeBool(v) if err != nil { return err @@ -233,9 +459,9 @@ func (dec *Decoder) AddBool(v *bool) error { return nil } -// AddString decodes the next key to a *string. -// If next key is not a JSON string nor null, InvalidTypeError will be returned. -func (dec *Decoder) AddString(v *string) error { +// String decodes the next key to a *string. +// If next key is not a JSON string nor null, InvalidUnmarshalError will be returned. +func (dec *Decoder) String(v *string) error { err := dec.decodeString(v) if err != nil { return err @@ -244,8 +470,8 @@ func (dec *Decoder) AddString(v *string) error { return nil } -// AddObject decodes the next key to a UnmarshalerObject. -func (dec *Decoder) AddObject(value UnmarshalerObject) error { +// Object decodes the next key to a UnmarshalerJSONObject. +func (dec *Decoder) Object(value UnmarshalerJSONObject) error { initialKeysDone := dec.keysDone initialChild := dec.child dec.keysDone = 0 @@ -262,8 +488,8 @@ func (dec *Decoder) AddObject(value UnmarshalerObject) error { return nil } -// AddArray decodes the next key to a UnmarshalerArray. -func (dec *Decoder) AddArray(value UnmarshalerArray) error { +// Array decodes the next key to a UnmarshalerJSONArray. +func (dec *Decoder) Array(value UnmarshalerJSONArray) error { newCursor, err := dec.decodeArray(value) if err != nil { return err diff --git a/decode_array.go b/decode_array.go @@ -1,22 +1,18 @@ package gojay -import ( - "fmt" -) - // DecodeArray reads the next JSON-encoded value from its input and stores it in the value pointed to by v. // -// v must implement UnmarshalerArray. +// v must implement UnmarshalerJSONArray. // // See the documentation for Unmarshal for details about the conversion of JSON into a Go value. -func (dec *Decoder) DecodeArray(arr UnmarshalerArray) error { +func (dec *Decoder) DecodeArray(arr UnmarshalerJSONArray) error { if dec.isPooled == 1 { panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) } _, err := dec.decodeArray(arr) return err } -func (dec *Decoder) decodeArray(arr UnmarshalerArray) (int, error) { +func (dec *Decoder) decodeArray(arr UnmarshalerJSONArray) (int, error) { // not an array not an error, but do not know what to do // do not check syntax for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { @@ -34,13 +30,13 @@ func (dec *Decoder) decodeArray(arr UnmarshalerArray) (int, error) { return dec.cursor, nil } // calling unmarshall function for each element of the slice - err := arr.UnmarshalArray(dec) + err := arr.UnmarshalJSONArray(dec) if err != nil { return 0, err } n++ } - return 0, InvalidJSONError("Invalid JSON could not find array closing bracket") + return 0, dec.raiseInvalidJSONErr(dec.cursor) case 'n': // is null dec.cursor++ @@ -48,27 +44,22 @@ func (dec *Decoder) decodeArray(arr UnmarshalerArray) (int, error) { if err != nil { return 0, err } + dec.cursor++ return dec.cursor, nil case '{', '"', 'f', 't', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': // can't unmarshall to struct // we skip array and set Error - dec.err = InvalidTypeError( - fmt.Sprintf( - "Cannot unmarshall to array, wrong char '%s' found at pos %d", - string(dec.data[dec.cursor]), - dec.cursor, - ), - ) + dec.err = dec.makeInvalidUnmarshalErr(arr) err := dec.skipData() if err != nil { return 0, err } return dec.cursor, nil default: - return 0, InvalidJSONError("Invalid JSON") + return 0, dec.raiseInvalidJSONErr(dec.cursor) } } - return 0, InvalidJSONError("Invalid JSON") + return 0, dec.raiseInvalidJSONErr(dec.cursor) } func (dec *Decoder) skipArray() (int, error) { @@ -113,5 +104,5 @@ func (dec *Decoder) skipArray() (int, error) { continue } } - return 0, InvalidJSONError("Invalid JSON") + return 0, dec.raiseInvalidJSONErr(dec.cursor) } diff --git a/decode_array_test.go b/decode_array_test.go @@ -7,9 +7,88 @@ import ( "github.com/stretchr/testify/assert" ) +type testSliceInts []int + +func (t *testSliceInts) UnmarshalJSONArray(dec *Decoder) error { + i := 0 + if err := dec.AddInt(&i); err != nil { + return err + } + *t = append(*t, i) + return nil +} + +func TestSliceInts(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult testSliceInts + err bool + errType interface{} + }{ + { + name: "basic-test", + json: "[1,2,3,43567788543,45777655,432,0]", + expectedResult: testSliceInts{1, 2, 3, 43567788543, 45777655, 432, 0}, + }, + { + name: "basic-test", + json: "[1,2,3,43567788543,null,432,0]", + expectedResult: testSliceInts{1, 2, 3, 43567788543, 0, 432, 0}, + }, + { + name: "empty", + json: "[]", + expectedResult: testSliceInts{}, + }, + { + name: "invalid-json", + json: "[", + expectedResult: testSliceInts{}, + err: true, + }, + { + name: "floats", + json: "[1,2,3,43567788543,457.7765,432,0,0.45]", + expectedResult: testSliceInts{1, 2, 3, 43567788543, 457, 432, 0, 0}, + }, + { + name: "invalid-type", + json: `[1,2,3,43567788543,457.7765,432,0,"test"]`, + expectedResult: testSliceInts{1, 2, 3, 43567788543, 457, 432, 0, 0}, + err: true, + errType: InvalidUnmarshalError(""), + }, + { + name: "invalid-json", + json: `[1,2,3",43567788543,457.7765,432,0,"test"]`, + expectedResult: testSliceInts{1, 2, 3, 43567788543, 457, 432, 0, 0}, + err: true, + errType: InvalidJSONError(""), + }, + } + + for _, testCase := range testCases { + s := make(testSliceInts, 0) + dec := BorrowDecoder(strings.NewReader(testCase.json)) + defer dec.Release() + err := dec.Decode(&s) + if testCase.err { + assert.NotNil(t, err, "err should not be nil") + if testCase.errType != nil { + assert.IsType(t, testCase.errType, err, "err should be of the given type") + } + continue + } + for k, v := range testCase.expectedResult { + assert.Equal(t, v, s[k], "value at given index should be the same as expected results") + } + } +} + type testSliceStrings []string -func (t *testSliceStrings) UnmarshalArray(dec *Decoder) error { +func (t *testSliceStrings) UnmarshalJSONArray(dec *Decoder) error { str := "" if err := dec.AddString(&str); err != nil { return err @@ -18,117 +97,305 @@ func (t *testSliceStrings) UnmarshalArray(dec *Decoder) error { return nil } -type testSliceInts []*int +func TestSliceStrings(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult testSliceStrings + err bool + errType interface{} + }{ + { + name: "basic-test", + json: `["hello world", "hey" , "foo","bar"]`, + expectedResult: testSliceStrings{"hello world", "hey", "foo", "bar"}, + }, + { + name: "basic-test", + json: `["hello world", "hey" , "foo","bar \\n escape"]`, + expectedResult: testSliceStrings{"hello world", "hey", "foo", "bar \n escape"}, + }, + { + name: "basic-test", + json: `["hello world", "hey" , null,"bar \\n escape"]`, + expectedResult: testSliceStrings{"hello world", "hey", "", "bar \n escape"}, + }, + { + name: "invalid-type", + json: `["foo",1,2,3,"test"]`, + expectedResult: testSliceStrings{}, + err: true, + errType: InvalidUnmarshalError(""), + }, + { + name: "invalid-json", + json: `["hello world]`, + expectedResult: testSliceStrings{}, + err: true, + errType: InvalidJSONError(""), + }, + } -func (t *testSliceInts) UnmarshalArray(dec *Decoder) error { - i := 0 - ptr := &i - *t = append(*t, ptr) - return dec.AddInt(ptr) + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + s := make(testSliceStrings, 0) + dec := BorrowDecoder(strings.NewReader(testCase.json)) + defer dec.Release() + err := dec.Decode(&s) + if testCase.err { + assert.NotNil(t, err, "err should not be nil") + if testCase.errType != nil { + assert.IsType(t, testCase.errType, err, "err should be of the given type") + } + return + } + assert.Nil(t, err, "err should be nil") + for k, v := range testCase.expectedResult { + assert.Equal(t, v, s[k], "value at given index should be the same as expected results") + } + }) + } } -type testSliceObj []*TestObj +type testSliceBools []bool -func (t *testSliceObj) UnmarshalArray(dec *Decoder) error { - obj := &TestObj{} - *t = append(*t, obj) - return dec.AddObject(obj) +func (t *testSliceBools) UnmarshalJSONArray(dec *Decoder) error { + b := false + if err := dec.AddBool(&b); err != nil { + return err + } + *t = append(*t, b) + return nil } -type testChannelArray chan *TestObj +func TestSliceBools(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult testSliceBools + err bool + errType interface{} + }{ + { + name: "basic-test", + json: `[true, false, false, true, true, false]`, + expectedResult: testSliceBools{true, false, false, true, true, false}, + }, + { + name: "basic-test2", + json: `[true, false, false, true, null,null,true,false]`, + expectedResult: testSliceBools{true, false, false, true, false, false, true, false}, + }, + { + name: "invalid-type", + json: `["foo",1,2,3,"test"]`, + expectedResult: testSliceBools{}, + err: true, + errType: InvalidUnmarshalError(""), + }, + { + name: "invalid-json", + json: `["hello world]`, + expectedResult: testSliceBools{}, + err: true, + errType: InvalidJSONError(""), + }, + } -func (c *testChannelArray) UnmarshalArray(dec *Decoder) error { - obj := &TestObj{} - if err := dec.AddObject(obj); err != nil { + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + s := make(testSliceBools, 0) + dec := BorrowDecoder(strings.NewReader(testCase.json)) + defer dec.Release() + err := dec.Decode(&s) + if testCase.err { + assert.NotNil(t, err, "err should not be nil") + if testCase.errType != nil { + assert.IsType(t, testCase.errType, err, "err should be of the given type") + } + return + } + assert.Nil(t, err, "err should be nil") + for k, v := range testCase.expectedResult { + assert.Equal(t, v, s[k], "value at given index should be the same as expected results") + } + }) + } +} + +type testSliceSlicesSlices []testSliceInts + +func (t *testSliceSlicesSlices) UnmarshalJSONArray(dec *Decoder) error { + sl := make(testSliceInts, 0) + if err := dec.AddArray(&sl); err != nil { return err } - *c <- obj + *t = append(*t, sl) return nil } -func TestDecoderSliceOfStringsBasic(t *testing.T) { - json := []byte(`["string","string1"]`) - testArr := testSliceStrings{} - err := Unmarshal(json, &testArr) - assert.Nil(t, err, "Err must be nil") - assert.Len(t, testArr, 2, "testArr should be of len 2") - assert.Equal(t, "string", testArr[0], "testArr[0] should be 'string'") - assert.Equal(t, "string1", testArr[1], "testArr[1] should be 'string1'") -} +func TestSliceSlices(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult testSliceSlicesSlices + err bool + errType interface{} + }{ + { + name: "basic-test", + json: `[[1,2],[1,2],[1,2]]`, + expectedResult: testSliceSlicesSlices{testSliceInts{1, 2}, testSliceInts{1, 2}, testSliceInts{1, 2}}, + }, + { + name: "basic-test", + json: `[[1,2],null,[1,2]]`, + expectedResult: testSliceSlicesSlices{testSliceInts{1, 2}, testSliceInts{}, testSliceInts{1, 2}}, + }, + { + name: "invalid-type", + json: `["foo",1,2,3,"test"]`, + expectedResult: testSliceSlicesSlices{}, + err: true, + errType: InvalidUnmarshalError(""), + }, + { + name: "invalid-json", + json: `["hello world]`, + expectedResult: testSliceSlicesSlices{}, + err: true, + errType: InvalidJSONError(""), + }, + } -func TestDecoderSliceNull(t *testing.T) { - json := []byte(`null`) - v := &testSliceStrings{} - err := Unmarshal(json, v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, len(*v), 0, "v must be of len 0") + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + s := make(testSliceSlicesSlices, 0) + dec := BorrowDecoder(strings.NewReader(testCase.json)) + defer dec.Release() + err := dec.Decode(&s) + if testCase.err { + assert.NotNil(t, err, "err should not be nil") + if testCase.errType != nil { + assert.IsType(t, testCase.errType, err, "err should be of the given type") + } + return + } + assert.Nil(t, err, "err should be nil") + for k, v := range testCase.expectedResult { + assert.Equal(t, v, s[k], "value at given index should be the same as expected results") + } + }) + } } -func TestDecoderSliceArrayOfIntsBasic(t *testing.T) { - json := []byte(`[ - 1, - 2 - ]`) - testArr := testSliceInts{} - err := UnmarshalArray(json, &testArr) - assert.Nil(t, err, "Err must be nil") - assert.Len(t, testArr, 2, "testArr should be of len 2") - assert.Equal(t, 1, *testArr[0], "testArr[0] should be 1") - assert.Equal(t, 2, *testArr[1], "testArr[1] should be 2") -} +type testSliceObjects []*testObject -func TestDecoderSliceArrayOfIntsBigInts(t *testing.T) { - json := []byte(`[ - 789034384533530523, - 545344023293232032 - ]`) - testArr := testSliceInts{} - err := UnmarshalArray(json, &testArr) - assert.Nil(t, err, "Err must be nil") - assert.Len(t, testArr, 2, "testArr should be of len 2") - assert.Equal(t, 789034384533530523, *testArr[0], "testArr[0] should be 789034384533530523") - assert.Equal(t, 545344023293232032, *testArr[1], "testArr[1] should be 545344023293232032") +func (t *testSliceObjects) UnmarshalJSONArray(dec *Decoder) error { + obj := &testObject{} + *t = append(*t, obj) + return dec.AddObject(obj) } -func TestDecoderSliceOfObjectsBasic(t *testing.T) { - json := []byte(`[ +func TestSliceObjects(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult testSliceObjects + err bool + errType interface{} + }{ { - "test": 245, - "test2": -246, - "test3": "string" + name: "basic-test", + json: `[{"testStr":"foo bar","testInt":123},{"testStr":"foo bar","testInt":123}]`, + expectedResult: testSliceObjects{ + &testObject{ + testStr: "foo bar", + testInt: 123, + }, + &testObject{ + testStr: "foo bar", + testInt: 123, + }, + }, }, { - "test": 247, - "test2": 248, - "test3": "string" + name: "basic-test", + json: `[{"testStr":"foo bar","testInt":123},null,{"testStr":"foo bar","testInt":123}]`, + expectedResult: testSliceObjects{ + &testObject{ + testStr: "foo bar", + testInt: 123, + }, + &testObject{}, + &testObject{ + testStr: "foo bar", + testInt: 123, + }, + }, }, { - "test": 777, - "test2": 456, - "test3": "string" - } - ]`) - testArr := testSliceObj{} - err := Unmarshal(json, &testArr) + name: "invalid-type", + json: `["foo",1,2,3,"test"]`, + expectedResult: testSliceObjects{}, + err: true, + errType: InvalidUnmarshalError(""), + }, + { + name: "invalid-json", + json: `["hello world]`, + expectedResult: testSliceObjects{}, + err: true, + errType: InvalidJSONError(""), + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + s := make(testSliceObjects, 0) + dec := BorrowDecoder(strings.NewReader(testCase.json)) + defer dec.Release() + err := dec.Decode(&s) + if testCase.err { + assert.NotNil(t, err, "err should not be nil") + if testCase.errType != nil { + assert.IsType(t, testCase.errType, err, "err should be of the given type") + } + return + } + assert.Nil(t, err, "err should be nil") + for k, v := range testCase.expectedResult { + assert.Equal(t, *v, *s[k], "value at given index should be the same as expected results") + } + }) + } +} + +type testChannelArray chan *TestObj + +func (c *testChannelArray) UnmarshalJSONArray(dec *Decoder) error { + obj := &TestObj{} + if err := dec.AddObject(obj); err != nil { + return err + } + *c <- obj + return nil +} + +func TestDecoderSliceNull(t *testing.T) { + json := []byte(`null`) + v := &testSliceStrings{} + err := Unmarshal(json, v) assert.Nil(t, err, "Err must be nil") - assert.Len(t, testArr, 3, "testArr should be of len 2") - assert.Equal(t, 245, testArr[0].test, "testArr[0] should be 245") - assert.Equal(t, -246, testArr[0].test2, "testArr[0] should be 246") - assert.Equal(t, "string", testArr[0].test3, "testArr[0].test3 should be 'string'") - assert.Equal(t, 247, testArr[1].test, "testArr[1] should be 247") - assert.Equal(t, 248, testArr[1].test2, "testArr[1] should be 248") - assert.Equal(t, "string", testArr[1].test3, "testArr[1].test3 should be 'string'") - assert.Equal(t, 777, testArr[2].test, "testArr[2] should be 777") - assert.Equal(t, 456, testArr[2].test2, "testArr[2] should be 456") - assert.Equal(t, "string", testArr[2].test3, "testArr[2].test3 should be 'string'") + assert.Equal(t, len(*v), 0, "v must be of len 0") } func TestDecodeSliceInvalidType(t *testing.T) { - result := testSliceObj{} - err := UnmarshalArray([]byte(`{}`), &result) + result := testSliceObjects{} + err := UnmarshalJSONArray([]byte(`{}`), &result) assert.NotNil(t, err, "err should not be nil") - assert.IsType(t, InvalidTypeError(""), err, "err should be of type InvalidTypeError") - assert.Equal(t, "Cannot unmarshall to array, wrong char '{' found at pos 0", err.Error(), "err should not be nil") + assert.IsType(t, InvalidUnmarshalError(""), err, "err should be of type InvalidUnmarshalError") + assert.Equal(t, "Cannot unmarshal JSON to type '*gojay.testSliceObjects'", err.Error(), "err should not be nil") } func TestDecoderChannelOfObjectsBasic(t *testing.T) { @@ -150,7 +417,7 @@ func TestDecoderChannelOfObjectsBasic(t *testing.T) { } ]`) testChan := testChannelArray(make(chan *TestObj, 3)) - err := UnmarshalArray(json, &testChan) + err := UnmarshalJSONArray(json, &testChan) assert.Nil(t, err, "Err must be nil") ct := 0 l := len(testChan) @@ -166,7 +433,7 @@ func TestDecoderChannelOfObjectsBasic(t *testing.T) { func TestDecoderSliceInvalidJSON(t *testing.T) { json := []byte(`hello`) testArr := testSliceInts{} - err := UnmarshalArray(json, &testArr) + err := UnmarshalJSONArray(json, &testArr) assert.NotNil(t, err, "Err must not be nil as JSON is invalid") assert.IsType(t, InvalidJSONError(""), err, "err message must be 'Invalid JSON'") } @@ -190,10 +457,10 @@ func TestDecoderSliceDecoderAPIError(t *testing.T) { assert.IsType(t, InvalidJSONError(""), err, "err message must be 'Invalid JSON'") } -func TestUnmarshalArrays(t *testing.T) { +func TestUnmarshalJSONArrays(t *testing.T) { testCases := []struct { name string - v UnmarshalerArray + v UnmarshalerJSONArray d []byte expectations func(err error, v interface{}, t *testing.T) }{ @@ -235,52 +502,12 @@ func TestUnmarshalArrays(t *testing.T) { for _, testCase := range testCases { testCase := testCase t.Run(testCase.name, func(*testing.T) { - err := UnmarshalArray(testCase.d, testCase.v) + err := UnmarshalJSONArray(testCase.d, testCase.v) testCase.expectations(err, testCase.v, t) }) } } -func TestDecodeArrayEmpty(t *testing.T) { - v := new(testDecodeSlice) - dec := NewDecoder(strings.NewReader("")) - err := dec.Decode(v) - assert.NotNil(t, err, "err should not be nil") - assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") -} - -func TestDecodeArrayInvalidJSONError(t *testing.T) { - v := new(testSliceStrings) - dec := NewDecoder(strings.NewReader(`["test",""`)) - err := dec.Decode(v) - assert.NotNil(t, err, "err should not be nil") - assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") -} - -func TestDecodeArrayInvalidJSONError2(t *testing.T) { - v := new(testSliceStrings) - dec := NewDecoder(strings.NewReader(`["test","\\""]`)) - err := dec.Decode(v) - assert.NotNil(t, err, "err should not be nil") - assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") -} - -func TestDecodeArraySkipError(t *testing.T) { - v := new(testDecodeSlice) - dec := NewDecoder(strings.NewReader("34fef")) - err := dec.Decode(v) - assert.NotNil(t, err, "err should not be nil") - assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") -} - -func TestDecodeArrayNullError(t *testing.T) { - v := new(testDecodeSlice) - dec := NewDecoder(strings.NewReader("nall")) - err := dec.Decode(v) - assert.NotNil(t, err, "err should not be nil") - assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") -} - func TestSkipArray(t *testing.T) { testCases := []struct { json string @@ -315,3 +542,27 @@ func TestSkipArray(t *testing.T) { test.expectations(t, i, err) } } + +func TestDecodeArrayEmpty(t *testing.T) { + v := new(testDecodeSlice) + dec := NewDecoder(strings.NewReader("")) + err := dec.Decode(v) + assert.NotNil(t, err, "err should not be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") +} + +func TestDecodeArraySkipError(t *testing.T) { + v := new(testDecodeSlice) + dec := NewDecoder(strings.NewReader("34fef")) + err := dec.Decode(v) + assert.NotNil(t, err, "err should not be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") +} + +func TestDecodeArrayNullError(t *testing.T) { + v := new(testDecodeSlice) + dec := NewDecoder(strings.NewReader("nall")) + err := dec.Decode(v) + assert.NotNil(t, err, "err should not be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") +} diff --git a/decode_bool.go b/decode_bool.go @@ -1,7 +1,5 @@ package gojay -import "fmt" - // DecodeBool reads the next JSON-encoded value from its input and stores it in the boolean pointed to by v. // // See the documentation for Unmarshal for details about the conversion of JSON into a Go value. @@ -23,6 +21,7 @@ func (dec *Decoder) decodeBool(v *bool) error { return err } *v = true + dec.cursor++ return nil case 'f': dec.cursor++ @@ -31,6 +30,7 @@ func (dec *Decoder) decodeBool(v *bool) error { return err } *v = false + dec.cursor++ return nil case 'n': dec.cursor++ @@ -42,13 +42,7 @@ func (dec *Decoder) decodeBool(v *bool) error { dec.cursor++ return nil default: - dec.err = InvalidUnmarshalError( - fmt.Sprintf( - "Cannot unmarshall to bool, wrong char '%s' found at pos %d", - string(dec.data[dec.cursor]), - dec.cursor, - ), - ) + dec.err = dec.makeInvalidUnmarshalErr(v) err := dec.skipData() if err != nil { return err @@ -65,15 +59,15 @@ func (dec *Decoder) assertTrue() error { switch i { case 0: if dec.data[dec.cursor] != 'r' { - return InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + return dec.raiseInvalidJSONErr(dec.cursor) } case 1: if dec.data[dec.cursor] != 'u' { - return InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + return dec.raiseInvalidJSONErr(dec.cursor) } case 2: if dec.data[dec.cursor] != 'e' { - return InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + return dec.raiseInvalidJSONErr(dec.cursor) } case 3: switch dec.data[dec.cursor] { @@ -81,7 +75,7 @@ func (dec *Decoder) assertTrue() error { dec.cursor-- return nil default: - return InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + return dec.raiseInvalidJSONErr(dec.cursor) } } i++ @@ -89,7 +83,7 @@ func (dec *Decoder) assertTrue() error { if i == 3 { return nil } - return InvalidJSONError("Invalid JSON") + return dec.raiseInvalidJSONErr(dec.cursor) } func (dec *Decoder) assertNull() error { @@ -98,15 +92,15 @@ func (dec *Decoder) assertNull() error { switch i { case 0: if dec.data[dec.cursor] != 'u' { - return InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + return dec.raiseInvalidJSONErr(dec.cursor) } case 1: if dec.data[dec.cursor] != 'l' { - return InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + return dec.raiseInvalidJSONErr(dec.cursor) } case 2: if dec.data[dec.cursor] != 'l' { - return InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + return dec.raiseInvalidJSONErr(dec.cursor) } case 3: switch dec.data[dec.cursor] { @@ -114,7 +108,7 @@ func (dec *Decoder) assertNull() error { dec.cursor-- return nil default: - return InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + return dec.raiseInvalidJSONErr(dec.cursor) } } i++ @@ -122,7 +116,7 @@ func (dec *Decoder) assertNull() error { if i == 3 { return nil } - return InvalidJSONError("Invalid JSON") + return dec.raiseInvalidJSONErr(dec.cursor) } func (dec *Decoder) assertFalse() error { @@ -131,19 +125,19 @@ func (dec *Decoder) assertFalse() error { switch i { case 0: if dec.data[dec.cursor] != 'a' { - return InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + return dec.raiseInvalidJSONErr(dec.cursor) } case 1: if dec.data[dec.cursor] != 'l' { - return InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + return dec.raiseInvalidJSONErr(dec.cursor) } case 2: if dec.data[dec.cursor] != 's' { - return InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + return dec.raiseInvalidJSONErr(dec.cursor) } case 3: if dec.data[dec.cursor] != 'e' { - return InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + return dec.raiseInvalidJSONErr(dec.cursor) } case 4: switch dec.data[dec.cursor] { @@ -151,7 +145,7 @@ func (dec *Decoder) assertFalse() error { dec.cursor-- return nil default: - return InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + return dec.raiseInvalidJSONErr(dec.cursor) } } i++ @@ -159,5 +153,5 @@ func (dec *Decoder) assertFalse() error { if i == 4 { return nil } - return InvalidJSONError("Invalid JSON") + return dec.raiseInvalidJSONErr(dec.cursor) } diff --git a/decode_embedded_json_test.go b/decode_embedded_json_test.go @@ -15,7 +15,7 @@ type Request struct { more int } -func (r *Request) UnmarshalObject(dec *Decoder, key string) error { +func (r *Request) UnmarshalJSONObject(dec *Decoder, key string) error { switch key { case "id": return dec.AddString(&r.id) diff --git a/decode_number.go b/decode_number.go @@ -1,23 +1,27 @@ package gojay import ( - "fmt" + "math" ) var digits []int8 -const maxUint32 = uint32(0xffffffff) -const maxUint64 = uint64(0xffffffffffffffff) -const maxInt32 = int32(0x7fffffff) -const maxInt64 = int64(0x7fffffffffffffff) -const maxInt64toMultiply = int64(0x7fffffffffffffff) / 10 -const maxInt32toMultiply = int32(0x7fffffff) / 10 -const maxUint32toMultiply = uint32(0xffffffff) / 10 -const maxUint64toMultiply = uint64(0xffffffffffffffff) / 10 +const maxInt64toMultiply = math.MaxInt64 / 10 +const maxInt32toMultiply = math.MaxInt32 / 10 +const maxInt16toMultiply = math.MaxInt16 / 10 +const maxInt8toMultiply = math.MaxInt8 / 10 +const maxUint8toMultiply = math.MaxUint8 / 10 +const maxUint16toMultiply = math.MaxUint16 / 10 +const maxUint32toMultiply = math.MaxUint32 / 10 +const maxUint64toMultiply = math.MaxUint64 / 10 const maxUint32Length = 10 const maxUint64Length = 20 +const maxUint16Length = 5 +const maxUint8Length = 3 const maxInt32Length = 10 const maxInt64Length = 19 +const maxInt16Length = 5 +const maxInt8Length = 3 const invalidNumber = int8(-1) var pow10uint64 = [20]uint64{ @@ -53,336 +57,6 @@ func init() { } } -// DecodeInt reads the next JSON-encoded value from its input and stores it in the int pointed to by v. -// -// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. -func (dec *Decoder) DecodeInt(v *int) error { - if dec.isPooled == 1 { - panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) - } - return dec.decodeInt(v) -} -func (dec *Decoder) decodeInt(v *int) error { - for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { - switch c := dec.data[dec.cursor]; c { - case ' ', '\n', '\t', '\r', ',': - continue - // we don't look for 0 as leading zeros are invalid per RFC - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - val, err := dec.getInt64(c) - if err != nil { - return err - } - *v = int(val) - return nil - case '-': - dec.cursor = dec.cursor + 1 - val, err := dec.getInt64(dec.data[dec.cursor]) - if err != nil { - return err - } - *v = -int(val) - return nil - case 'n': - dec.cursor++ - err := dec.assertNull() - if err != nil { - return err - } - dec.cursor++ - return nil - default: - dec.err = InvalidTypeError( - fmt.Sprintf( - "Cannot unmarshall to int, wrong char '%s' found at pos %d", - string(dec.data[dec.cursor]), - dec.cursor, - ), - ) - err := dec.skipData() - if err != nil { - return err - } - return nil - } - } - return InvalidJSONError("Invalid JSON while parsing int") -} - -// DecodeInt32 reads the next JSON-encoded value from its input and stores it in the int32 pointed to by v. -// -// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. -func (dec *Decoder) DecodeInt32(v *int32) error { - if dec.isPooled == 1 { - panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) - } - return dec.decodeInt32(v) -} -func (dec *Decoder) decodeInt32(v *int32) error { - for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { - switch c := dec.data[dec.cursor]; c { - case ' ', '\n', '\t', '\r', ',': - continue - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - val, err := dec.getInt32(c) - if err != nil { - return err - } - *v = val - return nil - case '-': - dec.cursor = dec.cursor + 1 - val, err := dec.getInt32(dec.data[dec.cursor]) - if err != nil { - return err - } - *v = -val - return nil - case 'n': - dec.cursor++ - err := dec.assertNull() - if err != nil { - return err - } - return nil - default: - dec.err = InvalidTypeError( - fmt.Sprintf( - "Cannot unmarshall to int, wrong char '%s' found at pos %d", - string(dec.data[dec.cursor]), - dec.cursor, - ), - ) - err := dec.skipData() - if err != nil { - return err - } - return nil - } - } - return InvalidJSONError("Invalid JSON while parsing int") -} - -// DecodeUint32 reads the next JSON-encoded value from its input and stores it in the uint32 pointed to by v. -// -// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. -func (dec *Decoder) DecodeUint32(v *uint32) error { - if dec.isPooled == 1 { - panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) - } - return dec.decodeUint32(v) -} - -func (dec *Decoder) decodeUint32(v *uint32) error { - for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { - switch c := dec.data[dec.cursor]; c { - case ' ', '\n', '\t', '\r', ',': - continue - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - val, err := dec.getUint32(c) - if err != nil { - return err - } - *v = val - return nil - case '-': - dec.cursor = dec.cursor + 1 - val, err := dec.getUint32(dec.data[dec.cursor]) - if err != nil { - return err - } - // unsigned int so we don't bother with the sign - *v = val - return nil - case 'n': - dec.cursor++ - err := dec.assertNull() - if err != nil { - return err - } - return nil - default: - dec.err = InvalidTypeError( - fmt.Sprintf( - "Cannot unmarshall to int, wrong char '%s' found at pos %d", - string(dec.data[dec.cursor]), - dec.cursor, - ), - ) - err := dec.skipData() - if err != nil { - return err - } - return nil - } - } - return InvalidJSONError("Invalid JSON while parsing int") -} - -// DecodeInt64 reads the next JSON-encoded value from its input and stores it in the int64 pointed to by v. -// -// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. -func (dec *Decoder) DecodeInt64(v *int64) error { - if dec.isPooled == 1 { - panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) - } - return dec.decodeInt64(v) -} - -func (dec *Decoder) decodeInt64(v *int64) error { - for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { - switch c := dec.data[dec.cursor]; c { - case ' ', '\n', '\t', '\r', ',': - continue - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - val, err := dec.getInt64(c) - if err != nil { - return err - } - *v = val - return nil - case '-': - dec.cursor = dec.cursor + 1 - val, err := dec.getInt64(dec.data[dec.cursor]) - if err != nil { - return err - } - *v = -val - return nil - case 'n': - dec.cursor++ - err := dec.assertNull() - if err != nil { - return err - } - return nil - default: - dec.err = InvalidTypeError( - fmt.Sprintf( - "Cannot unmarshall to int, wrong char '%s' found at pos %d", - string(dec.data[dec.cursor]), - dec.cursor, - ), - ) - err := dec.skipData() - if err != nil { - return err - } - return nil - } - } - return InvalidJSONError("Invalid JSON while parsing int") -} - -// DecodeUint64 reads the next JSON-encoded value from its input and stores it in the uint64 pointed to by v. -// -// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. -func (dec *Decoder) DecodeUint64(v *uint64) error { - if dec.isPooled == 1 { - panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) - } - return dec.decodeUint64(v) -} -func (dec *Decoder) decodeUint64(v *uint64) error { - for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { - switch c := dec.data[dec.cursor]; c { - case ' ', '\n', '\t', '\r', ',': - continue - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - val, err := dec.getUint64(c) - if err != nil { - return err - } - *v = val - return nil - case '-': - dec.cursor = dec.cursor + 1 - val, err := dec.getUint64(dec.data[dec.cursor]) - if err != nil { - return err - } - // unsigned int so we don't bother with the sign - *v = val - return nil - case 'n': - dec.cursor++ - err := dec.assertNull() - if err != nil { - return err - } - return nil - default: - dec.err = InvalidTypeError( - fmt.Sprintf( - "Cannot unmarshall to int, wrong char '%s' found at pos %d", - string(dec.data[dec.cursor]), - dec.cursor, - ), - ) - err := dec.skipData() - if err != nil { - return err - } - return nil - } - } - return InvalidJSONError("Invalid JSON while parsing int") -} - -// DecodeFloat64 reads the next JSON-encoded value from its input and stores it in the float64 pointed to by v. -// -// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. -func (dec *Decoder) DecodeFloat64(v *float64) error { - if dec.isPooled == 1 { - panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) - } - return dec.decodeFloat64(v) -} -func (dec *Decoder) decodeFloat64(v *float64) error { - for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { - switch c := dec.data[dec.cursor]; c { - case ' ', '\n', '\t', '\r', ',': - continue - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - val, err := dec.getFloat(c) - if err != nil { - return err - } - *v = val - return nil - case '-': - dec.cursor = dec.cursor + 1 - val, err := dec.getFloat(c) - if err != nil { - return err - } - *v = -val - return nil - case 'n': - dec.cursor++ - err := dec.assertNull() - if err != nil { - return err - } - return nil - default: - dec.err = InvalidTypeError( - fmt.Sprintf( - "Cannot unmarshall to float, wrong char '%s' found at pos %d", - string(dec.data[dec.cursor]), - dec.cursor, - ), - ) - err := dec.skipData() - if err != nil { - return err - } - return nil - } - } - return InvalidJSONError("Invalid JSON while parsing float") -} - func (dec *Decoder) skipNumber() (int, error) { end := dec.cursor + 1 // look for following numbers @@ -400,457 +74,17 @@ func (dec *Decoder) skipNumber() (int, error) { continue } // invalid json we expect numbers, dot (single one), comma, or spaces - return end, InvalidJSONError("Invalid JSON while parsing number") + return end, dec.raiseInvalidJSONErr(dec.cursor) } return end, nil } -func (dec *Decoder) getInt64(b byte) (int64, error) { - var end = dec.cursor - var start = dec.cursor - // look for following numbers - for j := dec.cursor + 1; j < dec.length || dec.read(); j++ { - switch dec.data[j] { - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - end = j - continue - case ' ', '\t', '\n', ',', '}', ']': - dec.cursor = j - return dec.atoi64(start, end), nil - case '.': - // if dot is found - // look for exponent (e,E) as exponent can change the - // way number should be parsed to int. - // if no exponent found, just unmarshal the number before decimal point - startDecimal := j + 1 - endDecimal := j + 1 - j++ - for ; j < dec.length || dec.read(); j++ { - switch dec.data[j] { - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - endDecimal = j - continue - case 'e', 'E': - dec.cursor = j + 1 - // can try unmarshalling to int as Exponent might change decimal number to non decimal - // let's get the float value first - // we get part before decimal as integer - beforeDecimal := dec.atoi64(start, end) - // get number after the decimal point - // multiple the before decimal point portion by 10 using bitwise - for i := startDecimal; i <= endDecimal; i++ { - beforeDecimal = (beforeDecimal << 3) + (beforeDecimal << 1) - } - // then we add both integers - // then we divide the number by the power found - afterDecimal := dec.atoi64(startDecimal, endDecimal) - pow := pow10uint64[endDecimal-startDecimal+2] - floatVal := float64(beforeDecimal+afterDecimal) / float64(pow) - // we have the floating value, now multiply by the exponent - exp := dec.getExponent() - val := floatVal * float64(pow10uint64[exp+1]) - return int64(val), nil - case ' ', '\t', '\n', ',', ']', '}': - dec.cursor = j - return dec.atoi64(start, end), nil - default: - dec.cursor = j - return 0, InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) - } - } - return dec.atoi64(start, end), nil - case 'e', 'E': - // get init n - return dec.getInt64WithExp(dec.atoi64(start, end), j+1) - } - // invalid json we expect numbers, dot (single one), comma, or spaces - return 0, InvalidJSONError("Invalid JSON while parsing number") - } - return dec.atoi64(start, end), nil -} - -func (dec *Decoder) getInt64WithExp(init int64, cursor int) (int64, error) { - var exp uint64 - var sign = int64(1) - for ; cursor < dec.length || dec.read(); cursor++ { - switch dec.data[cursor] { - case '+': - continue - case '-': - sign = -1 - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - uintv := uint64(digits[dec.data[cursor]]) - exp = (exp << 3) + (exp << 1) + uintv - cursor++ - for ; cursor < dec.length || dec.read(); cursor++ { - switch dec.data[cursor] { - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - uintv := uint64(digits[dec.data[cursor]]) - exp = (exp << 3) + (exp << 1) + uintv - case ' ', '\t', '\n', '}', ',', ']': - if sign == -1 { - return init * (1 / int64(pow10uint64[exp+1])), nil - } - return init * int64(pow10uint64[exp+1]), nil - default: - return 0, InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) - } - } - if sign == -1 { - return init * (1 / int64(pow10uint64[exp+1])), nil - } - return init * int64(pow10uint64[exp+1]), nil - default: - dec.err = InvalidJSONError("Invalid JSON") - return 0, dec.err - } - } - return 0, InvalidJSONError("Invalid JSON") -} - -func (dec *Decoder) getUint64(b byte) (uint64, error) { - var end = dec.cursor - var start = dec.cursor - // look for following numbers - for j := dec.cursor + 1; j < dec.length || dec.read(); j++ { - switch dec.data[j] { - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - end = j - continue - case ' ', '\n', '\t', '\r', '.', ',', '}', ']': - dec.cursor = j - return dec.atoui64(start, end), nil - } - // invalid json we expect numbers, dot (single one), comma, or spaces - return 0, InvalidJSONError("Invalid JSON while parsing number") - } - return dec.atoui64(start, end), nil -} - -func (dec *Decoder) getInt32(b byte) (int32, error) { - var end = dec.cursor - var start = dec.cursor - // look for following numbers - for j := dec.cursor + 1; j < dec.length || dec.read(); j++ { - switch dec.data[j] { - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - end = j - continue - case '.': - // if dot is found - // look for exponent (e,E) as exponent can change the - // way number should be parsed to int. - // if no exponent found, just unmarshal the number before decimal point - startDecimal := j + 1 - endDecimal := j + 1 - j++ - for ; j < dec.length || dec.read(); j++ { - switch dec.data[j] { - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - endDecimal = j - continue - case 'e', 'E': - dec.cursor = j + 1 - // can try unmarshalling to int as Exponent might change decimal number to non decimal - // let's get the float value first - // we get part before decimal as integer - beforeDecimal := dec.atoi64(start, end) - // get number after the decimal point - // multiple the before decimal point portion by 10 using bitwise - for i := startDecimal; i <= endDecimal; i++ { - beforeDecimal = (beforeDecimal << 3) + (beforeDecimal << 1) - } - // then we add both integers - // then we divide the number by the power found - afterDecimal := dec.atoi64(startDecimal, endDecimal) - pow := pow10uint64[endDecimal-startDecimal+2] - floatVal := float64(beforeDecimal+afterDecimal) / float64(pow) - // we have the floating value, now multiply by the exponent - exp := dec.getExponent() - val := floatVal * float64(pow10uint64[exp+1]) - return int32(val), nil - case ' ', '\t', '\n', ',', ']', '}': - dec.cursor = j - return dec.atoi32(start, end), nil - default: - dec.cursor = j - return 0, InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) - } - } - return dec.atoi32(start, end), nil - case 'e', 'E': - // get init n - return dec.getInt32WithExp(dec.atoi32(start, end), j+1) - case ' ', '\n', '\t', '\r', ',', '}', ']': - dec.cursor = j - return dec.atoi32(start, end), nil - } - // invalid json we expect numbers, dot (single one), comma, or spaces - return 0, InvalidJSONError("Invalid JSON while parsing number") - } - return dec.atoi32(start, end), nil -} - -func (dec *Decoder) getInt32WithExp(init int32, cursor int) (int32, error) { - var exp uint32 - var sign = int32(1) - for ; cursor < dec.length || dec.read(); cursor++ { - switch dec.data[cursor] { - case '+': - continue - case '-': - sign = -1 - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - uintv := uint32(digits[dec.data[cursor]]) - exp = (exp << 3) + (exp << 1) + uintv - cursor++ - for ; cursor < dec.length || dec.read(); cursor++ { - switch dec.data[cursor] { - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - uintv := uint32(digits[dec.data[cursor]]) - exp = (exp << 3) + (exp << 1) + uintv - case ' ', '\t', '\n', '}', ',', ']': - if sign == -1 { - return init * (1 / int32(pow10uint64[exp+1])), nil - } - return init * int32(pow10uint64[exp+1]), nil - default: - return 0, InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) - } - } - if sign == -1 { - return init * (1 / int32(pow10uint64[exp+1])), nil - } - return init * int32(pow10uint64[exp+1]), nil - default: - dec.err = InvalidJSONError("Invalid JSON") - return 0, dec.err - } - } - return 0, InvalidJSONError("Invalid JSON") -} - -func (dec *Decoder) getUint32(b byte) (uint32, error) { - var end = dec.cursor - var start = dec.cursor - // look for following numbers - for j := dec.cursor + 1; j < dec.length || dec.read(); j++ { - switch dec.data[j] { - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - end = j - continue - case ' ', '\n', '\t', '\r': - continue - case '.', ',', '}', ']': - dec.cursor = j - return dec.atoui32(start, end), nil - } - // invalid json we expect numbers, dot (single one), comma, or spaces - return 0, InvalidJSONError("Invalid JSON while parsing number") - } - return dec.atoui32(start, end), nil -} - -func (dec *Decoder) getFloat(b byte) (float64, error) { - var end = dec.cursor - var start = dec.cursor - // look for following numbers - for j := dec.cursor + 1; j < dec.length || dec.read(); j++ { - switch dec.data[j] { - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - end = j - continue - case '.': - // we get part before decimal as integer - beforeDecimal := dec.atoi64(start, end) - // then we get part after decimal as integer - start = j + 1 - // get number after the decimal point - // multiple the before decimal point portion by 10 using bitwise - for i := j + 1; i < dec.length || dec.read(); i++ { - c := dec.data[i] - if isDigit(c) { - end = i - beforeDecimal = (beforeDecimal << 3) + (beforeDecimal << 1) - continue - } else if c == 'e' || c == 'E' { - afterDecimal := dec.atoi64(start, end) - dec.cursor = i + 1 - pow := pow10uint64[end-start+2] - floatVal := float64(beforeDecimal+afterDecimal) / float64(pow) - exp := dec.getExponent() - // if exponent is negative - if exp < 0 { - return float64(floatVal) * (1 / float64(pow10uint64[exp*-1+1])), nil - } - return float64(floatVal) * float64(pow10uint64[exp+1]), nil - } - dec.cursor = i - break - } - // then we add both integers - // then we divide the number by the power found - afterDecimal := dec.atoi64(start, end) - pow := pow10uint64[end-start+2] - return float64(beforeDecimal+afterDecimal) / float64(pow), nil - case 'e', 'E': - dec.cursor = dec.cursor + 2 - // we get part before decimal as integer - beforeDecimal := uint64(dec.atoi64(start, end)) - // get exponent - exp := dec.getExponent() - // if exponent is negative - if exp < 0 { - return float64(beforeDecimal) * (1 / float64(pow10uint64[exp*-1+1])), nil - } - return float64(beforeDecimal) * float64(pow10uint64[exp+1]), nil - case ' ', '\n', '\t', '\r', ',', '}', ']': // does not have decimal - dec.cursor = j - return float64(dec.atoi64(start, end)), nil - } - // invalid json we expect numbers, dot (single one), comma, or spaces - return 0, InvalidJSONError("Invalid JSON while parsing number") - } - return float64(dec.atoi64(start, end)), nil -} - -func (dec *Decoder) atoi64(start, end int) int64 { - var ll = end + 1 - start - var val = int64(digits[dec.data[start]]) - end = end + 1 - if ll < maxInt64Length { - for i := start + 1; i < end; i++ { - intv := int64(digits[dec.data[i]]) - val = (val << 3) + (val << 1) + intv - } - return val - } else if ll == maxInt64Length { - for i := start + 1; i < end; i++ { - intv := int64(digits[dec.data[i]]) - if val > maxInt64toMultiply { - dec.err = InvalidTypeError("Overflows int64") - return 0 - } - val = (val << 3) + (val << 1) - if maxInt64-val < intv { - dec.err = InvalidTypeError("Overflows int64") - return 0 - } - val += intv - } - } else { - dec.err = InvalidTypeError("Overflows int64") - return 0 - } - return val -} - -func (dec *Decoder) atoui64(start, end int) uint64 { - var ll = end + 1 - start - var val = uint64(digits[dec.data[start]]) - end = end + 1 - if ll < maxUint64Length { - for i := start + 1; i < end; i++ { - uintv := uint64(digits[dec.data[i]]) - val = (val << 3) + (val << 1) + uintv - } - } else if ll == maxUint64Length { - for i := start + 1; i < end; i++ { - uintv := uint64(digits[dec.data[i]]) - if val > maxUint64toMultiply { - dec.err = InvalidTypeError("Overflows uint64") - return 0 - } - val = (val << 3) + (val << 1) - if maxUint64-val < uintv { - dec.err = InvalidTypeError("Overflows uint64") - return 0 - } - val += uintv - } - } else { - dec.err = InvalidTypeError("Overflows uint64") - return 0 - } - return val -} - -func (dec *Decoder) atoi32(start, end int) int32 { - var ll = end + 1 - start - var val = int32(digits[dec.data[start]]) - end = end + 1 - // overflowing - if ll < maxInt32Length { - for i := start + 1; i < end; i++ { - intv := int32(digits[dec.data[i]]) - val = (val << 3) + (val << 1) + intv - } - } else if ll == maxInt32Length { - for i := start + 1; i < end; i++ { - intv := int32(digits[dec.data[i]]) - if val > maxInt32toMultiply { - dec.err = InvalidTypeError("Overflows int32") - return 0 - } - val = (val << 3) + (val << 1) - if maxInt32-val < intv { - dec.err = InvalidTypeError("Overflows int32") - return 0 - } - val += intv - } - } else { - dec.err = InvalidTypeError("Overflows int32") - return 0 - } - return val -} - -func (dec *Decoder) atoui32(start, end int) uint32 { - var ll = end + 1 - start - var val uint32 - val = uint32(digits[dec.data[start]]) - end = end + 1 - if ll < maxUint32Length { - for i := start + 1; i < end; i++ { - uintv := uint32(digits[dec.data[i]]) - val = (val << 3) + (val << 1) + uintv - } - } else if ll == maxUint32Length { - for i := start + 1; i < end; i++ { - uintv := uint32(digits[dec.data[i]]) - if val > maxUint32toMultiply { - dec.err = InvalidTypeError("Overflows uint32") - return 0 - } - val = (val << 3) + (val << 1) - if maxUint32-val < uintv { - dec.err = InvalidTypeError("Overflows int32") - return 0 - } - val += uintv - } - } else if ll > maxUint32Length { - dec.err = InvalidTypeError("Overflows uint32") - val = 0 - } - return val -} - func (dec *Decoder) getExponent() int64 { start := dec.cursor end := dec.cursor for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { switch dec.data[dec.cursor] { // is positive - case '0': - // skip leading zeroes - if start == end { - start = dec.cursor - end = dec.cursor - continue - } - end = dec.cursor - case '1', '2', '3', '4', '5', '6', '7', '8', '9': + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': end = dec.cursor case '-': dec.cursor++ @@ -862,6 +96,7 @@ func (dec *Decoder) getExponent() int64 { // if nothing return 0 // could raise error if start == end { + dec.raiseInvalidJSONErr(dec.cursor) return 0 } return dec.atoi64(start, end) diff --git a/decode_number_float.go b/decode_number_float.go @@ -0,0 +1,223 @@ +package gojay + +// DecodeFloat64 reads the next JSON-encoded value from its input and stores it in the float64 pointed to by v. +// +// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. +func (dec *Decoder) DecodeFloat64(v *float64) error { + if dec.isPooled == 1 { + panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) + } + return dec.decodeFloat64(v) +} +func (dec *Decoder) decodeFloat64(v *float64) error { + for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { + switch c := dec.data[dec.cursor]; c { + case ' ', '\n', '\t', '\r', ',': + continue + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + val, err := dec.getFloat(c) + if err != nil { + return err + } + *v = val + return nil + case '-': + dec.cursor = dec.cursor + 1 + val, err := dec.getFloat(c) + if err != nil { + return err + } + *v = -val + return nil + case 'n': + dec.cursor++ + err := dec.assertNull() + if err != nil { + return err + } + return nil + default: + dec.err = dec.makeInvalidUnmarshalErr(v) + err := dec.skipData() + if err != nil { + return err + } + return nil + } + } + return dec.raiseInvalidJSONErr(dec.cursor) +} + +func (dec *Decoder) getFloat(b byte) (float64, error) { + var end = dec.cursor + var start = dec.cursor + // look for following numbers + for j := dec.cursor + 1; j < dec.length || dec.read(); j++ { + switch dec.data[j] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + end = j + continue + case '.': + // we get part before decimal as integer + beforeDecimal := dec.atoi64(start, end) + // then we get part after decimal as integer + start = j + 1 + // get number after the decimal point + // multiple the before decimal point portion by 10 using bitwise + for i := j + 1; i < dec.length || dec.read(); i++ { + c := dec.data[i] + if isDigit(c) { + end = i + beforeDecimal = (beforeDecimal << 3) + (beforeDecimal << 1) + continue + } else if c == 'e' || c == 'E' { + afterDecimal := dec.atoi64(start, end) + dec.cursor = i + 1 + pow := pow10uint64[end-start+2] + floatVal := float64(beforeDecimal+afterDecimal) / float64(pow) + exp := dec.getExponent() + // if exponent is negative + if exp < 0 { + return float64(floatVal) * (1 / float64(pow10uint64[exp*-1+1])), nil + } + return float64(floatVal) * float64(pow10uint64[exp+1]), nil + } + dec.cursor = i + break + } + // then we add both integers + // then we divide the number by the power found + afterDecimal := dec.atoi64(start, end) + pow := pow10uint64[end-start+2] + return float64(beforeDecimal+afterDecimal) / float64(pow), nil + case 'e', 'E': + dec.cursor = dec.cursor + 2 + // we get part before decimal as integer + beforeDecimal := uint64(dec.atoi64(start, end)) + // get exponent + exp := dec.getExponent() + // if exponent is negative + if exp < 0 { + return float64(beforeDecimal) * (1 / float64(pow10uint64[exp*-1+1])), nil + } + return float64(beforeDecimal) * float64(pow10uint64[exp+1]), nil + case ' ', '\n', '\t', '\r', ',', '}', ']': // does not have decimal + dec.cursor = j + return float64(dec.atoi64(start, end)), nil + } + // invalid json we expect numbers, dot (single one), comma, or spaces + return 0, dec.raiseInvalidJSONErr(dec.cursor) + } + return float64(dec.atoi64(start, end)), nil +} + +// DecodeFloat32 reads the next JSON-encoded value from its input and stores it in the float32 pointed to by v. +// +// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. +func (dec *Decoder) DecodeFloat32(v *float32) error { + if dec.isPooled == 1 { + panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) + } + return dec.decodeFloat32(v) +} +func (dec *Decoder) decodeFloat32(v *float32) error { + for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { + switch c := dec.data[dec.cursor]; c { + case ' ', '\n', '\t', '\r', ',': + continue + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + val, err := dec.getFloat32(c) + if err != nil { + return err + } + *v = val + return nil + case '-': + dec.cursor = dec.cursor + 1 + val, err := dec.getFloat32(c) + if err != nil { + return err + } + *v = -val + return nil + case 'n': + dec.cursor++ + err := dec.assertNull() + if err != nil { + return err + } + return nil + default: + dec.err = dec.makeInvalidUnmarshalErr(v) + err := dec.skipData() + if err != nil { + return err + } + return nil + } + } + return dec.raiseInvalidJSONErr(dec.cursor) +} + +func (dec *Decoder) getFloat32(b byte) (float32, error) { + var end = dec.cursor + var start = dec.cursor + // look for following numbers + for j := dec.cursor + 1; j < dec.length || dec.read(); j++ { + switch dec.data[j] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + end = j + continue + case '.': + // we get part before decimal as integer + beforeDecimal := dec.atoi32(start, end) + // then we get part after decimal as integer + start = j + 1 + // get number after the decimal point + // multiple the before decimal point portion by 10 using bitwise + for i := j + 1; i < dec.length || dec.read(); i++ { + c := dec.data[i] + if isDigit(c) { + end = i + beforeDecimal = (beforeDecimal << 3) + (beforeDecimal << 1) + continue + } else if c == 'e' || c == 'E' { + afterDecimal := dec.atoi32(start, end) + dec.cursor = i + 1 + pow := pow10uint64[end-start+2] + floatVal := float32(beforeDecimal+afterDecimal) / float32(pow) + exp := dec.getExponent() + // if exponent is negative + if exp < 0 { + return float32(floatVal) * (1 / float32(pow10uint64[exp*-1+1])), nil + } + return float32(floatVal) * float32(pow10uint64[exp+1]), nil + } + dec.cursor = i + break + } + // then we add both integers + // then we divide the number by the power found + afterDecimal := dec.atoi32(start, end) + pow := pow10uint64[end-start+2] + return float32(beforeDecimal+afterDecimal) / float32(pow), nil + case 'e', 'E': + dec.cursor = dec.cursor + 2 + // we get part before decimal as integer + beforeDecimal := uint32(dec.atoi32(start, end)) + // get exponent + exp := dec.getExponent() + // if exponent is negative + if exp < 0 { + return float32(beforeDecimal) * (1 / float32(pow10uint64[exp*-1+1])), nil + } + return float32(beforeDecimal) * float32(pow10uint64[exp+1]), nil + case ' ', '\n', '\t', '\r', ',', '}', ']': // does not have decimal + dec.cursor = j + return float32(dec.atoi32(start, end)), nil + } + // invalid json we expect numbers, dot (single one), comma, or spaces + return 0, dec.raiseInvalidJSONErr(dec.cursor) + } + return float32(dec.atoi32(start, end)), nil +} diff --git a/decode_number_float_test.go b/decode_number_float_test.go @@ -0,0 +1,417 @@ +package gojay + +import ( + "fmt" + "math" + "reflect" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDecoderFloat64(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult float64 + err bool + errType interface{} + }{ + { + name: "basic-exponent-positive-positive-exp", + json: "1e2", + expectedResult: 100, + }, + { + name: "basic-exponent-positive-positive-exp2", + json: "5e+06", + expectedResult: 5000000, + }, + { + name: "basic-exponent-positive-positive-exp3", + json: "3e+3", + expectedResult: 3000, + }, + { + name: "basic-null", + json: "null", + expectedResult: 0, + }, + { + name: "basic-null-err", + json: "nxll", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-null-err", + json: "trua", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-exponent-positive-positive-exp4", + json: "8e+005", + expectedResult: 800000, + }, + { + name: "basic-exponent-positive-negative-exp", + json: "1e-2", + expectedResult: 0.01, + }, + { + name: "basic-exponent-positive-negative-exp2", + json: "5e-6", + expectedResult: 0.000005, + }, + { + name: "basic-exponent-positive-negative-exp3", + json: "3e-3", + expectedResult: 0.003, + }, + { + name: "basic-exponent-positive-negative-exp4", + json: "8e-005", + expectedResult: 0.00008, + }, + { + name: "basic-exponent-negative-positive-exp", + json: "-1e2", + expectedResult: -100, + }, + { + name: "basic-exponent-negative-positive-exp2", + json: "-5e+06", + expectedResult: -5000000, + }, + { + name: "basic-exponent-negative-positive-exp3", + json: "-3e03", + expectedResult: -3000, + }, + { + name: "basic-float2", + json: "877 ", + expectedResult: 877, + }, + { + name: "basic-exponent-negative-positive-exp4", + json: "-8e+005", + expectedResult: -800000, + }, + { + name: "basic-exponent-negative-positive-exp4", + json: "-8.2e-005", + expectedResult: -0.000082, + }, + { + name: "basic-float", + json: "2.4595", + expectedResult: 2.4595, + }, + { + name: "basic-float2", + json: "877", + expectedResult: 877, + }, + { + name: "basic-float2", + json: "-7.8876", + expectedResult: -7.8876, + }, + { + name: "basic-float", + json: "2.4595e1", + expectedResult: 24.595, + }, + { + name: "basic-float2", + json: "-7.8876e002", + expectedResult: -788.76, + }, + { + name: "error", + json: "83zez4", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error", + json: "-83zez4", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "invalid-type", + json: `"string"`, + expectedResult: 0, + err: true, + errType: InvalidUnmarshalError(""), + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + json := []byte(testCase.json) + var v float64 + err := Unmarshal(json, &v) + if testCase.err { + assert.NotNil(t, err, "Err must not be nil") + if testCase.errType != nil { + assert.IsType( + t, + testCase.errType, + err, + fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), + ) + } + } else { + assert.Nil(t, err, "Err must be nil") + } + assert.Equal(t, testCase.expectedResult*1000000, math.Round(v*1000000), fmt.Sprintf("v must be equal to %f", testCase.expectedResult)) + }) + } + t.Run("pool-error", func(t *testing.T) { + result := float64(1) + dec := NewDecoder(nil) + dec.Release() + defer func() { + err := recover() + assert.NotNil(t, err, "err shouldnot be nil") + assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") + }() + _ = dec.DecodeFloat64(&result) + assert.True(t, false, "should not be called as decoder should have panicked") + }) + t.Run("decoder-api", func(t *testing.T) { + var v float64 + dec := NewDecoder(strings.NewReader(`1.25`)) + defer dec.Release() + err := dec.DecodeFloat64(&v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, 1.25, v, "v must be equal to 1.25") + }) + t.Run("decoder-api2", func(t *testing.T) { + var v float64 + dec := NewDecoder(strings.NewReader(`1.25`)) + defer dec.Release() + err := dec.DecodeFloat64(&v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, 1.25, v, "v must be equal to 1.25") + }) + t.Run("decoder-api-json-error", func(t *testing.T) { + var v float64 + dec := NewDecoder(strings.NewReader(``)) + defer dec.Release() + err := dec.DecodeFloat64(&v) + assert.NotNil(t, err, "Err must not be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + }) +} + +func TestDecoderFloat32(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult float32 + err bool + errType interface{} + }{ + { + name: "basic-exponent-positive-positive-exp", + json: "1e2", + expectedResult: 100, + }, + { + name: "basic-exponent-positive-positive-exp2", + json: "5e+06", + expectedResult: 5000000, + }, + { + name: "basic-exponent-positive-positive-exp3", + json: "3e+3", + expectedResult: 3000, + }, + { + name: "basic-null", + json: "null", + expectedResult: 0, + }, + { + name: "basic-null-err", + json: "trua", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-null-err", + json: "nxll", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-exponent-positive-positive-exp4", + json: "8e+005", + expectedResult: 800000, + }, + { + name: "basic-exponent-positive-negative-exp", + json: "1e-2", + expectedResult: 0.01, + }, + { + name: "basic-exponent-positive-negative-exp2", + json: "5e-6", + expectedResult: 0.000005, + }, + { + name: "basic-exponent-positive-negative-exp3", + json: "3e-3", + expectedResult: 0.003, + }, + { + name: "basic-exponent-positive-negative-exp4", + json: "8e-005", + expectedResult: 0.00008, + }, + { + name: "basic-exponent-negative-positive-exp", + json: "-1e2", + expectedResult: -100, + }, + { + name: "basic-exponent-negative-positive-exp2", + json: "-5e+06", + expectedResult: -5000000, + }, + { + name: "basic-exponent-negative-positive-exp3", + json: "-3e03", + expectedResult: -3000, + }, + { + name: "basic-exponent-negative-positive-exp4", + json: "-8e+005", + expectedResult: -800000, + }, + { + name: "basic-exponent-negative-positive-exp4", + json: "-8.2e-005", + expectedResult: -0.000082, + }, + { + name: "basic-float", + json: "2.4595", + expectedResult: 2.4595, + }, + { + name: "basic-float2", + json: "877", + expectedResult: 877, + }, + { + name: "basic-float2", + json: "877 ", + expectedResult: 877, + }, + { + name: "basic-float2", + json: "-7.8876", + expectedResult: -7.8876, + }, + { + name: "basic-float", + json: "2.459e1", + expectedResult: 24.59, + }, + { + name: "basic-float2", + json: "-7.8876e002", + expectedResult: -788.76, + }, + { + name: "error", + json: "83zez4", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error", + json: "-83zez4", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "invalid-type", + json: `"string"`, + expectedResult: 0, + err: true, + errType: InvalidUnmarshalError(""), + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + json := []byte(testCase.json) + var v float32 + err := Unmarshal(json, &v) + if testCase.err { + assert.NotNil(t, err, "Err must not be nil") + if testCase.errType != nil { + assert.IsType( + t, + testCase.errType, + err, + fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), + ) + } + } else { + assert.Nil(t, err, "Err must be nil") + } + assert.Equal(t, float64(testCase.expectedResult*1000000), math.Round(float64(v*1000000)), fmt.Sprintf("v must be equal to %f", testCase.expectedResult)) + }) + } + t.Run("pool-error", func(t *testing.T) { + result := float32(1) + dec := NewDecoder(nil) + dec.Release() + defer func() { + err := recover() + assert.NotNil(t, err, "err shouldnot be nil") + assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") + }() + _ = dec.DecodeFloat32(&result) + assert.True(t, false, "should not be called as decoder should have panicked") + }) + t.Run("decoder-api", func(t *testing.T) { + var v float32 + dec := NewDecoder(strings.NewReader(`1.25`)) + defer dec.Release() + err := dec.DecodeFloat32(&v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, float32(1.25), v, "v must be equal to 1.25") + }) + t.Run("decoder-api2", func(t *testing.T) { + var v float32 + dec := NewDecoder(strings.NewReader(`1.25`)) + defer dec.Release() + err := dec.Decode(&v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, float32(1.25), v, "v must be equal to 1.25") + }) + t.Run("decoder-api-json-error", func(t *testing.T) { + var v float32 + dec := NewDecoder(strings.NewReader(``)) + defer dec.Release() + err := dec.DecodeFloat32(&v) + assert.NotNil(t, err, "Err must not be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + }) +} diff --git a/decode_number_int.go b/decode_number_int.go @@ -0,0 +1,793 @@ +package gojay + +import ( + "fmt" + "math" +) + +// DecodeInt reads the next JSON-encoded value from its input and stores it in the int pointed to by v. +// +// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. +func (dec *Decoder) DecodeInt(v *int) error { + if dec.isPooled == 1 { + panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) + } + return dec.decodeInt(v) +} +func (dec *Decoder) decodeInt(v *int) error { + for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { + switch c := dec.data[dec.cursor]; c { + case ' ', '\n', '\t', '\r', ',': + continue + // we don't look for 0 as leading zeros are invalid per RFC + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + val, err := dec.getInt64(c) + if err != nil { + return err + } + *v = int(val) + return nil + case '-': + dec.cursor = dec.cursor + 1 + val, err := dec.getInt64(dec.data[dec.cursor]) + if err != nil { + return err + } + *v = -int(val) + return nil + case 'n': + dec.cursor++ + err := dec.assertNull() + if err != nil { + return err + } + dec.cursor++ + return nil + default: + dec.err = InvalidUnmarshalError( + fmt.Sprintf( + "Cannot unmarshall to int, wrong char '%s' found at pos %d", + string(dec.data[dec.cursor]), + dec.cursor, + ), + ) + err := dec.skipData() + if err != nil { + return err + } + return nil + } + } + return InvalidJSONError("Invalid JSON while parsing int") +} + +// DecodeInt16 reads the next JSON-encoded value from its input and stores it in the int16 pointed to by v. +// +// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. +func (dec *Decoder) DecodeInt16(v *int16) error { + if dec.isPooled == 1 { + panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) + } + return dec.decodeInt16(v) +} +func (dec *Decoder) decodeInt16(v *int16) error { + for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { + switch c := dec.data[dec.cursor]; c { + case ' ', '\n', '\t', '\r', ',': + continue + // we don't look for 0 as leading zeros are invalid per RFC + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + val, err := dec.getInt16(c) + if err != nil { + return err + } + *v = val + return nil + case '-': + dec.cursor = dec.cursor + 1 + val, err := dec.getInt16(dec.data[dec.cursor]) + if err != nil { + return err + } + *v = -val + return nil + case 'n': + dec.cursor++ + err := dec.assertNull() + if err != nil { + return err + } + dec.cursor++ + return nil + default: + dec.err = dec.makeInvalidUnmarshalErr(v) + err := dec.skipData() + if err != nil { + return err + } + return nil + } + } + return dec.raiseInvalidJSONErr(dec.cursor) +} + +func (dec *Decoder) getInt16(b byte) (int16, error) { + var end = dec.cursor + var start = dec.cursor + // look for following numbers + for j := dec.cursor + 1; j < dec.length || dec.read(); j++ { + switch dec.data[j] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + end = j + continue + case '.': + // if dot is found + // look for exponent (e,E) as exponent can change the + // way number should be parsed to int. + // if no exponent found, just unmarshal the number before decimal point + startDecimal := j + 1 + endDecimal := j + 1 + j++ + for ; j < dec.length || dec.read(); j++ { + switch dec.data[j] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + endDecimal = j + continue + case 'e', 'E': + dec.cursor = j + 1 + // can try unmarshalling to int as Exponent might change decimal number to non decimal + // let's get the float value first + // we get part before decimal as integer + beforeDecimal := dec.atoi16(start, end) + // get number after the decimal point + // multiple the before decimal point portion by 10 using bitwise + for i := startDecimal; i <= endDecimal; i++ { + beforeDecimal = (beforeDecimal << 3) + (beforeDecimal << 1) + } + // then we add both integers + // then we divide the number by the power found + afterDecimal := dec.atoi16(startDecimal, endDecimal) + pow := pow10uint64[endDecimal-startDecimal+2] + floatVal := float64(beforeDecimal+afterDecimal) / float64(pow) + // we have the floating value, now multiply by the exponent + exp := dec.getExponent() + val := floatVal * float64(pow10uint64[exp+1]) + return int16(val), nil + case ' ', '\t', '\n', ',', ']', '}': + dec.cursor = j + return dec.atoi16(start, end), nil + default: + dec.cursor = j + return 0, InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + } + } + return dec.atoi16(start, end), nil + case 'e', 'E': + // get init n + return dec.getInt16WithExp(dec.atoi16(start, end), j+1) + case ' ', '\n', '\t', '\r', ',', '}', ']': + dec.cursor = j + return dec.atoi16(start, end), nil + } + // invalid json we expect numbers, dot (single one), comma, or spaces + return 0, dec.raiseInvalidJSONErr(dec.cursor) + } + return dec.atoi16(start, end), nil +} + +func (dec *Decoder) getInt16WithExp(init int16, cursor int) (int16, error) { + var exp uint16 + var sign = int16(1) + for ; cursor < dec.length || dec.read(); cursor++ { + switch dec.data[cursor] { + case '+': + continue + case '-': + sign = -1 + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + uintv := uint16(digits[dec.data[cursor]]) + exp = (exp << 3) + (exp << 1) + uintv + cursor++ + for ; cursor < dec.length || dec.read(); cursor++ { + switch dec.data[cursor] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + uintv := uint16(digits[dec.data[cursor]]) + exp = (exp << 3) + (exp << 1) + uintv + case ' ', '\t', '\n', '}', ',', ']': + if sign == -1 { + return init * (1 / int16(pow10uint64[exp+1])), nil + } + return init * int16(pow10uint64[exp+1]), nil + default: + return 0, InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + } + } + if sign == -1 { + return init * (1 / int16(pow10uint64[exp+1])), nil + } + return init * int16(pow10uint64[exp+1]), nil + default: + return 0, dec.raiseInvalidJSONErr(dec.cursor) + } + } + return 0, dec.raiseInvalidJSONErr(dec.cursor) +} + +// DecodeInt8 reads the next JSON-encoded value from its input and stores it in the int8 pointed to by v. +// +// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. +func (dec *Decoder) DecodeInt8(v *int8) error { + if dec.isPooled == 1 { + panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) + } + return dec.decodeInt8(v) +} +func (dec *Decoder) decodeInt8(v *int8) error { + for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { + switch c := dec.data[dec.cursor]; c { + case ' ', '\n', '\t', '\r', ',': + continue + // we don't look for 0 as leading zeros are invalid per RFC + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + val, err := dec.getInt8(c) + if err != nil { + return err + } + *v = val + return nil + case '-': + dec.cursor = dec.cursor + 1 + val, err := dec.getInt8(dec.data[dec.cursor]) + if err != nil { + return err + } + *v = -val + return nil + case 'n': + dec.cursor++ + err := dec.assertNull() + if err != nil { + return err + } + dec.cursor++ + return nil + default: + dec.err = dec.makeInvalidUnmarshalErr(v) + err := dec.skipData() + if err != nil { + return err + } + return nil + } + } + return dec.raiseInvalidJSONErr(dec.cursor) +} + +func (dec *Decoder) getInt8(b byte) (int8, error) { + var end = dec.cursor + var start = dec.cursor + // look for following numbers + for j := dec.cursor + 1; j < dec.length || dec.read(); j++ { + switch dec.data[j] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + end = j + continue + case '.': + // if dot is found + // look for exponent (e,E) as exponent can change the + // way number should be parsed to int. + // if no exponent found, just unmarshal the number before decimal point + startDecimal := j + 1 + endDecimal := j + 1 + j++ + for ; j < dec.length || dec.read(); j++ { + switch dec.data[j] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + endDecimal = j + continue + case 'e', 'E': + dec.cursor = j + 1 + // can try unmarshalling to int as Exponent might change decimal number to non decimal + // let's get the float value first + // we get part before decimal as integer + beforeDecimal := dec.atoi8(start, end) + // get number after the decimal point + // multiple the before decimal point portion by 10 using bitwise + for i := startDecimal; i <= endDecimal; i++ { + beforeDecimal = (beforeDecimal << 3) + (beforeDecimal << 1) + } + // then we add both integers + // then we divide the number by the power found + afterDecimal := dec.atoi8(startDecimal, endDecimal) + pow := pow10uint64[endDecimal-startDecimal+2] + floatVal := float64(beforeDecimal+afterDecimal) / float64(pow) + // we have the floating value, now multiply by the exponent + exp := dec.getExponent() + val := floatVal * float64(pow10uint64[exp+1]) + return int8(val), nil + case ' ', '\t', '\n', ',', ']', '}': + dec.cursor = j + return dec.atoi8(start, end), nil + default: + dec.cursor = j + return 0, InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + } + } + return dec.atoi8(start, end), nil + case 'e', 'E': + // get init n + return dec.getInt8WithExp(dec.atoi8(start, end), j+1) + case ' ', '\n', '\t', '\r', ',', '}', ']': + dec.cursor = j + return dec.atoi8(start, end), nil + } + // invalid json we expect numbers, dot (single one), comma, or spaces + return 0, dec.raiseInvalidJSONErr(dec.cursor) + } + return dec.atoi8(start, end), nil +} + +func (dec *Decoder) getInt8WithExp(init int8, cursor int) (int8, error) { + var exp uint8 + var sign = int8(1) + for ; cursor < dec.length || dec.read(); cursor++ { + switch dec.data[cursor] { + case '+': + continue + case '-': + sign = -1 + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + uintv := uint8(digits[dec.data[cursor]]) + exp = (exp << 3) + (exp << 1) + uintv + cursor++ + for ; cursor < dec.length || dec.read(); cursor++ { + switch dec.data[cursor] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + uintv := uint8(digits[dec.data[cursor]]) + exp = (exp << 3) + (exp << 1) + uintv + case ' ', '\t', '\n', '}', ',', ']': + if sign == -1 { + return init * (1 / int8(pow10uint64[exp+1])), nil + } + return init * int8(pow10uint64[exp+1]), nil + default: + return 0, InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + } + } + if sign == -1 { + return init * (1 / int8(pow10uint64[exp+1])), nil + } + return init * int8(pow10uint64[exp+1]), nil + default: + dec.err = dec.raiseInvalidJSONErr(dec.cursor) + return 0, dec.err + } + } + return 0, dec.raiseInvalidJSONErr(dec.cursor) +} + +// DecodeInt32 reads the next JSON-encoded value from its input and stores it in the int32 pointed to by v. +// +// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. +func (dec *Decoder) DecodeInt32(v *int32) error { + if dec.isPooled == 1 { + panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) + } + return dec.decodeInt32(v) +} +func (dec *Decoder) decodeInt32(v *int32) error { + for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { + switch c := dec.data[dec.cursor]; c { + case ' ', '\n', '\t', '\r', ',': + continue + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + val, err := dec.getInt32(c) + if err != nil { + return err + } + *v = val + return nil + case '-': + dec.cursor = dec.cursor + 1 + val, err := dec.getInt32(dec.data[dec.cursor]) + if err != nil { + return err + } + *v = -val + return nil + case 'n': + dec.cursor++ + err := dec.assertNull() + if err != nil { + return err + } + return nil + default: + dec.err = dec.makeInvalidUnmarshalErr(v) + err := dec.skipData() + if err != nil { + return err + } + return nil + } + } + return dec.raiseInvalidJSONErr(dec.cursor) +} + +func (dec *Decoder) getInt32(b byte) (int32, error) { + var end = dec.cursor + var start = dec.cursor + // look for following numbers + for j := dec.cursor + 1; j < dec.length || dec.read(); j++ { + switch dec.data[j] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + end = j + continue + case '.': + // if dot is found + // look for exponent (e,E) as exponent can change the + // way number should be parsed to int. + // if no exponent found, just unmarshal the number before decimal point + startDecimal := j + 1 + endDecimal := j + 1 + j++ + for ; j < dec.length || dec.read(); j++ { + switch dec.data[j] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + endDecimal = j + continue + case 'e', 'E': + dec.cursor = j + 1 + // can try unmarshalling to int as Exponent might change decimal number to non decimal + // let's get the float value first + // we get part before decimal as integer + beforeDecimal := dec.atoi64(start, end) + // get number after the decimal point + // multiple the before decimal point portion by 10 using bitwise + for i := startDecimal; i <= endDecimal; i++ { + beforeDecimal = (beforeDecimal << 3) + (beforeDecimal << 1) + } + // then we add both integers + // then we divide the number by the power found + afterDecimal := dec.atoi64(startDecimal, endDecimal) + pow := pow10uint64[endDecimal-startDecimal+2] + floatVal := float64(beforeDecimal+afterDecimal) / float64(pow) + // we have the floating value, now multiply by the exponent + exp := dec.getExponent() + val := floatVal * float64(pow10uint64[exp+1]) + return int32(val), nil + case ' ', '\t', '\n', ',', ']', '}': + dec.cursor = j + return dec.atoi32(start, end), nil + default: + dec.cursor = j + return 0, InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + } + } + return dec.atoi32(start, end), nil + case 'e', 'E': + // get init n + return dec.getInt32WithExp(dec.atoi32(start, end), j+1) + case ' ', '\n', '\t', '\r', ',', '}', ']': + dec.cursor = j + return dec.atoi32(start, end), nil + } + // invalid json we expect numbers, dot (single one), comma, or spaces + return 0, dec.raiseInvalidJSONErr(dec.cursor) + } + return dec.atoi32(start, end), nil +} + +func (dec *Decoder) getInt32WithExp(init int32, cursor int) (int32, error) { + var exp uint32 + var sign = int32(1) + for ; cursor < dec.length || dec.read(); cursor++ { + switch dec.data[cursor] { + case '+': + continue + case '-': + sign = -1 + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + uintv := uint32(digits[dec.data[cursor]]) + exp = (exp << 3) + (exp << 1) + uintv + cursor++ + for ; cursor < dec.length || dec.read(); cursor++ { + switch dec.data[cursor] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + uintv := uint32(digits[dec.data[cursor]]) + exp = (exp << 3) + (exp << 1) + uintv + case ' ', '\t', '\n', '}', ',', ']': + if sign == -1 { + return init * (1 / int32(pow10uint64[exp+1])), nil + } + return init * int32(pow10uint64[exp+1]), nil + default: + return 0, InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + } + } + if sign == -1 { + return init * (1 / int32(pow10uint64[exp+1])), nil + } + return init * int32(pow10uint64[exp+1]), nil + default: + dec.err = dec.raiseInvalidJSONErr(dec.cursor) + return 0, dec.err + } + } + return 0, dec.raiseInvalidJSONErr(dec.cursor) +} + +// DecodeInt64 reads the next JSON-encoded value from its input and stores it in the int64 pointed to by v. +// +// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. +func (dec *Decoder) DecodeInt64(v *int64) error { + if dec.isPooled == 1 { + panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) + } + return dec.decodeInt64(v) +} + +func (dec *Decoder) decodeInt64(v *int64) error { + for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { + switch c := dec.data[dec.cursor]; c { + case ' ', '\n', '\t', '\r', ',': + continue + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + val, err := dec.getInt64(c) + if err != nil { + return err + } + *v = val + return nil + case '-': + dec.cursor = dec.cursor + 1 + val, err := dec.getInt64(dec.data[dec.cursor]) + if err != nil { + return err + } + *v = -val + return nil + case 'n': + dec.cursor++ + err := dec.assertNull() + if err != nil { + return err + } + return nil + default: + dec.err = dec.makeInvalidUnmarshalErr(v) + err := dec.skipData() + if err != nil { + return err + } + return nil + } + } + return dec.raiseInvalidJSONErr(dec.cursor) +} + +func (dec *Decoder) getInt64(b byte) (int64, error) { + var end = dec.cursor + var start = dec.cursor + // look for following numbers + for j := dec.cursor + 1; j < dec.length || dec.read(); j++ { + switch dec.data[j] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + end = j + continue + case ' ', '\t', '\n', ',', '}', ']': + dec.cursor = j + return dec.atoi64(start, end), nil + case '.': + // if dot is found + // look for exponent (e,E) as exponent can change the + // way number should be parsed to int. + // if no exponent found, just unmarshal the number before decimal point + startDecimal := j + 1 + endDecimal := j + 1 + j++ + for ; j < dec.length || dec.read(); j++ { + switch dec.data[j] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + endDecimal = j + continue + case 'e', 'E': + dec.cursor = j + 1 + // can try unmarshalling to int as Exponent might change decimal number to non decimal + // let's get the float value first + // we get part before decimal as integer + beforeDecimal := dec.atoi64(start, end) + // get number after the decimal point + // multiple the before decimal point portion by 10 using bitwise + for i := startDecimal; i <= endDecimal; i++ { + beforeDecimal = (beforeDecimal << 3) + (beforeDecimal << 1) + } + // then we add both integers + // then we divide the number by the power found + afterDecimal := dec.atoi64(startDecimal, endDecimal) + pow := pow10uint64[endDecimal-startDecimal+2] + floatVal := float64(beforeDecimal+afterDecimal) / float64(pow) + // we have the floating value, now multiply by the exponent + exp := dec.getExponent() + val := floatVal * float64(pow10uint64[exp+1]) + return int64(val), nil + case ' ', '\t', '\n', ',', ']', '}': + dec.cursor = j + return dec.atoi64(start, end), nil + default: + dec.cursor = j + return 0, InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + } + } + return dec.atoi64(start, end), nil + case 'e', 'E': + // get init n + return dec.getInt64WithExp(dec.atoi64(start, end), j+1) + } + // invalid json we expect numbers, dot (single one), comma, or spaces + return 0, dec.raiseInvalidJSONErr(dec.cursor) + } + return dec.atoi64(start, end), nil +} + +func (dec *Decoder) getInt64WithExp(init int64, cursor int) (int64, error) { + var exp uint64 + var sign = int64(1) + for ; cursor < dec.length || dec.read(); cursor++ { + switch dec.data[cursor] { + case '+': + continue + case '-': + sign = -1 + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + uintv := uint64(digits[dec.data[cursor]]) + exp = (exp << 3) + (exp << 1) + uintv + cursor++ + for ; cursor < dec.length || dec.read(); cursor++ { + switch dec.data[cursor] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + uintv := uint64(digits[dec.data[cursor]]) + exp = (exp << 3) + (exp << 1) + uintv + case ' ', '\t', '\n', '}', ',', ']': + if sign == -1 { + return init * (1 / int64(pow10uint64[exp+1])), nil + } + return init * int64(pow10uint64[exp+1]), nil + default: + return 0, InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + } + } + if sign == -1 { + return init * (1 / int64(pow10uint64[exp+1])), nil + } + return init * int64(pow10uint64[exp+1]), nil + default: + return 0, dec.raiseInvalidJSONErr(dec.cursor) + } + } + return 0, dec.raiseInvalidJSONErr(dec.cursor) +} + +func (dec *Decoder) atoi64(start, end int) int64 { + var ll = end + 1 - start + var val = int64(digits[dec.data[start]]) + end = end + 1 + if ll < maxInt64Length { + for i := start + 1; i < end; i++ { + intv := int64(digits[dec.data[i]]) + val = (val << 3) + (val << 1) + intv + } + return val + } else if ll == maxInt64Length { + for i := start + 1; i < end; i++ { + intv := int64(digits[dec.data[i]]) + if val > maxInt64toMultiply { + dec.err = dec.makeInvalidUnmarshalErr(val) + return 0 + } + val = (val << 3) + (val << 1) + if math.MaxInt64-val < intv { + dec.err = dec.makeInvalidUnmarshalErr(val) + return 0 + } + val += intv + } + } else { + dec.err = dec.makeInvalidUnmarshalErr(val) + return 0 + } + return val +} + +func (dec *Decoder) atoi32(start, end int) int32 { + var ll = end + 1 - start + var val = int32(digits[dec.data[start]]) + end = end + 1 + // overflowing + if ll < maxInt32Length { + for i := start + 1; i < end; i++ { + intv := int32(digits[dec.data[i]]) + val = (val << 3) + (val << 1) + intv + } + } else if ll == maxInt32Length { + for i := start + 1; i < end; i++ { + intv := int32(digits[dec.data[i]]) + if val > maxInt32toMultiply { + dec.err = dec.makeInvalidUnmarshalErr(val) + return 0 + } + val = (val << 3) + (val << 1) + if math.MaxInt32-val < intv { + dec.err = dec.makeInvalidUnmarshalErr(val) + return 0 + } + val += intv + } + } else { + dec.err = dec.makeInvalidUnmarshalErr(val) + return 0 + } + return val +} + +func (dec *Decoder) atoi16(start, end int) int16 { + var ll = end + 1 - start + var val = int16(digits[dec.data[start]]) + end = end + 1 + // overflowing + if ll < maxInt16Length { + for i := start + 1; i < end; i++ { + intv := int16(digits[dec.data[i]]) + val = (val << 3) + (val << 1) + intv + } + } else if ll == maxInt16Length { + for i := start + 1; i < end; i++ { + intv := int16(digits[dec.data[i]]) + if val > maxInt16toMultiply { + dec.err = dec.makeInvalidUnmarshalErr(val) + return 0 + } + val = (val << 3) + (val << 1) + if math.MaxInt16-val < intv { + dec.err = dec.makeInvalidUnmarshalErr(val) + return 0 + } + val += intv + } + } else { + dec.err = dec.makeInvalidUnmarshalErr(val) + return 0 + } + return val +} + +func (dec *Decoder) atoi8(start, end int) int8 { + var ll = end + 1 - start + var val = int8(digits[dec.data[start]]) + end = end + 1 + // overflowing + if ll < maxInt8Length { + for i := start + 1; i < end; i++ { + intv := int8(digits[dec.data[i]]) + val = (val << 3) + (val << 1) + intv + } + } else if ll == maxInt8Length { + for i := start + 1; i < end; i++ { + intv := int8(digits[dec.data[i]]) + if val > maxInt8toMultiply { + dec.err = dec.makeInvalidUnmarshalErr(val) + return 0 + } + val = (val << 3) + (val << 1) + if math.MaxInt8-val < intv { + dec.err = dec.makeInvalidUnmarshalErr(val) + return 0 + } + val += intv + } + } else { + dec.err = dec.makeInvalidUnmarshalErr(val) + return 0 + } + return val +} diff --git a/decode_number_int_test.go b/decode_number_int_test.go @@ -0,0 +1,1276 @@ +package gojay + +import ( + "fmt" + "reflect" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDecoderInt(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult int + err bool + errType interface{} + }{ + { + name: "basic-positive", + json: "100", + expectedResult: 100, + }, + { + name: "basic-positive2", + json: "1039405", + expectedResult: 1039405, + }, + { + name: "basic-negative", + json: "-2", + expectedResult: -2, + }, + { + name: "basic-null", + json: "null", + expectedResult: 0, + }, + { + name: "basic-null-err", + json: "nxll", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-skip-data-err", + json: "trua", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-big", + json: "9223372036854775807", + expectedResult: 9223372036854775807, + }, + { + name: "basic-big-overflow", + json: "9223372036854775808", + expectedResult: 0, + err: true, + errType: InvalidUnmarshalError(""), + }, + { + name: "basic-big-overflow2", + json: "92233720368547758089", + expectedResult: 0, + err: true, + errType: InvalidUnmarshalError(""), + }, + { + name: "basic-big-overflow3", + json: "92233720368547758089 ", + expectedResult: 0, + err: true, + errType: InvalidUnmarshalError(""), + }, + { + name: "basic-negative2", + json: "-2349557", + expectedResult: -2349557, + }, + { + name: "basic-float", + json: "2.4595", + expectedResult: 2, + }, + { + name: "basic-float2", + json: "-7.8876", + expectedResult: -7, + }, + { + name: "basic-float2", + json: "-7.8876 ", + expectedResult: -7, + }, + { + name: "basic-float2", + json: "-7.8876a", + expectedResult: 0, + err: true, + }, + { + name: "basic-exponent-positive-positive-exp", + json: "1e2", + expectedResult: 100, + }, + { + name: "basic-exponent-positive-positive-exp2", + json: "5e+06", + expectedResult: 5000000, + }, + { + name: "basic-exponent-positive-positive-exp2", + json: "5.01e+10", + expectedResult: 50100000000, + }, + { + name: "basic-exponent-positive-positive-exp3", + json: "3e+3", + expectedResult: 3000, + }, + { + name: "basic-exponent-positive-positive-exp4", + json: "8e+005", + expectedResult: 800000, + }, + { + name: "basic-exponent-positive-negative-exp", + json: "1e-2", + expectedResult: 0, + }, + { + name: "basic-exponent-positive-negative-exp2", + json: "5e-6", + expectedResult: 0, + }, + { + name: "basic-exponent-positive-negative-exp3", + json: "3e-3", + expectedResult: 0, + }, + { + name: "basic-exponent-positive-negative-exp4", + json: "8e-005", + expectedResult: 0, + }, + { + name: "basic-exponent-negative-positive-exp", + json: "-1e2", + expectedResult: -100, + }, + { + name: "basic-exponent-negative-positive-exp2", + json: "-5e+06", + expectedResult: -5000000, + }, + { + name: "basic-exponent-negative-positive-exp3", + json: "-3e03", + expectedResult: -3000, + }, + { + name: "basic-exponent-negative-positive-exp4", + json: "-8e+005", + expectedResult: -800000, + }, + { + name: "error1", + json: "132zz4", + expectedResult: 0, + err: true, + }, + { + name: "negative-error2", + json: " -1213xdde2323 ", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error3", + json: "-8e+00$aa5", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "invalid-type", + json: `"string"`, + expectedResult: 0, + err: true, + errType: InvalidUnmarshalError(""), + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + json := []byte(testCase.json) + var v int + err := Unmarshal(json, &v) + if testCase.err { + assert.NotNil(t, err, "Err must not be nil") + if testCase.errType != nil { + assert.IsType( + t, + testCase.errType, + err, + fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), + ) + } + } else { + assert.Nil(t, err, "Err must be nil") + } + assert.Equal(t, testCase.expectedResult, v, fmt.Sprintf("v must be equal to %d", testCase.expectedResult)) + }) + } + t.Run("pool-error", func(t *testing.T) { + result := int(1) + dec := NewDecoder(nil) + dec.Release() + defer func() { + err := recover() + assert.NotNil(t, err, "err shouldnot be nil") + assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") + }() + _ = dec.DecodeInt(&result) + assert.True(t, false, "should not be called as decoder should have panicked") + }) + t.Run("decoder-api", func(t *testing.T) { + var v int + dec := NewDecoder(strings.NewReader(`33`)) + defer dec.Release() + err := dec.DecodeInt(&v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, int(33), v, "v must be equal to 33") + }) + t.Run("decoder-api2", func(t *testing.T) { + var v int + dec := NewDecoder(strings.NewReader(`33`)) + defer dec.Release() + err := dec.Decode(&v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, int(33), v, "v must be equal to 33") + }) + t.Run("decoder-api-invalid-json", func(t *testing.T) { + var v int + dec := NewDecoder(strings.NewReader(``)) + defer dec.Release() + err := dec.DecodeInt(&v) + assert.NotNil(t, err, "Err must not be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + }) +} + +func TestDecoderInt64(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult int64 + err bool + errType interface{} + }{ + { + name: "basic-positive", + json: "100", + expectedResult: 100, + }, + { + name: "basic-positive2", + json: " 1039405", + expectedResult: 1039405, + }, + { + name: "basic-negative", + json: "-2", + expectedResult: -2, + }, + { + name: "basic-null", + json: "null", + expectedResult: 0, + }, + { + name: "basic-null-err", + json: "nxll", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-skip-data-err", + json: "trua", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-big", + json: "9223372036854775807", + expectedResult: 9223372036854775807, + }, + { + name: "basic-big-overflow", + json: " 9223372036854775808", + expectedResult: 0, + err: true, + }, + { + name: "basic-big-overflow", + json: " 9223372036854775827", + expectedResult: 0, + err: true, + }, + { + name: "basic-big-overflow2", + json: "92233720368547758089", + expectedResult: 0, + err: true, + }, + { + name: "basic-big-overflow3", + json: "92233720368547758089 ", + expectedResult: 0, + err: true, + }, + { + name: "basic-negative2", + json: "-2349557", + expectedResult: -2349557, + }, + { + name: "basic-float", + json: "2.4595", + expectedResult: 2, + }, + { + name: "basic-float2", + json: "-7.8876", + expectedResult: -7, + }, + { + name: "basic-float2", + json: "-7.8876a", + expectedResult: 0, + err: true, + }, + { + name: "basic-exponent-positive-positive-exp", + json: "1e2", + expectedResult: 100, + }, + { + name: "basic-exponent-positive-positive-exp2", + json: "5e+06 ", + expectedResult: 5000000, + }, + { + name: "basic-exponent-positive-positive-exp3", + json: "3e+3", + expectedResult: 3000, + }, + { + name: "basic-exponent-positive-positive-exp4", + json: "8e+005", + expectedResult: 800000, + }, + { + name: "basic-exponent-positive-negative-exp", + json: "1e-2 ", + expectedResult: 0, + }, + { + name: "basic-exponent-positive-negative-exp2", + json: "5e-6", + expectedResult: 0, + }, + { + name: "basic-exponent-positive-negative-exp3", + json: "3e-3", + expectedResult: 0, + }, + { + name: "basic-exponent-positive-negative-exp4", + json: "8e-005", + expectedResult: 0, + }, + { + name: "basic-exponent-negative-positive-exp", + json: "-1e2", + expectedResult: -100, + }, + { + name: "basic-exponent-negative-positive-exp2", + json: "-5e+06", + expectedResult: -5000000, + }, + { + name: "basic-exponent-negative-positive-exp2", + json: "-5.4e+06", + expectedResult: -5400000, + }, + { + name: "basic-exponent-negative-positive-exp3", + json: "-3e03", + expectedResult: -3000, + }, + { + name: "basic-exponent-negative-positive-exp4", + json: "-8e+005", + expectedResult: -800000, + }, + { + name: "basic-exponent-negative-positive-exp4", + json: "8ea+00a5", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-exponent-err", + json: "3e", + expectedResult: 0, + err: true, + }, + { + name: "error1", + json: "132zz4", + expectedResult: 0, + err: true, + }, + { + name: "invalid-type", + json: `"string"`, + expectedResult: 0, + err: true, + errType: InvalidUnmarshalError(""), + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + json := []byte(testCase.json) + var v int64 + err := Unmarshal(json, &v) + if testCase.err { + assert.NotNil(t, err, "Err must not be nil") + if testCase.errType != nil { + assert.IsType( + t, + testCase.errType, + err, + fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), + ) + } + } else { + assert.Nil(t, err, "Err must be nil") + } + assert.Equal(t, testCase.expectedResult, v, fmt.Sprintf("v must be equal to %d", testCase.expectedResult)) + }) + } + t.Run("pool-error", func(t *testing.T) { + result := int64(1) + dec := NewDecoder(nil) + dec.Release() + defer func() { + err := recover() + assert.NotNil(t, err, "err shouldnot be nil") + assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") + }() + _ = dec.DecodeInt64(&result) + assert.True(t, false, "should not be called as decoder should have panicked") + }) + t.Run("decoder-api", func(t *testing.T) { + var v int64 + dec := NewDecoder(strings.NewReader(`33`)) + defer dec.Release() + err := dec.DecodeInt64(&v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, int64(33), v, "v must be equal to 33") + }) + t.Run("decoder-api2", func(t *testing.T) { + var v int64 + dec := NewDecoder(strings.NewReader(`33`)) + defer dec.Release() + err := dec.Decode(&v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, int64(33), v, "v must be equal to 33") + }) + t.Run("decoder-api-invalid-json", func(t *testing.T) { + var v int64 + dec := NewDecoder(strings.NewReader(``)) + defer dec.Release() + err := dec.DecodeInt64(&v) + assert.NotNil(t, err, "Err must not be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + }) +} + +func TestDecoderInt32(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult int32 + err bool + errType interface{} + }{ + { + name: "basic-positive", + json: "100", + expectedResult: 100, + }, + { + name: "basic-positive2", + json: " 1039405", + expectedResult: 1039405, + }, + { + name: "basic-negative", + json: "-2", + expectedResult: -2, + }, + { + name: "basic-null", + json: "null", + expectedResult: 0, + }, + { + name: "basic-null-err", + json: "nxll", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-skip-data-err", + json: "trua", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-negative2", + json: "-2349557", + expectedResult: -2349557, + }, + { + name: "basic-big", + json: " 2147483647", + expectedResult: 2147483647, + }, + { + name: "basic-big-overflow", + json: " 2147483648", + expectedResult: 0, + err: true, + }, + { + name: "basic-big-overflow", + json: " 2147483657", + expectedResult: 0, + err: true, + }, + { + name: "basic-big-overflow2", + json: "21474836483", + expectedResult: 0, + err: true, + }, + { + name: "basic-float", + json: "2.4595", + expectedResult: 2, + }, + { + name: "basic-float2", + json: "-7.8876", + expectedResult: -7, + }, + { + name: "basic-float2", + json: "-7.8876a", + expectedResult: 0, + err: true, + }, + { + name: "basic-exponent-positive-positive-exp", + json: "1.2E2", + expectedResult: 120, + }, + { + name: "basic-exponent-positive-positive-exp1", + json: "3.5e+005 ", + expectedResult: 350000, + }, + { + name: "basic-exponent-positive-positive-exp1", + json: "3.5e+005", + expectedResult: 350000, + }, + { + name: "basic-exponent-positive-positive-exp2", + json: "5e+06", + expectedResult: 5000000, + }, + { + name: "basic-exponent-positive-positive-exp3", + json: "3e+3", + expectedResult: 3000, + }, + { + name: "basic-exponent-positive-positive-exp4", + json: "8e+005 ", + expectedResult: 800000, + }, + { + name: "basic-exponent-positive-negative-exp", + json: "1e-2 ", + expectedResult: 0, + }, + { + name: "basic-exponent-positive-negative-exp2", + json: "5E-6", + expectedResult: 0, + }, + { + name: "basic-exponent-positive-negative-exp3", + json: "3e-3", + expectedResult: 0, + }, + { + name: "basic-exponent-positive-negative-exp4", + json: "8e-005", + expectedResult: 0, + }, + { + name: "basic-exponent-negative-positive-exp", + json: "-1e2", + expectedResult: -100, + }, + { + name: "basic-exponent-negative-positive-exp2", + json: "-5e+06", + expectedResult: -5000000, + }, + { + name: "basic-exponent-negative-positive-exp3", + json: "-3e03", + expectedResult: -3000, + }, + { + name: "basic-exponent-negative-positive-exp4", + json: "-8e+005", + expectedResult: -800000, + }, + { + name: "basic-exponent-err", + json: "3e", + expectedResult: 0, + err: true, + }, + { + name: "basic-float", + json: "8.32 ", + expectedResult: 8, + }, + { + name: "error", + json: "83zez4", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error", + json: "8ea00$aa5", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error2", + json: "-8e+00$aa5", + expectedResult: 0, + err: true, + }, + { + name: "invalid-type", + json: `"string"`, + expectedResult: 0, + err: true, + errType: InvalidUnmarshalError(""), + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + json := []byte(testCase.json) + var v int32 + err := Unmarshal(json, &v) + if testCase.err { + assert.NotNil(t, err, "Err must not be nil") + if testCase.errType != nil { + assert.IsType( + t, + testCase.errType, + err, + fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), + ) + } + } else { + assert.Nil(t, err, "Err must be nil") + } + assert.Equal(t, testCase.expectedResult, v, fmt.Sprintf("v must be equal to %d", testCase.expectedResult)) + }) + } + t.Run("pool-error", func(t *testing.T) { + result := int32(1) + dec := NewDecoder(nil) + dec.Release() + defer func() { + err := recover() + assert.NotNil(t, err, "err shouldnot be nil") + assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") + }() + _ = dec.DecodeInt32(&result) + assert.True(t, false, "should not be called as decoder should have panicked") + + }) + t.Run("decoder-api", func(t *testing.T) { + var v int32 + dec := NewDecoder(strings.NewReader(`33`)) + defer dec.Release() + err := dec.DecodeInt32(&v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, int32(33), v, "v must be equal to 33") + }) + t.Run("decoder-api2", func(t *testing.T) { + var v int32 + dec := NewDecoder(strings.NewReader(`33`)) + defer dec.Release() + err := dec.Decode(&v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, int32(33), v, "v must be equal to 33") + }) + t.Run("decoder-api-invalid-json", func(t *testing.T) { + var v int32 + dec := NewDecoder(strings.NewReader(``)) + defer dec.Release() + err := dec.DecodeInt32(&v) + assert.NotNil(t, err, "Err must not be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + }) +} + +func TestDecoderInt16(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult int16 + err bool + errType interface{} + }{ + { + name: "basic-positive", + json: "100", + expectedResult: 100, + }, + { + name: "basic-positive2", + json: " 5321", + expectedResult: 5321, + }, + { + name: "basic-negative", + json: "-2", + expectedResult: -2, + }, + { + name: "basic-null", + json: "null", + expectedResult: 0, + }, + { + name: "basic-null-err", + json: "nxll", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-skip-data-err", + json: "trua", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-negative2", + json: "-2456", + expectedResult: -2456, + }, + { + name: "basic-big", + json: " 24566", + expectedResult: 24566, + }, + { + name: "basic-big-overflow", + json: "66535", + expectedResult: 0, + err: true, + }, + { + name: "basic-big-overflow", + json: "32768", + expectedResult: 0, + err: true, + }, + { + name: "basic-big-overflow", + json: " 2147483648", + expectedResult: 0, + err: true, + }, + { + name: "basic-big-overflow2", + json: "21474836483", + expectedResult: 0, + err: true, + }, + { + name: "basic-float", + json: "2.4595", + expectedResult: 2, + }, + { + name: "basic-float2", + json: "-7.8876", + expectedResult: -7, + }, + { + name: "basic-float2", + json: "-7.8876a", + expectedResult: 0, + err: true, + }, + { + name: "basic-exponent-positive-positive-exp", + json: "1.2E2", + expectedResult: 120, + }, + { + name: "basic-exponent-positive-positive-exp1", + json: "3.5e+001 ", + expectedResult: 35, + }, + { + name: "basic-exponent-positive-positive-exp1", + json: "3.5e+002", + expectedResult: 350, + }, + { + name: "basic-exponent-positive-positive-exp2", + json: "5e+03", + expectedResult: 5000, + }, + { + name: "basic-exponent-positive-positive-exp3", + json: "3e+3", + expectedResult: 3000, + }, + { + name: "basic-exponent-positive-positive-exp4", + json: "8e+02 ", + expectedResult: 800, + }, + { + name: "basic-exponent-positive-negative-exp", + json: "1e-2 ", + expectedResult: 0, + }, + { + name: "basic-exponent-positive-negative-exp2", + json: "5E-6", + expectedResult: 0, + }, + { + name: "basic-exponent-positive-negative-exp3", + json: "3e-3", + expectedResult: 0, + }, + { + name: "basic-exponent-positive-negative-exp4", + json: "8e-005", + expectedResult: 0, + }, + { + name: "basic-exponent-negative-positive-exp", + json: "-1e2", + expectedResult: -100, + }, + { + name: "basic-exponent-negative-positive-exp2", + json: "-5e+03", + expectedResult: -5000, + }, + { + name: "basic-exponent-negative-positive-exp3", + json: "-3e03", + expectedResult: -3000, + }, + { + name: "basic-exponent-negative-positive-exp4", + json: "-8e+003", + expectedResult: -8000, + }, + { + name: "basic-exponent-err", + json: "3e", + expectedResult: 0, + err: true, + }, + { + name: "basic-float", + json: "8.32 ", + expectedResult: 8, + }, + { + name: "error", + json: "83zez4", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error", + json: "8ea00$aa5", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error2", + json: "-8e+00$aa5", + expectedResult: 0, + err: true, + }, + { + name: "invalid-type", + json: `"string"`, + expectedResult: 0, + err: true, + errType: InvalidUnmarshalError(""), + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + json := []byte(testCase.json) + var v int16 + err := Unmarshal(json, &v) + if testCase.err { + assert.NotNil(t, err, "Err must not be nil") + if testCase.errType != nil { + assert.IsType( + t, + testCase.errType, + err, + fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), + ) + } + } else { + assert.Nil(t, err, "Err must be nil") + } + assert.Equal(t, testCase.expectedResult, v, fmt.Sprintf("v must be equal to %d", testCase.expectedResult)) + }) + } + t.Run("pool-error", func(t *testing.T) { + result := int16(1) + dec := NewDecoder(nil) + dec.Release() + defer func() { + err := recover() + assert.NotNil(t, err, "err shouldnot be nil") + assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") + }() + _ = dec.DecodeInt16(&result) + assert.True(t, false, "should not be called as decoder should have panicked") + + }) + t.Run("decoder-api", func(t *testing.T) { + var v int16 + dec := NewDecoder(strings.NewReader(`33`)) + defer dec.Release() + err := dec.DecodeInt16(&v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, int16(33), v, "v must be equal to 33") + }) + t.Run("decoder-api2", func(t *testing.T) { + var v int16 + dec := NewDecoder(strings.NewReader(`33`)) + defer dec.Release() + err := dec.Decode(&v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, int16(33), v, "v must be equal to 33") + }) + t.Run("decoder-api-invalid-json", func(t *testing.T) { + var v int16 + dec := NewDecoder(strings.NewReader(``)) + defer dec.Release() + err := dec.DecodeInt16(&v) + assert.NotNil(t, err, "Err must not be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + }) +} + +func TestDecoderInt8(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult int8 + err bool + errType interface{} + }{ + { + name: "basic-positive", + json: "100", + expectedResult: 100, + }, + { + name: "basic-positive2", + json: " 127", + expectedResult: 127, + }, + { + name: "basic-negative", + json: "-2", + expectedResult: -2, + }, + { + name: "basic-null", + json: "null", + expectedResult: 0, + }, + { + name: "basic-null-err", + json: "nxll", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-skip-data-err", + json: "trua", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-negative2", + json: "-123", + expectedResult: -123, + }, + { + name: "basic-big", + json: " 43", + expectedResult: 43, + }, + { + name: "basic-big-overflow", + json: " 2147483648", + expectedResult: 0, + err: true, + }, + { + name: "basic-big-overflow", + json: "137", + expectedResult: 0, + err: true, + }, + { + name: "basic-big-overflow", + json: "128", + expectedResult: 0, + err: true, + }, + { + name: "basic-big-overflow2", + json: "21474836483", + expectedResult: 0, + err: true, + }, + { + name: "basic-float", + json: "2.4595", + expectedResult: 2, + }, + { + name: "basic-float2", + json: "-7.8876", + expectedResult: -7, + }, + { + name: "basic-float2", + json: "-7.8876a", + expectedResult: 0, + err: true, + }, + { + name: "basic-exponent-positive-positive-exp", + json: "1.2E2", + expectedResult: 120, + }, + { + name: "basic-exponent-positive-positive-exp1", + json: "3.5e+001 ", + expectedResult: 35, + }, + { + name: "basic-exponent-positive-positive-exp1", + json: "3.5e+001", + expectedResult: 35, + }, + { + name: "basic-exponent-positive-positive-exp2", + json: "5e+01", + expectedResult: 50, + }, + { + name: "basic-exponent-positive-negative-exp", + json: "1e-2 ", + expectedResult: 0, + }, + { + name: "basic-exponent-positive-negative-exp2", + json: "5E-6", + expectedResult: 0, + }, + { + name: "basic-exponent-positive-negative-exp3", + json: "3e-3", + expectedResult: 0, + }, + { + name: "basic-exponent-positive-negative-exp4", + json: "8e-1 ", + expectedResult: 0, + }, + { + name: "basic-exponent-positive-negative-exp4", + json: "8e1 ", + expectedResult: 80, + }, + { + name: "basic-exponent-positive-negative-exp4", + json: "8e-1", + expectedResult: 0, + }, + { + name: "basic-exponent-negative-positive-exp", + json: "-1e2", + expectedResult: -100, + }, + { + name: "basic-exponent-negative-positive-exp2", + json: "-5e+01", + expectedResult: -50, + }, + { + name: "basic-exponent-negative-positive-exp3", + json: "-3e01", + expectedResult: -30, + }, + { + name: "basic-exponent-negative-positive-exp4", + json: "-8e+001", + expectedResult: -80, + }, + { + name: "basic-exponent-err", + json: "3e", + expectedResult: 0, + err: true, + }, + { + name: "basic-float", + json: "8.32 ", + expectedResult: 8, + }, + { + name: "error", + json: "83zez4", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error", + json: "8ea00$aa5", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error2", + json: "-8e+00$aa5", + expectedResult: 0, + err: true, + }, + { + name: "invalid-type", + json: `"string"`, + expectedResult: 0, + err: true, + errType: InvalidUnmarshalError(""), + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + json := []byte(testCase.json) + var v int8 + err := Unmarshal(json, &v) + if testCase.err { + assert.NotNil(t, err, "Err must not be nil") + if testCase.errType != nil { + assert.IsType( + t, + testCase.errType, + err, + fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), + ) + } + } else { + assert.Nil(t, err, "Err must be nil") + } + assert.Equal(t, testCase.expectedResult, v, fmt.Sprintf("v must be equal to %d", testCase.expectedResult)) + }) + } + t.Run("pool-error", func(t *testing.T) { + result := int8(1) + dec := NewDecoder(nil) + dec.Release() + defer func() { + err := recover() + assert.NotNil(t, err, "err shouldnot be nil") + assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") + }() + _ = dec.DecodeInt8(&result) + assert.True(t, false, "should not be called as decoder should have panicked") + + }) + t.Run("decoder-api", func(t *testing.T) { + var v int8 + dec := NewDecoder(strings.NewReader(`33`)) + defer dec.Release() + err := dec.DecodeInt8(&v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, int8(33), v, "v must be equal to 33") + }) + t.Run("decoder-api2", func(t *testing.T) { + var v int8 + dec := NewDecoder(strings.NewReader(`33`)) + defer dec.Release() + err := dec.Decode(&v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, int8(33), v, "v must be equal to 33") + }) + t.Run("decoder-api-invalid-json", func(t *testing.T) { + var v int8 + dec := NewDecoder(strings.NewReader(``)) + defer dec.Release() + err := dec.DecodeInt8(&v) + assert.NotNil(t, err, "Err must not be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + }) +} diff --git a/decode_number_test.go b/decode_number_test.go @@ -1,1151 +1,12 @@ package gojay import ( - "fmt" - "math" - "reflect" "strings" "testing" "github.com/stretchr/testify/assert" ) -func TestDecoderInt(t *testing.T) { - testCases := []struct { - name string - json string - expectedResult int - err bool - errType interface{} - }{ - { - name: "basic-positive", - json: "100", - expectedResult: 100, - }, - { - name: "basic-positive2", - json: "1039405", - expectedResult: 1039405, - }, - { - name: "basic-negative", - json: "-2", - expectedResult: -2, - }, - { - name: "basic-null", - json: "null", - expectedResult: 0, - }, - { - name: "basic-null-err", - json: "nxll", - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - { - name: "basic-big", - json: "9223372036854775807", - expectedResult: 9223372036854775807, - }, - { - name: "basic-big-overflow", - json: "9223372036854775808", - expectedResult: 0, - err: true, - errType: InvalidTypeError(""), - }, - { - name: "basic-big-overflow2", - json: "92233720368547758089", - expectedResult: 0, - err: true, - errType: InvalidTypeError(""), - }, - { - name: "basic-big-overflow3", - json: "92233720368547758089 ", - expectedResult: 0, - err: true, - errType: InvalidTypeError(""), - }, - { - name: "basic-negative2", - json: "-2349557", - expectedResult: -2349557, - }, - { - name: "basic-float", - json: "2.4595", - expectedResult: 2, - }, - { - name: "basic-float2", - json: "-7.8876", - expectedResult: -7, - }, - { - name: "basic-float2", - json: "-7.8876 ", - expectedResult: -7, - }, - { - name: "basic-float2", - json: "-7.8876a", - expectedResult: 0, - err: true, - }, - { - name: "basic-exponent-positive-positive-exp", - json: "1e2", - expectedResult: 100, - }, - { - name: "basic-exponent-positive-positive-exp2", - json: "5e+06", - expectedResult: 5000000, - }, - { - name: "basic-exponent-positive-positive-exp3", - json: "3e+3", - expectedResult: 3000, - }, - { - name: "basic-exponent-positive-positive-exp4", - json: "8e+005", - expectedResult: 800000, - }, - { - name: "basic-exponent-positive-negative-exp", - json: "1e-2", - expectedResult: 0, - }, - { - name: "basic-exponent-positive-negative-exp2", - json: "5e-6", - expectedResult: 0, - }, - { - name: "basic-exponent-positive-negative-exp3", - json: "3e-3", - expectedResult: 0, - }, - { - name: "basic-exponent-positive-negative-exp4", - json: "8e-005", - expectedResult: 0, - }, - { - name: "basic-exponent-negative-positive-exp", - json: "-1e2", - expectedResult: -100, - }, - { - name: "basic-exponent-negative-positive-exp2", - json: "-5e+06", - expectedResult: -5000000, - }, - { - name: "basic-exponent-negative-positive-exp3", - json: "-3e03", - expectedResult: -3000, - }, - { - name: "basic-exponent-negative-positive-exp4", - json: "-8e+005", - expectedResult: -800000, - }, - { - name: "error1", - json: "132zz4", - expectedResult: 0, - err: true, - }, - { - name: "negative-error2", - json: " -1213xdde2323 ", - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - { - name: "error3", - json: "-8e+00$aa5", - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - { - name: "invalid-type", - json: `"string"`, - expectedResult: 0, - err: true, - errType: InvalidTypeError(""), - }, - } - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - json := []byte(testCase.json) - var v int - err := Unmarshal(json, &v) - if testCase.err { - assert.NotNil(t, err, "Err must not be nil") - if testCase.errType != nil { - assert.IsType( - t, - testCase.errType, - err, - fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), - ) - } - } else { - assert.Nil(t, err, "Err must be nil") - } - assert.Equal(t, testCase.expectedResult, v, fmt.Sprintf("v must be equal to %d", testCase.expectedResult)) - }) - } - t.Run("pool-error", func(t *testing.T) { - result := int(1) - dec := NewDecoder(nil) - dec.Release() - defer func() { - err := recover() - assert.NotNil(t, err, "err shouldnot be nil") - assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") - }() - _ = dec.DecodeInt(&result) - assert.True(t, false, "should not be called as decoder should have panicked") - }) - t.Run("decoder-api", func(t *testing.T) { - var v int - dec := NewDecoder(strings.NewReader(`33`)) - defer dec.Release() - err := dec.DecodeInt(&v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, int(33), v, "v must be equal to 33") - }) - t.Run("decoder-api-invalid-json", func(t *testing.T) { - var v int - dec := NewDecoder(strings.NewReader(``)) - defer dec.Release() - err := dec.DecodeInt(&v) - assert.NotNil(t, err, "Err must not be nil") - assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") - }) -} -func TestDecoderInt64(t *testing.T) { - testCases := []struct { - name string - json string - expectedResult int64 - err bool - errType interface{} - }{ - { - name: "basic-positive", - json: "100", - expectedResult: 100, - }, - { - name: "basic-positive2", - json: " 1039405", - expectedResult: 1039405, - }, - { - name: "basic-negative", - json: "-2", - expectedResult: -2, - }, - { - name: "basic-null", - json: "null", - expectedResult: 0, - }, - { - name: "basic-null-err", - json: "nxll", - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - { - name: "basic-big", - json: "9223372036854775807", - expectedResult: 9223372036854775807, - }, - { - name: "basic-big-overflow", - json: " 9223372036854775808", - expectedResult: 0, - err: true, - }, - { - name: "basic-big-overflow2", - json: "92233720368547758089", - expectedResult: 0, - err: true, - }, - { - name: "basic-big-overflow3", - json: "92233720368547758089 ", - expectedResult: 0, - err: true, - }, - { - name: "basic-negative2", - json: "-2349557", - expectedResult: -2349557, - }, - { - name: "basic-float", - json: "2.4595", - expectedResult: 2, - }, - { - name: "basic-float2", - json: "-7.8876", - expectedResult: -7, - }, - { - name: "basic-float2", - json: "-7.8876a", - expectedResult: 0, - err: true, - }, - { - name: "basic-exponent-positive-positive-exp", - json: "1e2", - expectedResult: 100, - }, - { - name: "basic-exponent-positive-positive-exp2", - json: "5e+06 ", - expectedResult: 5000000, - }, - { - name: "basic-exponent-positive-positive-exp3", - json: "3e+3", - expectedResult: 3000, - }, - { - name: "basic-exponent-positive-positive-exp4", - json: "8e+005", - expectedResult: 800000, - }, - { - name: "basic-exponent-positive-negative-exp", - json: "1e-2 ", - expectedResult: 0, - }, - { - name: "basic-exponent-positive-negative-exp2", - json: "5e-6", - expectedResult: 0, - }, - { - name: "basic-exponent-positive-negative-exp3", - json: "3e-3", - expectedResult: 0, - }, - { - name: "basic-exponent-positive-negative-exp4", - json: "8e-005", - expectedResult: 0, - }, - { - name: "basic-exponent-negative-positive-exp", - json: "-1e2", - expectedResult: -100, - }, - { - name: "basic-exponent-negative-positive-exp2", - json: "-5e+06", - expectedResult: -5000000, - }, - { - name: "basic-exponent-negative-positive-exp2", - json: "-5.4e+06", - expectedResult: -5400000, - }, - { - name: "basic-exponent-negative-positive-exp3", - json: "-3e03", - expectedResult: -3000, - }, - { - name: "basic-exponent-negative-positive-exp4", - json: "-8e+005", - expectedResult: -800000, - }, - { - name: "basic-exponent-negative-positive-exp4", - json: "8ea+00a5", - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - { - name: "error1", - json: "132zz4", - expectedResult: 0, - err: true, - }, - { - name: "invalid-type", - json: `"string"`, - expectedResult: 0, - err: true, - errType: InvalidTypeError(""), - }, - } - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - json := []byte(testCase.json) - var v int64 - err := Unmarshal(json, &v) - if testCase.err { - assert.NotNil(t, err, "Err must not be nil") - if testCase.errType != nil { - assert.IsType( - t, - testCase.errType, - err, - fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), - ) - } - } else { - assert.Nil(t, err, "Err must be nil") - } - assert.Equal(t, testCase.expectedResult, v, fmt.Sprintf("v must be equal to %d", testCase.expectedResult)) - }) - } - t.Run("pool-error", func(t *testing.T) { - result := int64(1) - dec := NewDecoder(nil) - dec.Release() - defer func() { - err := recover() - assert.NotNil(t, err, "err shouldnot be nil") - assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") - }() - _ = dec.DecodeInt64(&result) - assert.True(t, false, "should not be called as decoder should have panicked") - }) - t.Run("decoder-api", func(t *testing.T) { - var v int64 - dec := NewDecoder(strings.NewReader(`33`)) - defer dec.Release() - err := dec.DecodeInt64(&v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, int64(33), v, "v must be equal to 33") - }) - t.Run("decoder-api-invalid-json", func(t *testing.T) { - var v int64 - dec := NewDecoder(strings.NewReader(``)) - defer dec.Release() - err := dec.DecodeInt64(&v) - assert.NotNil(t, err, "Err must not be nil") - assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") - }) -} -func TestDecoderUint64(t *testing.T) { - testCases := []struct { - name string - json string - expectedResult uint64 - err bool - errType interface{} - }{ - { - name: "basic-positive", - json: "100", - expectedResult: 100, - }, - { - name: "basic-positive2", - json: " 1039405", - expectedResult: 1039405, - }, - { - name: "basic-negative", - json: "-2", - expectedResult: 2, - }, - { - name: "basic-null", - json: "null", - expectedResult: 0, - }, - { - name: "basic-null-err", - json: "nxll", - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - { - name: "basic-big", - json: "18446744073709551615", - expectedResult: 18446744073709551615, - }, - { - name: "basic-big-overflow", - json: "18446744073709551616", - expectedResult: 0, - err: true, - }, - { - name: "basic-big-overflow2", - json: "184467440737095516161", - expectedResult: 0, - err: true, - }, - { - name: "basic-negative2", - json: "-2349557", - expectedResult: 2349557, - }, - { - name: "basic-float", - json: "2.4595", - expectedResult: 2, - }, - { - name: "basic-float2", - json: "-7.8876", - expectedResult: 7, - }, - { - name: "error1", - json: "132zz4", - expectedResult: 0, - err: true, - }, - { - name: "error", - json: "-83zez4", - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - { - name: "invalid-type", - json: `"string"`, - expectedResult: 0, - err: true, - errType: InvalidTypeError(""), - }, - } - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - json := []byte(testCase.json) - var v uint64 - err := Unmarshal(json, &v) - if testCase.err { - assert.NotNil(t, err, "Err must not be nil") - if testCase.errType != nil { - assert.IsType( - t, - testCase.errType, - err, - fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), - ) - } - } else { - assert.Nil(t, err, "Err must be nil") - } - assert.Equal(t, testCase.expectedResult, v, fmt.Sprintf("v must be equal to %d", testCase.expectedResult)) - }) - } - t.Run("pool-error", func(t *testing.T) { - result := uint64(1) - dec := NewDecoder(nil) - dec.Release() - defer func() { - err := recover() - assert.NotNil(t, err, "err shouldnot be nil") - assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") - }() - _ = dec.DecodeUint64(&result) - assert.True(t, false, "should not be called as decoder should have panicked") - }) - t.Run("decoder-api", func(t *testing.T) { - var v uint64 - dec := NewDecoder(strings.NewReader(`33`)) - defer dec.Release() - err := dec.DecodeUint64(&v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, uint64(33), v, "v must be equal to 33") - }) - t.Run("decoder-api-json-error", func(t *testing.T) { - var v uint64 - dec := NewDecoder(strings.NewReader(``)) - defer dec.Release() - err := dec.DecodeUint64(&v) - assert.NotNil(t, err, "Err must not be nil") - assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") - }) -} -func TestDecoderInt32(t *testing.T) { - testCases := []struct { - name string - json string - expectedResult int32 - err bool - errType interface{} - }{ - { - name: "basic-positive", - json: "100", - expectedResult: 100, - }, - { - name: "basic-positive2", - json: " 1039405", - expectedResult: 1039405, - }, - { - name: "basic-negative", - json: "-2", - expectedResult: -2, - }, - { - name: "basic-null", - json: "null", - expectedResult: 0, - }, - { - name: "basic-null-err", - json: "nxll", - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - { - name: "basic-negative2", - json: "-2349557", - expectedResult: -2349557, - }, - { - name: "basic-big", - json: " 2147483647", - expectedResult: 2147483647, - }, - { - name: "basic-big-overflow", - json: " 2147483648", - expectedResult: 0, - err: true, - }, - { - name: "basic-big-overflow2", - json: "21474836483", - expectedResult: 0, - err: true, - }, - { - name: "basic-float", - json: "2.4595", - expectedResult: 2, - }, - { - name: "basic-float2", - json: "-7.8876", - expectedResult: -7, - }, - { - name: "basic-float2", - json: "-7.8876a", - expectedResult: 0, - err: true, - }, - { - name: "basic-exponent-positive-positive-exp", - json: "1.2E2", - expectedResult: 120, - }, - { - name: "basic-exponent-positive-positive-exp1", - json: "3.5e+005 ", - expectedResult: 350000, - }, - { - name: "basic-exponent-positive-positive-exp1", - json: "3.5e+005", - expectedResult: 350000, - }, - { - name: "basic-exponent-positive-positive-exp2", - json: "5e+06", - expectedResult: 5000000, - }, - { - name: "basic-exponent-positive-positive-exp3", - json: "3e+3", - expectedResult: 3000, - }, - { - name: "basic-exponent-positive-positive-exp4", - json: "8e+005 ", - expectedResult: 800000, - }, - { - name: "basic-exponent-positive-negative-exp", - json: "1e-2 ", - expectedResult: 0, - }, - { - name: "basic-exponent-positive-negative-exp2", - json: "5E-6", - expectedResult: 0, - }, - { - name: "basic-exponent-positive-negative-exp3", - json: "3e-3", - expectedResult: 0, - }, - { - name: "basic-exponent-positive-negative-exp4", - json: "8e-005", - expectedResult: 0, - }, - { - name: "basic-exponent-negative-positive-exp", - json: "-1e2", - expectedResult: -100, - }, - { - name: "basic-exponent-negative-positive-exp2", - json: "-5e+06", - expectedResult: -5000000, - }, - { - name: "basic-exponent-negative-positive-exp3", - json: "-3e03", - expectedResult: -3000, - }, - { - name: "basic-exponent-negative-positive-exp4", - json: "-8e+005", - expectedResult: -800000, - }, - { - name: "basic-float", - json: "8.32 ", - expectedResult: 8, - }, - { - name: "error", - json: "83zez4", - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - { - name: "error", - json: "8ea00$aa5", - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - { - name: "error2", - json: "-8e+00$aa5", - expectedResult: 0, - err: true, - }, - { - name: "invalid-type", - json: `"string"`, - expectedResult: 0, - err: true, - errType: InvalidTypeError(""), - }, - } - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - json := []byte(testCase.json) - var v int32 - err := Unmarshal(json, &v) - if testCase.err { - assert.NotNil(t, err, "Err must not be nil") - if testCase.errType != nil { - assert.IsType( - t, - testCase.errType, - err, - fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), - ) - } - } else { - assert.Nil(t, err, "Err must be nil") - } - assert.Equal(t, testCase.expectedResult, v, fmt.Sprintf("v must be equal to %d", testCase.expectedResult)) - }) - } - t.Run("pool-error", func(t *testing.T) { - result := int32(1) - dec := NewDecoder(nil) - dec.Release() - defer func() { - err := recover() - assert.NotNil(t, err, "err shouldnot be nil") - assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") - }() - _ = dec.DecodeInt32(&result) - assert.True(t, false, "should not be called as decoder should have panicked") - - }) - t.Run("decoder-api", func(t *testing.T) { - var v int32 - dec := NewDecoder(strings.NewReader(`33`)) - defer dec.Release() - err := dec.DecodeInt32(&v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, int32(33), v, "v must be equal to 33") - }) - t.Run("decoder-api-invalid-json", func(t *testing.T) { - var v int32 - dec := NewDecoder(strings.NewReader(``)) - defer dec.Release() - err := dec.DecodeInt32(&v) - assert.NotNil(t, err, "Err must not be nil") - assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") - }) -} - -func TestDecoderUint32(t *testing.T) { - testCases := []struct { - name string - json string - expectedResult uint32 - err bool - errType interface{} - }{ - { - name: "basic-positive", - json: "100", - expectedResult: 100, - }, - { - name: "basic-positive2", - json: "1039405", - expectedResult: 1039405, - }, - { - name: "basic-negative", - json: "-2", - expectedResult: 2, - }, - { - name: "basic-null", - json: "null", - expectedResult: 0, - }, - { - name: "basic-null-err", - json: "nxll", - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - { - name: "basic-negative2", - json: "-2349557", - expectedResult: 2349557, - }, - { - name: "basic-big", - json: "4294967295", - expectedResult: 4294967295, - }, - { - name: "basic-big-overflow", - json: " 4294967298", - expectedResult: 0, - err: true, - }, - { - name: "basic-big-overflow2", - json: "42949672983", - expectedResult: 0, - err: true, - }, - { - name: "basic-float", - json: "2.4595", - expectedResult: 2, - }, - { - name: "basic-float2", - json: "-7.8876", - expectedResult: 7, - }, - { - name: "error", - json: "83zez4", - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - { - name: "error", - json: "-83zez4", - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - { - name: "invalid-type", - json: `"string"`, - expectedResult: 0, - err: true, - errType: InvalidTypeError(""), - }, - { - name: "invalid-json", - json: `123invalid`, - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - } - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - json := []byte(testCase.json) - var v uint32 - err := Unmarshal(json, &v) - if testCase.err { - assert.NotNil(t, err, "Err must not be nil") - if testCase.errType != nil { - assert.IsType( - t, - testCase.errType, - err, - fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), - ) - } - } else { - assert.Nil(t, err, "Err must be nil") - } - assert.Equal(t, testCase.expectedResult, v, fmt.Sprintf("v must be equal to %d", testCase.expectedResult)) - }) - } - t.Run("pool-error", func(t *testing.T) { - result := uint32(1) - dec := NewDecoder(nil) - dec.Release() - defer func() { - err := recover() - assert.NotNil(t, err, "err shouldnot be nil") - assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") - }() - _ = dec.DecodeUint32(&result) - assert.True(t, false, "should not be called as decoder should have panicked") - }) - t.Run("decoder-api", func(t *testing.T) { - var v uint32 - dec := NewDecoder(strings.NewReader(`33`)) - defer dec.Release() - err := dec.DecodeUint32(&v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, uint32(33), v, "v must be equal to 33") - }) - t.Run("decoder-api-json-error", func(t *testing.T) { - var v uint32 - dec := NewDecoder(strings.NewReader(``)) - defer dec.Release() - err := dec.DecodeUint32(&v) - assert.NotNil(t, err, "Err must not be nil") - assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") - }) -} - -func TestDecoderFloat64(t *testing.T) { - testCases := []struct { - name string - json string - expectedResult float64 - err bool - errType interface{} - }{ - { - name: "basic-exponent-positive-positive-exp", - json: "1e2", - expectedResult: 100, - }, - { - name: "basic-exponent-positive-positive-exp2", - json: "5e+06", - expectedResult: 5000000, - }, - { - name: "basic-exponent-positive-positive-exp3", - json: "3e+3", - expectedResult: 3000, - }, - { - name: "basic-null", - json: "null", - expectedResult: 0, - }, - { - name: "basic-null-err", - json: "nxll", - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - { - name: "basic-exponent-positive-positive-exp4", - json: "8e+005", - expectedResult: 800000, - }, - { - name: "basic-exponent-positive-negative-exp", - json: "1e-2", - expectedResult: 0.01, - }, - { - name: "basic-exponent-positive-negative-exp2", - json: "5e-6", - expectedResult: 0.000005, - }, - { - name: "basic-exponent-positive-negative-exp3", - json: "3e-3", - expectedResult: 0.003, - }, - { - name: "basic-exponent-positive-negative-exp4", - json: "8e-005", - expectedResult: 0.00008, - }, - { - name: "basic-exponent-negative-positive-exp", - json: "-1e2", - expectedResult: -100, - }, - { - name: "basic-exponent-negative-positive-exp2", - json: "-5e+06", - expectedResult: -5000000, - }, - { - name: "basic-exponent-negative-positive-exp3", - json: "-3e03", - expectedResult: -3000, - }, - { - name: "basic-exponent-negative-positive-exp4", - json: "-8e+005", - expectedResult: -800000, - }, - { - name: "basic-exponent-negative-positive-exp4", - json: "-8.2e-005", - expectedResult: -0.000082, - }, - { - name: "basic-float", - json: "2.4595", - expectedResult: 2.4595, - }, - { - name: "basic-float2", - json: "877", - expectedResult: 877, - }, - { - name: "basic-float2", - json: "-7.8876", - expectedResult: -7.8876, - }, - { - name: "basic-float", - json: "2.4595e1", - expectedResult: 24.595, - }, - { - name: "basic-float2", - json: "-7.8876e002", - expectedResult: -788.76, - }, - { - name: "error", - json: "83zez4", - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - { - name: "error", - json: "-83zez4", - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - { - name: "invalid-type", - json: `"string"`, - expectedResult: 0, - err: true, - errType: InvalidTypeError(""), - }, - } - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - json := []byte(testCase.json) - var v float64 - err := Unmarshal(json, &v) - if testCase.err { - assert.NotNil(t, err, "Err must not be nil") - if testCase.errType != nil { - assert.IsType( - t, - testCase.errType, - err, - fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), - ) - } - } else { - assert.Nil(t, err, "Err must be nil") - } - assert.Equal(t, testCase.expectedResult*1000000, math.Round(v*1000000), fmt.Sprintf("v must be equal to %f", testCase.expectedResult)) - }) - } - t.Run("pool-error", func(t *testing.T) { - result := float64(1) - dec := NewDecoder(nil) - dec.Release() - defer func() { - err := recover() - assert.NotNil(t, err, "err shouldnot be nil") - assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") - }() - _ = dec.DecodeFloat64(&result) - assert.True(t, false, "should not be called as decoder should have panicked") - }) - t.Run("decoder-api", func(t *testing.T) { - var v float64 - dec := NewDecoder(strings.NewReader(`1.25`)) - defer dec.Release() - err := dec.DecodeFloat64(&v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, 1.25, v, "v must be equal to 1.25") - }) - t.Run("decoder-api-json-error", func(t *testing.T) { - var v float64 - dec := NewDecoder(strings.NewReader(``)) - defer dec.Release() - err := dec.DecodeFloat64(&v) - assert.NotNil(t, err, "Err must not be nil") - assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") - }) -} - func TestDecodeNumberExra(t *testing.T) { t.Run("skip-number-err", func(t *testing.T) { dec := NewDecoder(strings.NewReader("123456afzfz343")) @@ -1153,4 +14,11 @@ func TestDecodeNumberExra(t *testing.T) { assert.NotNil(t, err, "err should not be nil") assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") }) + t.Run("get-exponent-err", func(t *testing.T) { + v := 0 + dec := NewDecoder(strings.NewReader("1.2Ea")) + err := dec.Decode(&v) + assert.NotNil(t, err, "err should not be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + }) } diff --git a/decode_number_uint.go b/decode_number_uint.go @@ -0,0 +1,409 @@ +package gojay + +import ( + "math" +) + +// DecodeUint8 reads the next JSON-encoded value from its input and stores it in the uint8 pointed to by v. +// +// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. +func (dec *Decoder) DecodeUint8(v *uint8) error { + if dec.isPooled == 1 { + panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) + } + return dec.decodeUint8(v) +} + +func (dec *Decoder) decodeUint8(v *uint8) error { + for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { + switch c := dec.data[dec.cursor]; c { + case ' ', '\n', '\t', '\r', ',': + continue + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + val, err := dec.getUint8(c) + if err != nil { + return err + } + *v = val + return nil + case '-': + dec.cursor = dec.cursor + 1 + val, err := dec.getUint8(dec.data[dec.cursor]) + if err != nil { + return err + } + // unsigned int so we don't bother with the sign + *v = val + return nil + case 'n': + dec.cursor++ + err := dec.assertNull() + if err != nil { + return err + } + return nil + default: + dec.err = dec.makeInvalidUnmarshalErr(v) + err := dec.skipData() + if err != nil { + return err + } + return nil + } + } + return dec.raiseInvalidJSONErr(dec.cursor) +} + +func (dec *Decoder) getUint8(b byte) (uint8, error) { + var end = dec.cursor + var start = dec.cursor + // look for following numbers + for j := dec.cursor + 1; j < dec.length || dec.read(); j++ { + switch dec.data[j] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + end = j + continue + case ' ', '\n', '\t', '\r': + continue + case '.', ',', '}', ']': + dec.cursor = j + return dec.atoui8(start, end), nil + } + // invalid json we expect numbers, dot (single one), comma, or spaces + return 0, dec.raiseInvalidJSONErr(dec.cursor) + } + return dec.atoui8(start, end), nil +} + +// DecodeUint16 reads the next JSON-encoded value from its input and stores it in the uint16 pointed to by v. +// +// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. +func (dec *Decoder) DecodeUint16(v *uint16) error { + if dec.isPooled == 1 { + panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) + } + return dec.decodeUint16(v) +} + +func (dec *Decoder) decodeUint16(v *uint16) error { + for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { + switch c := dec.data[dec.cursor]; c { + case ' ', '\n', '\t', '\r', ',': + continue + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + val, err := dec.getUint16(c) + if err != nil { + return err + } + *v = val + return nil + case '-': + dec.cursor = dec.cursor + 1 + val, err := dec.getUint16(dec.data[dec.cursor]) + if err != nil { + return err + } + // unsigned int so we don't bother with the sign + *v = val + return nil + case 'n': + dec.cursor++ + err := dec.assertNull() + if err != nil { + return err + } + return nil + default: + dec.err = dec.makeInvalidUnmarshalErr(v) + err := dec.skipData() + if err != nil { + return err + } + return nil + } + } + return dec.raiseInvalidJSONErr(dec.cursor) +} + +func (dec *Decoder) getUint16(b byte) (uint16, error) { + var end = dec.cursor + var start = dec.cursor + // look for following numbers + for j := dec.cursor + 1; j < dec.length || dec.read(); j++ { + switch dec.data[j] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + end = j + continue + case ' ', '\n', '\t', '\r': + continue + case '.', ',', '}', ']': + dec.cursor = j + return dec.atoui16(start, end), nil + } + // invalid json we expect numbers, dot (single one), comma, or spaces + return 0, dec.raiseInvalidJSONErr(dec.cursor) + } + return dec.atoui16(start, end), nil +} + +// DecodeUint32 reads the next JSON-encoded value from its input and stores it in the uint32 pointed to by v. +// +// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. +func (dec *Decoder) DecodeUint32(v *uint32) error { + if dec.isPooled == 1 { + panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) + } + return dec.decodeUint32(v) +} + +func (dec *Decoder) decodeUint32(v *uint32) error { + for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { + switch c := dec.data[dec.cursor]; c { + case ' ', '\n', '\t', '\r', ',': + continue + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + val, err := dec.getUint32(c) + if err != nil { + return err + } + *v = val + return nil + case '-': + dec.cursor = dec.cursor + 1 + val, err := dec.getUint32(dec.data[dec.cursor]) + if err != nil { + return err + } + // unsigned int so we don't bother with the sign + *v = val + return nil + case 'n': + dec.cursor++ + err := dec.assertNull() + if err != nil { + return err + } + return nil + default: + dec.err = dec.makeInvalidUnmarshalErr(v) + err := dec.skipData() + if err != nil { + return err + } + return nil + } + } + return dec.raiseInvalidJSONErr(dec.cursor) +} + +func (dec *Decoder) getUint32(b byte) (uint32, error) { + var end = dec.cursor + var start = dec.cursor + // look for following numbers + for j := dec.cursor + 1; j < dec.length || dec.read(); j++ { + switch dec.data[j] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + end = j + continue + case ' ', '\n', '\t', '\r': + continue + case '.', ',', '}', ']': + dec.cursor = j + return dec.atoui32(start, end), nil + } + // invalid json we expect numbers, dot (single one), comma, or spaces + return 0, dec.raiseInvalidJSONErr(dec.cursor) + } + return dec.atoui32(start, end), nil +} + +// DecodeUint64 reads the next JSON-encoded value from its input and stores it in the uint64 pointed to by v. +// +// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. +func (dec *Decoder) DecodeUint64(v *uint64) error { + if dec.isPooled == 1 { + panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) + } + return dec.decodeUint64(v) +} +func (dec *Decoder) decodeUint64(v *uint64) error { + for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { + switch c := dec.data[dec.cursor]; c { + case ' ', '\n', '\t', '\r', ',': + continue + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + val, err := dec.getUint64(c) + if err != nil { + return err + } + *v = val + return nil + case '-': + dec.cursor = dec.cursor + 1 + val, err := dec.getUint64(dec.data[dec.cursor]) + if err != nil { + return err + } + // unsigned int so we don't bother with the sign + *v = val + return nil + case 'n': + dec.cursor++ + err := dec.assertNull() + if err != nil { + return err + } + return nil + default: + dec.err = dec.makeInvalidUnmarshalErr(v) + err := dec.skipData() + if err != nil { + return err + } + return nil + } + } + return dec.raiseInvalidJSONErr(dec.cursor) +} + +func (dec *Decoder) getUint64(b byte) (uint64, error) { + var end = dec.cursor + var start = dec.cursor + // look for following numbers + for j := dec.cursor + 1; j < dec.length || dec.read(); j++ { + switch dec.data[j] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + end = j + continue + case ' ', '\n', '\t', '\r', '.', ',', '}', ']': + dec.cursor = j + return dec.atoui64(start, end), nil + } + // invalid json we expect numbers, dot (single one), comma, or spaces + return 0, dec.raiseInvalidJSONErr(dec.cursor) + } + return dec.atoui64(start, end), nil +} + +func (dec *Decoder) atoui64(start, end int) uint64 { + var ll = end + 1 - start + var val = uint64(digits[dec.data[start]]) + end = end + 1 + if ll < maxUint64Length { + for i := start + 1; i < end; i++ { + uintv := uint64(digits[dec.data[i]]) + val = (val << 3) + (val << 1) + uintv + } + } else if ll == maxUint64Length { + for i := start + 1; i < end; i++ { + uintv := uint64(digits[dec.data[i]]) + if val > maxUint64toMultiply { + dec.err = dec.makeInvalidUnmarshalErr(val) + return 0 + } + val = (val << 3) + (val << 1) + if math.MaxUint64-val < uintv { + dec.err = dec.makeInvalidUnmarshalErr(val) + return 0 + } + val += uintv + } + } else { + dec.err = dec.makeInvalidUnmarshalErr(val) + return 0 + } + return val +} + +func (dec *Decoder) atoui32(start, end int) uint32 { + var ll = end + 1 - start + var val uint32 + val = uint32(digits[dec.data[start]]) + end = end + 1 + if ll < maxUint32Length { + for i := start + 1; i < end; i++ { + uintv := uint32(digits[dec.data[i]]) + val = (val << 3) + (val << 1) + uintv + } + } else if ll == maxUint32Length { + for i := start + 1; i < end; i++ { + uintv := uint32(digits[dec.data[i]]) + if val > maxUint32toMultiply { + dec.err = dec.makeInvalidUnmarshalErr(val) + return 0 + } + val = (val << 3) + (val << 1) + if math.MaxUint32-val < uintv { + dec.err = dec.makeInvalidUnmarshalErr(val) + return 0 + } + val += uintv + } + } else if ll > maxUint32Length { + dec.err = dec.makeInvalidUnmarshalErr(val) + val = 0 + } + return val +} + +func (dec *Decoder) atoui16(start, end int) uint16 { + var ll = end + 1 - start + var val uint16 + val = uint16(digits[dec.data[start]]) + end = end + 1 + if ll < maxUint16Length { + for i := start + 1; i < end; i++ { + uintv := uint16(digits[dec.data[i]]) + val = (val << 3) + (val << 1) + uintv + } + } else if ll == maxUint16Length { + for i := start + 1; i < end; i++ { + uintv := uint16(digits[dec.data[i]]) + if val > maxUint16toMultiply { + dec.err = dec.makeInvalidUnmarshalErr(val) + return 0 + } + val = (val << 3) + (val << 1) + if math.MaxUint16-val < uintv { + dec.err = dec.makeInvalidUnmarshalErr(val) + return 0 + } + val += uintv + } + } else if ll > maxUint16Length { + dec.err = dec.makeInvalidUnmarshalErr(val) + val = 0 + } + return val +} + +func (dec *Decoder) atoui8(start, end int) uint8 { + var ll = end + 1 - start + var val uint8 + val = uint8(digits[dec.data[start]]) + end = end + 1 + if ll < maxUint8Length { + for i := start + 1; i < end; i++ { + uintv := uint8(digits[dec.data[i]]) + val = (val << 3) + (val << 1) + uintv + } + } else if ll == maxUint8Length { + for i := start + 1; i < end; i++ { + uintv := uint8(digits[dec.data[i]]) + if val > maxUint8toMultiply { + dec.err = dec.makeInvalidUnmarshalErr(val) + return 0 + } + val = (val << 3) + (val << 1) + if math.MaxUint8-val < uintv { + dec.err = dec.makeInvalidUnmarshalErr(val) + return 0 + } + val += uintv + } + } else if ll > maxUint8Length { + dec.err = dec.makeInvalidUnmarshalErr(val) + val = 0 + } + return val +} diff --git a/decode_number_uint_test.go b/decode_number_uint_test.go @@ -0,0 +1,686 @@ +package gojay + +import ( + "fmt" + "reflect" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDecoderUint64(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult uint64 + err bool + errType interface{} + }{ + { + name: "basic-positive", + json: "100", + expectedResult: 100, + }, + { + name: "basic-positive2", + json: " 1039405", + expectedResult: 1039405, + }, + { + name: "basic-negative", + json: "-2", + expectedResult: 2, + }, + { + name: "basic-null", + json: "null", + expectedResult: 0, + }, + { + name: "basic-null-err", + json: "nxll", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-skip-data-err", + json: "trua", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-big", + json: "18446744073709551615", + expectedResult: 18446744073709551615, + }, + { + name: "basic-big-overflow", + json: "18446744073709551616", + expectedResult: 0, + err: true, + }, + { + name: "basic-big-overflow", + json: "18446744073709551625", + expectedResult: 0, + err: true, + }, + { + name: "basic-big-overflow2", + json: "184467440737095516161", + expectedResult: 0, + err: true, + }, + { + name: "basic-negative2", + json: "-2349557", + expectedResult: 2349557, + }, + { + name: "basic-float", + json: "2.4595", + expectedResult: 2, + }, + { + name: "basic-float2", + json: "-7.8876", + expectedResult: 7, + }, + { + name: "error1", + json: "132zz4", + expectedResult: 0, + err: true, + }, + { + name: "error", + json: "-83zez4", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "invalid-type", + json: `"string"`, + expectedResult: 0, + err: true, + errType: InvalidUnmarshalError(""), + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + json := []byte(testCase.json) + var v uint64 + err := Unmarshal(json, &v) + if testCase.err { + assert.NotNil(t, err, "Err must not be nil") + if testCase.errType != nil { + assert.IsType( + t, + testCase.errType, + err, + fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), + ) + } + } else { + assert.Nil(t, err, "Err must be nil") + } + assert.Equal(t, testCase.expectedResult, v, fmt.Sprintf("v must be equal to %d", testCase.expectedResult)) + }) + } + t.Run("pool-error", func(t *testing.T) { + result := uint64(1) + dec := NewDecoder(nil) + dec.Release() + defer func() { + err := recover() + assert.NotNil(t, err, "err shouldnot be nil") + assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") + }() + _ = dec.DecodeUint64(&result) + assert.True(t, false, "should not be called as decoder should have panicked") + }) + t.Run("decoder-api", func(t *testing.T) { + var v uint64 + dec := NewDecoder(strings.NewReader(`33`)) + defer dec.Release() + err := dec.DecodeUint64(&v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, uint64(33), v, "v must be equal to 33") + }) + t.Run("decoder-api-json-error", func(t *testing.T) { + var v uint64 + dec := NewDecoder(strings.NewReader(``)) + defer dec.Release() + err := dec.DecodeUint64(&v) + assert.NotNil(t, err, "Err must not be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + }) +} + +func TestDecoderUint32(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult uint32 + err bool + errType interface{} + }{ + { + name: "basic-positive", + json: "100", + expectedResult: 100, + }, + { + name: "basic-positive2", + json: " 1039405 ", + expectedResult: 1039405, + }, + { + name: "basic-negative", + json: "-2", + expectedResult: 2, + }, + { + name: "basic-null", + json: "null", + expectedResult: 0, + }, + { + name: "basic-null-err", + json: "nxll", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-skip-data-err", + json: "trua", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-negative2", + json: "-2349557", + expectedResult: 2349557, + }, + { + name: "basic-big", + json: "4294967295", + expectedResult: 4294967295, + }, + { + name: "basic-big-overflow", + json: " 4294967298", + expectedResult: 0, + err: true, + }, + { + name: "basic-big-overflow", + json: "4294967395", + expectedResult: 0, + err: true, + }, + { + name: "basic-big-overflow2", + json: "42949672983", + expectedResult: 0, + err: true, + }, + { + name: "basic-float", + json: "2.4595", + expectedResult: 2, + }, + { + name: "basic-float2", + json: "-7.8876", + expectedResult: 7, + }, + { + name: "error", + json: "83zez4", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error", + json: "-83zez4", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "invalid-type", + json: `"string"`, + expectedResult: 0, + err: true, + errType: InvalidUnmarshalError(""), + }, + { + name: "invalid-json", + json: `123invalid`, + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + json := []byte(testCase.json) + var v uint32 + err := Unmarshal(json, &v) + if testCase.err { + assert.NotNil(t, err, "Err must not be nil") + if testCase.errType != nil { + assert.IsType( + t, + testCase.errType, + err, + fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), + ) + } + } else { + assert.Nil(t, err, "Err must be nil") + } + assert.Equal(t, testCase.expectedResult, v, fmt.Sprintf("v must be equal to %d", testCase.expectedResult)) + }) + } + t.Run("pool-error", func(t *testing.T) { + result := uint32(1) + dec := NewDecoder(nil) + dec.Release() + defer func() { + err := recover() + assert.NotNil(t, err, "err shouldnot be nil") + assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") + }() + _ = dec.DecodeUint32(&result) + assert.True(t, false, "should not be called as decoder should have panicked") + }) + t.Run("decoder-api", func(t *testing.T) { + var v uint32 + dec := NewDecoder(strings.NewReader(`33`)) + defer dec.Release() + err := dec.DecodeUint32(&v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, uint32(33), v, "v must be equal to 33") + }) + t.Run("decoder-api-json-error", func(t *testing.T) { + var v uint32 + dec := NewDecoder(strings.NewReader(``)) + defer dec.Release() + err := dec.DecodeUint32(&v) + assert.NotNil(t, err, "Err must not be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + }) +} + +func TestDecoderUint16(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult uint16 + err bool + errType interface{} + }{ + { + name: "basic-positive", + json: "100", + expectedResult: 100, + }, + { + name: "basic-positive2", + json: " 3224 ", + expectedResult: 3224, + }, + { + name: "basic-negative", + json: "-2", + expectedResult: 2, + }, + { + name: "basic-null", + json: "null", + expectedResult: 0, + }, + { + name: "basic-null-err", + json: "nxll", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-skip-data-err", + json: "trua", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-skip-data-err", + json: "trua", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-overflow", + json: "335346564", + expectedResult: 0, + err: true, + errType: InvalidUnmarshalError(""), + }, + { + name: "basic-negative2", + json: "-24467", + expectedResult: 24467, + }, + { + name: "basic-big", + json: "54546", + expectedResult: 54546, + }, + { + name: "basic-big-overflow", + json: " 4294967298", + expectedResult: 0, + err: true, + }, + { + name: "basic-big-overflow", + json: " 65537", + expectedResult: 0, + err: true, + }, + { + name: "basic-big-overflow", + json: " 66537", + expectedResult: 0, + err: true, + }, + { + name: "basic-big-overflow2", + json: "42949672983", + expectedResult: 0, + err: true, + }, + { + name: "basic-float", + json: "2.4595", + expectedResult: 2, + }, + { + name: "basic-float2", + json: "-7.8876", + expectedResult: 7, + }, + { + name: "error", + json: "83zez4", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error", + json: "-83zez4", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "invalid-type", + json: `"string"`, + expectedResult: 0, + err: true, + errType: InvalidUnmarshalError(""), + }, + { + name: "invalid-json", + json: `123invalid`, + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + json := []byte(testCase.json) + var v uint16 + err := Unmarshal(json, &v) + if testCase.err { + assert.NotNil(t, err, "Err must not be nil") + if testCase.errType != nil { + assert.IsType( + t, + testCase.errType, + err, + fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), + ) + } + } else { + assert.Nil(t, err, "Err must be nil") + } + assert.Equal(t, testCase.expectedResult, v, fmt.Sprintf("v must be equal to %d", testCase.expectedResult)) + }) + } + t.Run("pool-error", func(t *testing.T) { + result := uint16(1) + dec := NewDecoder(nil) + dec.Release() + defer func() { + err := recover() + assert.NotNil(t, err, "err shouldnot be nil") + assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") + }() + _ = dec.DecodeUint16(&result) + assert.True(t, false, "should not be called as decoder should have panicked") + }) + t.Run("decoder-api", func(t *testing.T) { + var v uint16 + dec := NewDecoder(strings.NewReader(`33`)) + defer dec.Release() + err := dec.DecodeUint16(&v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, uint16(33), v, "v must be equal to 33") + }) + t.Run("decoder-api2", func(t *testing.T) { + var v uint16 + dec := NewDecoder(strings.NewReader(`33`)) + defer dec.Release() + err := dec.Decode(&v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, uint16(33), v, "v must be equal to 33") + }) + t.Run("decoder-api-json-error", func(t *testing.T) { + var v uint16 + dec := NewDecoder(strings.NewReader(``)) + defer dec.Release() + err := dec.DecodeUint16(&v) + assert.NotNil(t, err, "Err must not be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + }) +} + +func TestDecoderUint8(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult uint8 + err bool + errType interface{} + }{ + { + name: "basic-positive", + json: "100", + expectedResult: 100, + }, + { + name: "basic-positive2", + json: " 255 ", + expectedResult: 255, + }, + { + name: "basic-negative", + json: "-2", + expectedResult: 2, + }, + { + name: "basic-null", + json: "null", + expectedResult: 0, + }, + { + name: "basic-null-err", + json: "nxll", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-skip-data-err", + json: "trua", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-negative2", + json: "-234", + expectedResult: 234, + }, + { + name: "basic-big", + json: "200", + expectedResult: 200, + }, + { + name: "basic-overflow", + json: "256", + expectedResult: 0, + err: true, + errType: InvalidUnmarshalError(""), + }, + { + name: "basic-overflow", + json: "274", + expectedResult: 0, + err: true, + errType: InvalidUnmarshalError(""), + }, + { + name: "basic-big-overflow", + json: " 4294967298", + expectedResult: 0, + err: true, + }, + { + name: "basic-big-overflow2", + json: "42949672983", + expectedResult: 0, + err: true, + }, + { + name: "basic-float", + json: "2.4595", + expectedResult: 2, + }, + { + name: "basic-float2", + json: "-7.8876", + expectedResult: 7, + }, + { + name: "error", + json: "83zez4", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error", + json: "-83zez4", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "invalid-type", + json: `"string"`, + expectedResult: 0, + err: true, + errType: InvalidUnmarshalError(""), + }, + { + name: "invalid-json", + json: `123invalid`, + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + json := []byte(testCase.json) + var v uint8 + err := Unmarshal(json, &v) + if testCase.err { + assert.NotNil(t, err, "Err must not be nil") + if testCase.errType != nil { + assert.IsType( + t, + testCase.errType, + err, + fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), + ) + } + } else { + assert.Nil(t, err, "Err must be nil") + } + assert.Equal(t, testCase.expectedResult, v, fmt.Sprintf("v must be equal to %d", testCase.expectedResult)) + }) + } + t.Run("pool-error", func(t *testing.T) { + result := uint8(1) + dec := NewDecoder(nil) + dec.Release() + defer func() { + err := recover() + assert.NotNil(t, err, "err shouldnot be nil") + assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") + }() + _ = dec.DecodeUint8(&result) + assert.True(t, false, "should not be called as decoder should have panicked") + }) + t.Run("decoder-api", func(t *testing.T) { + var v uint8 + dec := NewDecoder(strings.NewReader(`33`)) + defer dec.Release() + err := dec.DecodeUint8(&v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, uint8(33), v, "v must be equal to 33") + }) + t.Run("decoder-api2", func(t *testing.T) { + var v uint8 + dec := NewDecoder(strings.NewReader(`33`)) + defer dec.Release() + err := dec.Decode(&v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, uint8(33), v, "v must be equal to 33") + }) + t.Run("decoder-api-json-error", func(t *testing.T) { + var v uint8 + dec := NewDecoder(strings.NewReader(``)) + defer dec.Release() + err := dec.DecodeUint8(&v) + assert.NotNil(t, err, "Err must not be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + }) +} diff --git a/decode_object.go b/decode_object.go @@ -1,23 +1,22 @@ package gojay import ( - "fmt" "unsafe" ) // DecodeObject reads the next JSON-encoded value from its input and stores it in the value pointed to by v. // -// v must implement UnmarshalerObject. +// v must implement UnmarshalerJSONObject. // // See the documentation for Unmarshal for details about the conversion of JSON into a Go value. -func (dec *Decoder) DecodeObject(j UnmarshalerObject) error { +func (dec *Decoder) DecodeObject(j UnmarshalerJSONObject) error { if dec.isPooled == 1 { panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) } _, err := dec.decodeObject(j) return err } -func (dec *Decoder) decodeObject(j UnmarshalerObject) (int, error) { +func (dec *Decoder) decodeObject(j UnmarshalerJSONObject) (int, error) { keys := j.NKeys() for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { switch dec.data[dec.cursor] { @@ -34,8 +33,9 @@ func (dec *Decoder) decodeObject(j UnmarshalerObject) (int, error) { } else if done { return dec.cursor, nil } - err = j.UnmarshalObject(dec, k) + err = j.UnmarshalJSONObject(dec, k) if err != nil { + dec.err = err return 0, err } else if dec.called&1 == 0 { err := dec.skipData() @@ -55,8 +55,9 @@ func (dec *Decoder) decodeObject(j UnmarshalerObject) (int, error) { } else if done { return dec.cursor, nil } - err = j.UnmarshalObject(dec, k) + err = j.UnmarshalJSONObject(dec, k) if err != nil { + dec.err = err return 0, err } else if dec.called&1 == 0 { err := dec.skipData() @@ -87,14 +88,8 @@ func (dec *Decoder) decodeObject(j UnmarshalerObject) (int, error) { dec.cursor++ return dec.cursor, nil default: - // can't unmarshall to struct - dec.err = InvalidTypeError( - fmt.Sprintf( - "Cannot unmarshal to struct, wrong char '%s' found at pos %d", - string(dec.data[dec.cursor]), - dec.cursor, - ), - ) + // can't unmarshal to struct + dec.err = dec.makeInvalidUnmarshalErr(j) err := dec.skipData() if err != nil { return 0, err @@ -102,14 +97,13 @@ func (dec *Decoder) decodeObject(j UnmarshalerObject) (int, error) { return dec.cursor, nil } } - return 0, InvalidJSONError("Invalid JSON while parsing object") + return 0, dec.raiseInvalidJSONErr(dec.cursor) } func (dec *Decoder) skipObject() (int, error) { var objectsOpen = 1 var objectsClosed = 0 - // var stringOpen byte = 0 - for j := dec.cursor; j < dec.length; j++ { + for j := dec.cursor; j < dec.length || dec.read(); j++ { switch dec.data[j] { case '}': objectsClosed++ @@ -122,7 +116,7 @@ func (dec *Decoder) skipObject() (int, error) { objectsOpen++ case '"': j++ - for ; j < dec.length; j++ { + for ; j < dec.length || dec.read(); j++ { if dec.data[j] != '"' { continue } @@ -132,7 +126,7 @@ func (dec *Decoder) skipObject() (int, error) { // loop backward and count how many anti slash found // to see if string is effectively escaped ct := 1 - for i := j; i > 0; i-- { + for i := j - 1; i > 0; i-- { if dec.data[i] != '\\' { break } @@ -140,14 +134,14 @@ func (dec *Decoder) skipObject() (int, error) { } // is pair number of slashes, quote is not escaped if ct&1 == 0 { - break + continue } } default: continue } } - return 0, nil + return 0, dec.raiseInvalidJSONErr(dec.cursor) } func (dec *Decoder) nextKey() (string, bool, error) { @@ -173,13 +167,16 @@ func (dec *Decoder) nextKey() (string, bool, error) { d := dec.data[start : end-1] return *(*string)(unsafe.Pointer(&d)), false, nil } - return "", false, InvalidJSONError("Invalid JSON while parsing object key") + return "", false, dec.raiseInvalidJSONErr(dec.cursor) case '}': dec.cursor = dec.cursor + 1 return "", true, nil + default: + // can't unmarshall to struct + return "", false, dec.raiseInvalidJSONErr(dec.cursor) } } - return "", false, InvalidJSONError("Invalid JSON while parsing object key") + return "", false, dec.raiseInvalidJSONErr(dec.cursor) } func (dec *Decoder) skipData() error { @@ -194,6 +191,7 @@ func (dec *Decoder) skipData() error { if err != nil { return err } + dec.cursor++ return nil case 't': dec.cursor++ @@ -201,6 +199,7 @@ func (dec *Decoder) skipData() error { if err != nil { return err } + dec.cursor++ return nil // is false case 'f': @@ -209,6 +208,7 @@ func (dec *Decoder) skipData() error { if err != nil { return err } + dec.cursor++ return nil // is an object case '{': @@ -232,9 +232,9 @@ func (dec *Decoder) skipData() error { dec.cursor = end return err } - return InvalidJSONError("Invalid JSON") + return dec.raiseInvalidJSONErr(dec.cursor) } - return InvalidJSONError("Invalid JSON") + return dec.raiseInvalidJSONErr(dec.cursor) } // DecodeObjectFunc is a custom func type implementating UnarshaleObject. @@ -247,8 +247,8 @@ func (dec *Decoder) skipData() error { // })) type DecodeObjectFunc func(*Decoder, string) error -// UnmarshalObject implements UnarshalerObject. -func (f DecodeObjectFunc) UnmarshalObject(dec *Decoder, k string) error { +// UnmarshalJSONObject implements UnarshalerObject. +func (f DecodeObjectFunc) UnmarshalJSONObject(dec *Decoder, k string) error { return f(dec, k) } diff --git a/decode_object_test.go b/decode_object_test.go @@ -1,6 +1,7 @@ package gojay import ( + "fmt" "io" "strings" "testing" @@ -8,73 +9,813 @@ import ( "github.com/stretchr/testify/assert" ) -type TestObj struct { - test int - test2 int - test3 string - test4 string - test5 float64 - testArr testSliceObj - testSubObj *TestSubObj - testSubObj2 *TestSubObj -} +func TestDecodeObjectBasic(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult testObject + err bool + errType interface{} + skipCheckResult bool + }{ + { + name: "basic", + json: `{ + "testStr": "hello world!", + "testInt": 4535, + "testBool": true, + "testFloat32": 2.345, + "testFloat64": 123.677, + "testInt8": 23, + "testInt16": 1245, + "testInt32": 456778, + "testInt64": 1446685358, + "testUint8": 255, + "testUint16": 3455, + "testUint32": 343443, + "testUint64": 545665757 + }`, + expectedResult: testObject{ + testStr: "hello world!", + testInt: 4535, + testBool: true, + testFloat32: 2.345, + testFloat64: 123.677, + testInt8: 23, + testInt16: 1245, + testInt32: 456778, + testInt64: 1446685358, + testUint8: 255, + testUint16: 3455, + testUint32: 343443, + testUint64: 545665757, + }, + err: false, + }, + { + name: "basic-err-invalid-type", + json: `1`, + expectedResult: testObject{}, + err: true, + errType: InvalidUnmarshalError(""), + }, + { + name: "basic-err-invalid-json", + json: `hello`, + expectedResult: testObject{}, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-err-invalid-json", + json: `nall`, + expectedResult: testObject{}, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-err-invalid-type", + json: ``, + expectedResult: testObject{}, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-err", + json: `{ + "testStr": "hello world!", + "testInt": 453q5, + "testBool": trae, + "testFloat32": 2q.345, + "testFloat64": 12x3.677, + "testInt8": 2s3, + "testInt16": 1245, + "testInt32": 4567q78, + "testInt64": 14466e85358, + "testUint8": 2s55, + "testUint16": 345i5, + "testUint32": 343q443, + "testUint64": 5456657z57 + }`, + expectedResult: testObject{}, + err: true, + }, + { + name: "basic-err2", + json: `{ + "testStr": "hello world!", + "testInt": 4535, + "testBool": true, + "testFloat32": 2.345, + "testFloat64": 123.677, + "testInt8": 23, + "testInt16": 1245, + "testInt32": 4567x78, + "testInt64": 1446685358, + "testUint8": 255, + "testUint16": 3455, + "testUint32": 343443, + "testUint64": 545665757 + }`, + expectedResult: testObject{}, + err: true, + }, + { + name: "basic-err-float32", + json: `{ + "testStr": "hello world!", + "testInt": 4535, + "testBool": true, + "testFloat32": 2q.345, + "testFloat64": 123.677, + "testInt8": 23, + "testInt16": 1245, + "testInt32": 456778, + "testInt64": 1446685358, + "testUint8": 255, + "testUint16": 3455, + "testUint32": 343443, + "testUint64": 545665757 + }`, + expectedResult: testObject{}, + err: true, + }, + { + name: "basic-err-float64", + json: `{ + "testStr": "hello world!", + "testInt": 4535, + "testBool": true, + "testFloat32": 2.345, + "testFloat64": 1x23.677, + "testInt8": 23, + "testInt16": 1245, + "testInt32": 456778, + "testInt64": 1446685358, + "testUint8": 255, + "testUint16": 3455, + "testUint32": 343443, + "testUint64": 545665757 + }`, + expectedResult: testObject{}, + err: true, + }, + { + name: "basic-err3", + json: `{ + "testStr": "hello world!", + "testInt": 4535, + "testBool": true, + "testFloat32": 2.345, + "testFloat64": 123.677, + "testInt8": 2q3, + "testInt16": 1245, + "testInt32": 456778, + "testInt64": 1446685358, + "testUint8": 255, + "testUint16": 3455, + "testUint32": 343443, + "testUint64": 545665757 + }`, + expectedResult: testObject{}, + err: true, + }, + { + name: "basic-err-int16", + json: `{ + "testStr": "hello world!", + "testInt": 4535, + "testBool": true, + "testFloat32": 2.345, + "testFloat64": 123.677, + "testInt8": 23, + "testInt16": 1x245, + "testInt32": 456778, + "testInt64": 1446685358, + "testUint8": 255, + "testUint16": 3455, + "testUint32": 343443, + "testUint64": 545665757 + }`, + expectedResult: testObject{}, + err: true, + }, + { + name: "basic-err-int64", + json: `{ + "testStr": "hello world!", + "testInt": 4535, + "testBool": true, + "testFloat32": 2.345, + "testFloat64": 123.677, + "testInt8": 23, + "testInt16": 1245, + "testInt32": 456778, + "testInt64": 1446q685358, + "testUint8": 255, + "testUint16": 3455, + "testUint32": 343443, + "testUint64": 545665757 + }`, + expectedResult: testObject{}, + err: true, + }, + { + name: "basic-err-uint8", + json: `{ + "testStr": "hello world!", + "testInt": 4535, + "testBool": true, + "testFloat32": 2.345, + "testFloat64": 123.677, + "testInt8": 23, + "testInt16": 1245, + "testInt32": 456778, + "testInt64": 1446685358, + "testUint8": 2x55, + "testUint16": 3455, + "testUint32": 343443, + "testUint64": 545665757 + }`, + expectedResult: testObject{}, + err: true, + }, + { + name: "basic-err-uint16", + json: `{ + "testStr": "hello world!", + "testInt": 4535, + "testBool": true, + "testFloat32": 2.345, + "testFloat64": 123.677, + "testInt8": 23, + "testInt16": 1245, + "testInt32": 456778, + "testInt64": 1446685358, + "testUint8": 255, + "testUint16": 3x455, + "testUint32": 343443, + "testUint64": 545665757 + }`, + expectedResult: testObject{}, + err: true, + }, + { + name: "basic-err-uint32", + json: `{ + "testStr": "hello world!", + "testInt": 4535, + "testBool": true, + "testFloat32": 2.345, + "testFloat64": 123.677, + "testInt8": 23, + "testInt16": 1245, + "testInt32": 456778, + "testInt64": 1446685358, + "testUint8": 255, + "testUint16": 3455, + "testUint32": 3x43443, + "testUint64": 545665757 + }`, + expectedResult: testObject{}, + err: true, + }, + { + name: "basic-err-uint64", + json: `{ + "testStr": "hello world!", + "testInt": 4535, + "testBool": true, + "testFloat32": 2.345, + "testFloat64": 123.677, + "testInt8": 23, + "testInt16": 1245, + "testInt32": 456778, + "testInt64": 1446685358, + "testUint8": 255, + "testUint16": 3455, + "testUint32": 343443, + "testUint64": 5456x65757 + }`, + expectedResult: testObject{}, + err: true, + }, + { + name: "basic-skip-data", + json: `{ + "testStr": "hello world!", + "testInt": 4535, + "testBool": true, + "testFloat32": 2.345, + "testFloat64": 123.677, + "testInt8": 23, + "skipObject": { + "escapedString": "string with escaped \\n new line" + }, + "testInt16": 1245, + "testInt32": 456778, + "testInt64": 1446685358, + "testUint8": 255, + "skipArray": [[],[],{}], + "testUint16": 3455, + "skipBool": true, + "skipNull": null, + "testUint32": 343443, + "testUint64": 545665757, + "skipString": "skipping string with escaped \\n new line", + "skipInt": 3, + }`, + expectedResult: testObject{ + testStr: "hello world!", + testInt: 4535, + testBool: true, + testFloat32: 2.345, + testFloat64: 123.677, + testInt8: 23, + testInt16: 1245, + testInt32: 456778, + testInt64: 1446685358, + testUint8: 255, + testUint16: 3455, + testUint32: 343443, + testUint64: 545665757, + }, + err: false, + }, + } -type TestSubObj struct { - test3 int - test4 int - test5 string - testSubSubObj *TestSubObj - testSubSubObj2 *TestSubObj + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + s := testObject{} + dec := BorrowDecoder(strings.NewReader(testCase.json)) + defer dec.Release() + err := dec.Decode(&s) + if testCase.err { + t.Log(err) + assert.NotNil(t, err, "err should not be nil") + if testCase.errType != nil { + assert.IsType(t, testCase.errType, err, "err should be of the given type") + } + return + } + assert.Nil(t, err, "err should be nil") + if !testCase.skipCheckResult { + assert.Equal(t, testCase.expectedResult, s, "value at given index should be the same as expected results") + } + }) + } } -func (t *TestSubObj) UnmarshalObject(dec *Decoder, key string) error { - switch key { - case "test": - return dec.AddInt(&t.test3) - case "test2": - return dec.AddInt(&t.test4) - case "test3": - return dec.AddString(&t.test5) - case "testSubSubObj": - t.testSubSubObj = &TestSubObj{} - return dec.AddObject(t.testSubSubObj) - case "testSubSubObj2": - t.testSubSubObj2 = &TestSubObj{} - return dec.AddObject(t.testSubSubObj2) +func TestDecodeObjectBasic0Keys(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult testObject0Keys + err bool + errType interface{} + skipCheckResult bool + }{ + { + name: "basic", + json: `{ + "testStr": "hello world!", + "testInt": 4535, + "testBool": true, + "testFloat32": 2.345, + "testFloat64": 123.677, + "testInt8": 23, + "testInt16": 1245, + "testInt32": 456778, + "testInt64": 1446685358, + "testUint8": 255, + "testUint16": 3455, + "testUint32": 343443, + "testUint64": 545665757 + }`, + expectedResult: testObject0Keys{ + testStr: "hello world!", + testInt: 4535, + testBool: true, + testFloat32: 2.345, + testFloat64: 123.677, + testInt8: 23, + testInt16: 1245, + testInt32: 456778, + testInt64: 1446685358, + testUint8: 255, + testUint16: 3455, + testUint32: 343443, + testUint64: 545665757, + }, + err: false, + }, + { + name: "basic-err-invalid-type", + json: `1`, + expectedResult: testObject0Keys{}, + err: true, + errType: InvalidUnmarshalError(""), + }, + { + name: "basic-err-invalid-json", + json: `hello`, + expectedResult: testObject0Keys{}, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-err-invalid-json", + json: `nall`, + expectedResult: testObject0Keys{}, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-err", + json: `{ + "testStr": "hello world!", + "testInt": 453q5, + "testBool": trae, + "testFloat32": 2q.345, + "testFloat64": 12x3.677, + "testInt8": 2s3, + "testInt16": 1245, + "testInt32": 4567q78, + "testInt64": 14466e85358, + "testUint8": 2s55, + "testUint16": 345i5, + "testUint32": 343q443, + "testUint64": 5456657z57 + }`, + expectedResult: testObject0Keys{}, + err: true, + }, + { + name: "basic-err2", + json: `{ + "testStr": "hello world!", + "testInt": 4535, + "testBool": true, + "testFloat32": 2.345, + "testFloat64": 123.677, + "testInt8": 23, + "testInt16": 1245, + "testInt32": 4567x78, + "testInt64": 1446685358, + "testUint8": 255, + "testUint16": 3455, + "testUint32": 343443, + "testUint64": 545665757 + }`, + expectedResult: testObject0Keys{}, + err: true, + }, + { + name: "basic-err-float32", + json: `{ + "testStr": "hello world!", + "testInt": 4535, + "testBool": true, + "testFloat32": 2q.345, + "testFloat64": 123.677, + "testInt8": 23, + "testInt16": 1245, + "testInt32": 456778, + "testInt64": 1446685358, + "testUint8": 255, + "testUint16": 3455, + "testUint32": 343443, + "testUint64": 545665757 + }`, + expectedResult: testObject0Keys{}, + err: true, + }, + { + name: "basic-err-float64", + json: `{ + "testStr": "hello world!", + "testInt": 4535, + "testBool": true, + "testFloat32": 2.345, + "testFloat64": 1x23.677, + "testInt8": 23, + "testInt16": 1245, + "testInt32": 456778, + "testInt64": 1446685358, + "testUint8": 255, + "testUint16": 3455, + "testUint32": 343443, + "testUint64": 545665757 + }`, + expectedResult: testObject0Keys{}, + err: true, + }, + { + name: "basic-err3", + json: `{ + "testStr": "hello world!", + "testInt": 4535, + "testBool": true, + "testFloat32": 2.345, + "testFloat64": 123.677, + "testInt8": 2q3, + "testInt16": 1245, + "testInt32": 456778, + "testInt64": 1446685358, + "testUint8": 255, + "testUint16": 3455, + "testUint32": 343443, + "testUint64": 545665757 + }`, + expectedResult: testObject0Keys{}, + err: true, + }, + { + name: "basic-err-int16", + json: `{ + "testStr": "hello world!", + "testInt": 4535, + "testBool": true, + "testFloat32": 2.345, + "testFloat64": 123.677, + "testInt8": 23, + "testInt16": 1x245, + "testInt32": 456778, + "testInt64": 1446685358, + "testUint8": 255, + "testUint16": 3455, + "testUint32": 343443, + "testUint64": 545665757 + }`, + expectedResult: testObject0Keys{}, + err: true, + }, + { + name: "basic-err-int64", + json: `{ + "testStr": "hello world!", + "testInt": 4535, + "testBool": true, + "testFloat32": 2.345, + "testFloat64": 123.677, + "testInt8": 23, + "testInt16": 1245, + "testInt32": 456778, + "testInt64": 1446q685358, + "testUint8": 255, + "testUint16": 3455, + "testUint32": 343443, + "testUint64": 545665757 + }`, + expectedResult: testObject0Keys{}, + err: true, + }, + { + name: "basic-err-uint8", + json: `{ + "testStr": "hello world!", + "testInt": 4535, + "testBool": true, + "testFloat32": 2.345, + "testFloat64": 123.677, + "testInt8": 23, + "testInt16": 1245, + "testInt32": 456778, + "testInt64": 1446685358, + "testUint8": 2x55, + "testUint16": 3455, + "testUint32": 343443, + "testUint64": 545665757 + }`, + expectedResult: testObject0Keys{}, + err: true, + }, + { + name: "basic-err-uint16", + json: `{ + "testStr": "hello world!", + "testInt": 4535, + "testBool": true, + "testFloat32": 2.345, + "testFloat64": 123.677, + "testInt8": 23, + "testInt16": 1245, + "testInt32": 456778, + "testInt64": 1446685358, + "testUint8": 255, + "testUint16": 3x455, + "testUint32": 343443, + "testUint64": 545665757 + }`, + expectedResult: testObject0Keys{}, + err: true, + }, + { + name: "basic-err-uint32", + json: `{ + "testStr": "hello world!", + "testInt": 4535, + "testBool": true, + "testFloat32": 2.345, + "testFloat64": 123.677, + "testInt8": 23, + "testInt16": 1245, + "testInt32": 456778, + "testInt64": 1446685358, + "testUint8": 255, + "testUint16": 3455, + "testUint32": 3x43443, + "testUint64": 545665757 + }`, + expectedResult: testObject0Keys{}, + err: true, + }, + { + name: "basic-err-uint64", + json: `{ + "testStr": "hello world!", + "testInt": 4535, + "testBool": true, + "testFloat32": 2.345, + "testFloat64": 123.677, + "testInt8": 23, + "testInt16": 1245, + "testInt32": 456778, + "testInt64": 1446685358, + "testUint8": 255, + "testUint16": 3455, + "testUint32": 343443, + "testUint64": 5456x65757 + }`, + expectedResult: testObject0Keys{}, + err: true, + }, + { + name: "basic-skip-data", + json: `{ + "testStr": "hello world!", + "testInt": 4535, + "testBool": true, + "testFloat32": 2.345, + "testFloat64": 123.677, + "testInt8": 23, + "skipObject": { + "escapedString": "string with escaped \\n new line" + }, + "testInt16": 1245, + "testInt32": 456778, + "testInt64": 1446685358, + "testUint8": 255, + "skipArray": [[],[],{}], + "testUint16": 3455, + "skipBool": true, + "skipNull": null, + "testUint32": 343443, + "testUint64": 545665757, + "skipString": "skipping string with escaped \\n new line", + "skipInt": 3, + }`, + expectedResult: testObject0Keys{ + testStr: "hello world!", + testInt: 4535, + testBool: true, + testFloat32: 2.345, + testFloat64: 123.677, + testInt8: 23, + testInt16: 1245, + testInt32: 456778, + testInt64: 1446685358, + testUint8: 255, + testUint16: 3455, + testUint32: 343443, + testUint64: 545665757, + }, + err: false, + }, } - return nil -} -func (t *TestSubObj) NKeys() int { - return 0 + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + s := testObject0Keys{} + dec := BorrowDecoder(strings.NewReader(testCase.json)) + defer dec.Release() + err := dec.Decode(&s) + if testCase.err { + t.Log(err) + assert.NotNil(t, err, "err should not be nil") + if testCase.errType != nil { + assert.IsType(t, testCase.errType, err, "err should be of the given type") + } + return + } + assert.Nil(t, err, "err should be nil") + if !testCase.skipCheckResult { + assert.Equal(t, testCase.expectedResult, s, "value at given index should be the same as expected results") + } + }) + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + s := testObject0Keys{} + err := UnmarshalJSONObject([]byte(testCase.json), &s) + if testCase.err { + t.Log(err) + assert.NotNil(t, err, "err should not be nil") + if testCase.errType != nil { + assert.IsType(t, testCase.errType, err, "err should be of the given type") + } + return + } + assert.Nil(t, err, "err should be nil") + if !testCase.skipCheckResult { + assert.Equal(t, testCase.expectedResult, s, "value at given index should be the same as expected results") + } + }) + } } -func (t *TestObj) UnmarshalObject(dec *Decoder, key string) error { - switch key { - case "test": - return dec.AddInt(&t.test) - case "test2": - return dec.AddInt(&t.test2) - case "test3": - return dec.AddString(&t.test3) - case "test4": - return dec.AddString(&t.test4) - case "test5": - return dec.AddFloat(&t.test5) - case "testSubObj": - t.testSubObj = &TestSubObj{} - return dec.AddObject(t.testSubObj) - case "testSubObj2": - t.testSubObj2 = &TestSubObj{} - return dec.AddObject(t.testSubObj2) - case "testArr": - return dec.AddArray(&t.testArr) +func TestDecodeObjectComplex(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult testObjectComplex + err bool + errType interface{} + skipCheckResult bool + }{ + { + name: "basic", + json: `{ + "testSubObject": {}, + "testSubSliceInts": [1,2] + }`, + expectedResult: testObjectComplex{ + testSubObject: &testObject{}, + testSubSliceInts: &testSliceInts{1, 2}, + }, + err: false, + }, + { + name: "complex", + json: `{ + "testSubObject": { + "testStr": "some string", + "testInt":124465, + "testUint16":120, + "testUint8":15, + "testInt16":-135, + "testInt8":-23 + }, + "testSubSliceInts": [1,2,3,4,5], + "testStr": "some \\n string" + }`, + expectedResult: testObjectComplex{ + testSubObject: &testObject{ + testStr: "some string", + testInt: 124465, + testUint16: 120, + testUint8: 15, + testInt16: -135, + testInt8: -23, + }, + testSubSliceInts: &testSliceInts{1, 2, 3, 4, 5}, + testStr: "some \n string", + }, + err: false, + }, + { + name: "complex-json-err", + json: `{"testSubObject":{"testStr":"some string,"testInt":124465,"testUint16":120, "testUint8":15,"testInt16":-135,"testInt8":-23},"testSubSliceInts":[1,2],"testStr":"some \\n string"}`, + expectedResult: testObjectComplex{ + testSubObject: &testObject{}, + }, + err: true, + }, } - return nil -} -func (t *TestObj) NKeys() int { - return 8 + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + s := testObjectComplex{ + testSubObject: &testObject{}, + testSubSliceInts: &testSliceInts{}, + } + dec := BorrowDecoder(strings.NewReader(testCase.json)) + defer dec.Release() + err := dec.Decode(&s) + if testCase.err { + t.Log(err) + assert.NotNil(t, err, "err should not be nil") + if testCase.errType != nil { + assert.IsType(t, testCase.errType, err, "err should be of the given type") + } + return + } + assert.Nil(t, err, "err should be nil") + if !testCase.skipCheckResult { + assert.Equal(t, testCase.expectedResult, s, "value at given index should be the same as expected results") + } + }) + } } func assertResult(t *testing.T, v *TestObj, err error) { @@ -85,10 +826,6 @@ func assertResult(t *testing.T, v *TestObj, err error) { assert.Equal(t, "complex string with spaces and some slashes\"", v.test4, "v.test4 must be equal to 'string'") assert.Equal(t, -1.15657654376543, v.test5, "v.test5 must be equal to 1.15") assert.Len(t, v.testArr, 2, "v.testArr must be of len 2") - assert.Equal(t, v.testArr[0].test, 245, "v.testArr[0].test must be equal to 245") - assert.Equal(t, v.testArr[0].test2, 246, "v.testArr[0].test must be equal to 246") - assert.Equal(t, v.testArr[1].test, 245, "v.testArr[0].test must be equal to 245") - assert.Equal(t, v.testArr[1].test2, 246, "v.testArr[0].test must be equal to 246") assert.Equal(t, 121, v.testSubObj.test3, "v.testSubObj.test3 must be equal to 121") assert.Equal(t, 122, v.testSubObj.test4, "v.testSubObj.test4 must be equal to 122") @@ -196,7 +933,7 @@ type jsonObjectComplex struct { testObjInvalidType *jsonObjectComplex } -func (j *jsonObjectComplex) UnmarshalObject(dec *Decoder, key string) error { +func (j *jsonObjectComplex) UnmarshalJSONObject(dec *Decoder, key string) error { switch key { case "test": return dec.AddString(&j.Test) @@ -222,9 +959,9 @@ func (j *jsonObjectComplex) NKeys() int { func TestDecodeObjComplex(t *testing.T) { result := jsonObjectComplex{} - err := UnmarshalObject(jsonComplex, &result) + err := UnmarshalJSONObject(jsonComplex, &result) assert.NotNil(t, err, "err should not be as invalid type as been encountered nil") - assert.Equal(t, `Cannot unmarshal to struct, wrong char '"' found at pos 639`, err.Error(), "err should not be as invalid type as been encountered nil") + assert.Equal(t, `Cannot unmarshal JSON to type '*gojay.jsonObjectComplex'`, err.Error(), "err should not be as invalid type as been encountered nil") assert.Equal(t, `{"test":"1","test1":2}`, result.Test, "result.Test is not expected value") assert.Equal(t, "\\\\\\\\\n", result.Test2, "result.Test2 is not expected value") assert.Equal(t, 1, result.Test3, "result.test3 is not expected value") @@ -239,7 +976,7 @@ type jsonDecodePartial struct { Test2 string } -func (j *jsonDecodePartial) UnmarshalObject(dec *Decoder, key string) error { +func (j *jsonDecodePartial) UnmarshalJSONObject(dec *Decoder, key string) error { switch key { case "test": return dec.AddString(&j.Test) @@ -285,7 +1022,7 @@ func TestDecoderObjectInvalidJSON(t *testing.T) { type myMap map[string]string -func (m myMap) UnmarshalObject(dec *Decoder, k string) error { +func (m myMap) UnmarshalJSONObject(dec *Decoder, k string) error { str := "" err := dec.AddString(&str) if err != nil { @@ -459,6 +1196,90 @@ func TestDecoderObjectPoolError(t *testing.T) { assert.True(t, false, "should not be called as decoder should have panicked") } +func TestNextKey(t *testing.T) { + testCases := []struct { + name string + json string + expectedValue string + err bool + }{ + { + name: "basic", + json: `"key":"value"`, + expectedValue: "key", + }, + { + name: "basic-err", + json: ``, + expectedValue: "", + err: true, + }, + { + name: "basic-err2", + json: `"key"`, + expectedValue: "", + err: true, + }, + { + name: "basic-err3", + json: `"key`, + expectedValue: "", + err: true, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + dec := BorrowDecoder(strings.NewReader(testCase.json)) + s, _, err := dec.nextKey() + if testCase.err { + assert.NotNil(t, err, "err should not be nil") + return + } + assert.Nil(t, err, "err should be nil") + assert.Equal(t, testCase.expectedValue, s, fmt.Sprintf("s should be '%s'", testCase.expectedValue)) + }) + } +} + +func TestSkipObject(t *testing.T) { + testCases := []struct { + name string + json string + err bool + }{ + { + name: "basic", + json: `"key":"value"}`, + }, + { + name: "basic-escaped", + json: `"key":"value\\\\\\" hello"}`, + }, + { + name: "basic-err", + json: ``, + err: true, + }, + { + name: "basic-err2", + json: `{"key":"value"`, + err: true, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + dec := BorrowDecoder(strings.NewReader(testCase.json)) + defer dec.Release() + _, err := dec.skipObject() + if testCase.err { + assert.NotNil(t, err, "err should not be nil") + return + } + assert.Nil(t, err, "err should be nil") + }) + } +} + func TestSkipData(t *testing.T) { testCases := []struct { name string diff --git a/decode_pool.go b/decode_pool.go @@ -6,9 +6,7 @@ import ( ) var decPool = sync.Pool{ - New: func() interface{} { - return NewDecoder(nil) - }, + New: newDecoderPool, } func init() { @@ -31,6 +29,9 @@ func NewDecoder(r io.Reader) *Decoder { isPooled: 0, } } +func newDecoderPool() interface{} { + return NewDecoder(nil) +} // BorrowDecoder borrows a Decoder from the pool. // It takes an io.Reader implementation as data input. diff --git a/decode_pool_test.go b/decode_pool_test.go @@ -10,3 +10,8 @@ func TestDecoderBorrowFromPoolSetBuffSize(t *testing.T) { dec := borrowDecoder(nil, 512) assert.Len(t, dec.data, 512, "data buffer should be of len 512") } + +func TestDecoderNewPool(t *testing.T) { + dec := newDecoderPool() + assert.IsType(t, &Decoder{}, dec, "dec should be a *Decoder") +} diff --git a/decode_stream_pool.go b/decode_stream_pool.go @@ -6,9 +6,7 @@ import ( ) var streamDecPool = sync.Pool{ - New: func() interface{} { - return Stream.NewDecoder(nil) - }, + New: newStreamDecoderPool, } // NewDecoder returns a new StreamDecoder. @@ -22,6 +20,9 @@ func (s stream) NewDecoder(r io.Reader) *StreamDecoder { } return streamDec } +func newStreamDecoderPool() interface{} { + return Stream.NewDecoder(nil) +} // BorrowDecoder borrows a StreamDecoder from the pool. // It takes an io.Reader implementation as data input. diff --git a/decode_stream_pool_test.go b/decode_stream_pool_test.go @@ -51,3 +51,8 @@ func TestDecodeStreamDecodePooledDecoderError2(t *testing.T) { // make sure they are the same assert.True(t, false, "should not be called as decoder should have panicked") } + +func TestStreamDecoderNewPool(t *testing.T) { + dec := newStreamDecoderPool() + assert.IsType(t, &StreamDecoder{}, dec, "dec should be a *StreamDecoder") +} diff --git a/decode_stream_test.go b/decode_stream_test.go @@ -168,7 +168,7 @@ func TestStreamDecodingObjectsParallel(t *testing.T) { expectations: func(err error, result []*TestObj, t *testing.T) { assert.NotNil(t, err, "err is not nil as JSON is invalid") assert.IsType(t, InvalidJSONError(""), err, "err is of type InvalidJSONError") - assert.Equal(t, "Invalid JSON", err.Error(), "err message is Invalid JSON") + assert.Equal(t, "Invalid JSON, wrong char 'i' found at position 6", err.Error(), "err message is Invalid JSON") }, }, } @@ -276,7 +276,7 @@ func TestStreamDecodingStringsParallel(t *testing.T) { assert.NotNil(t, err, "err should not be nil") assert.IsType(t, InvalidJSONError(""), err, "err is of type InvalidJSONError") - assert.Equal(t, "Invalid JSON", err.Error(), "err message is Invalid JSON") + assert.Equal(t, "Invalid JSON, wrong char 'w' found at position 6", err.Error(), "err message is Invalid JSON") }, }, } diff --git a/decode_string.go b/decode_string.go @@ -1,7 +1,6 @@ package gojay import ( - "fmt" "unsafe" ) @@ -41,13 +40,7 @@ func (dec *Decoder) decodeString(v *string) error { dec.cursor++ return nil default: - dec.err = InvalidTypeError( - fmt.Sprintf( - "Cannot unmarshall to string, wrong char '%s' found at pos %d", - string(dec.data[dec.cursor]), - dec.cursor, - ), - ) + dec.err = dec.makeInvalidUnmarshalErr(v) err := dec.skipData() if err != nil { return err @@ -70,25 +63,57 @@ func (dec *Decoder) parseEscapedString() error { case '"': // nSlash must be odd if nSlash&1 != 1 { - return InvalidJSONError("Invalid JSON unescaped character") + return dec.raiseInvalidJSONErr(dec.cursor) } diff := (nSlash - 1) >> 1 dec.data = append(dec.data[:start+diff-1], dec.data[dec.cursor-1:]...) dec.length = len(dec.data) dec.cursor -= nSlash - diff return nil + case 'u': + if nSlash&1 == 0 { + diff := nSlash >> 1 + dec.data = append(dec.data[:start+diff-1], dec.data[dec.cursor-1:]...) + dec.length = len(dec.data) + dec.cursor -= nSlash - diff + return nil + } + start := dec.cursor - 2 - ((nSlash - 1) >> 1) + str, err := dec.parseUnicode() + if err != nil { + dec.err = err + return err + } + diff := dec.cursor - start + dec.data = append(append(dec.data[:start], str...), dec.data[dec.cursor:]...) + dec.length = len(dec.data) + dec.cursor = dec.cursor - diff + len(str) + return nil case 'b': // number of slash must be even // if is odd number of slashes // divide nSlash - 1 by 2 and leave last one // else divide nSlash by 2 and leave the letter + if nSlash&1 != 0 { + return dec.raiseInvalidJSONErr(dec.cursor) + } var diff int + diff = nSlash >> 1 + dec.data = append(append(dec.data[:start+diff-2], '\b'), dec.data[dec.cursor:]...) + dec.length = len(dec.data) + dec.cursor -= nSlash - diff + 1 + return nil + case 'f': + // number of slash must be even + // if is odd number of slashes + // divide nSlash - 1 by 2 and leave last one + // else divide nSlash by 2 and leave the letter if nSlash&1 != 0 { - return InvalidJSONError("Invalid JSON unescaped character") - } else { - diff = nSlash >> 1 - dec.data = append(append(dec.data[:start+diff-2], '\b'), dec.data[dec.cursor:]...) + return dec.raiseInvalidJSONErr(dec.cursor) } + var diff int + diff = nSlash >> 1 + dec.data = append(append(dec.data[:start+diff-2], '\f'), dec.data[dec.cursor:]...) dec.length = len(dec.data) dec.cursor -= nSlash - diff + 1 return nil @@ -97,13 +122,12 @@ func (dec *Decoder) parseEscapedString() error { // if is odd number of slashes // divide nSlash - 1 by 2 and leave last one // else divide nSlash by 2 and leave the letter - var diff int if nSlash&1 != 0 { - return InvalidJSONError("Invalid JSON unescaped character") - } else { - diff = nSlash >> 1 - dec.data = append(append(dec.data[:start+diff-2], '\n'), dec.data[dec.cursor:]...) + return dec.raiseInvalidJSONErr(dec.cursor) } + var diff int + diff = nSlash >> 1 + dec.data = append(append(dec.data[:start+diff-2], '\n'), dec.data[dec.cursor:]...) dec.length = len(dec.data) dec.cursor -= nSlash - diff + 1 return nil @@ -112,13 +136,12 @@ func (dec *Decoder) parseEscapedString() error { // if is odd number of slashes // divide nSlash - 1 by 2 and leave last one // else divide nSlash by 2 and leave the letter - var diff int if nSlash&1 != 0 { - return InvalidJSONError("Invalid JSON unescaped character") - } else { - diff = nSlash >> 1 - dec.data = append(append(dec.data[:start+diff-2], '\r'), dec.data[dec.cursor:]...) + return dec.raiseInvalidJSONErr(dec.cursor) } + var diff int + diff = nSlash >> 1 + dec.data = append(append(dec.data[:start+diff-2], '\r'), dec.data[dec.cursor:]...) dec.length = len(dec.data) dec.cursor -= nSlash - diff + 1 return nil @@ -127,20 +150,19 @@ func (dec *Decoder) parseEscapedString() error { // if is odd number of slashes // divide nSlash - 1 by 2 and leave last one // else divide nSlash by 2 and leave the letter - var diff int if nSlash&1 != 0 { - return InvalidJSONError("Invalid JSON unescaped character") - } else { - diff = nSlash >> 1 - dec.data = append(append(dec.data[:start+diff-2], '\t'), dec.data[dec.cursor:]...) + return dec.raiseInvalidJSONErr(dec.cursor) } + var diff int + diff = nSlash >> 1 + dec.data = append(append(dec.data[:start+diff-2], '\t'), dec.data[dec.cursor:]...) dec.length = len(dec.data) dec.cursor -= nSlash - diff + 1 return nil default: // nSlash must be even if nSlash&1 == 1 { - return InvalidJSONError("Invalid JSON unescaped character") + return dec.raiseInvalidJSONErr(dec.cursor) } diff := nSlash >> 1 dec.data = append(dec.data[:start+diff-1], dec.data[dec.cursor-1:]...) @@ -150,7 +172,7 @@ func (dec *Decoder) parseEscapedString() error { } } } - return nil + return dec.raiseInvalidJSONErr(dec.cursor) } func (dec *Decoder) getString() (int, int, error) { @@ -175,7 +197,7 @@ func (dec *Decoder) getString() (int, int, error) { continue } } - return 0, 0, InvalidJSONError("Invalid JSON while parsing string") + return 0, 0, dec.raiseInvalidJSONErr(dec.cursor) } func (dec *Decoder) skipEscapedString() error { @@ -189,7 +211,7 @@ func (dec *Decoder) skipEscapedString() error { case '"': // nSlash must be odd if nSlash&1 != 1 { - return InvalidJSONError("Invalid JSON unescaped character") + return dec.raiseInvalidJSONErr(dec.cursor) } return nil case 'n', 'r', 't': @@ -197,13 +219,13 @@ func (dec *Decoder) skipEscapedString() error { default: // nSlash must be even if nSlash&1 == 1 { - return InvalidJSONError("Invalid JSON unescaped character") + return dec.raiseInvalidJSONErr(dec.cursor) } return nil } } } - return InvalidJSONError("Invalid JSON") + return dec.raiseInvalidJSONErr(dec.cursor) } func (dec *Decoder) skipString() error { @@ -225,5 +247,5 @@ func (dec *Decoder) skipString() error { continue } } - return InvalidJSONError("Invalid JSON while parsing string") + return dec.raiseInvalidJSONErr(len(dec.data) - 1) } diff --git a/decode_string_test.go b/decode_string_test.go @@ -2,7 +2,6 @@ package gojay import ( "fmt" - "log" "strings" "sync" "testing" @@ -10,124 +9,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestDecoderStringBasic(t *testing.T) { - json := []byte(`"string"`) - var v string - err := Unmarshal(json, &v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, "string", v, "v must be equal to 'string'") -} - -func TestDecoderStringEmpty(t *testing.T) { - json := []byte(``) - var v string - err := Unmarshal(json, &v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, "", v, "v must be equal to 'string'") -} - -func TestDecoderStringNullInvalid(t *testing.T) { - json := []byte(`nall`) - var v string - err := Unmarshal(json, &v) - assert.NotNil(t, err, "Err must be nil") - assert.IsType(t, InvalidJSONError(""), err, "Err must be nil") - assert.Equal(t, "", v, "v must be equal to 'string'") -} - -func TestDecoderStringComplex(t *testing.T) { - json := []byte(` "string with spaces and \"escape\"d \"quotes\" and escaped line returns \\n and escaped \\\\ escaped char"`) - var v string - err := Unmarshal(json, &v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, "string with spaces and \"escape\"d \"quotes\" and escaped line returns \n and escaped \\\\ escaped char", v, "v is not equal to the value expected") -} - -func TestDecoderStringNull(t *testing.T) { - json := []byte(`null`) - var v string - err := Unmarshal(json, &v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, "", v, "v must be equal to ''") -} - -func TestDecoderStringInvalidJSON(t *testing.T) { - json := []byte(`"invalid JSONs`) - var v string - err := Unmarshal(json, &v) - assert.NotNil(t, err, "Err must not be nil as JSON is invalid") - assert.IsType(t, InvalidJSONError(""), err, "err message must be 'Invalid JSON'") -} - -func TestDecoderStringInvalidType(t *testing.T) { - json := []byte(`1`) - var v string - err := Unmarshal(json, &v) - assert.NotNil(t, err, "Err must not be nil as JSON is invalid") - assert.IsType(t, InvalidTypeError(""), err, "err message must be 'Invalid JSON'") -} - -func TestDecoderStringDecoderAPI(t *testing.T) { - var v string - dec := NewDecoder(strings.NewReader(`"hello world!"`)) - defer dec.Release() - err := dec.DecodeString(&v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, "hello world!", v, "v must be equal to 'hello world!'") -} - -func TestDecoderStringPoolError(t *testing.T) { - // reset the pool to make sure it's not full - decPool = sync.Pool{ - New: func() interface{} { - return NewDecoder(nil) - }, - } - result := "" - dec := NewDecoder(nil) - dec.Release() - defer func() { - err := recover() - assert.NotNil(t, err, "err shouldnot be nil") - assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") - }() - _ = dec.DecodeString(&result) - assert.True(t, false, "should not be called as decoder should have panicked") -} - -func TestDecoderSkipEscapedStringError(t *testing.T) { - dec := NewDecoder(strings.NewReader(``)) - defer dec.Release() - err := dec.skipEscapedString() - assert.NotNil(t, err, "Err must be nil") - assert.IsType(t, InvalidJSONError(""), err, "err must be of type InvalidJSONError") -} - -func TestDecoderSkipEscapedStringError2(t *testing.T) { - dec := NewDecoder(strings.NewReader(`\"`)) - defer dec.Release() - err := dec.skipEscapedString() - assert.NotNil(t, err, "Err must be nil") - assert.IsType(t, InvalidJSONError(""), err, "err must be of type InvalidJSONError") -} - -func TestDecoderSkipEscapedStringError3(t *testing.T) { - dec := NewDecoder(strings.NewReader(`invalid`)) - defer dec.Release() - err := dec.skipEscapedString() - assert.NotNil(t, err, "Err must be nil") - assert.IsType(t, InvalidJSONError(""), err, "err must be of type InvalidJSONError") -} - -func TestDecoderSkipStringError(t *testing.T) { - dec := NewDecoder(strings.NewReader(`invalid`)) - defer dec.Release() - err := dec.skipString() - assert.NotNil(t, err, "Err must be nil") - assert.IsType(t, InvalidJSONError(""), err, "err must be of type InvalidJSONError") -} - -func TestParseEscapedString(t *testing.T) { +func TestDecoderString(t *testing.T) { testCases := []struct { name string json string @@ -136,6 +18,209 @@ func TestParseEscapedString(t *testing.T) { errType interface{} }{ { + name: "basic-string", + json: `"string"`, + expectedResult: "string", + err: false, + }, + { + name: "basic-string", + json: ``, + expectedResult: "", + err: false, + }, + { + name: "basic-string", + json: `""`, + expectedResult: "", + err: false, + }, + { + name: "basic-string2", + json: `"hello world!"`, + expectedResult: "hello world!", + err: false, + }, + { + name: "escape-control-char", + json: `"\n"`, + expectedResult: "", + err: true, + }, + { + name: "escape-control-char", + json: `"\\n"`, + expectedResult: "\n", + err: false, + }, + { + name: "escape-control-char", + json: `"\t"`, + expectedResult: "", + err: true, + }, + { + name: "escape-control-char", + json: `"\\t"`, + expectedResult: "\t", + err: false, + }, + { + name: "escape-control-char", + json: `"\b"`, + expectedResult: "", + err: true, + }, + { + name: "escape-control-char", + json: `"\\b"`, + expectedResult: "\b", + err: false, + }, + { + name: "escape-control-char", + json: `"\f"`, + expectedResult: "", + err: true, + }, + { + name: "escape-control-char", + json: `"\\f"`, + expectedResult: "\f", + err: false, + }, + { + name: "escape-control-char", + json: `"\r"`, + expectedResult: "", + err: true, + }, + { + name: "escape-control-char", + json: `"\`, + expectedResult: "", + err: true, + }, + { + name: "escape-control-char", + json: `"\\r"`, + expectedResult: "\r", + err: false, + }, + { + name: "utf8", + json: `"𠜎 𠜱 𠝹 𠱓 𠱸 𠲖 𠳏 𠳕 𠴕 𠵼 𠵿"`, + expectedResult: "𠜎 𠜱 𠝹 𠱓 𠱸 𠲖 𠳏 𠳕 𠴕 𠵼 𠵿", + err: false, + }, + { + name: "utf8-code-point", + json: `"\u06fc"`, + expectedResult: `ۼ`, + err: false, + }, + { + name: "utf8-code-point-escaped", + json: `"\\u2070"`, + expectedResult: `\u2070`, + err: false, + }, + { + name: "utf8-code-point-err", + json: `"\u2Z70"`, + expectedResult: ``, + err: true, + }, + { + name: "utf16-surrogate", + json: `"\uD834\uDD1E"`, + expectedResult: `𝄞`, + err: false, + }, + { + name: "utf16-surrogate", + json: `"\uD834\\"`, + expectedResult: `�\`, + err: false, + }, + { + name: "utf16-surrogate", + json: `"\uD834\uD834"`, + expectedResult: "�\x00\x00\x00", + err: false, + }, + { + name: "utf16-surrogate", + json: `"\uD834"`, + expectedResult: `�`, + err: false, + }, + { + name: "utf16-surrogate-err", + json: `"\uD834\`, + expectedResult: ``, + err: true, + }, + { + name: "utf16-surrogate-err2", + json: `"\uD834\uDZ1E`, + expectedResult: ``, + err: true, + }, + { + name: "utf16-surrogate-err3", + json: `"\uD834`, + expectedResult: ``, + err: true, + }, + { + name: "utf16-surrogate-followed-by-control-char", + json: `"\uD834\t"`, + expectedResult: "�\t", + err: false, + }, + { + name: "utf16-surrogate-followed-by-control-char", + json: `"\uD834\n"`, + expectedResult: "�\n", + err: false, + }, + { + name: "utf16-surrogate-followed-by-control-char", + json: `"\uD834\f"`, + expectedResult: "�\f", + err: false, + }, + { + name: "utf16-surrogate-followed-by-control-char", + json: `"\uD834\b"`, + expectedResult: "�\b", + err: false, + }, + { + name: "utf16-surrogate-followed-by-control-char", + json: `"\uD834\r"`, + expectedResult: "�\r", + err: false, + }, + { + name: "utf16-surrogate-followed-by-control-char", + json: `"\uD834\h"`, + expectedResult: "", + err: true, + }, + { + name: "null", + json: `null`, + expectedResult: "", + }, + { + name: "null-err", + json: `nall`, + expectedResult: "", + err: true, + }, + { name: "escape quote err", json: `"test string \" escaped"`, expectedResult: `test string " escaped`, @@ -179,6 +264,18 @@ func TestParseEscapedString(t *testing.T) { err: true, errType: InvalidJSONError(""), }, + { + name: "invalid-json", + json: `invalid`, + expectedResult: ``, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "string-complex", + json: ` "string with spaces and \"escape\"d \"quotes\" and escaped line returns \\n and escaped \\\\ escaped char"`, + expectedResult: "string with spaces and \"escape\"d \"quotes\" and escaped line returns \n and escaped \\\\ escaped char", + }, } for _, testCase := range testCases { @@ -189,16 +286,81 @@ func TestParseEscapedString(t *testing.T) { if testCase.err { assert.NotNil(t, err, "err should not be nil") if testCase.errType != nil { - assert.IsType(t, testCase.errType, err, "err should be of expected type") + assert.IsType(t, testCase.errType, err, "err should of the given type") } - log.Print(err) } else { assert.Nil(t, err, "err should be nil") } - assert.Equal(t, testCase.expectedResult, str, fmt.Sprintf("str should be equal to '%s'", testCase.expectedResult)) + assert.Equal(t, testCase.expectedResult, str, fmt.Sprintf("'%s' should be equal to expectedResult", str)) }) } +} +func TestDecoderStringInvalidType(t *testing.T) { + json := []byte(`1`) + var v string + err := Unmarshal(json, &v) + assert.NotNil(t, err, "Err must not be nil as JSON is invalid") + assert.IsType(t, InvalidUnmarshalError(""), err, "err message must be 'Invalid JSON'") +} + +func TestDecoderStringDecoderAPI(t *testing.T) { + var v string + dec := NewDecoder(strings.NewReader(`"hello world!"`)) + defer dec.Release() + err := dec.DecodeString(&v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, "hello world!", v, "v must be equal to 'hello world!'") +} + +func TestDecoderStringPoolError(t *testing.T) { + // reset the pool to make sure it's not full + decPool = sync.Pool{ + New: func() interface{} { + return NewDecoder(nil) + }, + } + result := "" + dec := NewDecoder(nil) + dec.Release() + defer func() { + err := recover() + assert.NotNil(t, err, "err shouldnot be nil") + assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") + }() + _ = dec.DecodeString(&result) + assert.True(t, false, "should not be called as decoder should have panicked") +} + +func TestDecoderSkipEscapedStringError(t *testing.T) { + dec := NewDecoder(strings.NewReader(``)) + defer dec.Release() + err := dec.skipEscapedString() + assert.NotNil(t, err, "Err must be nil") + assert.IsType(t, InvalidJSONError(""), err, "err must be of type InvalidJSONError") +} + +func TestDecoderSkipEscapedStringError2(t *testing.T) { + dec := NewDecoder(strings.NewReader(`\"`)) + defer dec.Release() + err := dec.skipEscapedString() + assert.NotNil(t, err, "Err must be nil") + assert.IsType(t, InvalidJSONError(""), err, "err must be of type InvalidJSONError") +} +func TestDecoderSkipEscapedStringError3(t *testing.T) { + dec := NewDecoder(strings.NewReader(`invalid`)) + defer dec.Release() + err := dec.skipEscapedString() + assert.NotNil(t, err, "Err must be nil") + assert.IsType(t, InvalidJSONError(""), err, "err must be of type InvalidJSONError") +} + +func TestDecoderSkipStringError(t *testing.T) { + dec := NewDecoder(strings.NewReader(`invalid`)) + defer dec.Release() + err := dec.skipString() + assert.NotNil(t, err, "Err must be nil") + assert.IsType(t, InvalidJSONError(""), err, "err must be of type InvalidJSONError") } func TestSkipString(t *testing.T) { @@ -234,7 +396,6 @@ func TestSkipString(t *testing.T) { if testCase.errType != nil { assert.IsType(t, testCase.errType, err, "err should be of expected type") } - log.Print(err) } else { assert.Nil(t, err, "err should be nil") } diff --git a/decode_string_unicode.go b/decode_string_unicode.go @@ -0,0 +1,98 @@ +package gojay + +import ( + "unicode/utf16" + "unicode/utf8" +) + +func (dec *Decoder) getUnicode() (rune, error) { + i := 0 + r := rune(0) + for ; (dec.cursor < dec.length || dec.read()) && i < 4; dec.cursor++ { + c := dec.data[dec.cursor] + if c >= '0' && c <= '9' { + r = r*16 + rune(c-'0') + } else if c >= 'a' && c <= 'f' { + r = r*16 + rune(c-'a'+10) + } else if c >= 'A' && c <= 'F' { + r = r*16 + rune(c-'A'+10) + } else { + return 0, InvalidJSONError("Invalid unicode code point") + } + i++ + } + return r, nil +} + +func (dec *Decoder) appendEscapeChar(str []byte, c byte) ([]byte, error) { + switch c { + case 't': + str = append(str, '\t') + case 'n': + str = append(str, '\n') + case 'r': + str = append(str, '\r') + case 'b': + str = append(str, '\b') + case 'f': + str = append(str, '\f') + case '\\': + str = append(str, '\\') + default: + return nil, InvalidJSONError("Invalid JSON") + } + return str, nil +} + +func (dec *Decoder) parseUnicode() ([]byte, error) { + // get unicode after u + r, err := dec.getUnicode() + if err != nil { + return nil, err + } + // no error start making new string + str := make([]byte, 16, 16) + i := 0 + // check if code can be a surrogate utf16 + if utf16.IsSurrogate(r) { + if dec.cursor >= dec.length && !dec.read() { + return nil, InvalidJSONError("Invalid JSON") + } + c := dec.data[dec.cursor] + if c != '\\' { + i += utf8.EncodeRune(str, r) + return str[:i], nil + } + dec.cursor++ + if dec.cursor >= dec.length && !dec.read() { + return nil, InvalidJSONError("Invalid JSON") + } + c = dec.data[dec.cursor] + if c != 'u' { + i += utf8.EncodeRune(str, r) + str, err = dec.appendEscapeChar(str[:i], c) + if err != nil { + dec.err = err + return nil, err + } + i++ + dec.cursor++ + return str[:i], nil + } + dec.cursor++ + r2, err := dec.getUnicode() + if err != nil { + return nil, err + } + combined := utf16.DecodeRune(r, r2) + if combined == '\uFFFD' { + i += utf8.EncodeRune(str, r) + i += utf8.EncodeRune(str, r2) + } else { + i += utf8.EncodeRune(str, combined) + } + return str[:i], nil + } + i += utf8.EncodeRune(str, r) + return str[:i], nil +} diff --git a/decode_test.go b/decode_test.go @@ -13,7 +13,7 @@ type testDecodeObj struct { test string } -func (t *testDecodeObj) UnmarshalObject(dec *Decoder, key string) error { +func (t *testDecodeObj) UnmarshalJSONObject(dec *Decoder, key string) error { switch key { case "test": return dec.AddString(&t.test) @@ -26,7 +26,7 @@ func (t *testDecodeObj) NKeys() int { type testDecodeSlice []*testDecodeObj -func (t *testDecodeSlice) UnmarshalArray(dec *Decoder) error { +func (t *testDecodeSlice) UnmarshalJSONArray(dec *Decoder) error { obj := &testDecodeObj{} if err := dec.AddObject(obj); err != nil { return err @@ -497,10 +497,10 @@ func TestDecodeAllTypes(t *testing.T) { } } -func TestUnmarshalObjects(t *testing.T) { +func TestUnmarshalJSONObjects(t *testing.T) { testCases := []struct { name string - v UnmarshalerObject + v UnmarshalerJSONObject d []byte expectations func(err error, v interface{}, t *testing.T) }{ @@ -547,7 +547,7 @@ func TestUnmarshalObjects(t *testing.T) { for _, testCase := range testCases { testCase := testCase t.Run(testCase.name, func(*testing.T) { - err := UnmarshalObject(testCase.d, testCase.v) + err := UnmarshalJSONObject(testCase.d, testCase.v) testCase.expectations(err, testCase.v, t) }) } diff --git a/decode_unsafe.go b/decode_unsafe.go @@ -13,34 +13,22 @@ var Unsafe = decUnsafe{} type decUnsafe struct{} -func (u decUnsafe) UnmarshalArray(data []byte, v UnmarshalerArray) error { +func (u decUnsafe) UnmarshalJSONArray(data []byte, v UnmarshalerJSONArray) error { dec := borrowDecoder(nil, 0) defer dec.Release() dec.data = data dec.length = len(data) _, err := dec.decodeArray(v) - if err != nil { - return err - } - if dec.err != nil { - return dec.err - } - return nil + return err } -func (u decUnsafe) UnmarshalObject(data []byte, v UnmarshalerObject) error { +func (u decUnsafe) UnmarshalJSONObject(data []byte, v UnmarshalerJSONObject) error { dec := borrowDecoder(nil, 0) defer dec.Release() dec.data = data dec.length = len(data) _, err := dec.decodeObject(v) - if err != nil { - return err - } - if dec.err != nil { - return dec.err - } - return nil + return err } func (u decUnsafe) Unmarshal(data []byte, v interface{}) error { @@ -57,21 +45,41 @@ func (u decUnsafe) Unmarshal(data []byte, v interface{}) error { dec.length = len(data) dec.data = data err = dec.decodeInt(vt) - case *int32: + case *int8: dec = borrowDecoder(nil, 0) dec.length = len(data) dec.data = data - err = dec.decodeInt32(vt) - case *uint32: + err = dec.decodeInt8(vt) + case *int16: dec = borrowDecoder(nil, 0) dec.length = len(data) dec.data = data - err = dec.decodeUint32(vt) + err = dec.decodeInt16(vt) + case *int32: + dec = borrowDecoder(nil, 0) + dec.length = len(data) + dec.data = data + err = dec.decodeInt32(vt) case *int64: dec = borrowDecoder(nil, 0) dec.length = len(data) dec.data = data err = dec.decodeInt64(vt) + case *uint8: + dec = borrowDecoder(nil, 0) + dec.length = len(data) + dec.data = data + err = dec.decodeUint8(vt) + case *uint16: + dec = borrowDecoder(nil, 0) + dec.length = len(data) + dec.data = data + err = dec.decodeUint16(vt) + case *uint32: + dec = borrowDecoder(nil, 0) + dec.length = len(data) + dec.data = data + err = dec.decodeUint32(vt) case *uint64: dec = borrowDecoder(nil, 0) dec.length = len(data) @@ -82,17 +90,22 @@ func (u decUnsafe) Unmarshal(data []byte, v interface{}) error { dec.length = len(data) dec.data = data err = dec.decodeFloat64(vt) + case *float32: + dec = borrowDecoder(nil, 0) + dec.length = len(data) + dec.data = data + err = dec.decodeFloat32(vt) case *bool: dec = borrowDecoder(nil, 0) dec.length = len(data) dec.data = data err = dec.decodeBool(vt) - case UnmarshalerObject: + case UnmarshalerJSONObject: dec = borrowDecoder(nil, 0) dec.length = len(data) dec.data = data _, err = dec.decodeObject(vt) - case UnmarshalerArray: + case UnmarshalerJSONArray: dec = borrowDecoder(nil, 0) dec.length = len(data) dec.data = data diff --git a/decode_unsafe_test.go b/decode_unsafe_test.go @@ -46,6 +46,36 @@ func TestUnmarshalUnsafeAllTypes(t *testing.T) { }, }, { + v: new(int8), + d: []byte(`1`), + name: "test decode int8", + expectations: func(err error, v interface{}, t *testing.T) { + vt := v.(*int8) + assert.Nil(t, err, "err must be nil") + assert.Equal(t, int8(1), *vt, "v must be equal to 1") + }, + }, + { + v: new(int16), + d: []byte(`1`), + name: "test decode int16", + expectations: func(err error, v interface{}, t *testing.T) { + vt := v.(*int16) + assert.Nil(t, err, "err must be nil") + assert.Equal(t, int16(1), *vt, "v must be equal to 1") + }, + }, + { + v: new(int32), + d: []byte(`1`), + name: "test decode int32", + expectations: func(err error, v interface{}, t *testing.T) { + vt := v.(*int32) + assert.Nil(t, err, "err must be nil") + assert.Equal(t, int32(1), *vt, "v must be equal to 1") + }, + }, + { v: new(int64), d: []byte(`1`), name: "test decode int64", @@ -106,6 +136,26 @@ func TestUnmarshalUnsafeAllTypes(t *testing.T) { }, }, { + v: new(uint8), + d: []byte(`1`), + name: "test decode int8", + expectations: func(err error, v interface{}, t *testing.T) { + vt := v.(*uint8) + assert.Nil(t, err, "err must be nil") + assert.Equal(t, uint8(1), *vt, "v must be equal to 1") + }, + }, + { + v: new(uint16), + d: []byte(`1`), + name: "test decode uint16", + expectations: func(err error, v interface{}, t *testing.T) { + vt := v.(*uint16) + assert.Nil(t, err, "err must be nil") + assert.Equal(t, uint16(1), *vt, "v must be equal to 1") + }, + }, + { v: new(float64), d: []byte(`1.15`), name: "test decode float64", @@ -126,6 +176,16 @@ func TestUnmarshalUnsafeAllTypes(t *testing.T) { }, }, { + v: new(float32), + d: []byte(`1.15`), + name: "test decode float64", + expectations: func(err error, v interface{}, t *testing.T) { + vt := v.(*float32) + assert.Nil(t, err, "err must be nil") + assert.Equal(t, float32(1.15), *vt, "v must be equal to 1") + }, + }, + { v: new(bool), d: []byte(`true`), name: "test decode bool true", @@ -220,6 +280,15 @@ func TestUnmarshalUnsafeAllTypes(t *testing.T) { assert.Equal(t, fmt.Sprintf(invalidUnmarshalErrorMsg, reflect.TypeOf(v).String()), err.Error(), "err message should be equal to invalidUnmarshalErrorMsg") }, }, + { + v: new(int), + d: []byte(`1a2`), + name: "test decode invalid json", + expectations: func(err error, v interface{}, t *testing.T) { + assert.NotNil(t, err, "err must not be nil") + assert.IsType(t, InvalidJSONError(""), err, "err must be of type InvalidJSONError") + }, + }, } for _, testCase := range testCases { testCase := testCase @@ -233,7 +302,7 @@ func TestUnmarshalUnsafeAllTypes(t *testing.T) { func TestUnmarshalUnsafeObjects(t *testing.T) { testCases := []struct { name string - v UnmarshalerObject + v UnmarshalerJSONObject d []byte expectations func(err error, v interface{}, t *testing.T) }{ @@ -280,7 +349,7 @@ func TestUnmarshalUnsafeObjects(t *testing.T) { for _, testCase := range testCases { testCase := testCase t.Run(testCase.name, func(*testing.T) { - err := Unsafe.UnmarshalObject(testCase.d, testCase.v) + err := Unsafe.UnmarshalJSONObject(testCase.d, testCase.v) testCase.expectations(err, testCase.v, t) }) } @@ -289,7 +358,7 @@ func TestUnmarshalUnsafeObjects(t *testing.T) { func TestUnmarshalUnsafeArrays(t *testing.T) { testCases := []struct { name string - v UnmarshalerArray + v UnmarshalerJSONArray d []byte expectations func(err error, v interface{}, t *testing.T) }{ @@ -331,7 +400,7 @@ func TestUnmarshalUnsafeArrays(t *testing.T) { for _, testCase := range testCases { testCase := testCase t.Run(testCase.name, func(*testing.T) { - err := Unsafe.UnmarshalArray(testCase.d, testCase.v) + err := Unsafe.UnmarshalJSONArray(testCase.d, testCase.v) testCase.expectations(err, testCase.v, t) }) } diff --git a/encode.go b/encode.go @@ -6,7 +6,7 @@ import ( "reflect" ) -// MarshalObject returns the JSON encoding of v. +// MarshalJSONObject returns the JSON encoding of v. // // It takes a struct implementing Marshaler to a JSON slice of byte // it returns a slice of bytes and an error. @@ -14,7 +14,7 @@ import ( // type TestStruct struct { // id int // } -// func (s *TestStruct) MarshalObject(enc *gojay.Encoder) { +// func (s *TestStruct) MarshalJSONObject(enc *gojay.Encoder) { // enc.AddIntKey("id", s.id) // } // func (s *TestStruct) IsNil() bool { @@ -28,21 +28,21 @@ import ( // b, _ := gojay.Marshal(test) // fmt.Println(b) // {"id":123456} // } -func MarshalObject(v MarshalerObject) ([]byte, error) { +func MarshalJSONObject(v MarshalerJSONObject) ([]byte, error) { enc := BorrowEncoder(nil) enc.grow(512) defer enc.Release() return enc.encodeObject(v) } -// MarshalArray returns the JSON encoding of v. +// MarshalJSONArray returns the JSON encoding of v. // // It takes an array or a slice implementing Marshaler to a JSON slice of byte // it returns a slice of bytes and an error. // Example with an Marshaler: // type TestSlice []*TestStruct // -// func (t TestSlice) MarshalArray(enc *Encoder) { +// func (t TestSlice) MarshalJSONArray(enc *Encoder) { // for _, e := range t { // enc.AddObject(e) // } @@ -56,11 +56,11 @@ func MarshalObject(v MarshalerObject) ([]byte, error) { // b, _ := Marshal(test) // fmt.Println(b) // [{"id":123456},{"id":7890}] // } -func MarshalArray(v MarshalerArray) ([]byte, error) { +func MarshalJSONArray(v MarshalerJSONArray) ([]byte, error) { enc := BorrowEncoder(nil) enc.grow(512) enc.writeByte('[') - v.(MarshalerArray).MarshalArray(enc) + v.(MarshalerJSONArray).MarshalJSONArray(enc) enc.writeByte(']') defer enc.Release() return enc.buf, nil @@ -77,12 +77,12 @@ func MarshalArray(v MarshalerArray) ([]byte, error) { // it will call the corresponding methods. // // If a struct, slice, or array is passed and does not implement these interfaces -// it will return a a non nil InvalidTypeError error. +// it will return a a non nil InvalidUnmarshalError error. // Example with an Marshaler: // type TestStruct struct { // id int // } -// func (s *TestStruct) MarshalObject(enc *gojay.Encoder) { +// func (s *TestStruct) MarshalJSONObject(enc *gojay.Encoder) { // enc.AddIntKey("id", s.id) // } // func (s *TestStruct) IsNil() bool { @@ -98,11 +98,11 @@ func MarshalArray(v MarshalerArray) ([]byte, error) { // } func Marshal(v interface{}) ([]byte, error) { switch vt := v.(type) { - case MarshalerObject: + case MarshalerJSONObject: enc := BorrowEncoder(nil) defer enc.Release() return enc.encodeObject(vt) - case MarshalerArray: + case MarshalerJSONArray: enc := BorrowEncoder(nil) defer enc.Release() return enc.encodeArray(vt) @@ -167,16 +167,16 @@ func Marshal(v interface{}) ([]byte, error) { } } -// MarshalerObject is the interface to implement for struct to be encoded -type MarshalerObject interface { - MarshalObject(enc *Encoder) +// MarshalerJSONObject is the interface to implement for struct to be encoded +type MarshalerJSONObject interface { + MarshalJSONObject(enc *Encoder) IsNil() bool } -// MarshalerArray is the interface to implement +// MarshalerJSONArray is the interface to implement // for a slice or an array to be encoded -type MarshalerArray interface { - MarshalArray(enc *Encoder) +type MarshalerJSONArray interface { + MarshalJSONArray(enc *Encoder) IsNil() bool } diff --git a/encode_array.go b/encode_array.go @@ -1,7 +1,7 @@ package gojay -// EncodeArray encodes an implementation of MarshalerArray to JSON -func (enc *Encoder) EncodeArray(v MarshalerArray) error { +// EncodeArray encodes an implementation of MarshalerJSONArray to JSON +func (enc *Encoder) EncodeArray(v MarshalerJSONArray) error { if enc.isPooled == 1 { panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) } @@ -13,17 +13,41 @@ func (enc *Encoder) EncodeArray(v MarshalerArray) error { } return nil } -func (enc *Encoder) encodeArray(v MarshalerArray) ([]byte, error) { +func (enc *Encoder) encodeArray(v MarshalerJSONArray) ([]byte, error) { enc.grow(200) enc.writeByte('[') - v.MarshalArray(enc) + v.MarshalJSONArray(enc) enc.writeByte(']') return enc.buf, enc.err } -// AddArray adds an implementation of MarshalerArray to be encoded, must be used inside a slice or array encoding (does not encode a key) +// AddArray adds an implementation of MarshalerJSONArray to be encoded, must be used inside a slice or array encoding (does not encode a key) // value must implement Marshaler -func (enc *Encoder) AddArray(v MarshalerArray) { +func (enc *Encoder) AddArray(v MarshalerJSONArray) { + enc.Array(v) +} + +// AddArrayOmitEmpty adds an array or slice to be encoded, must be used inside a slice or array encoding (does not encode a key) +// value must implement Marshaler +func (enc *Encoder) AddArrayOmitEmpty(v MarshalerJSONArray) { + enc.ArrayOmitEmpty(v) +} + +// AddArrayKey adds an array or slice to be encoded, must be used inside an object as it will encode a key +// value must implement Marshaler +func (enc *Encoder) AddArrayKey(key string, v MarshalerJSONArray) { + enc.ArrayKey(key, v) +} + +// AddArrayKeyOmitEmpty adds an array or slice to be encoded and skips it if it is nil. +// Must be called inside an object as it will encode a key. +func (enc *Encoder) AddArrayKeyOmitEmpty(key string, v MarshalerJSONArray) { + enc.ArrayKeyOmitEmpty(key, v) +} + +// Array adds an implementation of MarshalerJSONArray to be encoded, must be used inside a slice or array encoding (does not encode a key) +// value must implement Marshaler +func (enc *Encoder) Array(v MarshalerJSONArray) { if v.IsNil() { enc.grow(3) r := enc.getPreviousRune() @@ -40,13 +64,13 @@ func (enc *Encoder) AddArray(v MarshalerArray) { enc.writeByte(',') } enc.writeByte('[') - v.MarshalArray(enc) + v.MarshalJSONArray(enc) enc.writeByte(']') } -// AddArrayOmitEmpty adds an array or slice to be encoded, must be used inside a slice or array encoding (does not encode a key) +// ArrayOmitEmpty adds an array or slice to be encoded, must be used inside a slice or array encoding (does not encode a key) // value must implement Marshaler -func (enc *Encoder) AddArrayOmitEmpty(v MarshalerArray) { +func (enc *Encoder) ArrayOmitEmpty(v MarshalerJSONArray) { if v.IsNil() { return } @@ -56,13 +80,13 @@ func (enc *Encoder) AddArrayOmitEmpty(v MarshalerArray) { enc.writeByte(',') } enc.writeByte('[') - v.MarshalArray(enc) + v.MarshalJSONArray(enc) enc.writeByte(']') } -// AddArrayKey adds an array or slice to be encoded, must be used inside an object as it will encode a key +// ArrayKey adds an array or slice to be encoded, must be used inside an object as it will encode a key // value must implement Marshaler -func (enc *Encoder) AddArrayKey(key string, v MarshalerArray) { +func (enc *Encoder) ArrayKey(key string, v MarshalerJSONArray) { if v.IsNil() { enc.grow(2 + len(key)) r := enc.getPreviousRune() @@ -83,13 +107,13 @@ func (enc *Encoder) AddArrayKey(key string, v MarshalerArray) { enc.writeByte('"') enc.writeStringEscape(key) enc.writeBytes(objKeyArr) - v.MarshalArray(enc) + v.MarshalJSONArray(enc) enc.writeByte(']') } -// AddArrayKeyOmitEmpty adds an array or slice to be encoded and skips it if it is nil. +// ArrayKeyOmitEmpty adds an array or slice to be encoded and skips it if it is nil. // Must be called inside an object as it will encode a key. -func (enc *Encoder) AddArrayKeyOmitEmpty(key string, v MarshalerArray) { +func (enc *Encoder) ArrayKeyOmitEmpty(key string, v MarshalerJSONArray) { if v.IsNil() { return } @@ -101,6 +125,6 @@ func (enc *Encoder) AddArrayKeyOmitEmpty(key string, v MarshalerArray) { enc.writeByte('"') enc.writeStringEscape(key) enc.writeBytes(objKeyArr) - v.MarshalArray(enc) + v.MarshalJSONArray(enc) enc.writeByte(']') } diff --git a/encode_array_test.go b/encode_array_test.go @@ -9,7 +9,7 @@ import ( type TestEncodingArrStrings []string -func (t TestEncodingArrStrings) MarshalArray(enc *Encoder) { +func (t TestEncodingArrStrings) MarshalJSONArray(enc *Encoder) { for _, e := range t { enc.AddString(e) } @@ -20,7 +20,7 @@ func (t TestEncodingArrStrings) IsNil() bool { type TestEncodingArr []*TestEncoding -func (t TestEncodingArr) MarshalArray(enc *Encoder) { +func (t TestEncodingArr) MarshalJSONArray(enc *Encoder) { for _, e := range t { enc.AddObject(e) } @@ -31,7 +31,7 @@ func (t TestEncodingArr) IsNil() bool { type testEncodingArrInterfaces []interface{} -func (t testEncodingArrInterfaces) MarshalArray(enc *Encoder) { +func (t testEncodingArrInterfaces) MarshalJSONArray(enc *Encoder) { for _, e := range t { enc.AddInterface(e) } @@ -118,7 +118,7 @@ func TestEncoderArrayMarshalAPI(t *testing.T) { testBool: true, }, } - r, err := MarshalArray(v) + r, err := MarshalJSONArray(v) assert.Nil(t, err, "Error should be nil") assert.Equal( t, @@ -178,7 +178,7 @@ func TestEncoderArrayEncodeAPI(t *testing.T) { type TestEncodingIntOmitEmpty []int -func (t TestEncodingIntOmitEmpty) MarshalArray(enc *Encoder) { +func (t TestEncodingIntOmitEmpty) MarshalJSONArray(enc *Encoder) { for _, e := range t { enc.AddIntOmitEmpty(e) } @@ -189,7 +189,7 @@ func (t TestEncodingIntOmitEmpty) IsNil() bool { type TestEncodingStringOmitEmpty []string -func (t TestEncodingStringOmitEmpty) MarshalArray(enc *Encoder) { +func (t TestEncodingStringOmitEmpty) MarshalJSONArray(enc *Encoder) { for _, e := range t { enc.AddStringOmitEmpty(e) } @@ -200,7 +200,7 @@ func (t TestEncodingStringOmitEmpty) IsNil() bool { type TestEncodingFloatOmitEmpty []float64 -func (t TestEncodingFloatOmitEmpty) MarshalArray(enc *Encoder) { +func (t TestEncodingFloatOmitEmpty) MarshalJSONArray(enc *Encoder) { for _, e := range t { enc.AddFloatOmitEmpty(e) } @@ -211,7 +211,7 @@ func (t TestEncodingFloatOmitEmpty) IsNil() bool { type TestEncodingFloat32OmitEmpty []float32 -func (t TestEncodingFloat32OmitEmpty) MarshalArray(enc *Encoder) { +func (t TestEncodingFloat32OmitEmpty) MarshalJSONArray(enc *Encoder) { for _, e := range t { enc.AddFloat32OmitEmpty(e) } @@ -222,7 +222,7 @@ func (t TestEncodingFloat32OmitEmpty) IsNil() bool { type TestEncodingBoolOmitEmpty []bool -func (t TestEncodingBoolOmitEmpty) MarshalArray(enc *Encoder) { +func (t TestEncodingBoolOmitEmpty) MarshalJSONArray(enc *Encoder) { for _, e := range t { enc.AddBoolOmitEmpty(e) } @@ -233,7 +233,7 @@ func (t TestEncodingBoolOmitEmpty) IsNil() bool { type TestEncodingArrOmitEmpty []TestEncodingBoolOmitEmpty -func (t TestEncodingArrOmitEmpty) MarshalArray(enc *Encoder) { +func (t TestEncodingArrOmitEmpty) MarshalJSONArray(enc *Encoder) { for _, e := range t { enc.AddArrayOmitEmpty(e) } @@ -246,7 +246,7 @@ type TestObjEmpty struct { empty bool } -func (t *TestObjEmpty) MarshalObject(enc *Encoder) { +func (t *TestObjEmpty) MarshalJSONObject(enc *Encoder) { } func (t *TestObjEmpty) IsNil() bool { @@ -255,7 +255,7 @@ func (t *TestObjEmpty) IsNil() bool { type TestEncodingObjOmitEmpty []*TestObjEmpty -func (t TestEncodingObjOmitEmpty) MarshalArray(enc *Encoder) { +func (t TestEncodingObjOmitEmpty) MarshalJSONArray(enc *Encoder) { for _, e := range t { enc.AddObjectOmitEmpty(e) } diff --git a/encode_bool.go b/encode_bool.go @@ -29,6 +29,27 @@ func (enc *Encoder) encodeBool(v bool) ([]byte, error) { // AddBool adds a bool to be encoded, must be used inside a slice or array encoding (does not encode a key) func (enc *Encoder) AddBool(v bool) { + enc.Bool(v) +} + +// AddBoolOmitEmpty adds a bool to be encoded, must be used inside a slice or array encoding (does not encode a key) +func (enc *Encoder) AddBoolOmitEmpty(v bool) { + enc.BoolOmitEmpty(v) +} + +// AddBoolKey adds a bool to be encoded, must be used inside an object as it will encode a key. +func (enc *Encoder) AddBoolKey(key string, v bool) { + enc.BoolKey(key, v) +} + +// AddBoolKeyOmitEmpty adds a bool to be encoded and skips it if it is zero value. +// Must be used inside an object as it will encode a key. +func (enc *Encoder) AddBoolKeyOmitEmpty(key string, v bool) { + enc.BoolKeyOmitEmpty(key, v) +} + +// Bool adds a bool to be encoded, must be used inside a slice or array encoding (does not encode a key) +func (enc *Encoder) Bool(v bool) { enc.grow(5) r := enc.getPreviousRune() if r != '[' { @@ -41,8 +62,8 @@ func (enc *Encoder) AddBool(v bool) { } } -// AddBoolOmitEmpty adds a bool to be encoded, must be used inside a slice or array encoding (does not encode a key) -func (enc *Encoder) AddBoolOmitEmpty(v bool) { +// BoolOmitEmpty adds a bool to be encoded, must be used inside a slice or array encoding (does not encode a key) +func (enc *Encoder) BoolOmitEmpty(v bool) { if v == false { return } @@ -54,8 +75,8 @@ func (enc *Encoder) AddBoolOmitEmpty(v bool) { enc.writeString("true") } -// AddBoolKey adds a bool to be encoded, must be used inside an object as it will encode a key. -func (enc *Encoder) AddBoolKey(key string, value bool) { +// BoolKey adds a bool to be encoded, must be used inside an object as it will encode a key. +func (enc *Encoder) BoolKey(key string, value bool) { enc.grow(5 + len(key)) r := enc.getPreviousRune() if r != '{' { @@ -67,9 +88,9 @@ func (enc *Encoder) AddBoolKey(key string, value bool) { enc.buf = strconv.AppendBool(enc.buf, value) } -// AddBoolKeyOmitEmpty adds a bool to be encoded and skips it if it is zero value. +// BoolKeyOmitEmpty adds a bool to be encoded and skips it if it is zero value. // Must be used inside an object as it will encode a key. -func (enc *Encoder) AddBoolKeyOmitEmpty(key string, v bool) { +func (enc *Encoder) BoolKeyOmitEmpty(key string, v bool) { if v == false { return } diff --git a/encode_embedded_json_test.go b/encode_embedded_json_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" ) -func (r *Request) MarshalObject(enc *Encoder) { +func (r *Request) MarshalJSONObject(enc *Encoder) { enc.AddStringKey("id", r.id) enc.AddStringKey("method", r.method) enc.AddEmbeddedJSONKey("params", &r.params) @@ -24,7 +24,7 @@ func (r *Request) IsNil() bool { type EmbeddedJSONArr []EmbeddedJSON -func (ear EmbeddedJSONArr) MarshalArray(enc *Encoder) { +func (ear EmbeddedJSONArr) MarshalJSONArray(enc *Encoder) { for _, e := range ear { enc.AddEmbeddedJSON(&e) } @@ -36,7 +36,7 @@ func (ear EmbeddedJSONArr) IsNil() bool { type EmbeddedJSONOmitEmptyArr []EmbeddedJSON -func (ear EmbeddedJSONOmitEmptyArr) MarshalArray(enc *Encoder) { +func (ear EmbeddedJSONOmitEmptyArr) MarshalJSONArray(enc *Encoder) { for _, e := range ear { enc.AddEmbeddedJSONOmitEmpty(&e) } diff --git a/encode_interface.go b/encode_interface.go @@ -18,9 +18,9 @@ func (enc *Encoder) Encode(v interface{}) error { return enc.EncodeString(vt) case bool: return enc.EncodeBool(vt) - case MarshalerArray: + case MarshalerJSONArray: return enc.EncodeArray(vt) - case MarshalerObject: + case MarshalerJSONObject: return enc.EncodeObject(vt) case int: return enc.EncodeInt(vt) @@ -31,7 +31,7 @@ func (enc *Encoder) Encode(v interface{}) error { case int8: return enc.EncodeInt(int(vt)) case uint64: - return enc.EncodeInt(int(vt)) + return enc.EncodeUint64(vt) case uint32: return enc.EncodeInt(int(vt)) case uint16: @@ -56,9 +56,9 @@ func (enc *Encoder) AddInterface(value interface{}) { enc.AddString(vt) case bool: enc.AddBool(vt) - case MarshalerArray: + case MarshalerJSONArray: enc.AddArray(vt) - case MarshalerObject: + case MarshalerJSONObject: enc.AddObject(vt) case int: enc.AddInt(vt) @@ -69,7 +69,7 @@ func (enc *Encoder) AddInterface(value interface{}) { case int8: enc.AddInt(int(vt)) case uint64: - enc.AddInt(int(vt)) + enc.AddUint64(vt) case uint32: enc.AddInt(int(vt)) case uint16: @@ -97,9 +97,9 @@ func (enc *Encoder) AddInterfaceKey(key string, value interface{}) { enc.AddStringKey(key, vt) case bool: enc.AddBoolKey(key, vt) - case MarshalerArray: + case MarshalerJSONArray: enc.AddArrayKey(key, vt) - case MarshalerObject: + case MarshalerJSONObject: enc.AddObjectKey(key, vt) case int: enc.AddIntKey(key, vt) @@ -140,9 +140,9 @@ func (enc *Encoder) AddInterfaceKeyOmitEmpty(key string, v interface{}) { enc.AddStringKeyOmitEmpty(key, vt) case bool: enc.AddBoolKeyOmitEmpty(key, vt) - case MarshalerArray: + case MarshalerJSONArray: enc.AddArrayKeyOmitEmpty(key, vt) - case MarshalerObject: + case MarshalerJSONObject: enc.AddObjectKeyOmitEmpty(key, vt) case int: enc.AddIntKeyOmitEmpty(key, vt) diff --git a/encode_interface_test.go b/encode_interface_test.go @@ -112,7 +112,7 @@ var encoderTestCases = []struct { }, }, { - v: &testObject{"漢字", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.1, 1.1, true}, + v: &testObject{"漢字", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.1, 1.1, true, &testObject{}, testSliceInts{}}, expectations: func(t *testing.T, b string, err error) { assert.Nil(t, err, "err should be nil") assert.Equal(t, `{"testStr":"漢字","testInt":1,"testInt64":1,"testInt32":1,"testInt16":1,"testInt8":1,"testUint64":1,"testUint32":1,"testUint16":1,"testUint8":1,"testFloat64":1.1,"testFloat32":1.1,"testBool":true}`, string(b), `string(b) should equal {"testStr":"漢字","testInt":1,"testInt64":1,"testInt32":1,"testInt16":1,"testInt8":1,"testUint64":1,"testUint32":1,"testUint16":1,"testUint8":1,"testFloat64":1.1,"testFloat32":1.1,"testBool":true}`) diff --git a/encode_number.go b/encode_number.go @@ -1,294 +1 @@ package gojay - -import "strconv" - -// EncodeInt encodes an int to JSON -func (enc *Encoder) EncodeInt(n int) error { - if enc.isPooled == 1 { - panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) - } - _, _ = enc.encodeInt(n) - _, err := enc.Write() - if err != nil { - return err - } - return nil -} - -// encodeInt encodes an int to JSON -func (enc *Encoder) encodeInt(n int) ([]byte, error) { - enc.buf = strconv.AppendInt(enc.buf, int64(n), 10) - return enc.buf, nil -} - -// EncodeInt64 encodes an int64 to JSON -func (enc *Encoder) EncodeInt64(n int64) error { - if enc.isPooled == 1 { - panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) - } - _, _ = enc.encodeInt64(n) - _, err := enc.Write() - if err != nil { - return err - } - return nil -} - -// encodeInt64 encodes an int to JSON -func (enc *Encoder) encodeInt64(n int64) ([]byte, error) { - enc.buf = strconv.AppendInt(enc.buf, n, 10) - return enc.buf, nil -} - -// EncodeFloat encodes a float64 to JSON -func (enc *Encoder) EncodeFloat(n float64) error { - if enc.isPooled == 1 { - panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) - } - _, _ = enc.encodeFloat(n) - _, err := enc.Write() - if err != nil { - return err - } - return nil -} - -// encodeFloat encodes a float64 to JSON -func (enc *Encoder) encodeFloat(n float64) ([]byte, error) { - enc.buf = strconv.AppendFloat(enc.buf, n, 'f', -1, 64) - return enc.buf, nil -} - -// EncodeFloat32 encodes a float32 to JSON -func (enc *Encoder) EncodeFloat32(n float32) error { - if enc.isPooled == 1 { - panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) - } - _, _ = enc.encodeFloat32(n) - _, err := enc.Write() - if err != nil { - return err - } - return nil -} - -func (enc *Encoder) encodeFloat32(n float32) ([]byte, error) { - enc.buf = strconv.AppendFloat(enc.buf, float64(n), 'f', -1, 32) - return enc.buf, nil -} - -// AddInt adds an int to be encoded, must be used inside a slice or array encoding (does not encode a key) -func (enc *Encoder) AddInt(v int) { - enc.grow(10) - r := enc.getPreviousRune() - if r != '[' { - enc.writeByte(',') - } - enc.buf = strconv.AppendInt(enc.buf, int64(v), 10) -} - -// AddIntOmitEmpty adds an int to be encoded and skips it if its value is 0, -// must be used inside a slice or array encoding (does not encode a key). -func (enc *Encoder) AddIntOmitEmpty(v int) { - if v == 0 { - return - } - enc.grow(10) - r := enc.getPreviousRune() - if r != '[' { - enc.writeByte(',') - } - enc.buf = strconv.AppendInt(enc.buf, int64(v), 10) -} - -// AddInt64 adds an int to be encoded, must be used inside a slice or array encoding (does not encode a key) -func (enc *Encoder) AddInt64(v int64) { - enc.grow(10) - r := enc.getPreviousRune() - if r != '[' { - enc.writeByte(',') - } - enc.buf = strconv.AppendInt(enc.buf, v, 10) -} - -// AddInt64OmitEmpty adds an int to be encoded and skips it if its value is 0, -// must be used inside a slice or array encoding (does not encode a key). -func (enc *Encoder) AddInt64OmitEmpty(v int64) { - if v == 0 { - return - } - enc.grow(10) - r := enc.getPreviousRune() - if r != '[' { - enc.writeByte(',') - } - enc.buf = strconv.AppendInt(enc.buf, v, 10) -} - -// AddFloat adds a float64 to be encoded, must be used inside a slice or array encoding (does not encode a key) -func (enc *Encoder) AddFloat(v float64) { - enc.grow(10) - r := enc.getPreviousRune() - if r != '[' { - enc.writeByte(',') - } - enc.buf = strconv.AppendFloat(enc.buf, v, 'f', -1, 64) -} - -// AddFloatOmitEmpty adds a float64 to be encoded and skips it if its value is 0, -// must be used inside a slice or array encoding (does not encode a key). -func (enc *Encoder) AddFloatOmitEmpty(v float64) { - if v == 0 { - return - } - enc.grow(10) - r := enc.getPreviousRune() - if r != '[' { - enc.writeByte(',') - } - enc.buf = strconv.AppendFloat(enc.buf, v, 'f', -1, 64) -} - -// AddFloat32 adds a float32 to be encoded, must be used inside a slice or array encoding (does not encode a key) -func (enc *Encoder) AddFloat32(v float32) { - r := enc.getPreviousRune() - if r != '[' { - enc.writeByte(',') - } - enc.buf = strconv.AppendFloat(enc.buf, float64(v), 'f', -1, 32) -} - -// AddFloat32OmitEmpty adds an int to be encoded and skips it if its value is 0, -// must be used inside a slice or array encoding (does not encode a key). -func (enc *Encoder) AddFloat32OmitEmpty(v float32) { - if v == 0 { - return - } - enc.grow(10) - r := enc.getPreviousRune() - if r != '[' { - enc.writeByte(',') - } - enc.buf = strconv.AppendFloat(enc.buf, float64(v), 'f', -1, 32) -} - -// AddIntKey adds an int to be encoded, must be used inside an object as it will encode a key -func (enc *Encoder) AddIntKey(key string, v int) { - enc.grow(10 + len(key)) - r := enc.getPreviousRune() - if r != '{' { - enc.writeByte(',') - } - enc.writeByte('"') - enc.writeStringEscape(key) - enc.writeBytes(objKey) - enc.buf = strconv.AppendInt(enc.buf, int64(v), 10) -} - -// AddIntKeyOmitEmpty adds an int to be encoded and skips it if its value is 0. -// Must be used inside an object as it will encode a key. -func (enc *Encoder) AddIntKeyOmitEmpty(key string, v int) { - if v == 0 { - return - } - enc.grow(10 + len(key)) - r := enc.getPreviousRune() - if r != '{' && r != '[' { - enc.writeByte(',') - } - enc.writeByte('"') - enc.writeStringEscape(key) - enc.writeBytes(objKey) - enc.buf = strconv.AppendInt(enc.buf, int64(v), 10) -} - -// AddInt64Key adds an int64 to be encoded, must be used inside an object as it will encode a key -func (enc *Encoder) AddInt64Key(key string, v int64) { - enc.grow(10 + len(key)) - r := enc.getPreviousRune() - if r != '{' { - enc.writeByte(',') - } - enc.writeByte('"') - enc.writeStringEscape(key) - enc.writeBytes(objKey) - enc.buf = strconv.AppendInt(enc.buf, v, 10) -} - -// AddInt64KeyOmitEmpty adds an int64 to be encoded and skips it if its value is 0. -// Must be used inside an object as it will encode a key. -func (enc *Encoder) AddInt64KeyOmitEmpty(key string, v int64) { - if v == 0 { - return - } - enc.grow(10 + len(key)) - r := enc.getPreviousRune() - if r != '{' { - enc.writeByte(',') - } - enc.writeByte('"') - enc.writeStringEscape(key) - enc.writeBytes(objKey) - enc.buf = strconv.AppendInt(enc.buf, v, 10) -} - -// AddFloatKey adds a float64 to be encoded, must be used inside an object as it will encode a key -func (enc *Encoder) AddFloatKey(key string, value float64) { - r := enc.getPreviousRune() - if r != '{' { - enc.writeByte(',') - } - enc.grow(10) - enc.writeByte('"') - enc.writeStringEscape(key) - enc.writeBytes(objKey) - enc.buf = strconv.AppendFloat(enc.buf, value, 'f', -1, 64) -} - -// AddFloatKeyOmitEmpty adds a float64 to be encoded and skips it if its value is 0. -// Must be used inside an object as it will encode a key -func (enc *Encoder) AddFloatKeyOmitEmpty(key string, v float64) { - if v == 0 { - return - } - enc.grow(10 + len(key)) - r := enc.getPreviousRune() - if r != '{' { - enc.writeByte(',') - } - enc.writeByte('"') - enc.writeStringEscape(key) - enc.writeBytes(objKey) - enc.buf = strconv.AppendFloat(enc.buf, v, 'f', -1, 64) -} - -// AddFloat32Key adds a float32 to be encoded, must be used inside an object as it will encode a key -func (enc *Encoder) AddFloat32Key(key string, v float32) { - enc.grow(10 + len(key)) - r := enc.getPreviousRune() - if r != '{' { - enc.writeByte(',') - } - enc.writeByte('"') - enc.writeStringEscape(key) - enc.writeByte('"') - enc.writeByte(':') - enc.buf = strconv.AppendFloat(enc.buf, float64(v), 'f', -1, 32) -} - -// AddFloat32KeyOmitEmpty adds a float64 to be encoded and skips it if its value is 0. -// Must be used inside an object as it will encode a key -func (enc *Encoder) AddFloat32KeyOmitEmpty(key string, v float32) { - if v == 0 { - return - } - enc.grow(10 + len(key)) - r := enc.getPreviousRune() - if r != '{' { - enc.writeByte(',') - } - enc.writeByte('"') - enc.writeStringEscape(key) - enc.writeBytes(objKey) - enc.buf = strconv.AppendFloat(enc.buf, float64(v), 'f', -1, 32) -} diff --git a/encode_number_float.go b/encode_number_float.go @@ -0,0 +1,192 @@ +package gojay + +import "strconv" + +// EncodeFloat encodes a float64 to JSON +func (enc *Encoder) EncodeFloat(n float64) error { + if enc.isPooled == 1 { + panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) + } + _, _ = enc.encodeFloat(n) + _, err := enc.Write() + if err != nil { + return err + } + return nil +} + +// encodeFloat encodes a float64 to JSON +func (enc *Encoder) encodeFloat(n float64) ([]byte, error) { + enc.buf = strconv.AppendFloat(enc.buf, n, 'f', -1, 64) + return enc.buf, nil +} + +// EncodeFloat32 encodes a float32 to JSON +func (enc *Encoder) EncodeFloat32(n float32) error { + if enc.isPooled == 1 { + panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) + } + _, _ = enc.encodeFloat32(n) + _, err := enc.Write() + if err != nil { + return err + } + return nil +} + +func (enc *Encoder) encodeFloat32(n float32) ([]byte, error) { + enc.buf = strconv.AppendFloat(enc.buf, float64(n), 'f', -1, 32) + return enc.buf, nil +} + +// AddFloat adds a float64 to be encoded, must be used inside a slice or array encoding (does not encode a key) +func (enc *Encoder) AddFloat(v float64) { + enc.Float(v) +} + +// AddFloatOmitEmpty adds a float64 to be encoded and skips it if its value is 0, +// must be used inside a slice or array encoding (does not encode a key). +func (enc *Encoder) AddFloatOmitEmpty(v float64) { + enc.FloatOmitEmpty(v) +} + +// Float adds a float64 to be encoded, must be used inside a slice or array encoding (does not encode a key) +func (enc *Encoder) Float(v float64) { + enc.grow(10) + r := enc.getPreviousRune() + if r != '[' { + enc.writeByte(',') + } + enc.buf = strconv.AppendFloat(enc.buf, v, 'f', -1, 64) +} + +// FloatOmitEmpty adds a float64 to be encoded and skips it if its value is 0, +// must be used inside a slice or array encoding (does not encode a key). +func (enc *Encoder) FloatOmitEmpty(v float64) { + if v == 0 { + return + } + enc.grow(10) + r := enc.getPreviousRune() + if r != '[' { + enc.writeByte(',') + } + enc.buf = strconv.AppendFloat(enc.buf, v, 'f', -1, 64) +} + +// AddFloatKey adds a float64 to be encoded, must be used inside an object as it will encode a key +func (enc *Encoder) AddFloatKey(key string, v float64) { + enc.FloatKey(key, v) +} + +// AddFloatKeyOmitEmpty adds a float64 to be encoded and skips it if its value is 0. +// Must be used inside an object as it will encode a key +func (enc *Encoder) AddFloatKeyOmitEmpty(key string, v float64) { + enc.FloatKeyOmitEmpty(key, v) +} + +// FloatKey adds a float64 to be encoded, must be used inside an object as it will encode a key +func (enc *Encoder) FloatKey(key string, value float64) { + r := enc.getPreviousRune() + if r != '{' { + enc.writeByte(',') + } + enc.grow(10) + enc.writeByte('"') + enc.writeStringEscape(key) + enc.writeBytes(objKey) + enc.buf = strconv.AppendFloat(enc.buf, value, 'f', -1, 64) +} + +// FloatKeyOmitEmpty adds a float64 to be encoded and skips it if its value is 0. +// Must be used inside an object as it will encode a key +func (enc *Encoder) FloatKeyOmitEmpty(key string, v float64) { + if v == 0 { + return + } + enc.grow(10 + len(key)) + r := enc.getPreviousRune() + if r != '{' { + enc.writeByte(',') + } + enc.writeByte('"') + enc.writeStringEscape(key) + enc.writeBytes(objKey) + enc.buf = strconv.AppendFloat(enc.buf, v, 'f', -1, 64) +} + +// AddFloat32 adds a float32 to be encoded, must be used inside a slice or array encoding (does not encode a key) +func (enc *Encoder) AddFloat32(v float32) { + enc.Float32(v) +} + +// AddFloat32OmitEmpty adds an int to be encoded and skips it if its value is 0, +// must be used inside a slice or array encoding (does not encode a key). +func (enc *Encoder) AddFloat32OmitEmpty(v float32) { + enc.Float32OmitEmpty(v) +} + +// Float32 adds a float32 to be encoded, must be used inside a slice or array encoding (does not encode a key) +func (enc *Encoder) Float32(v float32) { + r := enc.getPreviousRune() + if r != '[' { + enc.writeByte(',') + } + enc.buf = strconv.AppendFloat(enc.buf, float64(v), 'f', -1, 32) +} + +// Float32OmitEmpty adds an int to be encoded and skips it if its value is 0, +// must be used inside a slice or array encoding (does not encode a key). +func (enc *Encoder) Float32OmitEmpty(v float32) { + if v == 0 { + return + } + enc.grow(10) + r := enc.getPreviousRune() + if r != '[' { + enc.writeByte(',') + } + enc.buf = strconv.AppendFloat(enc.buf, float64(v), 'f', -1, 32) +} + +// AddFloat32Key adds a float32 to be encoded, must be used inside an object as it will encode a key +func (enc *Encoder) AddFloat32Key(key string, v float32) { + enc.Float32Key(key, v) +} + +// AddFloat32KeyOmitEmpty adds a float64 to be encoded and skips it if its value is 0. +// Must be used inside an object as it will encode a key +func (enc *Encoder) AddFloat32KeyOmitEmpty(key string, v float32) { + enc.Float32KeyOmitEmpty(key, v) +} + +// Float32Key adds a float32 to be encoded, must be used inside an object as it will encode a key +func (enc *Encoder) Float32Key(key string, v float32) { + enc.grow(10 + len(key)) + r := enc.getPreviousRune() + if r != '{' { + enc.writeByte(',') + } + enc.writeByte('"') + enc.writeStringEscape(key) + enc.writeByte('"') + enc.writeByte(':') + enc.buf = strconv.AppendFloat(enc.buf, float64(v), 'f', -1, 32) +} + +// Float32KeyOmitEmpty adds a float64 to be encoded and skips it if its value is 0. +// Must be used inside an object as it will encode a key +func (enc *Encoder) Float32KeyOmitEmpty(key string, v float32) { + if v == 0 { + return + } + enc.grow(10 + len(key)) + r := enc.getPreviousRune() + if r != '{' { + enc.writeByte(',') + } + enc.writeByte('"') + enc.writeStringEscape(key) + enc.writeBytes(objKey) + enc.buf = strconv.AppendFloat(enc.buf, float64(v), 'f', -1, 32) +} diff --git a/encode_number_int.go b/encode_number_int.go @@ -0,0 +1,193 @@ +package gojay + +import "strconv" + +// EncodeInt encodes an int to JSON +func (enc *Encoder) EncodeInt(n int) error { + if enc.isPooled == 1 { + panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) + } + _, _ = enc.encodeInt(n) + _, err := enc.Write() + if err != nil { + return err + } + return nil +} + +// encodeInt encodes an int to JSON +func (enc *Encoder) encodeInt(n int) ([]byte, error) { + enc.buf = strconv.AppendInt(enc.buf, int64(n), 10) + return enc.buf, nil +} + +// EncodeInt64 encodes an int64 to JSON +func (enc *Encoder) EncodeInt64(n int64) error { + if enc.isPooled == 1 { + panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) + } + _, _ = enc.encodeInt64(n) + _, err := enc.Write() + if err != nil { + return err + } + return nil +} + +// encodeInt64 encodes an int to JSON +func (enc *Encoder) encodeInt64(n int64) ([]byte, error) { + enc.buf = strconv.AppendInt(enc.buf, n, 10) + return enc.buf, nil +} + +// AddInt adds an int to be encoded, must be used inside a slice or array encoding (does not encode a key) +func (enc *Encoder) AddInt(v int) { + enc.Int(v) +} + +// AddIntOmitEmpty adds an int to be encoded and skips it if its value is 0, +// must be used inside a slice or array encoding (does not encode a key). +func (enc *Encoder) AddIntOmitEmpty(v int) { + enc.IntOmitEmpty(v) +} + +// Int adds an int to be encoded, must be used inside a slice or array encoding (does not encode a key) +func (enc *Encoder) Int(v int) { + enc.grow(10) + r := enc.getPreviousRune() + if r != '[' { + enc.writeByte(',') + } + enc.buf = strconv.AppendInt(enc.buf, int64(v), 10) +} + +// IntOmitEmpty adds an int to be encoded and skips it if its value is 0, +// must be used inside a slice or array encoding (does not encode a key). +func (enc *Encoder) IntOmitEmpty(v int) { + if v == 0 { + return + } + enc.grow(10) + r := enc.getPreviousRune() + if r != '[' { + enc.writeByte(',') + } + enc.buf = strconv.AppendInt(enc.buf, int64(v), 10) +} + +// AddIntKey adds an int to be encoded, must be used inside an object as it will encode a key +func (enc *Encoder) AddIntKey(key string, v int) { + enc.IntKey(key, v) +} + +// AddIntKeyOmitEmpty adds an int to be encoded and skips it if its value is 0. +// Must be used inside an object as it will encode a key. +func (enc *Encoder) AddIntKeyOmitEmpty(key string, v int) { + enc.IntKeyOmitEmpty(key, v) +} + +// IntKey adds an int to be encoded, must be used inside an object as it will encode a key +func (enc *Encoder) IntKey(key string, v int) { + enc.grow(10 + len(key)) + r := enc.getPreviousRune() + if r != '{' { + enc.writeByte(',') + } + enc.writeByte('"') + enc.writeStringEscape(key) + enc.writeBytes(objKey) + enc.buf = strconv.AppendInt(enc.buf, int64(v), 10) +} + +// IntKeyOmitEmpty adds an int to be encoded and skips it if its value is 0. +// Must be used inside an object as it will encode a key. +func (enc *Encoder) IntKeyOmitEmpty(key string, v int) { + if v == 0 { + return + } + enc.grow(10 + len(key)) + r := enc.getPreviousRune() + if r != '{' && r != '[' { + enc.writeByte(',') + } + enc.writeByte('"') + enc.writeStringEscape(key) + enc.writeBytes(objKey) + enc.buf = strconv.AppendInt(enc.buf, int64(v), 10) +} + +// AddInt64 adds an int to be encoded, must be used inside a slice or array encoding (does not encode a key) +func (enc *Encoder) AddInt64(v int64) { + enc.Int64(v) +} + +// AddInt64OmitEmpty adds an int to be encoded and skips it if its value is 0, +// must be used inside a slice or array encoding (does not encode a key). +func (enc *Encoder) AddInt64OmitEmpty(v int64) { + enc.Int64OmitEmpty(v) +} + +// Int64 adds an int to be encoded, must be used inside a slice or array encoding (does not encode a key) +func (enc *Encoder) Int64(v int64) { + enc.grow(10) + r := enc.getPreviousRune() + if r != '[' { + enc.writeByte(',') + } + enc.buf = strconv.AppendInt(enc.buf, v, 10) +} + +// Int64OmitEmpty adds an int to be encoded and skips it if its value is 0, +// must be used inside a slice or array encoding (does not encode a key). +func (enc *Encoder) Int64OmitEmpty(v int64) { + if v == 0 { + return + } + enc.grow(10) + r := enc.getPreviousRune() + if r != '[' { + enc.writeByte(',') + } + enc.buf = strconv.AppendInt(enc.buf, v, 10) +} + +// AddInt64Key adds an int64 to be encoded, must be used inside an object as it will encode a key +func (enc *Encoder) AddInt64Key(key string, v int64) { + enc.Int64Key(key, v) +} + +// AddInt64KeyOmitEmpty adds an int64 to be encoded and skips it if its value is 0. +// Must be used inside an object as it will encode a key. +func (enc *Encoder) AddInt64KeyOmitEmpty(key string, v int64) { + enc.Int64KeyOmitEmpty(key, v) +} + +// Int64Key adds an int64 to be encoded, must be used inside an object as it will encode a key +func (enc *Encoder) Int64Key(key string, v int64) { + enc.grow(10 + len(key)) + r := enc.getPreviousRune() + if r != '{' { + enc.writeByte(',') + } + enc.writeByte('"') + enc.writeStringEscape(key) + enc.writeBytes(objKey) + enc.buf = strconv.AppendInt(enc.buf, v, 10) +} + +// Int64KeyOmitEmpty adds an int64 to be encoded and skips it if its value is 0. +// Must be used inside an object as it will encode a key. +func (enc *Encoder) Int64KeyOmitEmpty(key string, v int64) { + if v == 0 { + return + } + enc.grow(10 + len(key)) + r := enc.getPreviousRune() + if r != '{' { + enc.writeByte(',') + } + enc.writeByte('"') + enc.writeStringEscape(key) + enc.writeBytes(objKey) + enc.buf = strconv.AppendInt(enc.buf, v, 10) +} diff --git a/encode_number_test.go b/encode_number_test.go @@ -95,6 +95,26 @@ func TestEncoderNumberEncodeAPIErrors(t *testing.T) { assert.Equal(t, "Test Error", err.Error(), "err should be of type InvalidUsagePooledEncoderError") }) + t.Run("encode-uint64-pool-error", func(t *testing.T) { + builder := &strings.Builder{} + enc := NewEncoder(builder) + enc.Release() + defer func() { + err := recover() + assert.NotNil(t, err, "err should not be nil") + assert.IsType(t, InvalidUsagePooledEncoderError(""), err, "err should be of type InvalidUsagePooledEncoderError") + }() + _ = enc.EncodeUint64(1) + assert.True(t, false, "should not be called as encoder should have panicked") + }) + t.Run("encode-unt64-write-error", func(t *testing.T) { + w := TestWriterError("") + enc := NewEncoder(w) + err := enc.EncodeUint64(1) + assert.NotNil(t, err, "err should not be nil") + assert.Equal(t, "Test Error", err.Error(), "err should be of type InvalidUsagePooledEncoderError") + + }) t.Run("encode-float64-pool-error", func(t *testing.T) { builder := &strings.Builder{} enc := NewEncoder(builder) @@ -322,3 +342,106 @@ func TestAddNumberFunc(t *testing.T) { assert.Equal(t, `[`, builder.String(), `builder.String() should be equal to {"test":10"`) }) } + +func TestEncoderUint64(t *testing.T) { + builder := &strings.Builder{} + enc := BorrowEncoder(builder) + err := enc.Encode(uint64(145509)) + assert.Nil(t, err, "err should be nil") + assert.Equal(t, "145509", builder.String(), "builder.String() should be 145509") +} + +func TestUint64Add(t *testing.T) { + t.Run("uint64-key", func(t *testing.T) { + builder := &strings.Builder{} + enc := BorrowEncoder(builder) + enc.writeByte('{') + enc.AddUint64Key("test", 10) + _, err := enc.Write() + assert.Nil(t, err, "err should be nil") + assert.Equal(t, `{"test":10`, builder.String(), `builder.String() should be equal to {"test":10"`) + }) + t.Run("uint64-key-2", func(t *testing.T) { + builder := &strings.Builder{} + enc := BorrowEncoder(builder) + enc.writeBytes([]byte(`{"test":1`)) + enc.AddUint64Key("test", 10) + _, err := enc.Write() + assert.Nil(t, err, "err should be nil") + assert.Equal(t, `{"test":1,"test":10`, builder.String(), `builder.String() should be equal to {"test":10"`) + }) + + t.Run("uint64-key-omit-empty", func(t *testing.T) { + builder := &strings.Builder{} + enc := BorrowEncoder(builder) + enc.writeByte('{') + enc.AddUint64KeyOmitEmpty("test", 10) + _, err := enc.Write() + assert.Nil(t, err, "err should be nil") + assert.Equal(t, `{"test":10`, builder.String(), `builder.String() should be equal to {"test":10"`) + }) + t.Run("uint64-key-omit-empty-2", func(t *testing.T) { + builder := &strings.Builder{} + enc := BorrowEncoder(builder) + enc.writeBytes([]byte(`{"test":1`)) + enc.AddUint64KeyOmitEmpty("test", 10) + _, err := enc.Write() + assert.Nil(t, err, "err should be nil") + assert.Equal(t, `{"test":1,"test":10`, builder.String(), `builder.String() should be equal to {"test":10"`) + }) + t.Run("uint64-key-omit-empty-3", func(t *testing.T) { + builder := &strings.Builder{} + enc := BorrowEncoder(builder) + enc.writeByte('{') + enc.AddUint64KeyOmitEmpty("test", 0) + _, err := enc.Write() + assert.Nil(t, err, "err should be nil") + assert.Equal(t, `{`, builder.String(), `builder.String() should be equal to {"test":10"`) + }) + t.Run("uint64", func(t *testing.T) { + builder := &strings.Builder{} + enc := BorrowEncoder(builder) + enc.writeByte('[') + enc.AddUint64(10) + _, err := enc.Write() + assert.Nil(t, err, "err should be nil") + assert.Equal(t, `[10`, builder.String(), `builder.String() should be equal to {"test":10"`) + }) + t.Run("uint64-2", func(t *testing.T) { + builder := &strings.Builder{} + enc := BorrowEncoder(builder) + enc.writeBytes([]byte(`[1`)) + enc.AddUint64(10) + _, err := enc.Write() + assert.Nil(t, err, "err should be nil") + assert.Equal(t, `[1,10`, builder.String(), `builder.String() should be equal to {"test":10"`) + }) + + t.Run("uint64-omit-empty", func(t *testing.T) { + builder := &strings.Builder{} + enc := BorrowEncoder(builder) + enc.writeByte('[') + enc.AddUint64OmitEmpty(10) + _, err := enc.Write() + assert.Nil(t, err, "err should be nil") + assert.Equal(t, `[10`, builder.String(), `builder.String() should be equal to {"test":10"`) + }) + t.Run("uint64-omit-empty-2", func(t *testing.T) { + builder := &strings.Builder{} + enc := BorrowEncoder(builder) + enc.writeBytes([]byte(`[1`)) + enc.AddUint64OmitEmpty(10) + _, err := enc.Write() + assert.Nil(t, err, "err should be nil") + assert.Equal(t, `[1,10`, builder.String(), `builder.String() should be equal to {"test":10"`) + }) + t.Run("uint64-omit-empty-3", func(t *testing.T) { + builder := &strings.Builder{} + enc := BorrowEncoder(builder) + enc.writeByte('[') + enc.AddUint64OmitEmpty(0) + _, err := enc.Write() + assert.Nil(t, err, "err should be nil") + assert.Equal(t, `[`, builder.String(), `builder.String() should be equal to {"test":10"`) + }) +} diff --git a/encode_number_uint.go b/encode_number_uint.go @@ -0,0 +1,98 @@ +package gojay + +import "strconv" + +// EncodeUint64 encodes an int64 to JSON +func (enc *Encoder) EncodeUint64(n uint64) error { + if enc.isPooled == 1 { + panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) + } + _, _ = enc.encodeUint64(n) + _, err := enc.Write() + if err != nil { + return err + } + return nil +} + +// encodeUint64 encodes an int to JSON +func (enc *Encoder) encodeUint64(n uint64) ([]byte, error) { + enc.buf = strconv.AppendUint(enc.buf, n, 10) + return enc.buf, nil +} + +// AddUint64 adds an int to be encoded, must be used inside a slice or array encoding (does not encode a key) +func (enc *Encoder) AddUint64(v uint64) { + enc.Uint64(v) +} + +// AddUint64OmitEmpty adds an int to be encoded and skips it if its value is 0, +// must be used inside a slice or array encoding (does not encode a key). +func (enc *Encoder) AddUint64OmitEmpty(v uint64) { + enc.Uint64OmitEmpty(v) +} + +// Uint64 adds an int to be encoded, must be used inside a slice or array encoding (does not encode a key) +func (enc *Encoder) Uint64(v uint64) { + enc.grow(10) + r := enc.getPreviousRune() + if r != '[' { + enc.writeByte(',') + } + enc.buf = strconv.AppendUint(enc.buf, v, 10) +} + +// Uint64OmitEmpty adds an int to be encoded and skips it if its value is 0, +// must be used inside a slice or array encoding (does not encode a key). +func (enc *Encoder) Uint64OmitEmpty(v uint64) { + if v == 0 { + return + } + enc.grow(10) + r := enc.getPreviousRune() + if r != '[' { + enc.writeByte(',') + } + enc.buf = strconv.AppendUint(enc.buf, v, 10) +} + +// AddUint64Key adds an int to be encoded, must be used inside an object as it will encode a key +func (enc *Encoder) AddUint64Key(key string, v uint64) { + enc.Uint64Key(key, v) +} + +// AddUint64KeyOmitEmpty adds an int to be encoded and skips it if its value is 0. +// Must be used inside an object as it will encode a key. +func (enc *Encoder) AddUint64KeyOmitEmpty(key string, v uint64) { + enc.Uint64KeyOmitEmpty(key, v) +} + +// Uint64Key adds an int to be encoded, must be used inside an object as it will encode a key +func (enc *Encoder) Uint64Key(key string, v uint64) { + enc.grow(10 + len(key)) + r := enc.getPreviousRune() + if r != '{' { + enc.writeByte(',') + } + enc.writeByte('"') + enc.writeStringEscape(key) + enc.writeBytes(objKey) + enc.buf = strconv.AppendUint(enc.buf, v, 10) +} + +// Uint64KeyOmitEmpty adds an int to be encoded and skips it if its value is 0. +// Must be used inside an object as it will encode a key. +func (enc *Encoder) Uint64KeyOmitEmpty(key string, v uint64) { + if v == 0 { + return + } + enc.grow(10 + len(key)) + r := enc.getPreviousRune() + if r != '{' && r != '[' { + enc.writeByte(',') + } + enc.writeByte('"') + enc.writeStringEscape(key) + enc.writeBytes(objKey) + enc.buf = strconv.AppendUint(enc.buf, v, 10) +} diff --git a/encode_object.go b/encode_object.go @@ -6,7 +6,7 @@ var objKeyArr = []byte(`":[`) var objKey = []byte(`":`) // EncodeObject encodes an object to JSON -func (enc *Encoder) EncodeObject(v MarshalerObject) error { +func (enc *Encoder) EncodeObject(v MarshalerJSONObject) error { if enc.isPooled == 1 { panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) } @@ -23,19 +23,45 @@ func (enc *Encoder) EncodeObject(v MarshalerObject) error { return nil } -func (enc *Encoder) encodeObject(v MarshalerObject) ([]byte, error) { +func (enc *Encoder) encodeObject(v MarshalerJSONObject) ([]byte, error) { enc.grow(500) enc.writeByte('{') if !v.IsNil() { - v.MarshalObject(enc) + v.MarshalJSONObject(enc) } enc.writeByte('}') return enc.buf, enc.err } // AddObject adds an object to be encoded, must be used inside a slice or array encoding (does not encode a key) -// value must implement MarshalerObject -func (enc *Encoder) AddObject(v MarshalerObject) { +// value must implement MarshalerJSONObject +func (enc *Encoder) AddObject(v MarshalerJSONObject) { + enc.Object(v) +} + +// AddObjectOmitEmpty adds an object to be encoded or skips it if IsNil returns true. +// Must be used inside a slice or array encoding (does not encode a key) +// value must implement MarshalerJSONObject +func (enc *Encoder) AddObjectOmitEmpty(v MarshalerJSONObject) { + enc.ObjectOmitEmpty(v) +} + +// AddObjectKey adds a struct to be encoded, must be used inside an object as it will encode a key +// value must implement MarshalerJSONObject +func (enc *Encoder) AddObjectKey(key string, v MarshalerJSONObject) { + enc.ObjectKey(key, v) +} + +// AddObjectKeyOmitEmpty adds an object to be encoded or skips it if IsNil returns true. +// Must be used inside a slice or array encoding (does not encode a key) +// value must implement MarshalerJSONObject +func (enc *Encoder) AddObjectKeyOmitEmpty(key string, v MarshalerJSONObject) { + enc.ObjectKeyOmitEmpty(key, v) +} + +// Object adds an object to be encoded, must be used inside a slice or array encoding (does not encode a key) +// value must implement MarshalerJSONObject +func (enc *Encoder) Object(v MarshalerJSONObject) { if v.IsNil() { enc.grow(2) r := enc.getPreviousRune() @@ -52,14 +78,14 @@ func (enc *Encoder) AddObject(v MarshalerObject) { enc.writeByte(',') } enc.writeByte('{') - v.MarshalObject(enc) + v.MarshalJSONObject(enc) enc.writeByte('}') } -// AddObjectOmitEmpty adds an object to be encoded or skips it if IsNil returns true. +// ObjectOmitEmpty adds an object to be encoded or skips it if IsNil returns true. // Must be used inside a slice or array encoding (does not encode a key) -// value must implement MarshalerObject -func (enc *Encoder) AddObjectOmitEmpty(v MarshalerObject) { +// value must implement MarshalerJSONObject +func (enc *Encoder) ObjectOmitEmpty(v MarshalerJSONObject) { if v.IsNil() { return } @@ -69,13 +95,13 @@ func (enc *Encoder) AddObjectOmitEmpty(v MarshalerObject) { enc.writeByte(',') } enc.writeByte('{') - v.MarshalObject(enc) + v.MarshalJSONObject(enc) enc.writeByte('}') } -// AddObjectKey adds a struct to be encoded, must be used inside an object as it will encode a key -// value must implement MarshalerObject -func (enc *Encoder) AddObjectKey(key string, value MarshalerObject) { +// ObjectKey adds a struct to be encoded, must be used inside an object as it will encode a key +// value must implement MarshalerJSONObject +func (enc *Encoder) ObjectKey(key string, value MarshalerJSONObject) { if value.IsNil() { enc.grow(2 + len(key)) r := enc.getPreviousRune() @@ -96,14 +122,14 @@ func (enc *Encoder) AddObjectKey(key string, value MarshalerObject) { enc.writeByte('"') enc.writeStringEscape(key) enc.writeBytes(objKeyObj) - value.MarshalObject(enc) + value.MarshalJSONObject(enc) enc.writeByte('}') } -// AddObjectKeyOmitEmpty adds an object to be encoded or skips it if IsNil returns true. +// ObjectKeyOmitEmpty adds an object to be encoded or skips it if IsNil returns true. // Must be used inside a slice or array encoding (does not encode a key) -// value must implement MarshalerObject -func (enc *Encoder) AddObjectKeyOmitEmpty(key string, value MarshalerObject) { +// value must implement MarshalerJSONObject +func (enc *Encoder) ObjectKeyOmitEmpty(key string, value MarshalerJSONObject) { if value.IsNil() { return } @@ -115,7 +141,7 @@ func (enc *Encoder) AddObjectKeyOmitEmpty(key string, value MarshalerObject) { enc.writeByte('"') enc.writeStringEscape(key) enc.writeBytes(objKeyObj) - value.MarshalObject(enc) + value.MarshalJSONObject(enc) enc.writeByte('}') } @@ -128,12 +154,12 @@ func (enc *Encoder) AddObjectKeyOmitEmpty(key string, value MarshalerObject) { // })) type EncodeObjectFunc func(*Encoder) -// MarshalObject implements MarshalerObject. -func (f EncodeObjectFunc) MarshalObject(enc *Encoder) { +// MarshalJSONObject implements MarshalerJSONObject. +func (f EncodeObjectFunc) MarshalJSONObject(enc *Encoder) { f(enc) } -// IsNil implements MarshalerObject. +// IsNil implements MarshalerJSONObject. func (f EncodeObjectFunc) IsNil() bool { return f == nil } diff --git a/encode_object_test.go b/encode_object_test.go @@ -7,42 +7,6 @@ import ( "github.com/stretchr/testify/assert" ) -type testObject struct { - testStr string - testInt int - testInt64 int64 - testInt32 int32 - testInt16 int16 - testInt8 int8 - testUint64 uint64 - testUint32 uint32 - testUint16 uint16 - testUint8 uint8 - testFloat64 float64 - testFloat32 float32 - testBool bool -} - -func (t *testObject) IsNil() bool { - return t == nil -} - -func (t *testObject) MarshalObject(enc *Encoder) { - enc.AddStringKey("testStr", t.testStr) - enc.AddIntKey("testInt", t.testInt) - enc.AddIntKey("testInt64", int(t.testInt64)) - enc.AddIntKey("testInt32", int(t.testInt32)) - enc.AddIntKey("testInt16", int(t.testInt16)) - enc.AddIntKey("testInt8", int(t.testInt8)) - enc.AddIntKey("testUint64", int(t.testUint64)) - enc.AddIntKey("testUint32", int(t.testUint32)) - enc.AddIntKey("testUint16", int(t.testUint16)) - enc.AddIntKey("testUint8", int(t.testUint8)) - enc.AddFloatKey("testFloat64", t.testFloat64) - enc.AddFloat32Key("testFloat32", t.testFloat32) - enc.AddBoolKey("testBool", t.testBool) -} - type testObjectWithUnknownType struct { unknownType struct{} } @@ -51,7 +15,7 @@ func (t *testObjectWithUnknownType) IsNil() bool { return t == nil } -func (t *testObjectWithUnknownType) MarshalObject(enc *Encoder) { +func (t *testObjectWithUnknownType) MarshalJSONObject(enc *Encoder) { enc.AddInterfaceKey("unknownType", t.unknownType) } @@ -71,7 +35,7 @@ func (t *TestEncoding) IsNil() bool { return t == nil } -func (t *TestEncoding) MarshalObject(enc *Encoder) { +func (t *TestEncoding) MarshalJSONObject(enc *Encoder) { enc.AddStringKey("test", t.test) enc.AddStringKey("test2", t.test2) enc.AddIntKey("testInt", t.testInt) @@ -95,7 +59,7 @@ func (t *SubObject) IsNil() bool { return t == nil } -func (t *SubObject) MarshalObject(enc *Encoder) { +func (t *SubObject) MarshalJSONObject(enc *Encoder) { enc.AddIntKey("test1", t.test1) enc.AddStringKey("test2", t.test2) enc.AddFloatKey("test3", t.test3) @@ -111,7 +75,7 @@ func (t *testEncodingObjInterfaces) IsNil() bool { return t == nil } -func (t *testEncodingObjInterfaces) MarshalObject(enc *Encoder) { +func (t *testEncodingObjInterfaces) MarshalJSONObject(enc *Encoder) { enc.AddInterfaceKey("interfaceVal", t.interfaceVal) } @@ -119,7 +83,7 @@ func TestEncoderObjectEncodeAPI(t *testing.T) { t.Run("encode-basic", func(t *testing.T) { builder := &strings.Builder{} enc := NewEncoder(builder) - err := enc.EncodeObject(&testObject{"漢字", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.1, 1.1, true}) + err := enc.EncodeObject(&testObject{"漢字", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.1, 1.1, true, &testObject{}, testSliceInts{}}) assert.Nil(t, err, "Error should be nil") assert.Equal( t, @@ -132,7 +96,7 @@ func TestEncoderObjectEncodeAPI(t *testing.T) { func TestEncoderObjectMarshalAPI(t *testing.T) { t.Run("marshal-basic", func(t *testing.T) { - r, err := Marshal(&testObject{"漢字", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.1, 1.1, true}) + r, err := Marshal(&testObject{"漢字", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.1, 1.1, true, &testObject{}, testSliceInts{}}) assert.Nil(t, err, "Error should be nil") assert.Equal( t, @@ -168,7 +132,7 @@ func TestEncoderObjectMarshalAPI(t *testing.T) { }, }, } - r, err := MarshalObject(v) + r, err := MarshalJSONObject(v) assert.Nil(t, err, "Error should be nil") assert.Equal( t, @@ -327,7 +291,7 @@ func (t *TestObectOmitEmpty) IsNil() bool { return t == nil } -func (t *TestObectOmitEmpty) MarshalObject(enc *Encoder) { +func (t *TestObectOmitEmpty) MarshalJSONObject(enc *Encoder) { enc.AddIntKeyOmitEmpty("testInt", t.testInt) enc.AddIntKeyOmitEmpty("testIntNotEmpty", 1) enc.AddFloatKeyOmitEmpty("testFloat", t.testFloat) @@ -350,7 +314,7 @@ func (t *TestObectOmitEmptyInterface) IsNil() bool { return t == nil } -func (t *TestObectOmitEmptyInterface) MarshalObject(enc *Encoder) { +func (t *TestObectOmitEmptyInterface) MarshalJSONObject(enc *Encoder) { enc.AddInterfaceKeyOmitEmpty("testInt", 0) enc.AddInterfaceKeyOmitEmpty("testInt64", int64(0)) enc.AddInterfaceKeyOmitEmpty("testInt32", int32(0)) @@ -381,7 +345,7 @@ func TestEncoderObjectOmitEmpty(t *testing.T) { testInt: 0, testObect: &TestObectOmitEmpty{testInt: 1}, } - r, err := MarshalObject(v) + r, err := MarshalJSONObject(v) assert.Nil(t, err, "Error should be nil") assert.Equal( t, @@ -393,7 +357,7 @@ func TestEncoderObjectOmitEmpty(t *testing.T) { t.Run("encoder-omit-empty-interface", func(t *testing.T) { v := &TestObectOmitEmptyInterface{} - r, err := MarshalObject(v) + r, err := MarshalJSONObject(v) assert.Nil(t, err, "Error should be nil") assert.Equal( t, @@ -415,7 +379,7 @@ func TestEncoderObjectEncodeAPIError(t *testing.T) { t.Run("write-error", func(t *testing.T) { w := TestWriterError("") enc := NewEncoder(w) - err := enc.EncodeObject(&testObject{"漢字", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.1, 1.1, true}) + err := enc.EncodeObject(&testObject{}) assert.NotNil(t, err, "Error should not be nil") assert.Equal(t, "Test Error", err.Error(), "err.Error() should be 'Test Error'") }) diff --git a/encode_stream.go b/encode_stream.go @@ -2,6 +2,7 @@ package gojay import ( "strconv" + "sync" "time" ) @@ -15,6 +16,7 @@ type MarshalerStream interface { // // It implements conext.Context and provide a channel to notify interruption. type StreamEncoder struct { + mux *sync.RWMutex *Encoder nConsumer int delimiter byte @@ -39,11 +41,19 @@ func (s *StreamEncoder) EncodeStream(m MarshalerStream) { // resulting in a weird JSON go consume(s, s, m) for i := 1; i < s.nConsumer; i++ { - ss := Stream.borrowEncoder(s.w) - ss.done = s.done - ss.buf = make([]byte, 0, 512) - ss.delimiter = s.delimiter - go consume(s, ss, m) + s.mux.RLock() + select { + case <-s.done: + default: + ss := Stream.borrowEncoder(s.w) + ss.mux.Lock() + ss.done = s.done + ss.buf = make([]byte, 0, 512) + ss.delimiter = s.delimiter + go consume(s, ss, m) + ss.mux.Unlock() + } + s.mux.RUnlock() } return } @@ -115,22 +125,24 @@ func (s *StreamEncoder) Value(key interface{}) interface{} { // // 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() } // AddObject adds an object to be encoded. -// value must implement MarshalerObject. -func (s *StreamEncoder) AddObject(v MarshalerObject) { +// value must implement MarshalerJSONObject. +func (s *StreamEncoder) AddObject(v MarshalerJSONObject) { if v.IsNil() { return } s.Encoder.writeByte('{') - v.MarshalObject(s.Encoder) + v.MarshalJSONObject(s.Encoder) s.Encoder.writeByte('}') s.Encoder.writeByte(s.delimiter) } @@ -143,10 +155,10 @@ func (s *StreamEncoder) AddString(v string) { s.Encoder.writeByte(s.delimiter) } -// AddArray adds an implementation of MarshalerArray to be encoded. -func (s *StreamEncoder) AddArray(v MarshalerArray) { +// AddArray adds an implementation of MarshalerJSONArray to be encoded. +func (s *StreamEncoder) AddArray(v MarshalerJSONArray) { s.Encoder.writeByte('[') - v.MarshalArray(s.Encoder) + v.MarshalJSONArray(s.Encoder) s.Encoder.writeByte(']') s.Encoder.writeByte(s.delimiter) } diff --git a/encode_stream_pool.go b/encode_stream_pool.go @@ -1,13 +1,16 @@ package gojay -import "io" +import ( + "io" + "sync" +) // NewEncoder returns a new StreamEncoder. // It takes an io.Writer implementation to output data. // It initiates the done channel returned by Done(). func (s stream) NewEncoder(w io.Writer) *StreamEncoder { enc := BorrowEncoder(w) - return &StreamEncoder{Encoder: enc, nConsumer: 1, done: make(chan struct{}, 1)} + return &StreamEncoder{Encoder: enc, nConsumer: 1, done: make(chan struct{}, 1), mux: &sync.RWMutex{}} } // BorrowEncoder borrows a StreamEncoder from the pool. diff --git a/encode_stream_test.go b/encode_stream_test.go @@ -301,8 +301,8 @@ func TestEncodeStream(t *testing.T) { enc := Stream.NewEncoder(w).NConsumer(50).LineDelimited() w.enc = enc s := StreamChanObject(make(chan *testObject)) - go enc.EncodeStream(s) go feedStream(s, 5000) + go enc.EncodeStream(s) select { case <-enc.Done(): assert.Nil(t, enc.Err(), "enc.Err() should be nil") @@ -313,6 +313,22 @@ func TestEncodeStream(t *testing.T) { } }) + t.Run("multiple-consumer-object-chan-closed", func(t *testing.T) { + // create our writer + w := &TestWriter{target: 5000, mux: &sync.RWMutex{}} + enc := Stream.NewEncoder(w).NConsumer(50).LineDelimited() + w.enc = enc + s := StreamChanObject(make(chan *testObject)) + close(enc.done) + go feedStream(s, 5000) + go enc.EncodeStream(s) + select { + case <-enc.Done(): + assert.Nil(t, enc.Err(), "enc.Err() should be nil") + assert.Len(t, w.result, 0, "w.result should be 0") + } + }) + t.Run("encoder-deadline", func(t *testing.T) { enc := Stream.NewEncoder(os.Stdout) now := time.Now() diff --git a/encode_string.go b/encode_string.go @@ -32,6 +32,28 @@ func (enc *Encoder) AppendString(v string) { // AddString adds a string to be encoded, must be used inside a slice or array encoding (does not encode a key) func (enc *Encoder) AddString(v string) { + enc.String(v) +} + +// AddStringOmitEmpty adds a string to be encoded or skips it if it is zero value. +// Must be used inside a slice or array encoding (does not encode a key) +func (enc *Encoder) AddStringOmitEmpty(v string) { + enc.StringOmitEmpty(v) +} + +// AddStringKey adds a string to be encoded, must be used inside an object as it will encode a key +func (enc *Encoder) AddStringKey(key, v string) { + enc.StringKey(key, v) +} + +// AddStringKeyOmitEmpty adds a string to be encoded or skips it if it is zero value. +// Must be used inside an object as it will encode a key +func (enc *Encoder) AddStringKeyOmitEmpty(key, v string) { + enc.StringKeyOmitEmpty(key, v) +} + +// String adds a string to be encoded, must be used inside a slice or array encoding (does not encode a key) +func (enc *Encoder) String(v string) { enc.grow(len(v) + 4) r := enc.getPreviousRune() if r != '[' { @@ -43,9 +65,9 @@ func (enc *Encoder) AddString(v string) { enc.writeByte('"') } -// AddStringOmitEmpty adds a string to be encoded or skips it if it is zero value. +// StringOmitEmpty adds a string to be encoded or skips it if it is zero value. // Must be used inside a slice or array encoding (does not encode a key) -func (enc *Encoder) AddStringOmitEmpty(v string) { +func (enc *Encoder) StringOmitEmpty(v string) { if v == "" { return } @@ -59,8 +81,8 @@ func (enc *Encoder) AddStringOmitEmpty(v string) { enc.writeByte('"') } -// AddStringKey adds a string to be encoded, must be used inside an object as it will encode a key -func (enc *Encoder) AddStringKey(key, v string) { +// StringKey adds a string to be encoded, must be used inside an object as it will encode a key +func (enc *Encoder) StringKey(key, v string) { enc.grow(len(key) + len(v) + 5) r := enc.getPreviousRune() if r != '{' { @@ -74,9 +96,9 @@ func (enc *Encoder) AddStringKey(key, v string) { enc.writeByte('"') } -// AddStringKeyOmitEmpty adds a string to be encoded or skips it if it is zero value. +// StringKeyOmitEmpty adds a string to be encoded or skips it if it is zero value. // Must be used inside an object as it will encode a key -func (enc *Encoder) AddStringKeyOmitEmpty(key, v string) { +func (enc *Encoder) StringKeyOmitEmpty(key, v string) { if v == "" { return } diff --git a/errors.go b/errors.go @@ -1,6 +1,10 @@ package gojay -const invalidJSONCharErrorMsg = "Invalid JSON character %c found at position %d" +import ( + "fmt" +) + +const invalidJSONCharErrorMsg = "Invalid JSON, wrong char '%c' found at position %d" // InvalidJSONError is a type representing an error returned when // Decoding encounters invalid JSON. @@ -10,24 +14,40 @@ func (err InvalidJSONError) Error() string { return string(err) } -// InvalidTypeError is a type representing an error returned when -// Decoding cannot unmarshal JSON to the receiver type for various reasons. -type InvalidTypeError string - -func (err InvalidTypeError) Error() string { - return string(err) +func (dec *Decoder) raiseInvalidJSONErr(pos int) error { + var c byte + if len(dec.data) > pos { + c = dec.data[pos] + } + dec.err = InvalidJSONError( + fmt.Sprintf( + invalidJSONCharErrorMsg, + c, + pos, + ), + ) + return dec.err } -const invalidUnmarshalErrorMsg = "Invalid type %s provided to Unmarshal" +const invalidUnmarshalErrorMsg = "Cannot unmarshal JSON to type '%T'" // InvalidUnmarshalError is a type representing an error returned when -// Decoding did not find the proper way to decode +// Decoding cannot unmarshal JSON to the receiver type for various reasons. type InvalidUnmarshalError string func (err InvalidUnmarshalError) Error() string { return string(err) } +func (dec *Decoder) makeInvalidUnmarshalErr(v interface{}) error { + return InvalidUnmarshalError( + fmt.Sprintf( + invalidUnmarshalErrorMsg, + v, + ), + ) +} + const invalidMarshalErrorMsg = "Invalid type %s provided to Marshal" // InvalidMarshalError is a type representing an error returned when diff --git a/examples/encode-decode-map/main.go b/examples/encode-decode-map/main.go @@ -7,11 +7,11 @@ import ( "github.com/francoispqt/gojay" ) -// define our custom map type implementing MarshalerObject and UnmarshalerObject +// define our custom map type implementing MarshalerJSONObject and UnmarshalerJSONObject type myMap map[string]string // Implementing Unmarshaler -func (m myMap) UnmarshalObject(dec *gojay.Decoder, k string) error { +func (m myMap) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { str := "" err := dec.AddString(&str) if err != nil { @@ -28,7 +28,7 @@ func (m myMap) NKeys() int { } // Implementing Marshaler -func (m myMap) MarshalObject(enc *gojay.Encoder) { +func (m myMap) MarshalJSONObject(enc *gojay.Encoder) { for k, v := range m { enc.AddStringKey(k, v) } diff --git a/examples/http-json/main.go b/examples/http-json/main.go @@ -11,7 +11,7 @@ type message struct { bar string } -func (m *message) UnmarshalObject(dec *gojay.Decoder, k string) error { +func (m *message) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { switch k { case "foo": return dec.AddString(&m.foo) @@ -25,7 +25,7 @@ func (m *message) NKeys() int { return 2 } -func (m *message) MarshalObject(dec *gojay.Encoder) { +func (m *message) MarshalJSONObject(dec *gojay.Encoder) { dec.AddStringKey("foo", m.foo) dec.AddStringKey("bar", m.bar) } diff --git a/examples/websocket/comm/comm.go b/examples/websocket/comm/comm.go @@ -15,7 +15,7 @@ type Message struct { UserName string } -func (m *Message) UnmarshalObject(dec *gojay.Decoder, k string) error { +func (m *Message) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { switch k { case "message": return dec.AddString(&m.Message) @@ -28,7 +28,7 @@ func (m *Message) NKeys() int { return 2 } -func (m *Message) MarshalObject(enc *gojay.Encoder) { +func (m *Message) MarshalJSONObject(enc *gojay.Encoder) { enc.AddStringKey("message", m.Message) enc.AddStringKey("userName", m.UserName) } @@ -37,7 +37,7 @@ func (u *Message) IsNil() bool { } // Here are defined our communication types -type Sender chan gojay.MarshalerObject +type Sender chan gojay.MarshalerJSONObject func (s Sender) MarshalStream(enc *gojay.StreamEncoder) { select { @@ -74,12 +74,12 @@ func (sc *SenderReceiver) SetReceiver() { } func (sc *SenderReceiver) SetSender(nCons int) { - sc.Send = Sender(make(chan gojay.MarshalerObject)) + sc.Send = Sender(make(chan gojay.MarshalerJSONObject)) sc.Enc = gojay.Stream.BorrowEncoder(sc.Conn).NConsumer(nCons).LineDelimited() go sc.Enc.EncodeStream(sc.Send) } -func (sc *SenderReceiver) SendMessage(m gojay.MarshalerObject) error { +func (sc *SenderReceiver) SendMessage(m gojay.MarshalerJSONObject) error { select { case <-sc.Enc.Done(): return errors.New("sender closed") diff --git a/gojay_test.go b/gojay_test.go @@ -0,0 +1,265 @@ +package gojay + +type testObject struct { + testStr string + testInt int + testInt64 int64 + testInt32 int32 + testInt16 int16 + testInt8 int8 + testUint64 uint64 + testUint32 uint32 + testUint16 uint16 + testUint8 uint8 + testFloat64 float64 + testFloat32 float32 + testBool bool + testSubObject *testObject + testSubArray testSliceInts +} + +// make sure it implements interfaces +var _ MarshalerJSONObject = &testObject{} +var _ UnmarshalerJSONObject = &testObject{} + +func (t *testObject) IsNil() bool { + return t == nil +} + +func (t *testObject) MarshalJSONObject(enc *Encoder) { + enc.AddStringKey("testStr", t.testStr) + enc.AddIntKey("testInt", t.testInt) + enc.AddIntKey("testInt64", int(t.testInt64)) + enc.AddIntKey("testInt32", int(t.testInt32)) + enc.AddIntKey("testInt16", int(t.testInt16)) + enc.AddIntKey("testInt8", int(t.testInt8)) + enc.AddIntKey("testUint64", int(t.testUint64)) + enc.AddIntKey("testUint32", int(t.testUint32)) + enc.AddIntKey("testUint16", int(t.testUint16)) + enc.AddIntKey("testUint8", int(t.testUint8)) + enc.AddFloatKey("testFloat64", t.testFloat64) + enc.AddFloat32Key("testFloat32", t.testFloat32) + enc.AddBoolKey("testBool", t.testBool) +} + +func (t *testObject) UnmarshalJSONObject(dec *Decoder, k string) error { + switch k { + case "testStr": + return dec.AddString(&t.testStr) + case "testInt": + return dec.AddInt(&t.testInt) + case "testInt64": + return dec.AddInt64(&t.testInt64) + case "testInt32": + return dec.AddInt32(&t.testInt32) + case "testInt16": + return dec.AddInt16(&t.testInt16) + case "testInt8": + return dec.AddInt8(&t.testInt8) + case "testUint64": + return dec.AddUint64(&t.testUint64) + case "testUint32": + return dec.AddUint32(&t.testUint32) + case "testUint16": + return dec.AddUint16(&t.testUint16) + case "testUint8": + return dec.AddUint8(&t.testUint8) + case "testFloat64": + return dec.AddFloat(&t.testFloat64) + case "testFloat32": + return dec.AddFloat32(&t.testFloat32) + case "testBool": + return dec.AddBool(&t.testBool) + } + return nil +} + +func (t *testObject) NKeys() int { + return 13 +} + +type testObject0Keys struct { + testStr string + testInt int + testInt64 int64 + testInt32 int32 + testInt16 int16 + testInt8 int8 + testUint64 uint64 + testUint32 uint32 + testUint16 uint16 + testUint8 uint8 + testFloat64 float64 + testFloat32 float32 + testBool bool + testSubObject *testObject0Keys + testSubArray testSliceInts +} + +// make sure it implements interfaces +var _ MarshalerJSONObject = &testObject0Keys{} +var _ UnmarshalerJSONObject = &testObject0Keys{} + +func (t *testObject0Keys) IsNil() bool { + return t == nil +} + +func (t *testObject0Keys) MarshalJSONObject(enc *Encoder) { + enc.AddStringKey("testStr", t.testStr) + enc.AddIntKey("testInt", t.testInt) + enc.AddIntKey("testInt64", int(t.testInt64)) + enc.AddIntKey("testInt32", int(t.testInt32)) + enc.AddIntKey("testInt16", int(t.testInt16)) + enc.AddIntKey("testInt8", int(t.testInt8)) + enc.AddIntKey("testUint64", int(t.testUint64)) + enc.AddIntKey("testUint32", int(t.testUint32)) + enc.AddIntKey("testUint16", int(t.testUint16)) + enc.AddIntKey("testUint8", int(t.testUint8)) + enc.AddFloatKey("testFloat64", t.testFloat64) + enc.AddFloat32Key("testFloat32", t.testFloat32) + enc.AddBoolKey("testBool", t.testBool) +} + +func (t *testObject0Keys) UnmarshalJSONObject(dec *Decoder, k string) error { + switch k { + case "testStr": + return dec.AddString(&t.testStr) + case "testInt": + return dec.AddInt(&t.testInt) + case "testInt64": + return dec.AddInt64(&t.testInt64) + case "testInt32": + return dec.AddInt32(&t.testInt32) + case "testInt16": + return dec.AddInt16(&t.testInt16) + case "testInt8": + return dec.AddInt8(&t.testInt8) + case "testUint64": + return dec.AddUint64(&t.testUint64) + case "testUint32": + return dec.AddUint32(&t.testUint32) + case "testUint16": + return dec.AddUint16(&t.testUint16) + case "testUint8": + return dec.AddUint8(&t.testUint8) + case "testFloat64": + return dec.AddFloat(&t.testFloat64) + case "testFloat32": + return dec.AddFloat32(&t.testFloat32) + case "testBool": + return dec.AddBool(&t.testBool) + } + return nil +} + +func (t *testObject0Keys) NKeys() int { + return 0 +} + +type testObjectComplex struct { + testSubObject *testObject + testSubSliceInts *testSliceInts + testStr string + testSubObject2 *testObjectComplex +} + +func (t *testObjectComplex) IsNil() bool { + return t == nil +} + +func (t *testObjectComplex) MarshalJSONObject(enc *Encoder) { + enc.AddObjectKey("testSubObject", t.testSubObject) + enc.AddStringKey("testStr", t.testStr) + enc.AddObjectKey("testStr", t.testSubObject2) +} + +func (t *testObjectComplex) UnmarshalJSONObject(dec *Decoder, k string) error { + switch k { + case "testSubObject": + return dec.AddObject(t.testSubObject) + case "testSubSliceInts": + return dec.AddArray(t.testSubSliceInts) + case "testStr": + return dec.AddString(&t.testStr) + case "testSubObject2": + return dec.AddObject(t.testSubObject2) + } + return nil +} + +func (t *testObjectComplex) NKeys() int { + return 4 +} + +// make sure it implements interfaces +var _ MarshalerJSONObject = &testObjectComplex{} +var _ UnmarshalerJSONObject = &testObjectComplex{} + +type TestObj struct { + test int + test2 int + test3 string + test4 string + test5 float64 + testArr testSliceObjects + testSubObj *TestSubObj + testSubObj2 *TestSubObj +} + +type TestSubObj struct { + test3 int + test4 int + test5 string + testSubSubObj *TestSubObj + testSubSubObj2 *TestSubObj +} + +func (t *TestSubObj) UnmarshalJSONObject(dec *Decoder, key string) error { + switch key { + case "test": + return dec.AddInt(&t.test3) + case "test2": + return dec.AddInt(&t.test4) + case "test3": + return dec.AddString(&t.test5) + case "testSubSubObj": + t.testSubSubObj = &TestSubObj{} + return dec.AddObject(t.testSubSubObj) + case "testSubSubObj2": + t.testSubSubObj2 = &TestSubObj{} + return dec.AddObject(t.testSubSubObj2) + } + return nil +} + +func (t *TestSubObj) NKeys() int { + return 0 +} + +func (t *TestObj) UnmarshalJSONObject(dec *Decoder, key string) error { + switch key { + case "test": + return dec.AddInt(&t.test) + case "test2": + return dec.AddInt(&t.test2) + case "test3": + return dec.AddString(&t.test3) + case "test4": + return dec.AddString(&t.test4) + case "test5": + return dec.AddFloat(&t.test5) + case "testSubObj": + t.testSubObj = &TestSubObj{} + return dec.AddObject(t.testSubObj) + case "testSubObj2": + t.testSubObj2 = &TestSubObj{} + return dec.AddObject(t.testSubObj2) + case "testArr": + return dec.AddArray(&t.testArr) + } + return nil +} + +func (t *TestObj) NKeys() int { + return 8 +}