gojay

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

commit f478553906c9b63f1581a1d5820503bd7c8c97ec
parent f5420ead5143cf0f53ba0d08ab8fc8b023b20efc
Author: Francois Parquet <francois.parquet@gmail.com>
Date:   Sat,  5 May 2018 03:01:23 +0800

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

add escaping sequence for encoding add EncodeObjectFunc and DecodeObjectFunc custom function types implementing MarshalerObject/UnmarshalerObject, add tests
Diffstat:
Mbenchmarks/encoder/encoder_bench_small_test.go | 19+++++++++++++++++++
Mdecode_object.go | 20++++++++++++++++++++
Mdecode_object_test.go | 25+++++++++++++++++++++++++
Mdecode_string.go | 2+-
Mdecode_string_test.go | 32++++++++++++++++++++++++++++++++
Mencode_array.go | 12+++++++++---
Mencode_bool.go | 9+++++++--
Mencode_builder.go | 30+++++++++++++++++++++++-------
Mencode_builder_test.go | 16----------------
Mencode_number.go | 23+++++++++++++++++------
Mencode_object.go | 44++++++++++++++++++++++++++++++++++++--------
Mencode_object_test.go | 12++++++++++++
Mencode_stream_test.go | 6++++++
Mencode_string.go | 21++++++++++++---------
Mencode_string_test.go | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Mexamples/http-json/main.go | 8+++++---
16 files changed, 274 insertions(+), 55 deletions(-)

