commit a2c156ad288ef79007855b4278be4954da00f5bf
parent f4c5a97b2ce16ce14e1d30875b7cecf1f1f4f7a8
Author: Francois Parquet <francois.parquet@gmail.com>
Date: Sun, 12 Aug 2018 23:00:24 +0800
Merge pull request #59 from francoispqt/version/v1.2.0
Version/v1.2.0
Diffstat:
13 files changed, 2182 insertions(+), 151 deletions(-)
diff --git a/decode.go b/decode.go
@@ -4,6 +4,7 @@ import (
"fmt"
"io"
"reflect"
+ "time"
)
// UnmarshalJSONArray parses the JSON-encoded data and stores the result in the value pointed to by v.
@@ -482,6 +483,21 @@ func (dec *Decoder) String(v *string) error {
return nil
}
+// AddTime decodes the next key to a *time.Time with the given format
+func (dec *Decoder) AddTime(v *time.Time, format string) error {
+ return dec.Time(v, format)
+}
+
+// Time decodes the next key to a *time.Time with the given format
+func (dec *Decoder) Time(v *time.Time, format string) error {
+ err := dec.decodeTime(v, format)
+ if err != nil {
+ return err
+ }
+ dec.called |= 1
+ return nil
+}
+
// Object decodes the next key to a UnmarshalerJSONObject.
func (dec *Decoder) Object(value UnmarshalerJSONObject) error {
initialKeysDone := dec.keysDone
diff --git a/decode_array_test.go b/decode_array_test.go
@@ -112,12 +112,12 @@ func TestSliceStrings(t *testing.T) {
},
{
name: "basic-test",
- json: `["hello world", "hey" , "foo","bar \\n escape"]`,
+ 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"]`,
+ json: `["hello world", "hey" , null,"bar \n escape"]`,
expectedResult: testSliceStrings{"hello world", "hey", "", "bar \n escape"},
},
{
@@ -534,6 +534,13 @@ func TestSkipArray(t *testing.T) {
assert.Nil(t, err)
},
},
+ {
+ json: `"test \n"]`,
+ expectations: func(t *testing.T, i int, err error) {
+ assert.Equal(t, len(`"test \n"]`), i)
+ assert.Nil(t, err)
+ },
+ },
}
for _, test := range testCases {
diff --git a/decode_object_test.go b/decode_object_test.go
@@ -877,14 +877,14 @@ func TestDecodeObjectComplex(t *testing.T) {
json: `{
"testSubObject": {
"testStr": "some string",
- "testInt":124465,
- "testUint16":120,
- "testUint8":15,
- "testInt16":-135,
+ "testInt":124465,
+ "testUint16":120,
+ "testUint8":15,
+ "testInt16":-135,
"testInt8":-23
},
"testSubSliceInts": [1,2,3,4,5],
- "testStr": "some \\n string"
+ "testStr": "some \n string"
}`,
expectedResult: testObjectComplex{
testSubObject: &testObject{
@@ -902,7 +902,7 @@ func TestDecodeObjectComplex(t *testing.T) {
},
{
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"}`,
+ 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{},
},
@@ -1012,9 +1012,9 @@ func TestDecodeObjectNull(t *testing.T) {
var jsonComplex = []byte(`{
"test": "{\"test\":\"1\",\"test1\":2}",
- "test2\\n": "\\\\\\\\\\n",
+ "test2\n": "\\\\\\\\\n",
"testArrSkip": ["testString with escaped \\\" quotes"],
- "testSkipString": "skip \\ string with \\n escaped char \" ",
+ "testSkipString": "skip \\ string with \n escaped char \" ",
"testSkipObject": {
"testSkipSubObj": {
"test": "test"
@@ -1028,7 +1028,7 @@ var jsonComplex = []byte(`{
"testSkipBoolNull": null,
"testSub": {
"test": "{\"test\":\"1\",\"test1\":2}",
- "test2\\n": "[1,2,3]",
+ "test2\n": "[1,2,3]",
"test3": 1,
"testObjSkip": {
"test": "test string with escaped \" quotes"
@@ -1382,6 +1382,11 @@ func TestSkipObject(t *testing.T) {
json: `{"key":"value"`,
err: true,
},
+ {
+ name: "basic-err2",
+ json: `"key":"value\n"}`,
+ err: false,
+ },
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
diff --git a/decode_sqlnull.go b/decode_sqlnull.go
@@ -0,0 +1,75 @@
+package gojay
+
+import "database/sql"
+
+// DecodeSQLNullString decodes a sql.NullString
+func (dec *Decoder) DecodeSQLNullString(v *sql.NullString) error {
+ if dec.isPooled == 1 {
+ panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder"))
+ }
+ return dec.decodeSQLNullString(v)
+}
+
+func (dec *Decoder) decodeSQLNullString(v *sql.NullString) error {
+ var str string
+ if err := dec.decodeString(&str); err != nil {
+ return err
+ }
+ v.String = str
+ v.Valid = true
+ return nil
+}
+
+// DecodeSQLNullInt64 decodes a sql.NullInt64
+func (dec *Decoder) DecodeSQLNullInt64(v *sql.NullInt64) error {
+ if dec.isPooled == 1 {
+ panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder"))
+ }
+ return dec.decodeSQLNullInt64(v)
+}
+
+func (dec *Decoder) decodeSQLNullInt64(v *sql.NullInt64) error {
+ var i int64
+ if err := dec.decodeInt64(&i); err != nil {
+ return err
+ }
+ v.Int64 = i
+ v.Valid = true
+ return nil
+}
+
+// DecodeSQLNullFloat64 decodes a sql.NullString with the given format
+func (dec *Decoder) DecodeSQLNullFloat64(v *sql.NullFloat64) error {
+ if dec.isPooled == 1 {
+ panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder"))
+ }
+ return dec.decodeSQLNullFloat64(v)
+}
+
+func (dec *Decoder) decodeSQLNullFloat64(v *sql.NullFloat64) error {
+ var i float64
+ if err := dec.decodeFloat64(&i); err != nil {
+ return err
+ }
+ v.Float64 = i
+ v.Valid = true
+ return nil
+}
+
+// DecodeSQLNullBool decodes a sql.NullString with the given format
+func (dec *Decoder) DecodeSQLNullBool(v *sql.NullBool) error {
+ if dec.isPooled == 1 {
+ panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder"))
+ }
+ return dec.decodeSQLNullBool(v)
+}
+
+func (dec *Decoder) decodeSQLNullBool(v *sql.NullBool) error {
+ var b bool
+ if err := dec.decodeBool(&b); err != nil {
+ return err
+ }
+ v.Bool = b
+ v.Valid = true
+ return nil
+}
diff --git a/decode_sqlnull_test.go b/decode_sqlnull_test.go
@@ -0,0 +1,201 @@
+package gojay
+
+import (
+ "database/sql"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestDecodeSQLNullString(t *testing.T) {
+ testCases := []struct {
+ name string
+ json string
+ expectedNullString sql.NullString
+ err bool
+ }{
+ {
+ name: "basic",
+ json: `"test"`,
+ expectedNullString: sql.NullString{String: "test", Valid: true},
+ },
+ {
+ name: "basic",
+ json: `"test`,
+ expectedNullString: sql.NullString{String: "test", Valid: true},
+ err: true,
+ },
+ }
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ nullString := sql.NullString{}
+ dec := NewDecoder(strings.NewReader(testCase.json))
+ err := dec.DecodeSQLNullString(&nullString)
+ if testCase.err {
+ assert.NotNil(t, err)
+ } else {
+ assert.Nil(t, err)
+ assert.Equal(t, testCase.expectedNullString, nullString)
+ }
+ })
+ }
+ t.Run(
+ "should panic because decoder is pooled",
+ func(t *testing.T) {
+ dec := NewDecoder(nil)
+ dec.Release()
+ defer func() {
+ err := recover()
+ assert.NotNil(t, err, "err shouldnt be nil")
+ assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError")
+ }()
+ _ = dec.DecodeSQLNullString(&sql.NullString{})
+ assert.True(t, false, "should not be called as decoder should have panicked")
+ },
+ )
+}
+
+func TestDecodeSQLNullInt64(t *testing.T) {
+ testCases := []struct {
+ name string
+ json string
+ expectedNullInt64 sql.NullInt64
+ err bool
+ }{
+ {
+ name: "basic",
+ json: `1`,
+ expectedNullInt64: sql.NullInt64{Int64: 1, Valid: true},
+ },
+ {
+ name: "basic",
+ json: `"test`,
+ expectedNullInt64: sql.NullInt64{Int64: 1, Valid: true},
+ err: true,
+ },
+ }
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ nullInt64 := sql.NullInt64{}
+ dec := NewDecoder(strings.NewReader(testCase.json))
+ err := dec.DecodeSQLNullInt64(&nullInt64)
+ if testCase.err {
+ assert.NotNil(t, err)
+ } else {
+ assert.Nil(t, err)
+ assert.Equal(t, testCase.expectedNullInt64, nullInt64)
+ }
+ })
+ }
+ t.Run(
+ "should panic because decoder is pooled",
+ func(t *testing.T) {
+ dec := NewDecoder(nil)
+ dec.Release()
+ defer func() {
+ err := recover()
+ assert.NotNil(t, err, "err shouldnt be nil")
+ assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError")
+ }()
+ _ = dec.DecodeSQLNullInt64(&sql.NullInt64{})
+ assert.True(t, false, "should not be called as decoder should have panicked")
+ },
+ )
+}
+
+func TestDecodeSQLNullFloat64(t *testing.T) {
+ testCases := []struct {
+ name string
+ json string
+ expectedNullFloat64 sql.NullFloat64
+ err bool
+ }{
+ {
+ name: "basic",
+ json: `1`,
+ expectedNullFloat64: sql.NullFloat64{Float64: 1, Valid: true},
+ },
+ {
+ name: "basic",
+ json: `"test`,
+ expectedNullFloat64: sql.NullFloat64{Float64: 1, Valid: true},
+ err: true,
+ },
+ }
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ nullFloat64 := sql.NullFloat64{}
+ dec := NewDecoder(strings.NewReader(testCase.json))
+ err := dec.DecodeSQLNullFloat64(&nullFloat64)
+ if testCase.err {
+ assert.NotNil(t, err)
+ } else {
+ assert.Nil(t, err)
+ assert.Equal(t, testCase.expectedNullFloat64, nullFloat64)
+ }
+ })
+ }
+ t.Run(
+ "should panic because decoder is pooled",
+ func(t *testing.T) {
+ dec := NewDecoder(nil)
+ dec.Release()
+ defer func() {
+ err := recover()
+ assert.NotNil(t, err, "err shouldnt be nil")
+ assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError")
+ }()
+ _ = dec.DecodeSQLNullFloat64(&sql.NullFloat64{})
+ assert.True(t, false, "should not be called as decoder should have panicked")
+ },
+ )
+}
+
+func TestDecodeSQLNullBool(t *testing.T) {
+ testCases := []struct {
+ name string
+ json string
+ expectedNullBool sql.NullBool
+ err bool
+ }{
+ {
+ name: "basic",
+ json: `true`,
+ expectedNullBool: sql.NullBool{Bool: true, Valid: true},
+ },
+ {
+ name: "basic",
+ json: `"&`,
+ expectedNullBool: sql.NullBool{Bool: true, Valid: true},
+ err: true,
+ },
+ }
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ nullBool := sql.NullBool{}
+ dec := NewDecoder(strings.NewReader(testCase.json))
+ err := dec.DecodeSQLNullBool(&nullBool)
+ if testCase.err {
+ assert.NotNil(t, err)
+ } else {
+ assert.Nil(t, err)
+ assert.Equal(t, testCase.expectedNullBool, nullBool)
+ }
+ })
+ }
+ t.Run(
+ "should panic because decoder is pooled",
+ func(t *testing.T) {
+ dec := NewDecoder(nil)
+ dec.Release()
+ defer func() {
+ err := recover()
+ assert.NotNil(t, err, "err shouldnt be nil")
+ assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError")
+ }()
+ _ = dec.DecodeSQLNullBool(&sql.NullBool{})
+ assert.True(t, false, "should not be called as decoder should have panicked")
+ },
+ )
+}
diff --git a/decode_string.go b/decode_string.go
@@ -52,127 +52,51 @@ func (dec *Decoder) decodeString(v *string) error {
}
func (dec *Decoder) parseEscapedString() error {
- // know where to stop slash
- start := dec.cursor
- for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
- if dec.data[dec.cursor] != '\\' {
- d := dec.data[dec.cursor]
- dec.cursor = dec.cursor + 1
- nSlash := dec.cursor - start
- switch d {
- case '"':
- // nSlash must be odd
- if nSlash&1 != 1 {
- 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 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
- case 'n':
- // 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], '\n'), dec.data[dec.cursor:]...)
- dec.length = len(dec.data)
- dec.cursor -= nSlash - diff + 1
- return nil
- case 'r':
- // 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], '\r'), dec.data[dec.cursor:]...)
- dec.length = len(dec.data)
- dec.cursor -= nSlash - diff + 1
- return nil
- case 't':
- // 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], '\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 dec.raiseInvalidJSONErr(dec.cursor)
- }
- 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
- }
+ if dec.cursor >= dec.length && !dec.read() {
+ return dec.raiseInvalidJSONErr(dec.cursor)
+ }
+ switch dec.data[dec.cursor] {
+ case '"':
+ dec.data[dec.cursor] = '"'
+ case '\\':
+ dec.data[dec.cursor] = '\\'
+ case '/':
+ dec.data[dec.cursor] = '/'
+ case 'b':
+ dec.data[dec.cursor] = '\b'
+ case 'f':
+ dec.data[dec.cursor] = '\f'
+ case 'n':
+ dec.data[dec.cursor] = '\n'
+ case 'r':
+ dec.data[dec.cursor] = '\r'
+ case 't':
+ dec.data[dec.cursor] = '\t'
+ case 'u':
+ start := dec.cursor
+ dec.cursor++
+ str, err := dec.parseUnicode()
+ if err != nil {
+ return err
}
+ diff := dec.cursor - start
+ dec.data = append(append(dec.data[:start-1], str...), dec.data[dec.cursor:]...)
+ dec.length = len(dec.data)
+ dec.cursor += len(str) - diff - 1
+
+ return nil
+ default:
+ return dec.raiseInvalidJSONErr(dec.cursor)
}
- return dec.raiseInvalidJSONErr(dec.cursor)
+ // Truncate the previous backslash character, and the
+ dec.data = append(dec.data[:dec.cursor-1], dec.data[dec.cursor:]...)
+ dec.length--
+
+ // Since we've lost a character, our dec.cursor offset is now
+ // 1 past the escaped character which is precisely where we
+ // want it.
+
+ return nil
}
func (dec *Decoder) getString() (int, int, error) {
diff --git a/decode_string_test.go b/decode_string_test.go
@@ -44,56 +44,56 @@ func TestDecoderString(t *testing.T) {
{
name: "escape-control-char",
json: `"\n"`,
- expectedResult: "",
- err: true,
+ expectedResult: "\n",
+ err: false,
},
{
name: "escape-control-char",
json: `"\\n"`,
- expectedResult: "\n",
+ expectedResult: `\n`,
err: false,
},
{
name: "escape-control-char",
json: `"\t"`,
- expectedResult: "",
- err: true,
+ expectedResult: "\t",
+ err: false,
},
{
name: "escape-control-char",
json: `"\\t"`,
- expectedResult: "\t",
+ expectedResult: `\t`,
err: false,
},
{
name: "escape-control-char",
json: `"\b"`,
- expectedResult: "",
- err: true,
+ expectedResult: "\b",
+ err: false,
},
{
name: "escape-control-char",
json: `"\\b"`,
- expectedResult: "\b",
+ expectedResult: `\b`,
err: false,
},
{
name: "escape-control-char",
json: `"\f"`,
- expectedResult: "",
- err: true,
+ expectedResult: "\f",
+ err: false,
},
{
name: "escape-control-char",
json: `"\\f"`,
- expectedResult: "\f",
+ expectedResult: `\f`,
err: false,
},
{
name: "escape-control-char",
json: `"\r"`,
- expectedResult: "",
- err: true,
+ expectedResult: "\r",
+ err: false,
},
{
name: "escape-control-char",
@@ -102,9 +102,27 @@ func TestDecoderString(t *testing.T) {
err: true,
},
{
+ name: "escape-control-char-solidus",
+ json: `"\/"`,
+ expectedResult: "/",
+ err: false,
+ },
+ {
+ name: "escape-control-char-solidus",
+ json: `"/"`,
+ expectedResult: "/",
+ err: false,
+ },
+ {
+ name: "escape-control-char-solidus-escape-char",
+ json: `"\\/"`,
+ expectedResult: `\/`,
+ err: false,
+ },
+ {
name: "escape-control-char",
json: `"\\r"`,
- expectedResult: "\r",
+ expectedResult: `\r`,
err: false,
},
{
@@ -228,31 +246,31 @@ func TestDecoderString(t *testing.T) {
},
{
name: "escape quote err2",
- json: `"test string \\t escaped"`,
+ json: `"test string \t escaped"`,
expectedResult: "test string \t escaped",
err: false,
},
{
name: "escape quote err2",
- json: `"test string \\r escaped"`,
+ json: `"test string \r escaped"`,
expectedResult: "test string \r escaped",
err: false,
},
{
name: "escape quote err2",
- json: `"test string \\b escaped"`,
+ json: `"test string \b escaped"`,
expectedResult: "test string \b escaped",
err: false,
},
{
name: "escape quote err",
- json: `"test string \\n escaped"`,
+ json: `"test string \n escaped"`,
expectedResult: "test string \n escaped",
err: false,
},
{
name: "escape quote err",
- json: `"test string \\" escaped"`,
+ json: `"test string \\\" escaped`,
expectedResult: ``,
err: true,
errType: InvalidJSONError(""),
@@ -273,7 +291,7 @@ func TestDecoderString(t *testing.T) {
},
{
name: "string-complex",
- json: ` "string with spaces and \"escape\"d \"quotes\" and escaped line returns \\n and escaped \\\\ escaped char"`,
+ 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",
},
}
diff --git a/decode_time.go b/decode_time.go
@@ -0,0 +1,36 @@
+package gojay
+
+import (
+ "time"
+)
+
+// DecodeTime decodes time with the given format
+func (dec *Decoder) DecodeTime(v *time.Time, format string) error {
+ if dec.isPooled == 1 {
+ panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder"))
+ }
+ return dec.decodeTime(v, format)
+}
+
+func (dec *Decoder) decodeTime(v *time.Time, format string) error {
+ if format == time.RFC3339 {
+ var ej = make(EmbeddedJSON, 0, 20)
+ if err := dec.decodeEmbeddedJSON(&ej); err != nil {
+ return err
+ }
+ if err := v.UnmarshalJSON(ej); err != nil {
+ return err
+ }
+ return nil
+ }
+ var str string
+ if err := dec.decodeString(&str); err != nil {
+ return err
+ }
+ tt, err := time.Parse(format, str)
+ if err != nil {
+ return err
+ }
+ *v = tt
+ return nil
+}
diff --git a/decode_time_test.go b/decode_time_test.go
@@ -0,0 +1,141 @@
+package gojay
+
+import (
+ "strings"
+ "sync"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestDecodeTime(t *testing.T) {
+ testCases := []struct {
+ name string
+ json string
+ format string
+ err bool
+ expectedTime string
+ }{
+ {
+ name: "basic",
+ json: `"2018-02-18"`,
+ format: `2006-01-02`,
+ err: false,
+ expectedTime: "2018-02-18",
+ },
+ {
+ name: "basic",
+ json: `"2017-01-02T15:04:05Z"`,
+ format: time.RFC3339,
+ err: false,
+ expectedTime: "2017-01-02T15:04:05Z",
+ },
+ {
+ name: "basic",
+ json: `"2017-01-02T15:04:05ZINVALID"`,
+ format: time.RFC3339,
+ err: true,
+ expectedTime: "",
+ },
+ {
+ name: "basic",
+ json: `"2017-01-02T15:04:05ZINVALID`,
+ format: time.RFC1123,
+ err: true,
+ expectedTime: "",
+ },
+ {
+ name: "basic",
+ json: `"2017-01-02T15:04:05ZINVALID"`,
+ format: time.RFC1123,
+ err: true,
+ expectedTime: "",
+ },
+ {
+ name: "basic",
+ json: `"2017-01-02T15:04:05ZINVALID`,
+ format: time.RFC3339,
+ err: true,
+ expectedTime: "",
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ tm := time.Time{}
+ dec := NewDecoder(strings.NewReader(testCase.json))
+ err := dec.DecodeTime(&tm, testCase.format)
+ if !testCase.err {
+ assert.Nil(t, err)
+ assert.Equal(t, testCase.expectedTime, tm.Format(testCase.format))
+ return
+ }
+ assert.NotNil(t, err)
+ })
+ }
+}
+
+func TestDecodeAddTime(t *testing.T) {
+ testCases := []struct {
+ name string
+ json string
+ format string
+ err bool
+ expectedTime string
+ }{
+ {
+ name: "basic",
+ json: `"2018-02-18"`,
+ format: `2006-01-02`,
+ err: false,
+ expectedTime: "2018-02-18",
+ },
+ {
+ name: "basic",
+ json: ` "2017-01-02T15:04:05Z"`,
+ format: time.RFC3339,
+ err: false,
+ expectedTime: "2017-01-02T15:04:05Z",
+ },
+ {
+ name: "basic",
+ json: ` "2017-01-02T15:04:05ZINVALID"`,
+ format: time.RFC3339,
+ err: true,
+ expectedTime: "",
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ tm := time.Time{}
+ dec := NewDecoder(strings.NewReader(testCase.json))
+ err := dec.AddTime(&tm, testCase.format)
+ if !testCase.err {
+ assert.Nil(t, err)
+ assert.Equal(t, testCase.expectedTime, tm.Format(testCase.format))
+ return
+ }
+ assert.NotNil(t, err)
+ })
+ }
+}
+
+func TestDecoderTimePoolError(t *testing.T) {
+ // reset the pool to make sure it's not full
+ decPool = sync.Pool{
+ New: func() interface{} {
+ return NewDecoder(nil)
+ },
+ }
+ dec := NewDecoder(nil)
+ dec.Release()
+ defer func() {
+ err := recover()
+ assert.NotNil(t, err, "err shouldnt be nil")
+ assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError")
+ }()
+ _ = dec.DecodeTime(&time.Time{}, time.RFC3339)
+ assert.True(t, false, "should not be called as decoder should have panicked")
+}
diff --git a/encode_sqlnull.go b/encode_sqlnull.go
@@ -0,0 +1,269 @@
+package gojay
+
+import "database/sql"
+
+// EncodeSQLNullString encodes a string to
+func (enc *Encoder) EncodeSQLNullString(v *sql.NullString) error {
+ if enc.isPooled == 1 {
+ panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder"))
+ }
+ _, _ = enc.encodeString(v.String)
+ _, err := enc.Write()
+ if err != nil {
+ enc.err = err
+ return err
+ }
+ return nil
+}
+
+// AddSQLNullString adds a string to be encoded, must be used inside a slice or array encoding (does not encode a key)
+func (enc *Encoder) AddSQLNullString(v *sql.NullString) {
+ enc.String(v.String)
+}
+
+// AddSQLNullStringOmitEmpty 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) AddSQLNullStringOmitEmpty(v *sql.NullString) {
+ if v != nil && v.Valid && v.String != "" {
+ enc.StringOmitEmpty(v.String)
+ }
+}
+
+// AddSQLNullStringKey adds a string to be encoded, must be used inside an object as it will encode a key
+func (enc *Encoder) AddSQLNullStringKey(key string, v *sql.NullString) {
+ enc.StringKey(key, v.String)
+}
+
+// AddSQLNullStringKeyOmitEmpty 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) AddSQLNullStringKeyOmitEmpty(key string, v *sql.NullString) {
+ if v != nil && v.Valid && v.String != "" {
+ enc.StringKeyOmitEmpty(key, v.String)
+ }
+}
+
+// SQLNullString adds a string to be encoded, must be used inside an object as it will encode a key
+func (enc *Encoder) SQLNullString(v *sql.NullString) {
+ enc.String(v.String)
+}
+
+// SQLNullStringOmitEmpty adds a string to be encoded, must be used inside an object as it will encode a key
+func (enc *Encoder) SQLNullStringOmitEmpty(v *sql.NullString) {
+ if v != nil && v.Valid && v.String != "" {
+ enc.String(v.String)
+ }
+}
+
+// SQLNullStringKey adds a string to be encoded, must be used inside an object as it will encode a key
+func (enc *Encoder) SQLNullStringKey(key string, v *sql.NullString) {
+ enc.StringKey(key, v.String)
+}
+
+// SQLNullStringKeyOmitEmpty 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) SQLNullStringKeyOmitEmpty(key string, v *sql.NullString) {
+ if v != nil && v.Valid && v.String != "" {
+ enc.StringKeyOmitEmpty(key, v.String)
+ }
+}
+
+// NullInt64
+
+// EncodeSQLNullInt64 encodes a string to
+func (enc *Encoder) EncodeSQLNullInt64(v *sql.NullInt64) error {
+ if enc.isPooled == 1 {
+ panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder"))
+ }
+ _, _ = enc.encodeInt64(v.Int64)
+ _, err := enc.Write()
+ if err != nil {
+ enc.err = err
+ return err
+ }
+ return nil
+}
+
+// AddSQLNullInt64 adds a string to be encoded, must be used inside a slice or array encoding (does not encode a key)
+func (enc *Encoder) AddSQLNullInt64(v *sql.NullInt64) {
+ enc.Int64(v.Int64)
+}
+
+// AddSQLNullInt64OmitEmpty 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) AddSQLNullInt64OmitEmpty(v *sql.NullInt64) {
+ if v != nil && v.Valid && v.Int64 != 0 {
+ enc.Int64OmitEmpty(v.Int64)
+ }
+}
+
+// AddSQLNullInt64Key adds a string to be encoded, must be used inside an object as it will encode a key
+func (enc *Encoder) AddSQLNullInt64Key(key string, v *sql.NullInt64) {
+ enc.Int64Key(key, v.Int64)
+}
+
+// AddSQLNullInt64KeyOmitEmpty 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) AddSQLNullInt64KeyOmitEmpty(key string, v *sql.NullInt64) {
+ if v != nil && v.Valid && v.Int64 != 0 {
+ enc.Int64KeyOmitEmpty(key, v.Int64)
+ }
+}
+
+// SQLNullInt64 adds a string to be encoded, must be used inside an object as it will encode a key
+func (enc *Encoder) SQLNullInt64(v *sql.NullInt64) {
+ enc.Int64(v.Int64)
+}
+
+// SQLNullInt64OmitEmpty adds a string to be encoded, must be used inside an object as it will encode a key
+func (enc *Encoder) SQLNullInt64OmitEmpty(v *sql.NullInt64) {
+ if v != nil && v.Valid && v.Int64 != 0 {
+ enc.Int64(v.Int64)
+ }
+}
+
+// SQLNullInt64Key adds a string to be encoded, must be used inside an object as it will encode a key
+func (enc *Encoder) SQLNullInt64Key(key string, v *sql.NullInt64) {
+ enc.Int64Key(key, v.Int64)
+}
+
+// SQLNullInt64KeyOmitEmpty 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) SQLNullInt64KeyOmitEmpty(key string, v *sql.NullInt64) {
+ if v != nil && v.Valid && v.Int64 != 0 {
+ enc.Int64KeyOmitEmpty(key, v.Int64)
+ }
+}
+
+// NullFloat64
+
+// EncodeSQLNullFloat64 encodes a string to
+func (enc *Encoder) EncodeSQLNullFloat64(v *sql.NullFloat64) error {
+ if enc.isPooled == 1 {
+ panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder"))
+ }
+ _, _ = enc.encodeFloat(v.Float64)
+ _, err := enc.Write()
+ if err != nil {
+ enc.err = err
+ return err
+ }
+ return nil
+}
+
+// AddSQLNullFloat64 adds a string to be encoded, must be used inside a slice or array encoding (does not encode a key)
+func (enc *Encoder) AddSQLNullFloat64(v *sql.NullFloat64) {
+ enc.Float64(v.Float64)
+}
+
+// AddSQLNullFloat64OmitEmpty 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) AddSQLNullFloat64OmitEmpty(v *sql.NullFloat64) {
+ if v != nil && v.Valid && v.Float64 != 0 {
+ enc.Float64OmitEmpty(v.Float64)
+ }
+}
+
+// AddSQLNullFloat64Key adds a string to be encoded, must be used inside an object as it will encode a key
+func (enc *Encoder) AddSQLNullFloat64Key(key string, v *sql.NullFloat64) {
+ enc.Float64Key(key, v.Float64)
+}
+
+// AddSQLNullFloat64KeyOmitEmpty 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) AddSQLNullFloat64KeyOmitEmpty(key string, v *sql.NullFloat64) {
+ if v != nil && v.Valid && v.Float64 != 0 {
+ enc.Float64KeyOmitEmpty(key, v.Float64)
+ }
+}
+
+// SQLNullFloat64 adds a string to be encoded, must be used inside an object as it will encode a key
+func (enc *Encoder) SQLNullFloat64(v *sql.NullFloat64) {
+ enc.Float64(v.Float64)
+}
+
+// SQLNullFloat64OmitEmpty adds a string to be encoded, must be used inside an object as it will encode a key
+func (enc *Encoder) SQLNullFloat64OmitEmpty(v *sql.NullFloat64) {
+ if v != nil && v.Valid && v.Float64 != 0 {
+ enc.Float64(v.Float64)
+ }
+}
+
+// SQLNullFloat64Key adds a string to be encoded, must be used inside an object as it will encode a key
+func (enc *Encoder) SQLNullFloat64Key(key string, v *sql.NullFloat64) {
+ enc.Float64Key(key, v.Float64)
+}
+
+// SQLNullFloat64KeyOmitEmpty 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) SQLNullFloat64KeyOmitEmpty(key string, v *sql.NullFloat64) {
+ if v != nil && v.Valid && v.Float64 != 0 {
+ enc.Float64KeyOmitEmpty(key, v.Float64)
+ }
+}
+
+// NullBool
+
+// EncodeSQLNullBool encodes a string to
+func (enc *Encoder) EncodeSQLNullBool(v *sql.NullBool) error {
+ if enc.isPooled == 1 {
+ panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder"))
+ }
+ _, _ = enc.encodeBool(v.Bool)
+ _, err := enc.Write()
+ if err != nil {
+ enc.err = err
+ return err
+ }
+ return nil
+}
+
+// AddSQLNullBool adds a string to be encoded, must be used inside a slice or array encoding (does not encode a key)
+func (enc *Encoder) AddSQLNullBool(v *sql.NullBool) {
+ enc.Bool(v.Bool)
+}
+
+// AddSQLNullBoolOmitEmpty 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) AddSQLNullBoolOmitEmpty(v *sql.NullBool) {
+ if v != nil && v.Valid && v.Bool != false {
+ enc.BoolOmitEmpty(v.Bool)
+ }
+}
+
+// AddSQLNullBoolKey adds a string to be encoded, must be used inside an object as it will encode a key
+func (enc *Encoder) AddSQLNullBoolKey(key string, v *sql.NullBool) {
+ enc.BoolKey(key, v.Bool)
+}
+
+// AddSQLNullBoolKeyOmitEmpty 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) AddSQLNullBoolKeyOmitEmpty(key string, v *sql.NullBool) {
+ if v != nil && v.Valid && v.Bool != false {
+ enc.BoolKeyOmitEmpty(key, v.Bool)
+ }
+}
+
+// SQLNullBool adds a string to be encoded, must be used inside an object as it will encode a key
+func (enc *Encoder) SQLNullBool(v *sql.NullBool) {
+ enc.Bool(v.Bool)
+}
+
+// SQLNullBoolOmitEmpty adds a string to be encoded, must be used inside an object as it will encode a key
+func (enc *Encoder) SQLNullBoolOmitEmpty(v *sql.NullBool) {
+ if v != nil && v.Valid && v.Bool != false {
+ enc.Bool(v.Bool)
+ }
+}
+
+// SQLNullBoolKey adds a string to be encoded, must be used inside an object as it will encode a key
+func (enc *Encoder) SQLNullBoolKey(key string, v *sql.NullBool) {
+ enc.BoolKey(key, v.Bool)
+}
+
+// SQLNullBoolKeyOmitEmpty 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) SQLNullBoolKeyOmitEmpty(key string, v *sql.NullBool) {
+ if v != nil && v.Valid && v.Bool != false {
+ enc.BoolKeyOmitEmpty(key, v.Bool)
+ }
+}
diff --git a/encode_sqlnull_test.go b/encode_sqlnull_test.go
@@ -0,0 +1,1121 @@
+package gojay
+
+import (
+ "database/sql"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+// Null String
+func TestEncoceSQLNullString(t *testing.T) {
+ testCases := []struct {
+ name string
+ sqlNullString sql.NullString
+ expectedResult string
+ err bool
+ }{
+ {
+ name: "it should encode a null string",
+ sqlNullString: sql.NullString{
+ String: "foo bar",
+ },
+ expectedResult: `"foo bar"`,
+ },
+ {
+ name: "it should return an err as the string is invalid",
+ sqlNullString: sql.NullString{
+ String: "foo \t bar",
+ },
+ expectedResult: `"foo \t bar"`,
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ var b strings.Builder
+ enc := NewEncoder(&b)
+ err := enc.EncodeSQLNullString(&testCase.sqlNullString)
+ if testCase.err {
+ assert.NotNil(t, err)
+ } else {
+ assert.Nil(t, err)
+ assert.Equal(t, testCase.expectedResult, b.String())
+ }
+ })
+ }
+
+ t.Run(
+ "should panic as the encoder is pooled",
+ 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.EncodeSQLNullString(&sql.NullString{})
+ assert.True(t, false, "should not be called as encoder should have panicked")
+ },
+ )
+
+ t.Run(
+ "should return an error as the writer encounters an error",
+ func(t *testing.T) {
+ builder := TestWriterError("")
+ enc := NewEncoder(builder)
+ err := enc.EncodeSQLNullString(&sql.NullString{})
+ assert.NotNil(t, err)
+ },
+ )
+}
+
+func TestAddSQLNullStringKey(t *testing.T) {
+ t.Run(
+ "AddSQLNullStringKey",
+ func(t *testing.T) {
+ testCases := []struct {
+ name string
+ sqlNullString sql.NullString
+ baseJSON string
+ expectedResult string
+ err bool
+ }{
+ {
+ name: "it should encode a null string",
+ sqlNullString: sql.NullString{
+ String: "foo bar",
+ },
+ baseJSON: "{",
+ expectedResult: `{"foo":"foo bar"`,
+ },
+ {
+ name: "it should encode a null string",
+ sqlNullString: sql.NullString{
+ String: "foo \t bar",
+ },
+ baseJSON: "{",
+ expectedResult: `{"foo":"foo \t bar"`,
+ },
+ {
+ name: "it should encode a null string",
+ sqlNullString: sql.NullString{
+ String: "foo \t bar",
+ },
+ baseJSON: "{",
+ expectedResult: `{"foo":"foo \t bar"`,
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ var b strings.Builder
+ enc := NewEncoder(&b)
+ enc.writeString(testCase.baseJSON)
+ enc.AddSQLNullStringKey("foo", &testCase.sqlNullString)
+ enc.Write()
+ assert.Equal(t, testCase.expectedResult, b.String())
+
+ var b2 strings.Builder
+ enc = NewEncoder(&b2)
+ enc.writeString(testCase.baseJSON)
+ enc.SQLNullStringKey("foo", &testCase.sqlNullString)
+ enc.Write()
+ assert.Equal(t, testCase.expectedResult, b2.String())
+ })
+ }
+ },
+ )
+ t.Run(
+ "AddSQLNullStringKeyOmitEmpty, is should encode a sql.NullString",
+ func(t *testing.T) {
+ testCases := []struct {
+ name string
+ sqlNullString sql.NullString
+ baseJSON string
+ expectedResult string
+ err bool
+ }{
+ {
+ name: "it should encode a null string",
+ sqlNullString: sql.NullString{
+ String: "foo bar",
+ Valid: true,
+ },
+ baseJSON: "{",
+ expectedResult: `{"foo":"foo bar"`,
+ },
+ {
+ name: "it should not encode anything as null string is invalid",
+ sqlNullString: sql.NullString{
+ String: "foo \t bar",
+ Valid: false,
+ },
+ baseJSON: "{",
+ expectedResult: `{`,
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ var b strings.Builder
+ enc := NewEncoder(&b)
+ enc.writeString(testCase.baseJSON)
+ enc.AddSQLNullStringKeyOmitEmpty("foo", &testCase.sqlNullString)
+ enc.Write()
+ assert.Equal(t, testCase.expectedResult, b.String())
+
+ var b2 strings.Builder
+ enc = NewEncoder(&b2)
+ enc.writeString(testCase.baseJSON)
+ enc.SQLNullStringKeyOmitEmpty("foo", &testCase.sqlNullString)
+ enc.Write()
+ assert.Equal(t, testCase.expectedResult, b2.String())
+ })
+ }
+ },
+ )
+}
+
+func TestAddSQLNullString(t *testing.T) {
+ t.Run(
+ "AddSQLNullString",
+ func(t *testing.T) {
+ testCases := []struct {
+ name string
+ sqlNullString sql.NullString
+ baseJSON string
+ expectedResult string
+ err bool
+ }{
+ {
+ name: "it should encode a null string",
+ sqlNullString: sql.NullString{
+ String: "foo bar",
+ },
+ baseJSON: "[",
+ expectedResult: `["foo bar"`,
+ },
+ {
+ name: "it should encode a null string",
+ sqlNullString: sql.NullString{
+ String: "foo \t bar",
+ },
+ baseJSON: "[",
+ expectedResult: `["foo \t bar"`,
+ },
+ {
+ name: "it should encode a null string",
+ sqlNullString: sql.NullString{
+ String: "foo \t bar",
+ },
+ baseJSON: "[",
+ expectedResult: `["foo \t bar"`,
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ var b strings.Builder
+ enc := NewEncoder(&b)
+ enc.writeString(testCase.baseJSON)
+ enc.AddSQLNullString(&testCase.sqlNullString)
+ enc.Write()
+ assert.Equal(t, testCase.expectedResult, b.String())
+
+ var b2 strings.Builder
+ enc = NewEncoder(&b2)
+ enc.writeString(testCase.baseJSON)
+ enc.SQLNullString(&testCase.sqlNullString)
+ enc.Write()
+ assert.Equal(t, testCase.expectedResult, b2.String())
+ })
+ }
+ },
+ )
+ t.Run(
+ "AddSQLNullStringKeyOmitEmpty, is should encode a sql.NullString",
+ func(t *testing.T) {
+ testCases := []struct {
+ name string
+ sqlNullString sql.NullString
+ baseJSON string
+ expectedResult string
+ err bool
+ }{
+ {
+ name: "it should encode a null string",
+ sqlNullString: sql.NullString{
+ String: "foo bar",
+ Valid: true,
+ },
+ baseJSON: "[",
+ expectedResult: `["foo bar"`,
+ },
+ {
+ name: "it should not encode anything as null string is invalid",
+ sqlNullString: sql.NullString{
+ String: "foo \t bar",
+ Valid: false,
+ },
+ baseJSON: "[",
+ expectedResult: `[`,
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ var b strings.Builder
+ enc := NewEncoder(&b)
+ enc.writeString(testCase.baseJSON)
+ enc.AddSQLNullStringOmitEmpty(&testCase.sqlNullString)
+ enc.Write()
+ assert.Equal(t, testCase.expectedResult, b.String())
+
+ var b2 strings.Builder
+ enc = NewEncoder(&b2)
+ enc.writeString(testCase.baseJSON)
+ enc.SQLNullStringOmitEmpty(&testCase.sqlNullString)
+ enc.Write()
+ assert.Equal(t, testCase.expectedResult, b2.String())
+ })
+ }
+ },
+ )
+}
+
+// NullInt64
+func TestEncoceSQLNullInt64(t *testing.T) {
+ testCases := []struct {
+ name string
+ sqlNullInt64 sql.NullInt64
+ expectedResult string
+ err bool
+ }{
+ {
+ name: "it should encode a null string",
+ sqlNullInt64: sql.NullInt64{
+ Int64: int64(1),
+ },
+ expectedResult: `1`,
+ },
+ {
+ name: "it should return an err as the string is invalid",
+ sqlNullInt64: sql.NullInt64{
+ Int64: int64(2),
+ },
+ expectedResult: `2`,
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ var b strings.Builder
+ enc := NewEncoder(&b)
+ err := enc.EncodeSQLNullInt64(&testCase.sqlNullInt64)
+ if testCase.err {
+ assert.NotNil(t, err)
+ } else {
+ assert.Nil(t, err)
+ assert.Equal(t, testCase.expectedResult, b.String())
+ }
+ })
+ }
+ t.Run(
+ "should panic as the encoder is pooled",
+ 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.EncodeSQLNullInt64(&sql.NullInt64{})
+ assert.True(t, false, "should not be called as encoder should have panicked")
+ },
+ )
+ t.Run(
+ "should return an error as the writer encounters an error",
+ func(t *testing.T) {
+ builder := TestWriterError("")
+ enc := NewEncoder(builder)
+ err := enc.EncodeSQLNullInt64(&sql.NullInt64{})
+ assert.NotNil(t, err)
+ },
+ )
+}
+
+func TestAddSQLNullInt64Key(t *testing.T) {
+ t.Run(
+ "AddSQLNullInt64Key",
+ func(t *testing.T) {
+ testCases := []struct {
+ name string
+ sqlNullInt64 sql.NullInt64
+ baseJSON string
+ expectedResult string
+ err bool
+ }{
+ {
+ name: "it should encode a null string",
+ sqlNullInt64: sql.NullInt64{
+ Int64: 1,
+ },
+ baseJSON: "{",
+ expectedResult: `{"foo":1`,
+ },
+ {
+ name: "it should encode a null string",
+ sqlNullInt64: sql.NullInt64{
+ Int64: 2,
+ },
+ baseJSON: "{",
+ expectedResult: `{"foo":2`,
+ },
+ {
+ name: "it should encode a null string",
+ sqlNullInt64: sql.NullInt64{
+ Int64: 2,
+ },
+ baseJSON: "{",
+ expectedResult: `{"foo":2`,
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ var b strings.Builder
+ enc := NewEncoder(&b)
+ enc.writeString(testCase.baseJSON)
+ enc.AddSQLNullInt64Key("foo", &testCase.sqlNullInt64)
+ enc.Write()
+ assert.Equal(t, testCase.expectedResult, b.String())
+
+ var b2 strings.Builder
+ enc = NewEncoder(&b2)
+ enc.writeString(testCase.baseJSON)
+ enc.SQLNullInt64Key("foo", &testCase.sqlNullInt64)
+ enc.Write()
+ assert.Equal(t, testCase.expectedResult, b2.String())
+ })
+ }
+ },
+ )
+ t.Run(
+ "AddSQLNullInt64KeyOmitEmpty, is should encode a sql.NullInt64",
+ func(t *testing.T) {
+ testCases := []struct {
+ name string
+ sqlNullInt64 sql.NullInt64
+ baseJSON string
+ expectedResult string
+ err bool
+ }{
+ {
+ name: "it should encode a null string",
+ sqlNullInt64: sql.NullInt64{
+ Int64: 1,
+ Valid: true,
+ },
+ baseJSON: "{",
+ expectedResult: `{"foo":1`,
+ },
+ {
+ name: "it should not encode anything as null string is invalid",
+ sqlNullInt64: sql.NullInt64{
+ Int64: 2,
+ Valid: false,
+ },
+ baseJSON: "{",
+ expectedResult: `{`,
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ var b strings.Builder
+ enc := NewEncoder(&b)
+ enc.writeString(testCase.baseJSON)
+ enc.AddSQLNullInt64KeyOmitEmpty("foo", &testCase.sqlNullInt64)
+ enc.Write()
+ assert.Equal(t, testCase.expectedResult, b.String())
+
+ var b2 strings.Builder
+ enc = NewEncoder(&b2)
+ enc.writeString(testCase.baseJSON)
+ enc.SQLNullInt64KeyOmitEmpty("foo", &testCase.sqlNullInt64)
+ enc.Write()
+ assert.Equal(t, testCase.expectedResult, b2.String())
+ })
+ }
+ },
+ )
+}
+
+func TestAddSQLNullInt64(t *testing.T) {
+ t.Run(
+ "AddSQLNullInt64",
+ func(t *testing.T) {
+ testCases := []struct {
+ name string
+ sqlNullInt64 sql.NullInt64
+ baseJSON string
+ expectedResult string
+ err bool
+ }{
+ {
+ name: "it should encode a null string",
+ sqlNullInt64: sql.NullInt64{
+ Int64: 1,
+ },
+ baseJSON: "[",
+ expectedResult: `[1`,
+ },
+ {
+ name: "it should encode a null string",
+ sqlNullInt64: sql.NullInt64{
+ Int64: 2,
+ },
+ baseJSON: "[",
+ expectedResult: `[2`,
+ },
+ {
+ name: "it should encode a null string",
+ sqlNullInt64: sql.NullInt64{
+ Int64: 2,
+ },
+ baseJSON: "[",
+ expectedResult: `[2`,
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ var b strings.Builder
+ enc := NewEncoder(&b)
+ enc.writeString(testCase.baseJSON)
+ enc.AddSQLNullInt64(&testCase.sqlNullInt64)
+ enc.Write()
+ assert.Equal(t, testCase.expectedResult, b.String())
+
+ var b2 strings.Builder
+ enc = NewEncoder(&b2)
+ enc.writeString(testCase.baseJSON)
+ enc.SQLNullInt64(&testCase.sqlNullInt64)
+ enc.Write()
+ assert.Equal(t, testCase.expectedResult, b2.String())
+ })
+ }
+ },
+ )
+ t.Run(
+ "AddSQLNullInt64KeyOmitEmpty, is should encode a sql.NullInt64",
+ func(t *testing.T) {
+ testCases := []struct {
+ name string
+ sqlNullInt64 sql.NullInt64
+ baseJSON string
+ expectedResult string
+ err bool
+ }{
+ {
+ name: "it should encode a null string",
+ sqlNullInt64: sql.NullInt64{
+ Int64: 2,
+ Valid: true,
+ },
+ baseJSON: "[",
+ expectedResult: `[2`,
+ },
+ {
+ name: "it should not encode anything as null string is invalid",
+ sqlNullInt64: sql.NullInt64{
+ Int64: 2,
+ Valid: false,
+ },
+ baseJSON: "[",
+ expectedResult: `[`,
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ var b strings.Builder
+ enc := NewEncoder(&b)
+ enc.writeString(testCase.baseJSON)
+ enc.AddSQLNullInt64OmitEmpty(&testCase.sqlNullInt64)
+ enc.Write()
+ assert.Equal(t, testCase.expectedResult, b.String())
+
+ var b2 strings.Builder
+ enc = NewEncoder(&b2)
+ enc.writeString(testCase.baseJSON)
+ enc.SQLNullInt64OmitEmpty(&testCase.sqlNullInt64)
+ enc.Write()
+ assert.Equal(t, testCase.expectedResult, b2.String())
+ })
+ }
+ },
+ )
+}
+
+// NullFloat64
+func TestEncoceSQLNullFloat64(t *testing.T) {
+ testCases := []struct {
+ name string
+ sqlNullFloat64 sql.NullFloat64
+ expectedResult string
+ err bool
+ }{
+ {
+ name: "it should encode a null string",
+ sqlNullFloat64: sql.NullFloat64{
+ Float64: float64(1),
+ },
+ expectedResult: `1`,
+ },
+ {
+ name: "it should return an err as the string is invalid",
+ sqlNullFloat64: sql.NullFloat64{
+ Float64: float64(2),
+ },
+ expectedResult: `2`,
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ var b strings.Builder
+ enc := NewEncoder(&b)
+ err := enc.EncodeSQLNullFloat64(&testCase.sqlNullFloat64)
+ if testCase.err {
+ assert.NotNil(t, err)
+ } else {
+ assert.Nil(t, err)
+ assert.Equal(t, testCase.expectedResult, b.String())
+ }
+ })
+ }
+ t.Run(
+ "should panic as the encoder is pooled",
+ 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.EncodeSQLNullFloat64(&sql.NullFloat64{})
+ assert.True(t, false, "should not be called as encoder should have panicked")
+ },
+ )
+
+ t.Run(
+ "should return an error as the writer encounters an error",
+ func(t *testing.T) {
+ builder := TestWriterError("")
+ enc := NewEncoder(builder)
+ err := enc.EncodeSQLNullFloat64(&sql.NullFloat64{})
+ assert.NotNil(t, err)
+ },
+ )
+}
+
+func TestAddSQLNullFloat64Key(t *testing.T) {
+ t.Run(
+ "AddSQLNullFloat64Key",
+ func(t *testing.T) {
+ testCases := []struct {
+ name string
+ sqlNullFloat64 sql.NullFloat64
+ baseJSON string
+ expectedResult string
+ err bool
+ }{
+ {
+ name: "it should encode a null string",
+ sqlNullFloat64: sql.NullFloat64{
+ Float64: 1,
+ },
+ baseJSON: "{",
+ expectedResult: `{"foo":1`,
+ },
+ {
+ name: "it should encode a null string",
+ sqlNullFloat64: sql.NullFloat64{
+ Float64: 2,
+ },
+ baseJSON: "{",
+ expectedResult: `{"foo":2`,
+ },
+ {
+ name: "it should encode a null string",
+ sqlNullFloat64: sql.NullFloat64{
+ Float64: 2,
+ },
+ baseJSON: "{",
+ expectedResult: `{"foo":2`,
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ var b strings.Builder
+ enc := NewEncoder(&b)
+ enc.writeString(testCase.baseJSON)
+ enc.AddSQLNullFloat64Key("foo", &testCase.sqlNullFloat64)
+ enc.Write()
+ assert.Equal(t, testCase.expectedResult, b.String())
+
+ var b2 strings.Builder
+ enc = NewEncoder(&b2)
+ enc.writeString(testCase.baseJSON)
+ enc.SQLNullFloat64Key("foo", &testCase.sqlNullFloat64)
+ enc.Write()
+ assert.Equal(t, testCase.expectedResult, b2.String())
+ })
+ }
+ },
+ )
+ t.Run(
+ "AddSQLNullFloat64KeyOmitEmpty, is should encode a sql.NullFloat64",
+ func(t *testing.T) {
+ testCases := []struct {
+ name string
+ sqlNullFloat64 sql.NullFloat64
+ baseJSON string
+ expectedResult string
+ err bool
+ }{
+ {
+ name: "it should encode a null string",
+ sqlNullFloat64: sql.NullFloat64{
+ Float64: 1,
+ Valid: true,
+ },
+ baseJSON: "{",
+ expectedResult: `{"foo":1`,
+ },
+ {
+ name: "it should not encode anything as null string is invalid",
+ sqlNullFloat64: sql.NullFloat64{
+ Float64: 2,
+ Valid: false,
+ },
+ baseJSON: "{",
+ expectedResult: `{`,
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ var b strings.Builder
+ enc := NewEncoder(&b)
+ enc.writeString(testCase.baseJSON)
+ enc.AddSQLNullFloat64KeyOmitEmpty("foo", &testCase.sqlNullFloat64)
+ enc.Write()
+ assert.Equal(t, testCase.expectedResult, b.String())
+
+ var b2 strings.Builder
+ enc = NewEncoder(&b2)
+ enc.writeString(testCase.baseJSON)
+ enc.SQLNullFloat64KeyOmitEmpty("foo", &testCase.sqlNullFloat64)
+ enc.Write()
+ assert.Equal(t, testCase.expectedResult, b2.String())
+ })
+ }
+ },
+ )
+}
+
+func TestAddSQLNullFloat64(t *testing.T) {
+ t.Run(
+ "AddSQLNullFloat64",
+ func(t *testing.T) {
+ testCases := []struct {
+ name string
+ sqlNullFloat64 sql.NullFloat64
+ baseJSON string
+ expectedResult string
+ err bool
+ }{
+ {
+ name: "it should encode a null string",
+ sqlNullFloat64: sql.NullFloat64{
+ Float64: 1,
+ },
+ baseJSON: "[",
+ expectedResult: `[1`,
+ },
+ {
+ name: "it should encode a null string",
+ sqlNullFloat64: sql.NullFloat64{
+ Float64: 2,
+ },
+ baseJSON: "[",
+ expectedResult: `[2`,
+ },
+ {
+ name: "it should encode a null string",
+ sqlNullFloat64: sql.NullFloat64{
+ Float64: 2,
+ },
+ baseJSON: "[",
+ expectedResult: `[2`,
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ var b strings.Builder
+ enc := NewEncoder(&b)
+ enc.writeString(testCase.baseJSON)
+ enc.AddSQLNullFloat64(&testCase.sqlNullFloat64)
+ enc.Write()
+ assert.Equal(t, testCase.expectedResult, b.String())
+
+ var b2 strings.Builder
+ enc = NewEncoder(&b2)
+ enc.writeString(testCase.baseJSON)
+ enc.SQLNullFloat64(&testCase.sqlNullFloat64)
+ enc.Write()
+ assert.Equal(t, testCase.expectedResult, b2.String())
+ })
+ }
+ },
+ )
+ t.Run(
+ "AddSQLNullFloat64KeyOmitEmpty, is should encode a sql.NullFloat64",
+ func(t *testing.T) {
+ testCases := []struct {
+ name string
+ sqlNullFloat64 sql.NullFloat64
+ baseJSON string
+ expectedResult string
+ err bool
+ }{
+ {
+ name: "it should encode a null string",
+ sqlNullFloat64: sql.NullFloat64{
+ Float64: 2,
+ Valid: true,
+ },
+ baseJSON: "[",
+ expectedResult: `[2`,
+ },
+ {
+ name: "it should not encode anything as null string is invalid",
+ sqlNullFloat64: sql.NullFloat64{
+ Float64: 2,
+ Valid: false,
+ },
+ baseJSON: "[",
+ expectedResult: `[`,
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ var b strings.Builder
+ enc := NewEncoder(&b)
+ enc.writeString(testCase.baseJSON)
+ enc.AddSQLNullFloat64OmitEmpty(&testCase.sqlNullFloat64)
+ enc.Write()
+ assert.Equal(t, testCase.expectedResult, b.String())
+
+ var b2 strings.Builder
+ enc = NewEncoder(&b2)
+ enc.writeString(testCase.baseJSON)
+ enc.SQLNullFloat64OmitEmpty(&testCase.sqlNullFloat64)
+ enc.Write()
+ assert.Equal(t, testCase.expectedResult, b2.String())
+ })
+ }
+ },
+ )
+}
+
+// NullBool
+func TestEncoceSQLNullBool(t *testing.T) {
+ testCases := []struct {
+ name string
+ sqlNullBool sql.NullBool
+ expectedResult string
+ err bool
+ }{
+ {
+ name: "it should encode a null string",
+ sqlNullBool: sql.NullBool{
+ Bool: true,
+ },
+ expectedResult: `true`,
+ },
+ {
+ name: "it should return an err as the string is invalid",
+ sqlNullBool: sql.NullBool{
+ Bool: false,
+ },
+ expectedResult: `false`,
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ var b strings.Builder
+ enc := NewEncoder(&b)
+ err := enc.EncodeSQLNullBool(&testCase.sqlNullBool)
+ if testCase.err {
+ assert.NotNil(t, err)
+ } else {
+ assert.Nil(t, err)
+ assert.Equal(t, testCase.expectedResult, b.String())
+ }
+ })
+ }
+ t.Run(
+ "should panic as the encoder is pooled",
+ 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.EncodeSQLNullBool(&sql.NullBool{})
+ assert.True(t, false, "should not be called as encoder should have panicked")
+ },
+ )
+
+ t.Run(
+ "should return an error as the writer encounters an error",
+ func(t *testing.T) {
+ builder := TestWriterError("")
+ enc := NewEncoder(builder)
+ err := enc.EncodeSQLNullBool(&sql.NullBool{})
+ assert.NotNil(t, err)
+ },
+ )
+}
+
+func TestAddSQLNullBoolKey(t *testing.T) {
+ t.Run(
+ "AddSQLNullBoolKey",
+ func(t *testing.T) {
+ testCases := []struct {
+ name string
+ sqlNullBool sql.NullBool
+ baseJSON string
+ expectedResult string
+ err bool
+ }{
+ {
+ name: "it should encode a null string",
+ sqlNullBool: sql.NullBool{
+ Bool: true,
+ },
+ baseJSON: "{",
+ expectedResult: `{"foo":true`,
+ },
+ {
+ name: "it should encode a null string",
+ sqlNullBool: sql.NullBool{
+ Bool: false,
+ },
+ baseJSON: "{",
+ expectedResult: `{"foo":false`,
+ },
+ {
+ name: "it should encode a null string",
+ sqlNullBool: sql.NullBool{
+ Bool: true,
+ },
+ baseJSON: "{",
+ expectedResult: `{"foo":true`,
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ var b strings.Builder
+ enc := NewEncoder(&b)
+ enc.writeString(testCase.baseJSON)
+ enc.AddSQLNullBoolKey("foo", &testCase.sqlNullBool)
+ enc.Write()
+ assert.Equal(t, testCase.expectedResult, b.String())
+
+ var b2 strings.Builder
+ enc = NewEncoder(&b2)
+ enc.writeString(testCase.baseJSON)
+ enc.SQLNullBoolKey("foo", &testCase.sqlNullBool)
+ enc.Write()
+ assert.Equal(t, testCase.expectedResult, b2.String())
+ })
+ }
+ },
+ )
+ t.Run(
+ "AddSQLNullBoolKeyOmitEmpty, is should encode a sql.NullBool",
+ func(t *testing.T) {
+ testCases := []struct {
+ name string
+ sqlNullBool sql.NullBool
+ baseJSON string
+ expectedResult string
+ err bool
+ }{
+ {
+ name: "it should encode a null string",
+ sqlNullBool: sql.NullBool{
+ Bool: true,
+ Valid: true,
+ },
+ baseJSON: "{",
+ expectedResult: `{"foo":true`,
+ },
+ {
+ name: "it should not encode anything as null string is invalid",
+ sqlNullBool: sql.NullBool{
+ Bool: true,
+ Valid: false,
+ },
+ baseJSON: "{",
+ expectedResult: `{`,
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ var b strings.Builder
+ enc := NewEncoder(&b)
+ enc.writeString(testCase.baseJSON)
+ enc.AddSQLNullBoolKeyOmitEmpty("foo", &testCase.sqlNullBool)
+ enc.Write()
+ assert.Equal(t, testCase.expectedResult, b.String())
+
+ var b2 strings.Builder
+ enc = NewEncoder(&b2)
+ enc.writeString(testCase.baseJSON)
+ enc.SQLNullBoolKeyOmitEmpty("foo", &testCase.sqlNullBool)
+ enc.Write()
+ assert.Equal(t, testCase.expectedResult, b2.String())
+ })
+ }
+ },
+ )
+}
+
+func TestAddSQLNullBool(t *testing.T) {
+ t.Run(
+ "AddSQLNullBool",
+ func(t *testing.T) {
+ testCases := []struct {
+ name string
+ sqlNullBool sql.NullBool
+ baseJSON string
+ expectedResult string
+ err bool
+ }{
+ {
+ name: "it should encode a null string",
+ sqlNullBool: sql.NullBool{
+ Bool: true,
+ },
+ baseJSON: "[",
+ expectedResult: `[true`,
+ },
+ {
+ name: "it should encode a null string",
+ sqlNullBool: sql.NullBool{
+ Bool: true,
+ },
+ baseJSON: "[",
+ expectedResult: `[true`,
+ },
+ {
+ name: "it should encode a null string",
+ sqlNullBool: sql.NullBool{
+ Bool: false,
+ },
+ baseJSON: "[",
+ expectedResult: `[false`,
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ var b strings.Builder
+ enc := NewEncoder(&b)
+ enc.writeString(testCase.baseJSON)
+ enc.AddSQLNullBool(&testCase.sqlNullBool)
+ enc.Write()
+ assert.Equal(t, testCase.expectedResult, b.String())
+
+ var b2 strings.Builder
+ enc = NewEncoder(&b2)
+ enc.writeString(testCase.baseJSON)
+ enc.SQLNullBool(&testCase.sqlNullBool)
+ enc.Write()
+ assert.Equal(t, testCase.expectedResult, b2.String())
+ })
+ }
+ },
+ )
+ t.Run(
+ "AddSQLNullBoolKeyOmitEmpty, is should encode a sql.NullBool",
+ func(t *testing.T) {
+ testCases := []struct {
+ name string
+ sqlNullBool sql.NullBool
+ baseJSON string
+ expectedResult string
+ err bool
+ }{
+ {
+ name: "it should encode a null string",
+ sqlNullBool: sql.NullBool{
+ Bool: true,
+ Valid: true,
+ },
+ baseJSON: "[",
+ expectedResult: `[true`,
+ },
+ {
+ name: "it should not encode anything as null string is invalid",
+ sqlNullBool: sql.NullBool{
+ Bool: true,
+ Valid: false,
+ },
+ baseJSON: "[",
+ expectedResult: `[`,
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ var b strings.Builder
+ enc := NewEncoder(&b)
+ enc.writeString(testCase.baseJSON)
+ enc.AddSQLNullBoolOmitEmpty(&testCase.sqlNullBool)
+ enc.Write()
+ assert.Equal(t, testCase.expectedResult, b.String())
+
+ var b2 strings.Builder
+ enc = NewEncoder(&b2)
+ enc.writeString(testCase.baseJSON)
+ enc.SQLNullBoolOmitEmpty(&testCase.sqlNullBool)
+ enc.Write()
+ assert.Equal(t, testCase.expectedResult, b2.String())
+ })
+ }
+ },
+ )
+}
diff --git a/encode_time.go b/encode_time.go
@@ -0,0 +1,62 @@
+package gojay
+
+import (
+ "time"
+)
+
+func (enc *Encoder) EncodeTime(t *time.Time, format string) error {
+ if enc.isPooled == 1 {
+ panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder"))
+ }
+ _, _ = enc.encodeTime(t, format)
+ _, err := enc.Write()
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// encodeInt encodes an int to JSON
+func (enc *Encoder) encodeTime(t *time.Time, format string) ([]byte, error) {
+ enc.writeByte('"')
+ enc.buf = t.AppendFormat(enc.buf, format)
+ enc.writeByte('"')
+ return enc.buf, nil
+}
+
+// AddTimeKey adds an *time.Time to be encoded with the given format, must be used inside an object as it will encode a key
+func (enc *Encoder) AddTimeKey(key string, t *time.Time, format string) {
+ enc.TimeKey(key, t, format)
+}
+
+// TimeKey adds an *time.Time to be encoded with the given format, must be used inside an object as it will encode a key
+func (enc *Encoder) TimeKey(key string, t *time.Time, format string) {
+ enc.grow(10 + len(key))
+ r := enc.getPreviousRune()
+ if r != '{' {
+ enc.writeTwoBytes(',', '"')
+ } else {
+ enc.writeByte('"')
+ }
+ enc.writeStringEscape(key)
+ enc.writeBytes(objKeyStr)
+ enc.buf = t.AppendFormat(enc.buf, format)
+ enc.writeByte('"')
+}
+
+// AddTime adds an *time.Time to be encoded with the given format, must be used inside a slice or array encoding (does not encode a key)
+func (enc *Encoder) AddTime(t *time.Time, format string) {
+ enc.Time(t, format)
+}
+
+// Time adds an *time.Time to be encoded with the given format, must be used inside a slice or array encoding (does not encode a key)
+func (enc *Encoder) Time(t *time.Time, format string) {
+ enc.grow(10)
+ r := enc.getPreviousRune()
+ if r != '[' {
+ enc.writeByte(',')
+ }
+ enc.writeByte('"')
+ enc.buf = t.AppendFormat(enc.buf, format)
+ enc.writeByte('"')
+}
diff --git a/encode_time_test.go b/encode_time_test.go
@@ -0,0 +1,156 @@
+package gojay
+
+import (
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestEncodeTime(t *testing.T) {
+ testCases := []struct {
+ name string
+ tt string
+ format string
+ expectedJSON string
+ err bool
+ }{
+ {
+ name: "basic",
+ tt: "2018-02-01",
+ format: "2006-01-02",
+ expectedJSON: `"2018-02-01"`,
+ err: false,
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ b := strings.Builder{}
+ tt, err := time.Parse(testCase.format, testCase.tt)
+ assert.Nil(t, err)
+ enc := NewEncoder(&b)
+ err = enc.EncodeTime(&tt, testCase.format)
+ if !testCase.err {
+ assert.Nil(t, err)
+ assert.Equal(t, testCase.expectedJSON, b.String())
+ }
+ })
+ }
+ t.Run("encode-time-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.EncodeTime(&time.Time{}, "")
+ assert.True(t, false, "should not be called as encoder should have panicked")
+ })
+ t.Run("write-error", func(t *testing.T) {
+ w := TestWriterError("")
+ enc := BorrowEncoder(w)
+ defer enc.Release()
+ err := enc.EncodeTime(&time.Time{}, "")
+ assert.NotNil(t, err, "err should not be nil")
+ })
+}
+
+func TestAddTimeKey(t *testing.T) {
+ testCases := []struct {
+ name string
+ tt string
+ format string
+ expectedJSON string
+ baseJSON string
+ err bool
+ }{
+ {
+ name: "basic",
+ tt: "2018-02-01",
+ format: "2006-01-02",
+ baseJSON: "{",
+ expectedJSON: `{"test":"2018-02-01"`,
+ err: false,
+ },
+ {
+ name: "basic",
+ tt: "2018-02-01",
+ format: "2006-01-02",
+ baseJSON: `{""`,
+ expectedJSON: `{"","test":"2018-02-01"`,
+ err: false,
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ b := strings.Builder{}
+ tt, err := time.Parse(testCase.format, testCase.tt)
+ assert.Nil(t, err)
+ enc := NewEncoder(&b)
+ enc.writeString(testCase.baseJSON)
+ enc.AddTimeKey("test", &tt, testCase.format)
+ enc.Write()
+ if !testCase.err {
+ assert.Nil(t, err)
+ assert.Equal(t, testCase.expectedJSON, b.String())
+ }
+ })
+ }
+}
+
+func TestAddTime(t *testing.T) {
+ testCases := []struct {
+ name string
+ tt string
+ format string
+ expectedJSON string
+ baseJSON string
+ err bool
+ }{
+ {
+ name: "basic",
+ tt: "2018-02-01",
+ format: "2006-01-02",
+ baseJSON: "[",
+ expectedJSON: `["2018-02-01"`,
+ err: false,
+ },
+ {
+ name: "basic",
+ tt: "2018-02-01",
+ format: "2006-01-02",
+ baseJSON: "[",
+ expectedJSON: `["2018-02-01"`,
+ err: false,
+ },
+ {
+ name: "basic",
+ tt: "2018-02-01",
+ format: "2006-01-02",
+ baseJSON: `[""`,
+ expectedJSON: `["","2018-02-01"`,
+ err: false,
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ b := strings.Builder{}
+ tt, err := time.Parse(testCase.format, testCase.tt)
+ assert.Nil(t, err)
+ enc := NewEncoder(&b)
+ enc.writeString(testCase.baseJSON)
+ enc.AddTime(&tt, testCase.format)
+ enc.Write()
+ if !testCase.err {
+ assert.Nil(t, err)
+ assert.Equal(t, testCase.expectedJSON, b.String())
+ }
+ })
+ }
+}