From 30437cb8d844e513d6d40ce62ece1c45c48dd96c Mon Sep 17 00:00:00 2001 From: Aleksandr Razumov Date: Sun, 7 Nov 2021 18:55:37 +0300 Subject: [PATCH] feat(enc): add Field helper --- alloc_test.go | 9 -------- any_test.go | 2 +- enc.go | 30 ++++++++++++++++++++++---- enc_bench_test.go | 20 ++++++++--------- enc_comma_test.go | 6 +++--- enc_test.go | 55 +++++++++++++++++++++++++++++++++++++++++------ examples_test.go | 10 ++++----- obj_test.go | 10 ++++----- 8 files changed, 98 insertions(+), 44 deletions(-) diff --git a/alloc_test.go b/alloc_test.go index e0dbfc6..868c74d 100644 --- a/alloc_test.go +++ b/alloc_test.go @@ -59,15 +59,6 @@ func TestZeroAlloc(t *testing.T) { return err }) }) - t.Run("Str", func(t *testing.T) { - zeroAllocDecStr(t, `"hello"`, func(d *Decoder) error { - v, err := d.Str() - if v != "hello" { - t.Fatal(err) - } - return err - }) - }) t.Run("ArrBigFile", func(t *testing.T) { zeroAllocDec(t, benchData, func(d *Decoder) error { return d.Arr(nil) diff --git a/any_test.go b/any_test.go index aaeaf7b..741e93b 100644 --- a/any_test.go +++ b/any_test.go @@ -156,7 +156,7 @@ func (v *Any) Read(d *Decoder) error { // Write json representation of Any to Encoder. func (v Any) Write(w *Encoder) { if v.KeyValid { - w.Field(v.Key) + w.FieldStart(v.Key) } switch v.Type { case AnyStr: diff --git a/enc.go b/enc.go index 5a0cb89..3eac29b 100644 --- a/enc.go +++ b/enc.go @@ -16,7 +16,7 @@ type Encoder struct { // // We write commas only before non-first element of Array or Object. // - // See comma, begin, end and Field for implementation details. + // See comma, begin, end and FieldStart for implementation details. // // Note: probably, this can be optimized as bit set to ease memory // consumption. @@ -117,7 +117,9 @@ func (e *Encoder) Bool(v bool) { } } -// ObjStart writes object start, performing indentation if needed +// ObjStart writes object start, performing indentation if needed. +// +// Use Obj as convenience helper for writing objects. func (e *Encoder) ObjStart() { e.comma() e.byte('{') @@ -125,10 +127,12 @@ func (e *Encoder) ObjStart() { e.writeIndent() } -// Field encodes field name and writes colon. +// FieldStart encodes field name and writes colon. // // For non-zero indentation also writes single space after colon. -func (e *Encoder) Field(field string) { +// +// Use Field as convenience helper for encoding fields. +func (e *Encoder) FieldStart(field string) { e.Str(field) if e.indent > 0 { e.twoBytes(':', ' ') @@ -140,7 +144,17 @@ func (e *Encoder) Field(field string) { } } +// Field encodes field start and then invokes callback. +// +// Has ~5ns overhead over FieldStart. +func (e *Encoder) Field(name string, f func(e *Encoder)) { + e.FieldStart(name) + f(e) +} + // ObjEnd writes end of object token, performing indentation if needed. +// +// Use Obj as convenience helper for writing objects. func (e *Encoder) ObjEnd() { e.end() e.writeIndent() @@ -154,6 +168,8 @@ func (e *Encoder) ObjEmpty() { } // Obj writes start of object, invokes callback and writes end of object. +// +// If callback is nil, writes empty object. func (e *Encoder) Obj(f func(e *Encoder)) { if f == nil { e.ObjEmpty() @@ -165,6 +181,8 @@ func (e *Encoder) Obj(f func(e *Encoder)) { } // ArrStart writes start of array, performing indentation if needed. +// +// Use Arr as convenience helper for writing arrays. func (e *Encoder) ArrStart() { e.comma() e.byte('[') @@ -179,6 +197,8 @@ func (e *Encoder) ArrEmpty() { } // ArrEnd writes end of array, performing indentation if needed. +// +// Use Arr as convenience helper for writing arrays. func (e *Encoder) ArrEnd() { e.end() e.writeIndent() @@ -186,6 +206,8 @@ func (e *Encoder) ArrEnd() { } // Arr writes start of array, invokes callback and writes end of array. +// +// If callback is nil, writes empty array. func (e *Encoder) Arr(f func(e *Encoder)) { if f == nil { e.ArrEmpty() diff --git a/enc_bench_test.go b/enc_bench_test.go index 72c73dc..a5b2f62 100644 --- a/enc_bench_test.go +++ b/enc_bench_test.go @@ -22,31 +22,31 @@ func BenchmarkEncoderBigObject(b *testing.B) { func encodeObject(w *Encoder) { w.ObjStart() - w.Field("objectId") + w.FieldStart("objectId") w.Uint64(8838243212) - w.Field("name") + w.FieldStart("name") w.Str("Jane Doe") - w.Field("address") + w.FieldStart("address") w.ObjStart() for _, field := range addressFields { - w.Field(field.key) + w.FieldStart(field.key) w.Str(field.val) } - w.Field("geo") + w.FieldStart("geo") { w.ObjStart() - w.Field("latitude") + w.FieldStart("latitude") w.Float64(-154.550817) - w.Field("longitude") + w.FieldStart("longitude") w.Float64(-84.176159) w.ObjEnd() } w.ObjEnd() - w.Field("specialties") + w.FieldStart("specialties") w.ArrStart() for _, s := range specialties { w.Str(s) @@ -54,13 +54,13 @@ func encodeObject(w *Encoder) { w.ArrEnd() for i, text := range longText { - w.Field("longText" + strconv.Itoa(i)) + w.FieldStart("longText" + strconv.Itoa(i)) w.Str(text) } for i := 0; i < 25; i++ { num := i * 18328 - w.Field("integerField" + strconv.Itoa(i)) + w.FieldStart("integerField" + strconv.Itoa(i)) w.Int64(int64(num)) } diff --git a/enc_comma_test.go b/enc_comma_test.go index 8371e54..129ecb4 100644 --- a/enc_comma_test.go +++ b/enc_comma_test.go @@ -22,11 +22,11 @@ func TestEncoder_comma(t *testing.T) { t.Run("Object", func(t *testing.T) { var e Encoder e.ObjStart() - e.Field("a") + e.FieldStart("a") e.Int(1) - e.Field("b") + e.FieldStart("b") e.Int(2) - e.Field("c") + e.FieldStart("c") e.ArrStart() e.Int(1) e.Int(2) diff --git a/enc_test.go b/enc_test.go index bbd47ce..9c6a2c3 100644 --- a/enc_test.go +++ b/enc_test.go @@ -1,6 +1,7 @@ package jx import ( + "fmt" "testing" "github.com/stretchr/testify/require" @@ -69,11 +70,12 @@ func TestEncoder_ObjEmpty(t *testing.T) { } func TestEncoder_Obj(t *testing.T) { - t.Run("Field", func(t *testing.T) { + t.Run("FieldStart", func(t *testing.T) { var e Encoder e.Obj(func(e *Encoder) { - e.Field("hello") - e.Str("world") + e.Field("hello", func(e *Encoder) { + e.Str("world") + }) }) require.Equal(t, `{"hello":"world"}`, e.String()) }) @@ -105,8 +107,7 @@ func BenchmarkEncoder_Arr(b *testing.B) { var e Encoder for i := 0; i < b.N; i++ { e.ArrStart() - e.Int(1) - e.Int(2) + e.Null() e.ArrEnd() e.Reset() @@ -117,11 +118,51 @@ func BenchmarkEncoder_Arr(b *testing.B) { var e Encoder for i := 0; i < b.N; i++ { e.Arr(func(e *Encoder) { - e.Int(1) - e.Int(2) + e.Null() }) e.Reset() } }) } + +func BenchmarkEncoder_Field(b *testing.B) { + for _, fields := range []int{ + 1, + 5, + 10, + 100, + } { + b.Run(fmt.Sprintf("%d", fields), func(b *testing.B) { + b.Run("Manual", func(b *testing.B) { + b.ReportAllocs() + var e Encoder + for i := 0; i < b.N; i++ { + e.ObjStart() + for j := 0; j < fields; j++ { + e.FieldStart("field") + e.Null() + } + e.ObjEnd() + + e.Reset() + } + }) + b.Run("Callback", func(b *testing.B) { + b.ReportAllocs() + var e Encoder + for i := 0; i < b.N; i++ { + e.Obj(func(e *Encoder) { + for j := 0; j < fields; j++ { + e.Field("field", func(e *Encoder) { + e.Null() + }) + } + }) + + e.Reset() + } + }) + }) + } +} diff --git a/examples_test.go b/examples_test.go index b4a431b..e1a4dea 100644 --- a/examples_test.go +++ b/examples_test.go @@ -39,9 +39,9 @@ func ExampleDecodeStr() { func ExampleEncoder_String() { var e jx.Encoder - e.ObjStart() // { - e.Field("values") // "values": - e.ArrStart() // [ + e.ObjStart() // { + e.FieldStart("values") // "values": + e.ArrStart() // [ for _, v := range []int{4, 8, 15, 16, 23, 42} { e.Int(v) } @@ -157,7 +157,7 @@ func ExampleDecoder_Base64() { func Example() { var e jx.Encoder e.Obj(func(e *jx.Encoder) { - e.Field("data") + e.FieldStart("data") e.Base64([]byte("hello")) }) fmt.Println(e) @@ -178,7 +178,7 @@ func ExampleEncoder_SetIdent() { e.SetIdent(2) e.ObjStart() - e.Field("data") + e.FieldStart("data") e.ArrStart() e.Int(1) e.Int(2) diff --git a/obj_test.go b/obj_test.go index e278cdb..6d99da1 100644 --- a/obj_test.go +++ b/obj_test.go @@ -31,16 +31,16 @@ func TestEncoder_SetIdent(t *testing.T) { e := GetEncoder() e.SetIdent(2) e.ObjStart() - e.Field("hello") + e.FieldStart("hello") e.Int(1) - e.Field("world") + e.FieldStart("world") e.Int(2) - e.Field("obj") + e.FieldStart("obj") e.ObjStart() - e.Field("a") + e.FieldStart("a") e.Str("b") e.ObjEnd() - e.Field("data") + e.FieldStart("data") e.ArrStart() e.Int(1) e.Int(2)