diff --git a/benchmarks/encoder/encoder_bench_small_test.go b/benchmarks/encoder/encoder_bench_small_test.go @@ -48,6 +48,25 @@ 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) { + enc.AddIntKey("st", 1) + enc.AddIntKey("sid", 1) + enc.AddStringKey("tt", "test") + enc.AddIntKey("gr", 1) + enc.AddStringKey("uuid", "test") + enc.AddStringKey("ip", "test") + enc.AddStringKey("ua", "test") + enc.AddIntKey("tz", 1) + enc.AddIntKey("v", 1) + })); err != nil { + b.Fatal(err) + } + } +} + func TestGoJayEncodeSmallStruct(t *testing.T) { if output, err := gojay.MarshalObject(benchmarks.NewSmallPayload()); err != nil { t.Fatal(err) diff --git a/decode_object.go b/decode_object.go @@ -217,3 +217,23 @@ func (dec *Decoder) skipData() error { } return InvalidJSONError("Invalid JSON") } + +// DecodeObjectFunc is a custom func type implementating UnarshaleObject. +// Use it to cast a func(*Decoder) to Unmarshal an object. +// +// str := "" +// dec := gojay.NewDecoder(io.Reader) +// dec.DecodeObject(gojay.DecodeObjectFunc(func(dec *gojay.Decoder, k string) error { +// return dec.AddString(&str) +// })) +type DecodeObjectFunc func(*Decoder, string) error + +// UnmarshalObject implements UnarshalerObject. +func (f DecodeObjectFunc) UnmarshalObject(dec *Decoder, k string) error { + return f(dec, k) +} + +// NKeys implements UnarshalerObject. +func (f DecodeObjectFunc) NKeys() int { + return 0 +} diff --git a/decode_object_test.go b/decode_object_test.go @@ -381,6 +381,31 @@ func TestDecoderObjectDecoderAPIReadCloser(t *testing.T) { assert.Len(t, m, 5, "len of m should be 5") } +func TestDecoderObjectDecoderAPIFuncReadCloser(t *testing.T) { + readCloser := ReadCloser{ + json: []byte(`{ + "test": "string", + "test2": "string", + "test3": "string", + "test4": "string", + "test5": "string", + }`), + } + m := myMap(make(map[string]string)) + dec := NewDecoder(&readCloser) + err := dec.DecodeObject(DecodeObjectFunc(func(dec *Decoder, k string) error { + str := "" + err := dec.AddString(&str) + if err != nil { + return err + } + m[k] = str + return nil + })) + assert.Nil(t, err, "err should be nil") + assert.Len(t, m, 5, "len of m should be 5") +} + func TestDecoderObjectDecoderInvalidJSONError(t *testing.T) { v := &TestObj{} dec := NewDecoder(strings.NewReader(`{"err:}`)) diff --git a/decode_string.go b/decode_string.go @@ -154,7 +154,7 @@ func (dec *Decoder) skipEscapedString() error { } } } - return nil + return InvalidJSONError("Invalid JSON") } func (dec *Decoder) skipString() error { diff --git a/decode_string_test.go b/decode_string_test.go @@ -70,3 +70,35 @@ func TestDecoderStringPoolError(t *testing.T) { _ = 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") +} diff --git a/encode_array.go b/encode_array.go @@ -25,6 +25,7 @@ func (enc *Encoder) encodeArray(v MarshalerArray) ([]byte, error) { // value must implement Marshaler func (enc *Encoder) AddArray(v MarshalerArray) { if v.IsNil() { + enc.grow(3) r, ok := enc.getPreviousRune() if ok && r != '[' { enc.writeByte(',') @@ -33,6 +34,7 @@ func (enc *Encoder) AddArray(v MarshalerArray) { enc.writeByte(']') return } + enc.grow(100) r, ok := enc.getPreviousRune() if ok && r != '[' { enc.writeByte(',') @@ -48,6 +50,7 @@ func (enc *Encoder) AddArrayOmitEmpty(v MarshalerArray) { if v.IsNil() { return } + enc.grow(4) r, ok := enc.getPreviousRune() if ok && r != '[' { enc.writeByte(',') @@ -61,22 +64,24 @@ func (enc *Encoder) AddArrayOmitEmpty(v MarshalerArray) { // value must implement Marshaler func (enc *Encoder) AddArrayKey(key string, v MarshalerArray) { if v.IsNil() { + enc.grow(2 + len(key)) r, ok := enc.getPreviousRune() if ok && r != '[' { enc.writeByte(',') } enc.writeByte('"') - enc.writeString(key) + enc.writeStringEscape(key) enc.writeBytes(objKeyArr) enc.writeByte(']') return } + enc.grow(5 + len(key)) r, ok := enc.getPreviousRune() if ok && r != '[' && r != '{' { enc.writeByte(',') } enc.writeByte('"') - enc.writeString(key) + enc.writeStringEscape(key) enc.writeBytes(objKeyArr) v.MarshalArray(enc) enc.writeByte(']') @@ -88,12 +93,13 @@ func (enc *Encoder) AddArrayKeyOmitEmpty(key string, v MarshalerArray) { if v.IsNil() { return } + enc.grow(5 + len(key)) r, ok := enc.getPreviousRune() if ok && r != '[' && r != '{' { enc.writeByte(',') } enc.writeByte('"') - enc.writeString(key) + enc.writeStringEscape(key) enc.writeBytes(objKeyArr) v.MarshalArray(enc) enc.writeByte(']') diff --git a/encode_bool.go b/encode_bool.go @@ -18,6 +18,7 @@ func (enc *Encoder) EncodeBool(v bool) error { // encodeBool encodes a bool to JSON func (enc *Encoder) encodeBool(v bool) ([]byte, error) { + enc.grow(5) if v { enc.writeString("true") } else { @@ -28,6 +29,7 @@ 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.grow(5) r, ok := enc.getPreviousRune() if ok && r != '[' { enc.writeByte(',') @@ -44,6 +46,7 @@ func (enc *Encoder) AddBoolOmitEmpty(v bool) { if v == false { return } + enc.grow(5) r, ok := enc.getPreviousRune() if ok && r != '[' { enc.writeByte(',') @@ -53,12 +56,13 @@ func (enc *Encoder) AddBoolOmitEmpty(v bool) { // 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) { + enc.grow(5 + len(key)) r, ok := enc.getPreviousRune() if ok && r != '{' && r != '[' { enc.writeByte(',') } enc.writeByte('"') - enc.writeString(key) + enc.writeStringEscape(key) enc.writeBytes(objKey) enc.buf = strconv.AppendBool(enc.buf, value) } @@ -69,12 +73,13 @@ func (enc *Encoder) AddBoolKeyOmitEmpty(key string, v bool) { if v == false { return } + enc.grow(5 + len(key)) r, ok := enc.getPreviousRune() if ok && r != '{' && r != '[' { enc.writeByte(',') } enc.writeByte('"') - enc.writeString(key) + enc.writeStringEscape(key) enc.writeBytes(objKey) enc.buf = strconv.AppendBool(enc.buf, v) } diff --git a/encode_builder.go b/encode_builder.go @@ -1,16 +1,9 @@ -// Copyright 2017 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - package gojay // grow grows b's capacity, if necessary, to guarantee space for // another n bytes. After grow(n), at least n bytes can be written to b // without another allocation. If n is negative, grow panics. func (enc *Encoder) grow(n int) { - if n < 0 { - panic("Builder.grow: negative count") - } if cap(enc.buf)-len(enc.buf) < n { Buf := make([]byte, len(enc.buf), 2*cap(enc.buf)+n) copy(Buf, enc.buf) @@ -35,3 +28,26 @@ func (enc *Encoder) writeByte(c byte) { func (enc *Encoder) writeString(s string) { enc.buf = append(enc.buf, s...) } + +func (enc *Encoder) writeStringEscape(s string) { + l := len(s) + for i := 0; i < l; i++ { + switch s[i] { + case '\\', '"': + enc.writeByte('\\') + enc.writeByte(s[i]) + case '\n': + enc.writeByte('\\') + enc.writeByte('n') + case '\r': + enc.writeByte('\\') + enc.writeByte('r') + case '\t': + enc.writeByte('\\') + enc.writeByte('t') + default: + enc.writeByte(s[i]) + } + } + +} diff --git a/encode_builder_test.go b/encode_builder_test.go @@ -1,17 +1 @@ package gojay - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestEncoderBuilderError(t *testing.T) { - enc := NewEncoder(nil) - defer func() { - err := recover() - assert.NotNil(t, err, "err is not nil as we pass an invalid number to grow") - }() - enc.grow(-1) - assert.True(t, false, "should not be called") -} diff --git a/encode_number.go b/encode_number.go @@ -79,6 +79,7 @@ func (enc *Encoder) encodeFloat32(n float32) ([]byte, error) { // 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, ok := enc.getPreviousRune() if ok && r != '[' { enc.writeByte(',') @@ -92,6 +93,7 @@ func (enc *Encoder) AddIntOmitEmpty(v int) { if v == 0 { return } + enc.grow(10) r, ok := enc.getPreviousRune() if ok && r != '[' { enc.writeByte(',') @@ -101,6 +103,7 @@ func (enc *Encoder) AddIntOmitEmpty(v int) { // 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, ok := enc.getPreviousRune() if ok && r != '[' { enc.writeByte(',') @@ -114,6 +117,7 @@ func (enc *Encoder) AddFloatOmitEmpty(v float64) { if v == 0 { return } + enc.grow(10) r, ok := enc.getPreviousRune() if ok && r != '[' { enc.writeByte(',') @@ -136,6 +140,7 @@ func (enc *Encoder) AddFloat32OmitEmpty(v float32) { if v == 0 { return } + enc.grow(10) r, ok := enc.getPreviousRune() if ok && r != '[' { enc.writeByte(',') @@ -145,12 +150,13 @@ func (enc *Encoder) AddFloat32OmitEmpty(v float32) { // 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, ok := enc.getPreviousRune() if ok && r != '{' && r != '[' { enc.writeByte(',') } enc.writeByte('"') - enc.writeString(key) + enc.writeStringEscape(key) enc.writeBytes(objKey) enc.buf = strconv.AppendInt(enc.buf, int64(v), 10) } @@ -161,12 +167,13 @@ func (enc *Encoder) AddIntKeyOmitEmpty(key string, v int) { if v == 0 { return } + enc.grow(10 + len(key)) r, ok := enc.getPreviousRune() if ok && r != '{' && r != '[' { enc.writeByte(',') } enc.writeByte('"') - enc.writeString(key) + enc.writeStringEscape(key) enc.writeBytes(objKey) enc.buf = strconv.AppendInt(enc.buf, int64(v), 10) } @@ -177,8 +184,9 @@ func (enc *Encoder) AddFloatKey(key string, value float64) { if ok && r != '{' && r != '[' { enc.writeByte(',') } + enc.grow(10) enc.writeByte('"') - enc.writeString(key) + enc.writeStringEscape(key) enc.writeBytes(objKey) enc.buf = strconv.AppendFloat(enc.buf, value, 'f', -1, 64) } @@ -189,24 +197,26 @@ func (enc *Encoder) AddFloatKeyOmitEmpty(key string, v float64) { if v == 0 { return } + enc.grow(10 + len(key)) r, ok := enc.getPreviousRune() if ok && r != '{' && r != '[' { enc.writeByte(',') } enc.writeByte('"') - enc.writeString(key) + 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, ok := enc.getPreviousRune() if ok && r != '{' && r != '[' { enc.writeByte(',') } enc.writeByte('"') - enc.writeString(key) + enc.writeStringEscape(key) enc.writeByte('"') enc.writeByte(':') enc.buf = strconv.AppendFloat(enc.buf, float64(v), 'f', -1, 32) @@ -218,12 +228,13 @@ func (enc *Encoder) AddFloat32KeyOmitEmpty(key string, v float32) { if v == 0 { return } + enc.grow(10 + len(key)) r, ok := enc.getPreviousRune() if ok && r != '{' && r != '[' { enc.writeByte(',') } enc.writeByte('"') - enc.writeString(key) + enc.writeStringEscape(key) enc.writeBytes(objKey) enc.buf = strconv.AppendFloat(enc.buf, float64(v), 'f', -1, 32) } diff --git a/encode_object.go b/encode_object.go @@ -22,10 +22,13 @@ func (enc *Encoder) EncodeObject(v MarshalerObject) error { } return nil } + func (enc *Encoder) encodeObject(v MarshalerObject) ([]byte, error) { - enc.grow(200) + enc.grow(500) enc.writeByte('{') - v.MarshalObject(enc) + if !v.IsNil() { + v.MarshalObject(enc) + } enc.writeByte('}') return enc.buf, enc.err } @@ -34,6 +37,7 @@ func (enc *Encoder) encodeObject(v MarshalerObject) ([]byte, error) { // value must implement MarshalerObject func (enc *Encoder) AddObject(v MarshalerObject) { if v.IsNil() { + enc.grow(2) r, ok := enc.getPreviousRune() if ok && r != '{' && r != '[' { enc.writeByte(',') @@ -42,6 +46,7 @@ func (enc *Encoder) AddObject(v MarshalerObject) { enc.writeByte('}') return } + enc.grow(4) r, ok := enc.getPreviousRune() if ok && r != '[' { enc.writeByte(',') @@ -58,6 +63,7 @@ func (enc *Encoder) AddObjectOmitEmpty(v MarshalerObject) { if v.IsNil() { return } + enc.grow(2) r, ok := enc.getPreviousRune() if ok && r != '[' { enc.writeByte(',') @@ -71,22 +77,24 @@ func (enc *Encoder) AddObjectOmitEmpty(v MarshalerObject) { // value must implement MarshalerObject func (enc *Encoder) AddObjectKey(key string, value MarshalerObject) { if value.IsNil() { + enc.grow(2 + len(key)) r, ok := enc.getPreviousRune() - if ok && r != '{' && r != '[' { + if ok && r != '{' { enc.writeByte(',') } enc.writeByte('"') - enc.writeString(key) + enc.writeStringEscape(key) enc.writeBytes(objKeyObj) enc.writeByte('}') return } + enc.grow(5 + len(key)) r, ok := enc.getPreviousRune() - if ok && r != '{' && r != '[' { + if ok && r != '{' { enc.writeByte(',') } enc.writeByte('"') - enc.writeString(key) + enc.writeStringEscape(key) enc.writeBytes(objKeyObj) value.MarshalObject(enc) enc.writeByte('}') @@ -99,13 +107,33 @@ func (enc *Encoder) AddObjectKeyOmitEmpty(key string, value MarshalerObject) { if value.IsNil() { return } + enc.grow(5 + len(key)) r, ok := enc.getPreviousRune() - if ok && r != '{' && r != '[' { + if ok && r != '{' { enc.writeByte(',') } enc.writeByte('"') - enc.writeString(key) + enc.writeStringEscape(key) enc.writeBytes(objKeyObj) value.MarshalObject(enc) enc.writeByte('}') } + +// EncodeObjectFunc is a custom func type implementating MarshaleObject. +// Use it to cast a func(*Encoder) to Marshal an object. +// +// enc := gojay.NewEncoder(io.Writer) +// enc.EncodeObject(gojay.EncodeObjectFunc(func(enc *gojay.Encoder) { +// enc.AddStringKey("hello", "world") +// })) +type EncodeObjectFunc func(*Encoder) + +// MarshalObject implements MarshalerObject. +func (f EncodeObjectFunc) MarshalObject(enc *Encoder) { + f(enc) +} + +// IsNil implements MarshalerObject. +func (f EncodeObjectFunc) IsNil() bool { + return f == nil +} diff --git a/encode_object_test.go b/encode_object_test.go @@ -298,6 +298,18 @@ func TestEncoderObjectMarshalAPI(t *testing.T) { string(r), "Result of marshalling is different as the one expected") }) + t.Run("marshal-object-func", func(t *testing.T) { + f := EncodeObjectFunc(func(enc *Encoder) { + enc.AddStringKey("test", "test") + }) + r, err := Marshal(f) + assert.Nil(t, err, "Error should be nil") + assert.Equal( + t, + `{"test":"test"}`, + string(r), + "Result of marshalling is different as the one expected") + }) } type TestObectOmitEmpty struct { diff --git a/encode_stream_test.go b/encode_stream_test.go @@ -321,6 +321,12 @@ func TestEncodeStream(t *testing.T) { assert.Equal(t, now, d, "deadline should be the one just set") }) + t.Run("encoder-deadline-unset", func(t *testing.T) { + enc := Stream.NewEncoder(os.Stdout) + d, _ := enc.Deadline() + assert.Equal(t, time.Time{}, d, "deadline should be the one just set") + }) + // just for coverage t.Run("encoder-context-value", func(t *testing.T) { enc := Stream.NewEncoder(os.Stdout) diff --git a/encode_string.go b/encode_string.go @@ -17,19 +17,20 @@ func (enc *Encoder) EncodeString(s string) error { // encodeString encodes a string to func (enc *Encoder) encodeString(v string) ([]byte, error) { enc.writeByte('"') - enc.writeString(v) + enc.writeStringEscape(v) enc.writeByte('"') return enc.buf, nil } // 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.grow(len(v) + 4) r, ok := enc.getPreviousRune() if ok && r != '[' { enc.writeByte(',') } enc.writeByte('"') - enc.writeString(v) + enc.writeStringEscape(v) enc.writeByte('"') } @@ -44,20 +45,21 @@ func (enc *Encoder) AddStringOmitEmpty(v string) { enc.writeByte(',') } enc.writeByte('"') - enc.writeString(v) + enc.writeStringEscape(v) 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) { + enc.grow(len(key) + len(v) + 5) r, ok := enc.getPreviousRune() - if ok && r != '{' && r != '[' { + if ok && r != '{' { enc.writeByte(',') } enc.writeByte('"') - enc.writeString(key) + enc.writeStringEscape(key) enc.writeBytes(objKeyStr) - enc.writeString(v) + enc.writeStringEscape(v) enc.writeByte('"') } @@ -67,13 +69,14 @@ func (enc *Encoder) AddStringKeyOmitEmpty(key, v string) { if v == "" { return } + enc.grow(len(key) + len(v) + 5) r, ok := enc.getPreviousRune() - if ok && r != '{' && r != '[' { + if ok && r != '{' { enc.writeByte(',') } enc.writeByte('"') - enc.writeString(key) + enc.writeStringEscape(key) enc.writeBytes(objKeyStr) - enc.writeString(v) + enc.writeStringEscape(v) enc.writeByte('"') } diff --git a/encode_string_test.go b/encode_string_test.go @@ -30,6 +30,56 @@ func TestEncoderStringEncodeAPI(t *testing.T) { builder.String(), "Result of marshalling is different as the one expected") }) + t.Run("utf8-multibyte", func(t *testing.T) { + str := "テュールスト マーティン ヤコブ 😁" + builder := &strings.Builder{} + enc := NewEncoder(builder) + err := enc.EncodeString(str) + assert.Nil(t, err, "Error should be nil") + assert.Equal( + t, + `"テュールスト マーティン ヤコブ 😁"`, + builder.String(), + "Result of marshalling is different as the one expected") + }) + t.Run("escaped-sequence1", func(t *testing.T) { + str := `テュールスト マ\ーテ +ィン ヤコブ 😁` + builder := &strings.Builder{} + enc := NewEncoder(builder) + err := enc.EncodeString(str) + assert.Nil(t, err, "Error should be nil") + assert.Equal( + t, + `"テュールスト マ\\ーテ\nィン ヤコブ 😁"`, + builder.String(), + "Result of marshalling is different as the one expected") + }) + t.Run("escaped-sequence2", func(t *testing.T) { + str := `テュールスト マ\ーテ +ィン ヤコブ 😁 ` + builder := &strings.Builder{} + enc := NewEncoder(builder) + err := enc.EncodeString(str) + assert.Nil(t, err, "Error should be nil") + assert.Equal( + t, + `"テュールスト マ\\ーテ\nィン ヤコブ 😁\t"`, + builder.String(), + "Result of marshalling is different as the one expected") + }) + t.Run("escaped-sequence3", func(t *testing.T) { + str := "hello \r world 𝄞" + builder := &strings.Builder{} + enc := NewEncoder(builder) + err := enc.EncodeString(str) + assert.Nil(t, err, "Error should be nil") + assert.Equal( + t, + `"hello \r world 𝄞"`, + builder.String(), + "Result of marshalling is different as the one expected") + }) } func TestEncoderStringEncodeAPIErrors(t *testing.T) { diff --git a/examples/http-json/main.go b/examples/http-json/main.go @@ -55,9 +55,11 @@ func home(w http.ResponseWriter, r *http.Request) { enc := gojay.BorrowEncoder(w) defer enc.Release() err = enc.Encode(m) - i, err := w.Write([]byte(err.Error())) - if err != nil || i == 0 { - panic(err) + if err != nil { + i, err := w.Write([]byte(err.Error())) + if err != nil || i == 0 { + panic(err) + } } return }