gojay

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

commit 0329bd66c38182f4fe026f2f3d4924ace6936cd1
parent 9f5ebac1e960c619bbd22b2da9d6c13d64f24591
Author: Francois Parquet <francois.parquet@gmail.com>
Date:   Mon, 25 Jun 2018 00:08:28 +0800

Merge pull request #40 from francoispqt/feature/generator

Feature/generator
Diffstat:
M.travis.yml | 1+
MREADME.md | 7++++++-
Mdecode.go | 14+++++++++++++-
Mdecode_array.go | 20++++++++++++++++++++
Mdecode_array_test.go | 5+++++
Mdecode_number_float_test.go | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mencode_array.go | 19+++++++++++++++++++
Mencode_array_test.go | 5+++++
Mencode_number_float.go | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Aencode_number_float_test.go | 119+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mencode_number_int.go | 132+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aencode_number_int_test.go | 538+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mencode_number_test.go | 7++++---
Mencode_number_uint.go | 134++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Aencode_number_uint_test.go | 533+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mencode_stream.go | 9+++++++--
Agojay.png | 0
Agojay/Gopkg.lock | 14++++++++++++++
Agojay/Gopkg.toml | 26++++++++++++++++++++++++++
Agojay/Makefile | 16++++++++++++++++
Agojay/README.md | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agojay/gen.go | 146+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agojay/gen_array.go | 17+++++++++++++++++
Agojay/gen_array_marshal.go | 241+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agojay/gen_array_marshal_tpl.go | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agojay/gen_array_test.go | 436+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agojay/gen_array_unmarshal.go | 230+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agojay/gen_array_unmarshal_tpl.go | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agojay/gen_map.go | 19+++++++++++++++++++
Agojay/gen_map_marshal.go | 201+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agojay/gen_map_marshal_tpl.go | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agojay/gen_map_test.go | 616+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agojay/gen_map_unmarshal.go | 242+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agojay/gen_map_unmarshal_tpl.go | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agojay/gen_parse.go | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agojay/gen_struct_marshal.go | 290+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agojay/gen_struct_marshal_tpl.go | 42++++++++++++++++++++++++++++++++++++++++++
Agojay/gen_struct_test.go | 433+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agojay/gen_struct_unmarshal.go | 378+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agojay/gen_struct_unmarshal_tpl.go | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agojay/gen_stuct.go | 30++++++++++++++++++++++++++++++
Agojay/gen_tag.go | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agojay/gen_test.go | 24++++++++++++++++++++++++
Agojay/main.go | 116+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agojay/tests/maps.go | 4++++
Agojay/tests/slices.go | 16++++++++++++++++
Agojay/tests/structs.go | 44++++++++++++++++++++++++++++++++++++++++++++
Agojay/vendor/github.com/fatih/structtag/.travis.yml | 4++++
Agojay/vendor/github.com/fatih/structtag/LICENSE | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agojay/vendor/github.com/fatih/structtag/README.md | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agojay/vendor/github.com/fatih/structtag/tags.go | 303+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agojay/vendor/github.com/fatih/structtag/tags_test.go | 390+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agojay/visitor.go | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
53 files changed, 6649 insertions(+), 20 deletions(-)

diff --git a/.travis.yml b/.travis.yml @@ -6,6 +6,7 @@ go: script: - go get github.com/stretchr/testify + - go test ./gojay -race - go test -race -coverprofile=coverage.txt -covermode=atomic after_success: diff --git a/README.md b/README.md @@ -8,12 +8,17 @@ ![stability-stable](https://img.shields.io/badge/stability-stable-green.svg) # GoJay + +<img src="https://github.com/francoispqt/gojay/raw/feature/generator/gojay.png" width="200px"> + GoJay is a performant JSON encoder/decoder for Golang (currently the most performant, [see benchmarks](#benchmark-results)). It has a simple API and doesn't use reflection. It relies on small interfaces to decode/encode structures and slices. Gojay also comes with powerful stream decoding features and an even faster [Unsafe](#unsafe-api) API. +There is also a [code generation tool](https://github.com/francoispqt/gojay/tree/master/gojay) to make usage easier and faster. + # Why another JSON parser? I looked at other fast decoder/encoder and realised it was mostly hardly readable static code generation or a lot of reflection, poor streaming features, and not so fast in the end. @@ -31,7 +36,7 @@ go get github.com/francoispqt/gojay * [Encoder](#encoding) * [Decoder](#decoding) * [Stream API](#stream-api) - +* [Code Generation](https://github.com/francoispqt/gojay/tree/master/gojay) ## Decoding diff --git a/decode.go b/decode.go @@ -294,7 +294,13 @@ func (dec *Decoder) AddUint64(v *uint64) error { // 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) + return dec.Float64(v) +} + +// AddFloat64 decodes the next key to a *float64. +// If next key value overflows float64, an InvalidUnmarshalError error will be returned. +func (dec *Decoder) AddFloat64(v *float64) error { + return dec.Float64(v) } // AddFloat32 decodes the next key to a *float64. @@ -428,6 +434,12 @@ func (dec *Decoder) Uint64(v *uint64) error { // 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 { + return dec.Float64(v) +} + +// Float64 decodes the next key to a *float64. +// If next key value overflows float64, an InvalidUnmarshalError error will be returned. +func (dec *Decoder) Float64(v *float64) error { err := dec.decodeFloat64(v) if err != nil { return err diff --git a/decode_array.go b/decode_array.go @@ -106,3 +106,23 @@ func (dec *Decoder) skipArray() (int, error) { } return 0, dec.raiseInvalidJSONErr(dec.cursor) } + +// DecodeArrayFunc is a custom func type implementing UnarshaleArray. +// Use it to cast a func(*Decoder) to Unmarshal an object. +// +// str := "" +// dec := gojay.NewDecoder(io.Reader) +// dec.DecodeArray(gojay.DecodeArrayFunc(func(dec *gojay.Decoder, k string) error { +// return dec.AddString(&str) +// })) +type DecodeArrayFunc func(*Decoder) error + +// UnmarshalJSONArray implements UnarshalerArray. +func (f DecodeArrayFunc) UnmarshalJSONArray(dec *Decoder) error { + return f(dec) +} + +// IsNil implements UnarshalerArray. +func (f DecodeArrayFunc) IsNil() bool { + return f == nil +} diff --git a/decode_array_test.go b/decode_array_test.go @@ -566,3 +566,8 @@ func TestDecodeArrayNullError(t *testing.T) { assert.NotNil(t, err, "err should not be nil") assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") } + +func TestDecoderArrayFunc(t *testing.T) { + var f DecodeArrayFunc + assert.True(t, f.IsNil()) +} diff --git a/decode_number_float_test.go b/decode_number_float_test.go @@ -630,3 +630,58 @@ func TestDecoderFloat32(t *testing.T) { assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") }) } + +func TestDecoderFloat64Field(t *testing.T) { + var testCasesBasic = []struct { + name string + json string + value float64 + }{ + { + name: "basic", + json: "[1]", + value: float64(1), + }, + { + name: "big", + json: "[0]", + value: float64(0), + }, + } + for _, testCase := range testCasesBasic { + t.Run(testCase.name, func(t *testing.T) { + var dec = NewDecoder(strings.NewReader(testCase.json)) + var v float64 + dec.DecodeArray(DecodeArrayFunc(func(dec *Decoder) error { + return dec.AddFloat64(&v) + })) + assert.Equal(t, testCase.value, v) + }) + } + var testCasesBasicAlt = []struct { + name string + json string + value float64 + }{ + { + name: "basic", + json: "[1]", + value: float64(1), + }, + { + name: "big", + json: "[0]", + value: float64(0), + }, + } + for _, testCase := range testCasesBasicAlt { + t.Run(testCase.name, func(t *testing.T) { + var dec = NewDecoder(strings.NewReader(testCase.json)) + var v float64 + dec.DecodeArray(DecodeArrayFunc(func(dec *Decoder) error { + return dec.Float(&v) + })) + assert.Equal(t, testCase.value, v) + }) + } +} diff --git a/encode_array.go b/encode_array.go @@ -128,3 +128,22 @@ func (enc *Encoder) ArrayKeyOmitEmpty(key string, v MarshalerJSONArray) { v.MarshalJSONArray(enc) enc.writeByte(']') } + +// EncodeArrayFunc is a custom func type implementing MarshaleArray. +// Use it to cast a func(*Encoder) to Marshal an object. +// +// enc := gojay.NewEncoder(io.Writer) +// enc.EncodeArray(gojay.EncodeArrayFunc(func(enc *gojay.Encoder) { +// enc.AddStringKey("hello", "world") +// })) +type EncodeArrayFunc func(*Encoder) + +// MarshalJSONArray implements MarshalerJSONArray. +func (f EncodeArrayFunc) MarshalJSONArray(enc *Encoder) { + f(enc) +} + +// IsNil implements MarshalerJSONArray. +func (f EncodeArrayFunc) IsNil() bool { + return f == nil +} diff --git a/encode_array_test.go b/encode_array_test.go @@ -331,3 +331,8 @@ func TestEncoderArrErrors(t *testing.T) { assert.True(t, false, "should not be called as it should have panicked") }) } + +func TestEncoderArrayFunc(t *testing.T) { + var f EncodeArrayFunc + assert.True(t, f.IsNil()) +} diff --git a/encode_number_float.go b/encode_number_float.go @@ -41,17 +41,61 @@ func (enc *Encoder) encodeFloat32(n float32) ([]byte, error) { // 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) + enc.Float64(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) + enc.Float64OmitEmpty(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.Float64(v) +} + +// 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) { + enc.Float64OmitEmpty(v) +} + +// 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.Float64Key(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.Float64KeyOmitEmpty(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, v float64) { + enc.Float64Key(key, v) +} + +// 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) { + enc.Float64KeyOmitEmpty(key, v) +} + +// AddFloat64 adds a float64 to be encoded, must be used inside a slice or array encoding (does not encode a key) +func (enc *Encoder) AddFloat64(v float64) { + enc.Float(v) +} + +// AddFloat64OmitEmpty 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) AddFloat64OmitEmpty(v float64) { + enc.FloatOmitEmpty(v) +} + +// Float64 adds a float64 to be encoded, must be used inside a slice or array encoding (does not encode a key) +func (enc *Encoder) Float64(v float64) { enc.grow(10) r := enc.getPreviousRune() if r != '[' { @@ -60,9 +104,9 @@ func (enc *Encoder) Float(v float64) { 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, +// Float64OmitEmpty 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) { +func (enc *Encoder) Float64OmitEmpty(v float64) { if v == 0 { return } @@ -74,19 +118,19 @@ func (enc *Encoder) FloatOmitEmpty(v float64) { 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) { +// AddFloat64Key adds a float64 to be encoded, must be used inside an object as it will encode a key +func (enc *Encoder) AddFloat64Key(key string, v float64) { enc.FloatKey(key, v) } -// AddFloatKeyOmitEmpty adds a float64 to be encoded and skips it if its value is 0. +// AddFloat64KeyOmitEmpty 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) { +func (enc *Encoder) AddFloat64KeyOmitEmpty(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) { +// Float64Key adds a float64 to be encoded, must be used inside an object as it will encode a key +func (enc *Encoder) Float64Key(key string, value float64) { r := enc.getPreviousRune() if r != '{' { enc.writeByte(',') @@ -98,9 +142,9 @@ func (enc *Encoder) FloatKey(key string, value float64) { 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. +// Float64KeyOmitEmpty 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) { +func (enc *Encoder) Float64KeyOmitEmpty(key string, v float64) { if v == 0 { return } diff --git a/encode_number_float_test.go b/encode_number_float_test.go @@ -0,0 +1,119 @@ +package gojay + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEncoderFloat64(t *testing.T) { + var testCasesBasic = []struct { + name string + v float64 + expectedJSON string + }{ + { + name: "basic", + v: float64(1), + expectedJSON: "[1,1]", + }, + { + name: "big", + v: float64(0), + expectedJSON: "[0,0]", + }, + } + for _, testCase := range testCasesBasic { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeArrayFunc(func(enc *Encoder) { + enc.Float64(testCase.v) + enc.AddFloat64(testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } + var testCasesOmitEmpty = []struct { + name string + v float64 + expectedJSON string + }{ + { + name: "basic", + v: float64(1), + expectedJSON: "[1,1]", + }, + { + name: "big", + v: float64(0), + expectedJSON: "[]", + }, + } + for _, testCase := range testCasesOmitEmpty { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeArrayFunc(func(enc *Encoder) { + enc.Float64OmitEmpty(testCase.v) + enc.AddFloat64OmitEmpty(testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } + var testCasesKeyBasic = []struct { + name string + v float64 + expectedJSON string + }{ + { + name: "basic", + v: float64(1), + expectedJSON: `{"foo":1,"bar":1}`, + }, + { + name: "big", + v: float64(0), + expectedJSON: `{"foo":0,"bar":0}`, + }, + } + for _, testCase := range testCasesKeyBasic { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeObjectFunc(func(enc *Encoder) { + enc.Float64Key("foo", testCase.v) + enc.AddFloat64Key("bar", testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } + var testCasesKeyOmitEmpty = []struct { + name string + v float64 + expectedJSON string + }{ + { + name: "basic", + v: float64(1), + expectedJSON: `{"foo":1,"bar":1}`, + }, + { + name: "big", + v: float64(0), + expectedJSON: "{}", + }, + } + for _, testCase := range testCasesKeyOmitEmpty { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeObjectFunc(func(enc *Encoder) { + enc.Float64KeyOmitEmpty("foo", testCase.v) + enc.AddFloat64KeyOmitEmpty("bar", testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } +} diff --git a/encode_number_int.go b/encode_number_int.go @@ -191,3 +191,135 @@ func (enc *Encoder) Int64KeyOmitEmpty(key string, v int64) { enc.writeBytes(objKey) enc.buf = strconv.AppendInt(enc.buf, v, 10) } + +// AddInt32 adds an int to be encoded, must be used inside a slice or array encoding (does not encode a key) +func (enc *Encoder) AddInt32(v int32) { + enc.Int64(int64(v)) +} + +// AddInt32OmitEmpty 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) AddInt32OmitEmpty(v int32) { + enc.Int64OmitEmpty(int64(v)) +} + +// Int32 adds an int to be encoded, must be used inside a slice or array encoding (does not encode a key) +func (enc *Encoder) Int32(v int32) { + enc.Int64(int64(v)) +} + +// Int32OmitEmpty 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) Int32OmitEmpty(v int32) { + enc.Int64OmitEmpty(int64(v)) +} + +// AddInt32Key adds an int32 to be encoded, must be used inside an object as it will encode a key +func (enc *Encoder) AddInt32Key(key string, v int32) { + enc.Int64Key(key, int64(v)) +} + +// AddInt32KeyOmitEmpty adds an int32 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) AddInt32KeyOmitEmpty(key string, v int32) { + enc.Int64KeyOmitEmpty(key, int64(v)) +} + +// Int32Key adds an int32 to be encoded, must be used inside an object as it will encode a key +func (enc *Encoder) Int32Key(key string, v int32) { + enc.Int64Key(key, int64(v)) +} + +// Int32KeyOmitEmpty adds an int32 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) Int32KeyOmitEmpty(key string, v int32) { + enc.Int64KeyOmitEmpty(key, int64(v)) +} + +// AddInt16 adds an int to be encoded, must be used inside a slice or array encoding (does not encode a key) +func (enc *Encoder) AddInt16(v int16) { + enc.Int64(int64(v)) +} + +// AddInt16OmitEmpty 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) AddInt16OmitEmpty(v int16) { + enc.Int64OmitEmpty(int64(v)) +} + +// Int16 adds an int to be encoded, must be used inside a slice or array encoding (does not encode a key) +func (enc *Encoder) Int16(v int16) { + enc.Int64(int64(v)) +} + +// Int16OmitEmpty 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) Int16OmitEmpty(v int16) { + enc.Int64OmitEmpty(int64(v)) +} + +// AddInt16Key adds an int16 to be encoded, must be used inside an object as it will encode a key +func (enc *Encoder) AddInt16Key(key string, v int16) { + enc.Int64Key(key, int64(v)) +} + +// AddInt16KeyOmitEmpty adds an int16 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) AddInt16KeyOmitEmpty(key string, v int16) { + enc.Int64KeyOmitEmpty(key, int64(v)) +} + +// Int16Key adds an int16 to be encoded, must be used inside an object as it will encode a key +func (enc *Encoder) Int16Key(key string, v int16) { + enc.Int64Key(key, int64(v)) +} + +// Int16KeyOmitEmpty adds an int16 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) Int16KeyOmitEmpty(key string, v int16) { + enc.Int64KeyOmitEmpty(key, int64(v)) +} + +// AddInt8 adds an int to be encoded, must be used inside a slice or array encoding (does not encode a key) +func (enc *Encoder) AddInt8(v int8) { + enc.Int64(int64(v)) +} + +// AddInt8OmitEmpty 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) AddInt8OmitEmpty(v int8) { + enc.Int64OmitEmpty(int64(v)) +} + +// Int8 adds an int to be encoded, must be used inside a slice or array encoding (does not encode a key) +func (enc *Encoder) Int8(v int8) { + enc.Int64(int64(v)) +} + +// Int8OmitEmpty 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) Int8OmitEmpty(v int8) { + enc.Int64OmitEmpty(int64(v)) +} + +// AddInt8Key adds an int8 to be encoded, must be used inside an object as it will encode a key +func (enc *Encoder) AddInt8Key(key string, v int8) { + enc.Int64Key(key, int64(v)) +} + +// AddInt8KeyOmitEmpty adds an int8 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) AddInt8KeyOmitEmpty(key string, v int8) { + enc.Int64KeyOmitEmpty(key, int64(v)) +} + +// Int8Key adds an int8 to be encoded, must be used inside an object as it will encode a key +func (enc *Encoder) Int8Key(key string, v int8) { + enc.Int64Key(key, int64(v)) +} + +// Int8KeyOmitEmpty adds an int8 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) Int8KeyOmitEmpty(key string, v int8) { + enc.Int64KeyOmitEmpty(key, int64(v)) +} diff --git a/encode_number_int_test.go b/encode_number_int_test.go @@ -0,0 +1,538 @@ +package gojay + +import ( + "math" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEncoderInt64(t *testing.T) { + var testCasesBasic = []struct { + name string + v int64 + expectedJSON string + }{ + { + name: "basic", + v: int64(1), + expectedJSON: "[1,1]", + }, + { + name: "big", + v: math.MaxInt64, + expectedJSON: "[9223372036854775807,9223372036854775807]", + }, + { + name: "big", + v: int64(0), + expectedJSON: "[0,0]", + }, + } + for _, testCase := range testCasesBasic { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeArrayFunc(func(enc *Encoder) { + enc.Int64(testCase.v) + enc.AddInt64(testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } + + var testCasesOmitEmpty = []struct { + name string + v int64 + expectedJSON string + }{ + { + name: "basic", + v: int64(1), + expectedJSON: "[1,1]", + }, + { + name: "big", + v: math.MaxInt64, + expectedJSON: "[9223372036854775807,9223372036854775807]", + }, + { + name: "big", + v: int64(0), + expectedJSON: "[]", + }, + } + for _, testCase := range testCasesOmitEmpty { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeArrayFunc(func(enc *Encoder) { + enc.Int64OmitEmpty(testCase.v) + enc.AddInt64OmitEmpty(testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } + var testCasesKeyBasic = []struct { + name string + v int64 + expectedJSON string + }{ + { + name: "basic", + v: int64(1), + expectedJSON: `{"foo":1,"bar":1}`, + }, + { + name: "big", + v: math.MaxInt64, + expectedJSON: `{"foo":9223372036854775807,"bar":9223372036854775807}`, + }, + { + name: "big", + v: int64(0), + expectedJSON: `{"foo":0,"bar":0}`, + }, + } + for _, testCase := range testCasesKeyBasic { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeObjectFunc(func(enc *Encoder) { + enc.Int64Key("foo", testCase.v) + enc.AddInt64Key("bar", testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } + + var testCasesKeyOmitEmpty = []struct { + name string + v int64 + expectedJSON string + }{ + { + name: "basic", + v: int64(1), + expectedJSON: `{"foo":1,"bar":1}`, + }, + { + name: "big", + v: math.MaxInt64, + expectedJSON: `{"foo":9223372036854775807,"bar":9223372036854775807}`, + }, + { + name: "big", + v: int64(0), + expectedJSON: `{}`, + }, + } + for _, testCase := range testCasesKeyOmitEmpty { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeObjectFunc(func(enc *Encoder) { + enc.Int64KeyOmitEmpty("foo", testCase.v) + enc.AddInt64KeyOmitEmpty("bar", testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } +} + +func TestEncoderInt32(t *testing.T) { + var testCasesBasic = []struct { + name string + v int32 + expectedJSON string + }{ + { + name: "basic", + v: int32(1), + expectedJSON: "[1,1]", + }, + { + name: "big", + v: math.MaxInt32, + expectedJSON: "[2147483647,2147483647]", + }, + { + name: "big", + v: int32(0), + expectedJSON: "[0,0]", + }, + } + for _, testCase := range testCasesBasic { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeArrayFunc(func(enc *Encoder) { + enc.Int32(testCase.v) + enc.AddInt32(testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } + var testCasesOmitEmpty = []struct { + name string + v int32 + expectedJSON string + }{ + { + name: "basic", + v: int32(1), + expectedJSON: "[1,1]", + }, + { + name: "big", + v: math.MaxInt32, + expectedJSON: "[2147483647,2147483647]", + }, + { + name: "big", + v: int32(0), + expectedJSON: "[]", + }, + } + for _, testCase := range testCasesOmitEmpty { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeArrayFunc(func(enc *Encoder) { + enc.Int32OmitEmpty(testCase.v) + enc.AddInt32OmitEmpty(testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } + var testCasesKeyBasic = []struct { + name string + v int32 + expectedJSON string + }{ + { + name: "basic", + v: int32(1), + expectedJSON: `{"foo":1,"bar":1}`, + }, + { + name: "big", + v: math.MaxInt32, + expectedJSON: `{"foo":2147483647,"bar":2147483647}`, + }, + { + name: "big", + v: int32(0), + expectedJSON: `{"foo":0,"bar":0}`, + }, + } + for _, testCase := range testCasesKeyBasic { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeObjectFunc(func(enc *Encoder) { + enc.Int32Key("foo", testCase.v) + enc.AddInt32Key("bar", testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } + + var testCasesKeyOmitEmpty = []struct { + name string + v int32 + expectedJSON string + }{ + { + name: "basic", + v: int32(1), + expectedJSON: `{"foo":1,"bar":1}`, + }, + { + name: "big", + v: math.MaxInt32, + expectedJSON: `{"foo":2147483647,"bar":2147483647}`, + }, + { + name: "big", + v: int32(0), + expectedJSON: `{}`, + }, + } + for _, testCase := range testCasesKeyOmitEmpty { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeObjectFunc(func(enc *Encoder) { + enc.Int32KeyOmitEmpty("foo", testCase.v) + enc.AddInt32KeyOmitEmpty("bar", testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } +} + +func TestEncoderInt16(t *testing.T) { + var testCasesBasic = []struct { + name string + v int16 + expectedJSON string + }{ + { + name: "basic", + v: int16(1), + expectedJSON: "[1,1]", + }, + { + name: "big", + v: math.MaxInt16, + expectedJSON: "[32767,32767]", + }, + { + name: "big", + v: int16(0), + expectedJSON: "[0,0]", + }, + } + for _, testCase := range testCasesBasic { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeArrayFunc(func(enc *Encoder) { + enc.Int16(testCase.v) + enc.AddInt16(testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } + var testCasesOmitEmpty = []struct { + name string + v int16 + expectedJSON string + }{ + { + name: "basic", + v: int16(1), + expectedJSON: "[1,1]", + }, + { + name: "big", + v: math.MaxInt16, + expectedJSON: "[32767,32767]", + }, + { + name: "big", + v: int16(0), + expectedJSON: "[]", + }, + } + for _, testCase := range testCasesOmitEmpty { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeArrayFunc(func(enc *Encoder) { + enc.Int16OmitEmpty(testCase.v) + enc.AddInt16OmitEmpty(testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } + var testCasesKeyBasic = []struct { + name string + v int16 + expectedJSON string + }{ + { + name: "basic", + v: int16(1), + expectedJSON: `{"foo":1,"bar":1}`, + }, + { + name: "big", + v: math.MaxInt16, + expectedJSON: `{"foo":32767,"bar":32767}`, + }, + { + name: "big", + v: int16(0), + expectedJSON: `{"foo":0,"bar":0}`, + }, + } + for _, testCase := range testCasesKeyBasic { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeObjectFunc(func(enc *Encoder) { + enc.Int16Key("foo", testCase.v) + enc.AddInt16Key("bar", testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } + + var testCasesKeyOmitEmpty = []struct { + name string + v int16 + expectedJSON string + }{ + { + name: "basic", + v: int16(1), + expectedJSON: `{"foo":1,"bar":1}`, + }, + { + name: "big", + v: math.MaxInt16, + expectedJSON: `{"foo":32767,"bar":32767}`, + }, + { + name: "big", + v: int16(0), + expectedJSON: `{}`, + }, + } + for _, testCase := range testCasesKeyOmitEmpty { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeObjectFunc(func(enc *Encoder) { + enc.Int16KeyOmitEmpty("foo", testCase.v) + enc.AddInt16KeyOmitEmpty("bar", testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } +} + +func TestEncoderInt8(t *testing.T) { + var testCasesBasic = []struct { + name string + v int8 + expectedJSON string + }{ + { + name: "basic", + v: int8(1), + expectedJSON: "[1,1]", + }, + { + name: "big", + v: math.MaxInt8, + expectedJSON: "[127,127]", + }, + { + name: "big", + v: int8(0), + expectedJSON: "[0,0]", + }, + } + for _, testCase := range testCasesBasic { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeArrayFunc(func(enc *Encoder) { + enc.Int8(testCase.v) + enc.AddInt8(testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } + var testCasesOmitEmpty = []struct { + name string + v int8 + expectedJSON string + }{ + { + name: "basic", + v: int8(1), + expectedJSON: "[1,1]", + }, + { + name: "big", + v: math.MaxInt8, + expectedJSON: "[127,127]", + }, + { + name: "big", + v: int8(0), + expectedJSON: "[]", + }, + } + for _, testCase := range testCasesOmitEmpty { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeArrayFunc(func(enc *Encoder) { + enc.Int8OmitEmpty(testCase.v) + enc.AddInt8OmitEmpty(testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } + var testCasesKeyBasic = []struct { + name string + v int8 + expectedJSON string + }{ + { + name: "basic", + v: int8(1), + expectedJSON: `{"foo":1,"bar":1}`, + }, + { + name: "big", + v: math.MaxInt8, + expectedJSON: `{"foo":127,"bar":127}`, + }, + { + name: "big", + v: int8(0), + expectedJSON: `{"foo":0,"bar":0}`, + }, + } + for _, testCase := range testCasesKeyBasic { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeObjectFunc(func(enc *Encoder) { + enc.Int8Key("foo", testCase.v) + enc.AddInt8Key("bar", testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } + + var testCasesKeyOmitEmpty = []struct { + name string + v int8 + expectedJSON string + }{ + { + name: "basic", + v: int8(1), + expectedJSON: `{"foo":1,"bar":1}`, + }, + { + name: "big", + v: math.MaxInt8, + expectedJSON: `{"foo":127,"bar":127}`, + }, + { + name: "big", + v: int8(0), + expectedJSON: `{}`, + }, + } + for _, testCase := range testCasesKeyOmitEmpty { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeObjectFunc(func(enc *Encoder) { + enc.Int8KeyOmitEmpty("foo", testCase.v) + enc.AddInt8KeyOmitEmpty("bar", testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } +} diff --git a/encode_number_test.go b/encode_number_test.go @@ -4,9 +4,10 @@ import ( "strings" "testing" - "github.com/stretchr/testify/assert" - "math" "fmt" + "math" + + "github.com/stretchr/testify/assert" ) func TestEncoderNumberEncodeAPI(t *testing.T) { @@ -356,7 +357,7 @@ func TestAddNumberFunc(t *testing.T) { }) } -func TestEncoderUint64(t *testing.T) { +func TestEncodeUint64(t *testing.T) { builder := &strings.Builder{} enc := BorrowEncoder(builder) err := enc.Encode(uint64(145509)) diff --git a/encode_number_uint.go b/encode_number_uint.go @@ -2,7 +2,7 @@ package gojay import "strconv" -// EncodeUint64 encodes an int64 to JSON +// EncodeUint32 encodes an int64 to JSON func (enc *Encoder) EncodeUint64(n uint64) error { if enc.isPooled == 1 { panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) @@ -96,3 +96,135 @@ func (enc *Encoder) Uint64KeyOmitEmpty(key string, v uint64) { enc.writeBytes(objKey) enc.buf = strconv.AppendUint(enc.buf, v, 10) } + +// AddUint32 adds an int to be encoded, must be used inside a slice or array encoding (does not encode a key) +func (enc *Encoder) AddUint32(v uint32) { + enc.Uint64(uint64(v)) +} + +// AddUint32OmitEmpty 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) AddUint32OmitEmpty(v uint32) { + enc.Uint64OmitEmpty(uint64(v)) +} + +// Uint32 adds an int to be encoded, must be used inside a slice or array encoding (does not encode a key) +func (enc *Encoder) Uint32(v uint32) { + enc.Uint64(uint64(v)) +} + +// Uint32OmitEmpty 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) Uint32OmitEmpty(v uint32) { + enc.Uint64OmitEmpty(uint64(v)) +} + +// AddUint32Key adds an int to be encoded, must be used inside an object as it will encode a key +func (enc *Encoder) AddUint32Key(key string, v uint32) { + enc.Uint64Key(key, uint64(v)) +} + +// AddUint32KeyOmitEmpty 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) AddUint32KeyOmitEmpty(key string, v uint32) { + enc.Uint64KeyOmitEmpty(key, uint64(v)) +} + +// Uint32Key adds an int to be encoded, must be used inside an object as it will encode a key +func (enc *Encoder) Uint32Key(key string, v uint32) { + enc.Uint64Key(key, uint64(v)) +} + +// Uint32KeyOmitEmpty 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) Uint32KeyOmitEmpty(key string, v uint32) { + enc.Uint64KeyOmitEmpty(key, uint64(v)) +} + +// AddUint16 adds an int to be encoded, must be used inside a slice or array encoding (does not encode a key) +func (enc *Encoder) AddUint16(v uint16) { + enc.Uint64(uint64(v)) +} + +// AddUint16OmitEmpty 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) AddUint16OmitEmpty(v uint16) { + enc.Uint64OmitEmpty(uint64(v)) +} + +// Uint16 adds an int to be encoded, must be used inside a slice or array encoding (does not encode a key) +func (enc *Encoder) Uint16(v uint16) { + enc.Uint64(uint64(v)) +} + +// Uint16OmitEmpty 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) Uint16OmitEmpty(v uint16) { + enc.Uint64OmitEmpty(uint64(v)) +} + +// AddUint16Key adds an int to be encoded, must be used inside an object as it will encode a key +func (enc *Encoder) AddUint16Key(key string, v uint16) { + enc.Uint64Key(key, uint64(v)) +} + +// AddUint16KeyOmitEmpty 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) AddUint16KeyOmitEmpty(key string, v uint16) { + enc.Uint64KeyOmitEmpty(key, uint64(v)) +} + +// Uint16Key adds an int to be encoded, must be used inside an object as it will encode a key +func (enc *Encoder) Uint16Key(key string, v uint16) { + enc.Uint64Key(key, uint64(v)) +} + +// Uint16KeyOmitEmpty 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) Uint16KeyOmitEmpty(key string, v uint16) { + enc.Uint64KeyOmitEmpty(key, uint64(v)) +} + +// AddUint8 adds an int to be encoded, must be used inside a slice or array encoding (does not encode a key) +func (enc *Encoder) AddUint8(v uint8) { + enc.Uint64(uint64(v)) +} + +// AddUint8OmitEmpty 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) AddUint8OmitEmpty(v uint8) { + enc.Uint64OmitEmpty(uint64(v)) +} + +// Uint8 adds an int to be encoded, must be used inside a slice or array encoding (does not encode a key) +func (enc *Encoder) Uint8(v uint8) { + enc.Uint64(uint64(v)) +} + +// Uint8OmitEmpty 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) Uint8OmitEmpty(v uint8) { + enc.Uint64OmitEmpty(uint64(v)) +} + +// AddUint8Key adds an int to be encoded, must be used inside an object as it will encode a key +func (enc *Encoder) AddUint8Key(key string, v uint8) { + enc.Uint64Key(key, uint64(v)) +} + +// AddUint8KeyOmitEmpty 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) AddUint8KeyOmitEmpty(key string, v uint8) { + enc.Uint64KeyOmitEmpty(key, uint64(v)) +} + +// Uint8Key adds an int to be encoded, must be used inside an object as it will encode a key +func (enc *Encoder) Uint8Key(key string, v uint8) { + enc.Uint64Key(key, uint64(v)) +} + +// Uint8KeyOmitEmpty 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) Uint8KeyOmitEmpty(key string, v uint8) { + enc.Uint64KeyOmitEmpty(key, uint64(v)) +} diff --git a/encode_number_uint_test.go b/encode_number_uint_test.go @@ -0,0 +1,533 @@ +package gojay + +import ( + "math" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEncoderUint64(t *testing.T) { + var testCasesBasic = []struct { + name string + v uint64 + expectedJSON string + }{ + { + name: "basic", + v: uint64(1), + expectedJSON: "[1,1]", + }, + { + name: "big", + v: math.MaxUint64, + expectedJSON: "[18446744073709551615,18446744073709551615]", + }, + { + name: "big", + v: uint64(0), + expectedJSON: "[0,0]", + }, + } + for _, testCase := range testCasesBasic { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeArrayFunc(func(enc *Encoder) { + enc.Uint64(testCase.v) + enc.AddUint64(testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } + var testCasesOmitEmpty = []struct { + name string + v uint64 + expectedJSON string + }{ + { + name: "basic", + v: uint64(1), + expectedJSON: "[1,1]", + }, + { + name: "big", + v: math.MaxUint64, + expectedJSON: "[18446744073709551615,18446744073709551615]", + }, + { + name: "big", + v: uint64(0), + expectedJSON: "[]", + }, + } + for _, testCase := range testCasesOmitEmpty { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeArrayFunc(func(enc *Encoder) { + enc.Uint64OmitEmpty(testCase.v) + enc.AddUint64OmitEmpty(testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } + var testCasesKeyBasic = []struct { + name string + v uint64 + expectedJSON string + }{ + { + name: "basic", + v: uint64(1), + expectedJSON: `{"foo":1,"bar":1}`, + }, + { + name: "big", + v: math.MaxUint64, + expectedJSON: `{"foo":18446744073709551615,"bar":18446744073709551615}`, + }, + { + name: "big", + v: uint64(0), + expectedJSON: `{"foo":0,"bar":0}`, + }, + } + for _, testCase := range testCasesKeyBasic { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeObjectFunc(func(enc *Encoder) { + enc.Uint64Key("foo", testCase.v) + enc.AddUint64Key("bar", testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } + var testCasesKeyOmitEmpty = []struct { + name string + v uint64 + expectedJSON string + }{ + { + name: "basic", + v: uint64(1), + expectedJSON: `{"foo":1,"bar":1}`, + }, + { + name: "big", + v: math.MaxUint64, + expectedJSON: `{"foo":18446744073709551615,"bar":18446744073709551615}`, + }, + { + name: "big", + v: uint64(0), + expectedJSON: "{}", + }, + } + for _, testCase := range testCasesKeyOmitEmpty { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeObjectFunc(func(enc *Encoder) { + enc.Uint64KeyOmitEmpty("foo", testCase.v) + enc.AddUint64KeyOmitEmpty("bar", testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } +} + +func TestEncoderUint32(t *testing.T) { + var testCasesBasic = []struct { + name string + v uint32 + expectedJSON string + }{ + { + name: "basic", + v: uint32(1), + expectedJSON: "[1,1]", + }, + { + name: "big", + v: math.MaxUint32, + expectedJSON: "[4294967295,4294967295]", + }, + { + name: "big", + v: uint32(0), + expectedJSON: "[0,0]", + }, + } + for _, testCase := range testCasesBasic { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeArrayFunc(func(enc *Encoder) { + enc.Uint32(testCase.v) + enc.AddUint32(testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } + var testCasesOmitEmpty = []struct { + name string + v uint32 + expectedJSON string + }{ + { + name: "basic", + v: uint32(1), + expectedJSON: "[1,1]", + }, + { + name: "big", + v: math.MaxUint32, + expectedJSON: "[4294967295,4294967295]", + }, + { + name: "big", + v: uint32(0), + expectedJSON: "[]", + }, + } + for _, testCase := range testCasesOmitEmpty { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeArrayFunc(func(enc *Encoder) { + enc.Uint32OmitEmpty(testCase.v) + enc.AddUint32OmitEmpty(testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } + var testCasesKeyBasic = []struct { + name string + v uint32 + expectedJSON string + }{ + { + name: "basic", + v: uint32(1), + expectedJSON: `{"foo":1,"bar":1}`, + }, + { + name: "big", + v: math.MaxUint32, + expectedJSON: `{"foo":4294967295,"bar":4294967295}`, + }, + { + name: "big", + v: uint32(0), + expectedJSON: `{"foo":0,"bar":0}`, + }, + } + for _, testCase := range testCasesKeyBasic { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeObjectFunc(func(enc *Encoder) { + enc.Uint32Key("foo", testCase.v) + enc.AddUint32Key("bar", testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } + var testCasesKeyOmitEmpty = []struct { + name string + v uint32 + expectedJSON string + }{ + { + name: "basic", + v: uint32(1), + expectedJSON: `{"foo":1,"bar":1}`, + }, + { + name: "big", + v: math.MaxUint32, + expectedJSON: `{"foo":4294967295,"bar":4294967295}`, + }, + { + name: "big", + v: uint32(0), + expectedJSON: `{}`, + }, + } + for _, testCase := range testCasesKeyOmitEmpty { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeObjectFunc(func(enc *Encoder) { + enc.Uint32KeyOmitEmpty("foo", testCase.v) + enc.AddUint32KeyOmitEmpty("bar", testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } +} + +func TestEncoderUint16(t *testing.T) { + var testCasesBasic = []struct { + name string + v uint16 + expectedJSON string + }{ + { + name: "basic", + v: uint16(1), + expectedJSON: "[1,1]", + }, + { + name: "big", + v: math.MaxUint16, + expectedJSON: "[65535,65535]", + }, + { + name: "big", + v: uint16(0), + expectedJSON: "[0,0]", + }, + } + for _, testCase := range testCasesBasic { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeArrayFunc(func(enc *Encoder) { + enc.Uint16(testCase.v) + enc.AddUint16(testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } + var testCasesOmitEmpty = []struct { + name string + v uint16 + expectedJSON string + }{ + { + name: "basic", + v: uint16(1), + expectedJSON: "[1,1]", + }, + { + name: "big", + v: math.MaxUint16, + expectedJSON: "[65535,65535]", + }, + { + name: "big", + v: uint16(0), + expectedJSON: "[]", + }, + } + for _, testCase := range testCasesOmitEmpty { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeArrayFunc(func(enc *Encoder) { + enc.Uint16OmitEmpty(testCase.v) + enc.AddUint16OmitEmpty(testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } + var testCasesKeyBasic = []struct { + name string + v uint16 + expectedJSON string + }{ + { + name: "basic", + v: uint16(1), + expectedJSON: `{"foo":1,"bar":1}`, + }, + { + name: "big", + v: math.MaxUint16, + expectedJSON: `{"foo":65535,"bar":65535}`, + }, + { + name: "big", + v: uint16(0), + expectedJSON: `{"foo":0,"bar":0}`, + }, + } + for _, testCase := range testCasesKeyBasic { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeObjectFunc(func(enc *Encoder) { + enc.Uint16Key("foo", testCase.v) + enc.AddUint16Key("bar", testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } + var testCasesKeyOmitEmpty = []struct { + name string + v uint16 + expectedJSON string + }{ + { + name: "basic", + v: uint16(1), + expectedJSON: `{"foo":1,"bar":1}`, + }, + { + name: "big", + v: math.MaxUint16, + expectedJSON: `{"foo":65535,"bar":65535}`, + }, + { + name: "big", + v: uint16(0), + expectedJSON: `{}`, + }, + } + for _, testCase := range testCasesKeyOmitEmpty { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeObjectFunc(func(enc *Encoder) { + enc.Uint16KeyOmitEmpty("foo", testCase.v) + enc.AddUint16KeyOmitEmpty("bar", testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } +} + +func TestEncoderUint8(t *testing.T) { + var testCasesBasic = []struct { + name string + v uint8 + expectedJSON string + }{ + { + name: "basic", + v: uint8(1), + expectedJSON: "[1,1]", + }, + { + name: "big", + v: math.MaxUint8, + expectedJSON: "[255,255]", + }, + { + name: "big", + v: uint8(0), + expectedJSON: "[0,0]", + }, + } + for _, testCase := range testCasesBasic { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeArrayFunc(func(enc *Encoder) { + enc.Uint8(testCase.v) + enc.AddUint8(testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } + var testCasesOmitEmpty = []struct { + name string + v uint8 + expectedJSON string + }{ + { + name: "basic", + v: uint8(1), + expectedJSON: "[1,1]", + }, + { + name: "big", + v: math.MaxUint8, + expectedJSON: "[255,255]", + }, + { + name: "big", + v: uint8(0), + expectedJSON: "[]", + }, + } + for _, testCase := range testCasesOmitEmpty { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeArrayFunc(func(enc *Encoder) { + enc.Uint8OmitEmpty(testCase.v) + enc.AddUint8OmitEmpty(testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } + var testCasesKeyBasic = []struct { + name string + v uint8 + expectedJSON string + }{ + { + name: "basic", + v: uint8(1), + expectedJSON: `{"foo":1,"bar":1}`, + }, + { + name: "big", + v: math.MaxUint8, + expectedJSON: `{"foo":255,"bar":255}`, + }, + { + name: "big", + v: uint8(0), + expectedJSON: `{"foo":0,"bar":0}`, + }, + } + for _, testCase := range testCasesKeyBasic { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeObjectFunc(func(enc *Encoder) { + enc.Uint8Key("foo", testCase.v) + enc.AddUint8Key("bar", testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } + var testCasesKeyOmitEmpty = []struct { + name string + v uint8 + expectedJSON string + }{ + { + name: "basic", + v: uint8(1), + expectedJSON: `{"foo":1,"bar":1}`, + }, + { + name: "big", + v: math.MaxUint8, + expectedJSON: `{"foo":255,"bar":255}`, + }, + { + name: "big", + v: uint8(0), + expectedJSON: `{}`, + }, + } + for _, testCase := range testCasesKeyOmitEmpty { + t.Run(testCase.name, func(t *testing.T) { + var b = &strings.Builder{} + var enc = NewEncoder(b) + enc.Encode(EncodeObjectFunc(func(enc *Encoder) { + enc.Uint8KeyOmitEmpty("foo", testCase.v) + enc.AddUint8KeyOmitEmpty("bar", testCase.v) + })) + assert.Equal(t, testCase.expectedJSON, b.String()) + }) + } +} diff --git a/encode_stream.go b/encode_stream.go @@ -169,12 +169,17 @@ func (s *StreamEncoder) AddInt(value int) { s.Encoder.writeByte(s.delimiter) } -// AddFloat adds a float64 to be encoded. -func (s *StreamEncoder) AddFloat(value float64) { +// AddFloat64 adds a float64 to be encoded. +func (s *StreamEncoder) AddFloat64(value float64) { s.buf = strconv.AppendFloat(s.buf, value, 'f', -1, 64) s.Encoder.writeByte(s.delimiter) } +// AddFloat adds a float64 to be encoded. +func (s *StreamEncoder) AddFloat(value float64) { + s.AddFloat64(value) +} + // Non exposed func consume(init *StreamEncoder, s *StreamEncoder, m MarshalerStream) { diff --git a/gojay.png b/gojay.png Binary files differ. diff --git a/gojay/Gopkg.lock b/gojay/Gopkg.lock @@ -0,0 +1,14 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + +[[projects]] + name = "github.com/fatih/structtag" + packages = ["."] + revision = "da3d9ab5b78fdc25d3a7614853b085200bd10da9" + version = "v0.1.0" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "859116499e74f6c88afba2065cef9e2ee1caa81d532e01ed144541f9d36faae8" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/gojay/Gopkg.toml b/gojay/Gopkg.toml @@ -0,0 +1,26 @@ + +# Gopkg.toml example +# +# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" + + +[[constraint]] + name = "github.com/fatih/structtag" + version = "0.1.0" diff --git a/gojay/Makefile b/gojay/Makefile @@ -0,0 +1,15 @@ +.PHONY: build +build: + go build ./ + +.PHONY: test +test: + go test -race -v . + +.PHONY: cover +cover: + go test -tags test -coverprofile=coverage.out + +.PHONY: coverhtml +coverhtml: + go tool cover -html=coverage.out +\ No newline at end of file diff --git a/gojay/README.md b/gojay/README.md @@ -0,0 +1,63 @@ +# Gojay code generator + +This package provides a command line tool to generate gojay's marshaling and unmarshing interface implementation for struct, slice and map types. + +## Get started + +```sh +go install github.com/francoispqt/gojay/gojay +``` + +## Generate code + +### Basic command + +The basic command is strait forward and easy to use: +```sh +gojay github.com/some/package TypeA,TypeB,TypeC output.go +``` +If you just want to the output to stdout, omit the third parameter. + +### Using flags + +- p package to parse, relative path to $GOPATH/src +- s file/dir to path, can be a relative or absolute path +- t types to generate (comma separated) +- o output file (relative or absolute path) + +Examples: +- Specific types in a go package, to stdout: +```sh +gojay -p github.com/francoispqt/gojay/gojay/tests -t A,B,StrSlice +``` + +- Specific types in a go package, write to a file: +```sh +gojay -p github.com/francoispqt/gojay/gojay/tests -t A,B,StrSlice -o output.go +``` + +- Specific types in a go file, to stdout: +```sh +gojay -s somegofile.go -t SomeType +``` + + +## Gojay tags + +You can add tags to your structs to control: +- the JSON key +- skip a struct field only for unmarshaling +- skip a struct field only for marshaling +- skip a struct field +- the use of omit empty methods for marshaling + +### Example: +```go +type A struct { + Str string `gojay:"string"` + StrOmitEmpty string `gojay:"string,omitempty"` + SkipUnmarshal string `gojay:"skipUnmarshal,-u"` + SkipMarshal string `gojay:"skipMarshal,-m"` + Skip string `gojay:"-"` +} +``` diff --git a/gojay/gen.go b/gojay/gen.go @@ -0,0 +1,146 @@ +package main + +import ( + "go/ast" + "log" + "strings" + "text/template" +) + +const gojayAnnotation = "//gojay:json" +const genFileSuffix = "_gojay.go" +const omitEmptyFuncName = "OmitEmpty" + +var pkgTpl *template.Template +var genHeader = []byte("// Code generated by GoJay. DO NOT EDIT.\n\n") +var gojayImport = []byte("import \"github.com/francoispqt/gojay\"\n") + +// Gen is the structure representing a generator +type Gen struct { + b *strings.Builder + pkg string + src string + types []string + genTypes map[string]*ast.TypeSpec + vis *vis +} + +type genTpl struct { + strTpl string + tpl *template.Template +} + +type templateList map[string]*genTpl + +func init() { + t, err := template.New("pkgDef"). + Parse("package {{.PkgName}} \n\n") + if err != nil { + log.Fatal(err) + } + pkgTpl = t +} + +func parseTemplates(tpls templateList, pfx string) { + for k, t := range tpls { + tpl, err := template.New(pfx + k).Parse(t.strTpl) + if err != nil { + log.Fatal(err) + } + t.tpl = tpl + } +} + +// NewGen returns a new generator +func NewGen(p string, types []string) *Gen { + g := &Gen{ + src: p, + types: types, + b: &strings.Builder{}, + genTypes: make(map[string]*ast.TypeSpec), + } + return g +} + +func (g *Gen) writePkg(pkg string) error { + err := pkgTpl.Execute(g.b, struct { + PkgName string + }{ + PkgName: pkg, + }) + if err != nil { + return err + } + return nil +} + +func (g *Gen) writeGojayImport() error { + _, err := g.b.Write(gojayImport) + if err != nil { + return err + } + return nil +} + +func (g *Gen) writeGenHeader() error { + _, err := g.b.Write(genHeader) + if err != nil { + return err + } + return nil +} + +func (g *Gen) isGenType(typeName string) bool { + // check if type is in types + for _, t := range g.types { + if t == typeName { + return true + } + } + return false +} + +// Gen starts the generation writing to the string builder +func (g *Gen) Gen() error { + err := g.writeGenHeader() + if err != nil { + return err + } + + // write package + err = g.writePkg(g.pkg) + if err != nil { + return err + } + // write import of gojay + err = g.writeGojayImport() + if err != nil { + return err + } + // range over specs + // generate interfaces implementations based on type + for _, s := range g.genTypes { + switch t := s.Type.(type) { + // is struct + case *ast.StructType: + err = g.genStruct(s.Name.String(), t) + if err != nil { + return err + } + // is array + case *ast.ArrayType: + err = g.genArray(s.Name.String(), t) + if err != nil { + return err + } + // is map + case *ast.MapType: + // TODO: generate for map type + err = g.genMap(s.Name.String(), t) + if err != nil { + return err + } + } + } + return nil +} diff --git a/gojay/gen_array.go b/gojay/gen_array.go @@ -0,0 +1,17 @@ +package main + +import ( + "go/ast" +) + +func (g *Gen) genArray(n string, s *ast.ArrayType) error { + err := g.arrGenUnmarshal(n, s) + if err != nil { + return err + } + err = g.arrGenMarshal(n, s) + if err != nil { + return err + } + return g.arrGenIsNil(n) +} diff --git a/gojay/gen_array_marshal.go b/gojay/gen_array_marshal.go @@ -0,0 +1,241 @@ +package main + +import ( + "errors" + "fmt" + "go/ast" + "log" +) + +func init() {} + +func (g *Gen) arrGenIsNil(n string) error { + err := arrMarshalTpl["isNil"].tpl.Execute(g.b, struct { + TypeName string + }{ + TypeName: n, + }) + return err +} + +func (g *Gen) arrGenMarshal(n string, s *ast.ArrayType) error { + err := arrMarshalTpl["def"].tpl.Execute(g.b, struct { + TypeName string + }{ + TypeName: n, + }) + if err != nil { + return err + } + // determine type of element in array + switch t := s.Elt.(type) { + case *ast.Ident: + err := g.arrGenMarshalIdent(t, false) + if err != nil { + return err + } + case *ast.StarExpr: + switch ptrExp := t.X.(type) { + case *ast.Ident: + err := g.arrGenMarshalIdent(ptrExp, true) + if err != nil { + return err + } + default: + return fmt.Errorf("Unknown type %s", n) + } + } + _, err = g.b.Write([]byte("}\n")) + if err != nil { + return err + } + return err +} + +func (g *Gen) arrGenMarshalIdent(i *ast.Ident, ptr bool) error { + switch i.String() { + case "string": + g.arrMarshalString(ptr) + case "bool": + g.arrMarshalBool(ptr) + case "int": + g.arrMarshalInt("", ptr) + case "int64": + g.arrMarshalInt("64", ptr) + case "int32": + g.arrMarshalInt("32", ptr) + case "int16": + g.arrMarshalInt("16", ptr) + case "int8": + g.arrMarshalInt("8", ptr) + case "uint64": + g.arrMarshalUint("64", ptr) + case "uint32": + g.arrMarshalUint("32", ptr) + case "uint16": + g.arrMarshalUint("16", ptr) + case "uint8": + g.arrMarshalUint("8", ptr) + case "float64": + g.arrMarshalFloat("64", ptr) + case "float32": + g.arrMarshalFloat("32", ptr) + default: + // if ident is already in our spec list + if sp, ok := g.genTypes[i.Name]; ok { + return g.arrMarshalNonPrim(sp, ptr) + } else if i.Obj != nil { + // else check the obj infos + switch t := i.Obj.Decl.(type) { + case *ast.TypeSpec: + return g.arrMarshalNonPrim(t, ptr) + default: + return errors.New("could not determine what to do with type " + i.String()) + } + } + return fmt.Errorf("Unknown type %s", i.Name) + } + return nil +} + +func (g *Gen) arrMarshalNonPrim(sp *ast.TypeSpec, ptr bool) error { + switch sp.Type.(type) { + case *ast.StructType: + g.arrMarshalStruct(sp, ptr) + case *ast.ArrayType: + g.arrMarshalArr(sp, ptr) + } + return nil +} + +func (g *Gen) arrMarshalString(ptr bool) { + if ptr { + err := arrMarshalTpl["stringPtr"].tpl.Execute(g.b, struct { + Ptr string + }{""}) + if err != nil { + log.Fatal(err) + } + } else { + err := arrMarshalTpl["string"].tpl.Execute(g.b, struct { + Ptr string + }{"&"}) + if err != nil { + log.Fatal(err) + } + } +} + +func (g *Gen) arrMarshalBool(ptr bool) { + if ptr { + err := arrMarshalTpl["bool"].tpl.Execute(g.b, struct { + Ptr string + }{""}) + if err != nil { + log.Fatal(err) + } + } else { + err := arrMarshalTpl["bool"].tpl.Execute(g.b, struct { + Ptr string + }{"&"}) + if err != nil { + log.Fatal(err) + } + } +} + +func (g *Gen) arrMarshalInt(intLen string, ptr bool) { + if ptr { + err := arrMarshalTpl["int"].tpl.Execute(g.b, struct { + IntLen string + Ptr string + }{intLen, ""}) + if err != nil { + log.Fatal(err) + } + } else { + err := arrMarshalTpl["int"].tpl.Execute(g.b, struct { + IntLen string + Ptr string + }{intLen, "&"}) + if err != nil { + log.Fatal(err) + } + } +} + +func (g *Gen) arrMarshalFloat(intLen string, ptr bool) { + if ptr { + err := arrMarshalTpl["float"].tpl.Execute(g.b, struct { + IntLen string + Ptr string + }{intLen, ""}) + if err != nil { + log.Fatal(err) + } + } else { + err := arrMarshalTpl["float"].tpl.Execute(g.b, struct { + IntLen string + Ptr string + }{intLen, "&"}) + if err != nil { + log.Fatal(err) + } + } +} + +func (g *Gen) arrMarshalUint(intLen string, ptr bool) { + if ptr { + err := arrMarshalTpl["uint"].tpl.Execute(g.b, struct { + IntLen string + Ptr string + }{intLen, ""}) + if err != nil { + log.Fatal(err) + } + } else { + err := arrMarshalTpl["uint"].tpl.Execute(g.b, struct { + IntLen string + Ptr string + }{intLen, "&"}) + if err != nil { + log.Fatal(err) + } + } +} + +func (g *Gen) arrMarshalStruct(st *ast.TypeSpec, ptr bool) { + if ptr { + err := arrMarshalTpl["structPtr"].tpl.Execute(g.b, struct { + StructName string + }{st.Name.String()}) + if err != nil { + log.Fatal(err) + } + } else { + err := arrMarshalTpl["struct"].tpl.Execute(g.b, struct { + StructName string + }{st.Name.String()}) + if err != nil { + log.Fatal(err) + } + } +} + +func (g *Gen) arrMarshalArr(st *ast.TypeSpec, ptr bool) { + if ptr { + err := arrMarshalTpl["arrPtr"].tpl.Execute(g.b, struct { + StructName string + }{st.Name.String()}) + if err != nil { + log.Fatal(err) + } + } else { + err := arrMarshalTpl["arr"].tpl.Execute(g.b, struct { + StructName string + }{st.Name.String()}) + if err != nil { + log.Fatal(err) + } + } +} diff --git a/gojay/gen_array_marshal_tpl.go b/gojay/gen_array_marshal_tpl.go @@ -0,0 +1,63 @@ +package main + +var arrMarshalTpl = templateList{ + "def": &genTpl{ + strTpl: "\n// MarshalJSONArray implements gojay's MarshalerJSONArray" + + "\nfunc (v *{{.TypeName}}) MarshalJSONArray(enc *gojay.Encoder) {\n", + }, + "isNil": &genTpl{ + strTpl: "\n// IsNil implements gojay's MarshalerJSONArray" + + "\nfunc (v *{{.TypeName}}) IsNil() bool {\n" + + "\treturn *v == nil || len(*v) == 0\n" + + "}\n", + }, + "string": &genTpl{ + strTpl: "\tfor _, s := range *v {\n" + + "\t\tenc.String(s)\n" + + "\t}\n", + }, + "bool": &genTpl{ + strTpl: "\tfor _, s := range *v {\n" + + "\t\tenc.Bool(s)\n" + + "\t}\n", + }, + "int": &genTpl{ + strTpl: "\tfor _, s := range *v {\n" + + "\t\tenc.Int{{.IntLen}}(s)\n" + + "\t}\n", + }, + "uint": &genTpl{ + strTpl: "\tfor _, s := range *v {\n" + + "\t\tenc.Uint{{.IntLen}}(s)\n" + + "\t}\n", + }, + "float": &genTpl{ + strTpl: "\tfor _, s := range *v {\n" + + "\t\tenc.Float{{.IntLen}}(s)\n" + + "\t}\n", + }, + "struct": &genTpl{ + strTpl: "\tfor _, s := range *v {\n" + + "\t\tenc.Object(s)\n" + + "\t}\n", + }, + "structPtr": &genTpl{ + strTpl: "\tfor _, s := range *v {\n" + + "\t\tenc.Object(s)\n" + + "\t}\n", + }, + "arr": &genTpl{ + strTpl: "\tfor _, s := range *v {\n" + + "\t\tenc.Array(s)\n" + + "\t}\n", + }, + "arrPtr": &genTpl{ + strTpl: "\tfor _, s := range *v {\n" + + "\t\tenc.Array(s)\n" + + "\t}\n", + }, +} + +func init() { + parseTemplates(arrMarshalTpl, "arrMarshal") +} diff --git a/gojay/gen_array_test.go b/gojay/gen_array_test.go @@ -0,0 +1,436 @@ +package main + +import ( + "io" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGenArray(t *testing.T) { + testCases := map[string]struct { + input io.Reader + expectedResult string + }{ + "basicStringSlice": { + input: strings.NewReader(`package test + +//gojay:json +type StrSlice []string + `), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONArray implements gojay's UnmarshalerJSONArray +func (v *StrSlice) UnmarshalJSONArray(dec *gojay.Decoder) error { + var str string + if err := dec.String(&str); err != nil { + return err + } + *v = append(*v, str) + return nil +} + +// MarshalJSONArray implements gojay's MarshalerJSONArray +func (v *StrSlice) MarshalJSONArray(enc *gojay.Encoder) { + for _, s := range *v { + enc.String(s) + } +} + +// IsNil implements gojay's MarshalerJSONArray +func (v *StrSlice) IsNil() bool { + return *v == nil || len(*v) == 0 +} +`, + }, + "basicStringBool": { + input: strings.NewReader(`package test + +//gojay:json +type BoolSlice []bool + `), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONArray implements gojay's UnmarshalerJSONArray +func (v *BoolSlice) UnmarshalJSONArray(dec *gojay.Decoder) error { + var b bool + if err := dec.Bool(&b); err != nil { + return err + } + *v = append(*v, b) + return nil +} + +// MarshalJSONArray implements gojay's MarshalerJSONArray +func (v *BoolSlice) MarshalJSONArray(enc *gojay.Encoder) { + for _, s := range *v { + enc.Bool(s) + } +} + +// IsNil implements gojay's MarshalerJSONArray +func (v *BoolSlice) IsNil() bool { + return *v == nil || len(*v) == 0 +} +`, + }, + "basicIntSlice": { + input: strings.NewReader(`package test + +//gojay:json +type IntSlice []int + `), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONArray implements gojay's UnmarshalerJSONArray +func (v *IntSlice) UnmarshalJSONArray(dec *gojay.Decoder) error { + var i int + if err := dec.Int(&i); err != nil { + return err + } + *v = append(*v, i) + return nil +} + +// MarshalJSONArray implements gojay's MarshalerJSONArray +func (v *IntSlice) MarshalJSONArray(enc *gojay.Encoder) { + for _, s := range *v { + enc.Int(s) + } +} + +// IsNil implements gojay's MarshalerJSONArray +func (v *IntSlice) IsNil() bool { + return *v == nil || len(*v) == 0 +} +`, + }, + "basicInt8Slice": { + input: strings.NewReader(`package test + + //gojay:json + type IntSlice []int8 + `), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONArray implements gojay's UnmarshalerJSONArray +func (v *IntSlice) UnmarshalJSONArray(dec *gojay.Decoder) error { + var i int8 + if err := dec.Int8(&i); err != nil { + return err + } + *v = append(*v, i) + return nil +} + +// MarshalJSONArray implements gojay's MarshalerJSONArray +func (v *IntSlice) MarshalJSONArray(enc *gojay.Encoder) { + for _, s := range *v { + enc.Int8(s) + } +} + +// IsNil implements gojay's MarshalerJSONArray +func (v *IntSlice) IsNil() bool { + return *v == nil || len(*v) == 0 +} +`, + }, + "basicInt16Slice": { + input: strings.NewReader(`package test + +//gojay:json +type IntSlice []int16 + `), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONArray implements gojay's UnmarshalerJSONArray +func (v *IntSlice) UnmarshalJSONArray(dec *gojay.Decoder) error { + var i int16 + if err := dec.Int16(&i); err != nil { + return err + } + *v = append(*v, i) + return nil +} + +// MarshalJSONArray implements gojay's MarshalerJSONArray +func (v *IntSlice) MarshalJSONArray(enc *gojay.Encoder) { + for _, s := range *v { + enc.Int16(s) + } +} + +// IsNil implements gojay's MarshalerJSONArray +func (v *IntSlice) IsNil() bool { + return *v == nil || len(*v) == 0 +} +`, + }, + "basicInt32Slice": { + input: strings.NewReader(`package test + + //gojay:json + type IntSlice []int32 + `), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONArray implements gojay's UnmarshalerJSONArray +func (v *IntSlice) UnmarshalJSONArray(dec *gojay.Decoder) error { + var i int32 + if err := dec.Int32(&i); err != nil { + return err + } + *v = append(*v, i) + return nil +} + +// MarshalJSONArray implements gojay's MarshalerJSONArray +func (v *IntSlice) MarshalJSONArray(enc *gojay.Encoder) { + for _, s := range *v { + enc.Int32(s) + } +} + +// IsNil implements gojay's MarshalerJSONArray +func (v *IntSlice) IsNil() bool { + return *v == nil || len(*v) == 0 +} +`, + }, + "basicInt64Slice": { + input: strings.NewReader(`package test + + //gojay:json + type IntSlice []int64 + `), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONArray implements gojay's UnmarshalerJSONArray +func (v *IntSlice) UnmarshalJSONArray(dec *gojay.Decoder) error { + var i int64 + if err := dec.Int64(&i); err != nil { + return err + } + *v = append(*v, i) + return nil +} + +// MarshalJSONArray implements gojay's MarshalerJSONArray +func (v *IntSlice) MarshalJSONArray(enc *gojay.Encoder) { + for _, s := range *v { + enc.Int64(s) + } +} + +// IsNil implements gojay's MarshalerJSONArray +func (v *IntSlice) IsNil() bool { + return *v == nil || len(*v) == 0 +} +`, + }, + "basicUint64Slice": { + input: strings.NewReader(`package test + + //gojay:json + type IntSlice []uint64 + `), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONArray implements gojay's UnmarshalerJSONArray +func (v *IntSlice) UnmarshalJSONArray(dec *gojay.Decoder) error { + var i uint64 + if err := dec.Uint64(&i); err != nil { + return err + } + *v = append(*v, i) + return nil +} + +// MarshalJSONArray implements gojay's MarshalerJSONArray +func (v *IntSlice) MarshalJSONArray(enc *gojay.Encoder) { + for _, s := range *v { + enc.Uint64(s) + } +} + +// IsNil implements gojay's MarshalerJSONArray +func (v *IntSlice) IsNil() bool { + return *v == nil || len(*v) == 0 +} +`, + }, + "basicFloatSlice": { + input: strings.NewReader(`package test + +//gojay:json +type IntSlice []float64 + `), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONArray implements gojay's UnmarshalerJSONArray +func (v *IntSlice) UnmarshalJSONArray(dec *gojay.Decoder) error { + var i float64 + if err := dec.Float64(&i); err != nil { + return err + } + *v = append(*v, i) + return nil +} + +// MarshalJSONArray implements gojay's MarshalerJSONArray +func (v *IntSlice) MarshalJSONArray(enc *gojay.Encoder) { + for _, s := range *v { + enc.Float64(s) + } +} + +// IsNil implements gojay's MarshalerJSONArray +func (v *IntSlice) IsNil() bool { + return *v == nil || len(*v) == 0 +} +`, + }, + "basicFloat32Slice": { + input: strings.NewReader(`package test + +//gojay:json +type IntSlice []float32 + `), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONArray implements gojay's UnmarshalerJSONArray +func (v *IntSlice) UnmarshalJSONArray(dec *gojay.Decoder) error { + var i float32 + if err := dec.Float32(&i); err != nil { + return err + } + *v = append(*v, i) + return nil +} + +// MarshalJSONArray implements gojay's MarshalerJSONArray +func (v *IntSlice) MarshalJSONArray(enc *gojay.Encoder) { + for _, s := range *v { + enc.Float32(s) + } +} + +// IsNil implements gojay's MarshalerJSONArray +func (v *IntSlice) IsNil() bool { + return *v == nil || len(*v) == 0 +} +`, + }, + "basicStructSlice": { + input: strings.NewReader(`package test + +//gojay:json +type StructSlice []*Struct + +type Struct struct{ + Str string +} + `), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONArray implements gojay's UnmarshalerJSONArray +func (v *StructSlice) UnmarshalJSONArray(dec *gojay.Decoder) error { + var s = &Struct{} + if err := dec.Object(s); err != nil { + return err + } + *v = append(*v, s) + return nil +} + +// MarshalJSONArray implements gojay's MarshalerJSONArray +func (v *StructSlice) MarshalJSONArray(enc *gojay.Encoder) { + for _, s := range *v { + enc.Object(s) + } +} + +// IsNil implements gojay's MarshalerJSONArray +func (v *StructSlice) IsNil() bool { + return *v == nil || len(*v) == 0 +} +`, + }, + "basicSliceSlice": { + input: strings.NewReader(`package test + +//gojay:json +type SliceStrSlice []StrSlice + +type StrSlice []string + `), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONArray implements gojay's UnmarshalerJSONArray +func (v *SliceStrSlice) UnmarshalJSONArray(dec *gojay.Decoder) error { + var s = make(StrSlice, 0) + if err := dec.Array(&s); err != nil { + return err + } + *v = append(*v, s) + return nil +} + +// MarshalJSONArray implements gojay's MarshalerJSONArray +func (v *SliceStrSlice) MarshalJSONArray(enc *gojay.Encoder) { + for _, s := range *v { + enc.Array(s) + } +} + +// IsNil implements gojay's MarshalerJSONArray +func (v *SliceStrSlice) IsNil() bool { + return *v == nil || len(*v) == 0 +} +`, + }, + } + for n, testCase := range testCases { + t.Run(n, func(t *testing.T) { + g, err := MakeGenFromReader(testCase.input) + if err != nil { + t.Fatal(err) + } + err = g.Gen() + if err != nil { + t.Fatal(err) + } + assert.Equal( + t, + string(genHeader)+testCase.expectedResult, + g.b.String(), + ) + }) + } +} diff --git a/gojay/gen_array_unmarshal.go b/gojay/gen_array_unmarshal.go @@ -0,0 +1,230 @@ +package main + +import ( + "errors" + "fmt" + "go/ast" + "log" +) + +func (g *Gen) arrGenUnmarshal(n string, s *ast.ArrayType) error { + err := arrUnmarshalTpl["def"].tpl.Execute(g.b, struct { + TypeName string + }{ + TypeName: n, + }) + if err != nil { + return err + } + // determine type of element in array + switch t := s.Elt.(type) { + case *ast.Ident: + err := g.arrGenUnmarshalIdent(t, false) + if err != nil { + return err + } + case *ast.StarExpr: + switch ptrExp := t.X.(type) { + case *ast.Ident: + err := g.arrGenUnmarshalIdent(ptrExp, true) + if err != nil { + return err + } + default: + return fmt.Errorf("Unknown type %s", n) + } + } + _, err = g.b.Write(structUnmarshalClose) + if err != nil { + return err + } + return err +} + +func (g *Gen) arrGenUnmarshalIdent(i *ast.Ident, ptr bool) error { + switch i.String() { + case "string": + g.arrUnmarshalString(ptr) + case "bool": + g.arrUnmarshalBool(ptr) + case "int": + g.arrUnmarshalInt("", ptr) + case "int64": + g.arrUnmarshalInt("64", ptr) + case "int32": + g.arrUnmarshalInt("32", ptr) + case "int16": + g.arrUnmarshalInt("16", ptr) + case "int8": + g.arrUnmarshalInt("8", ptr) + case "uint64": + g.arrUnmarshalUint("64", ptr) + case "uint32": + g.arrUnmarshalUint("32", ptr) + case "uint16": + g.arrUnmarshalUint("16", ptr) + case "uint8": + g.arrUnmarshalUint("8", ptr) + case "float64": + g.arrUnmarshalFloat("64", ptr) + case "float32": + g.arrUnmarshalFloat("32", ptr) + default: + // if ident is already in our spec list + if sp, ok := g.genTypes[i.Name]; ok { + return g.arrUnmarshalNonPrim(sp, ptr) + } else if i.Obj != nil { + // else check the obj infos + switch t := i.Obj.Decl.(type) { + case *ast.TypeSpec: + return g.arrUnmarshalNonPrim(t, ptr) + default: + return errors.New("could not determine what to do with type " + i.String()) + } + } + return fmt.Errorf("Unknown type %s", i.Name) + } + return nil +} + +func (g *Gen) arrUnmarshalNonPrim(sp *ast.TypeSpec, ptr bool) error { + switch sp.Type.(type) { + case *ast.StructType: + g.arrUnmarshalStruct(sp, ptr) + case *ast.ArrayType: + g.arrUnmarshalArr(sp, ptr) + } + return nil +} + +func (g *Gen) arrUnmarshalString(ptr bool) { + if ptr { + err := arrUnmarshalTpl["stringPtr"].tpl.Execute(g.b, struct { + Ptr string + }{""}) + if err != nil { + log.Fatal(err) + } + } else { + err := arrUnmarshalTpl["string"].tpl.Execute(g.b, struct { + Ptr string + }{"&"}) + if err != nil { + log.Fatal(err) + } + } +} + +func (g *Gen) arrUnmarshalBool(ptr bool) { + if ptr { + err := arrUnmarshalTpl["bool"].tpl.Execute(g.b, struct { + Ptr string + }{""}) + if err != nil { + log.Fatal(err) + } + } else { + err := arrUnmarshalTpl["bool"].tpl.Execute(g.b, struct { + Ptr string + }{"&"}) + if err != nil { + log.Fatal(err) + } + } +} + +func (g *Gen) arrUnmarshalInt(intLen string, ptr bool) { + if ptr { + err := arrUnmarshalTpl["int"].tpl.Execute(g.b, struct { + IntLen string + Ptr string + }{intLen, ""}) + if err != nil { + log.Fatal(err) + } + } else { + err := arrUnmarshalTpl["int"].tpl.Execute(g.b, struct { + IntLen string + Ptr string + }{intLen, "&"}) + if err != nil { + log.Fatal(err) + } + } +} + +func (g *Gen) arrUnmarshalUint(intLen string, ptr bool) { + if ptr { + err := arrUnmarshalTpl["uint"].tpl.Execute(g.b, struct { + IntLen string + Ptr string + }{intLen, ""}) + if err != nil { + log.Fatal(err) + } + } else { + err := arrUnmarshalTpl["uint"].tpl.Execute(g.b, struct { + IntLen string + Ptr string + }{intLen, "&"}) + if err != nil { + log.Fatal(err) + } + } +} + +func (g *Gen) arrUnmarshalFloat(intLen string, ptr bool) { + if ptr { + err := arrUnmarshalTpl["float"].tpl.Execute(g.b, struct { + IntLen string + Ptr string + }{intLen, ""}) + if err != nil { + log.Fatal(err) + } + } else { + err := arrUnmarshalTpl["float"].tpl.Execute(g.b, struct { + IntLen string + Ptr string + }{intLen, "&"}) + if err != nil { + log.Fatal(err) + } + } +} + +func (g *Gen) arrUnmarshalStruct(st *ast.TypeSpec, ptr bool) { + if ptr { + err := arrUnmarshalTpl["structPtr"].tpl.Execute(g.b, struct { + StructName string + }{st.Name.String()}) + if err != nil { + log.Fatal(err) + } + } else { + err := arrUnmarshalTpl["struct"].tpl.Execute(g.b, struct { + StructName string + }{st.Name.String()}) + if err != nil { + log.Fatal(err) + } + } +} + +func (g *Gen) arrUnmarshalArr(st *ast.TypeSpec, ptr bool) { + if ptr { + err := arrUnmarshalTpl["arrPtr"].tpl.Execute(g.b, struct { + StructName string + }{st.Name.String()}) + if err != nil { + log.Fatal(err) + } + } else { + err := arrUnmarshalTpl["arr"].tpl.Execute(g.b, struct { + StructName string + }{st.Name.String()}) + if err != nil { + log.Fatal(err) + } + } +} diff --git a/gojay/gen_array_unmarshal_tpl.go b/gojay/gen_array_unmarshal_tpl.go @@ -0,0 +1,70 @@ +package main + +var arrUnmarshalTpl = templateList{ + "def": &genTpl{ + strTpl: "\n// UnmarshalJSONArray implements gojay's UnmarshalerJSONArray" + + "\nfunc (v *{{.TypeName}}) UnmarshalJSONArray(dec *gojay.Decoder) error {\n", + }, + "string": &genTpl{ + strTpl: "\tvar str string" + + "\n\tif err := dec.String(&str); err != nil {\n" + + "\t\treturn err\n\t}\n" + + "\t*v = append(*v, str)\n", + }, + "stringPtr": &genTpl{ + strTpl: "\n// UnmarshalJSONArray implements gojay's UnmarshalerJSONArray" + + "\nfunc (v *{{.TypeName}}) UnmarshalJSONArray(dec *gojay.Decoder) error {\n", + }, + "int": &genTpl{ + strTpl: "\tvar i int{{.IntLen}}" + + "\n\tif err := dec.Int{{.IntLen}}(&i); err != nil {\n" + + "\t\treturn err\n\t}\n" + + "\t*v = append(*v, i)\n", + }, + "uint": &genTpl{ + strTpl: "\tvar i uint{{.IntLen}}" + + "\n\tif err := dec.Uint{{.IntLen}}(&i); err != nil {\n" + + "\t\treturn err\n\t}\n" + + "\t*v = append(*v, i)\n", + }, + "float": &genTpl{ + strTpl: "\tvar i float{{.IntLen}}" + + "\n\tif err := dec.Float{{.IntLen}}(&i); err != nil {\n" + + "\t\treturn err\n\t}\n" + + "\t*v = append(*v, i)\n", + }, + "bool": &genTpl{ + strTpl: "\tvar b bool" + + "\n\tif err := dec.Bool(&b); err != nil {\n" + + "\t\treturn err\n\t}\n" + + "\t*v = append(*v, b)\n", + }, + "struct": &genTpl{ + strTpl: "\tvar s = {{.StructName}}{}" + + "\n\tif err := dec.Object(&s); err != nil {\n" + + "\t\treturn err\n\t}\n" + + "\t*v = append(*v, s)\n", + }, + "structPtr": &genTpl{ + strTpl: "\tvar s = &{{.StructName}}{}" + + "\n\tif err := dec.Object(s); err != nil {\n" + + "\t\treturn err\n\t}\n" + + "\t*v = append(*v, s)\n", + }, + "arr": &genTpl{ + strTpl: "\tvar s = make({{.StructName}}, 0)" + + "\n\tif err := dec.Array(&s); err != nil {\n" + + "\t\treturn err\n\t}\n" + + "\t*v = append(*v, s)\n", + }, + "arrPtr": &genTpl{ + strTpl: "\tvar s = make({{.StructName}}, 0)" + + "\n\tif err := dec.Array(&s); err != nil {\n" + + "\t\treturn err\n\t}\n" + + "\t*v = append(*v, &s)\n", + }, +} + +func init() { + parseTemplates(arrUnmarshalTpl, "arrUnarshal") +} diff --git a/gojay/gen_map.go b/gojay/gen_map.go @@ -0,0 +1,19 @@ +package main + +import "go/ast" + +func (g *Gen) genMap(n string, s *ast.MapType) error { + err := g.mapGenUnmarshalObj(n, s) + if err != nil { + return err + } + err = g.mapGenNKeys(n, 0) + if err != nil { + return err + } + err = g.mapGenMarshalObj(n, s) + if err != nil { + return err + } + return g.mapGenIsNil(n) +} diff --git a/gojay/gen_map_marshal.go b/gojay/gen_map_marshal.go @@ -0,0 +1,201 @@ +package main + +import ( + "errors" + "fmt" + "go/ast" + "log" +) + +func (g *Gen) mapGenIsNil(n string) error { + err := mapMarshalTpl["isNil"].tpl.Execute(g.b, struct { + StructName string + }{ + StructName: n, + }) + return err +} + +func (g *Gen) mapGenMarshalObj(n string, s *ast.MapType) error { + err := mapMarshalTpl["def"].tpl.Execute(g.b, struct { + StructName string + }{ + StructName: n, + }) + if err != nil { + return err + } + switch t := s.Value.(type) { + case *ast.Ident: + var err error + err = g.mapGenMarshalIdent(t, false) + if err != nil { + return err + } + case *ast.StarExpr: + switch ptrExp := t.X.(type) { + case *ast.Ident: + var err error + err = g.mapGenMarshalIdent(ptrExp, true) + if err != nil { + return err + } + default: + return fmt.Errorf("Unknown type %s", n) + } + } + _, err = g.b.Write([]byte("}\n")) + if err != nil { + return err + } + return nil +} + +func (g *Gen) mapGenMarshalIdent(i *ast.Ident, ptr bool) error { + switch i.String() { + case "string": + g.mapMarshalString(ptr) + case "bool": + g.mapMarshalBool(ptr) + case "int": + g.mapMarshalInt("", ptr) + case "int64": + g.mapMarshalInt("64", ptr) + case "int32": + g.mapMarshalInt("32", ptr) + case "int16": + g.mapMarshalInt("16", ptr) + case "int8": + g.mapMarshalInt("8", ptr) + case "uint64": + g.mapMarshalUint("64", ptr) + case "uint32": + g.mapMarshalUint("32", ptr) + case "uint16": + g.mapMarshalUint("16", ptr) + case "uint8": + g.mapMarshalUint("8", ptr) + case "float64": + g.mapMarshalFloat("64", ptr) + case "float32": + g.mapMarshalFloat("32", ptr) + default: + // if ident is already in our spec list + if sp, ok := g.genTypes[i.Name]; ok { + g.mapMarshalNonPrim(sp, ptr) + } else if i.Obj != nil { + switch t := i.Obj.Decl.(type) { + case *ast.TypeSpec: + g.mapMarshalNonPrim(t, ptr) + default: + return errors.New("could not determine what to do with type " + i.String()) + } + } else { + return fmt.Errorf("Unknown type %s", i.Name) + } + } + return nil +} + +func (g *Gen) mapMarshalNonPrim(sp *ast.TypeSpec, ptr bool) { + switch sp.Type.(type) { + case *ast.StructType: + g.mapMarshalStruct(sp, ptr) + case *ast.ArrayType: + g.mapMarshalArr(sp, ptr) + } +} + +func (g *Gen) mapMarshalString(ptr bool) { + ptrStr := "" + if ptr { + ptrStr = "*" + } + err := mapMarshalTpl["string"].tpl.Execute(g.b, struct { + Ptr string + }{ptrStr}) + if err != nil { + log.Fatal(err) + } +} + +func (g *Gen) mapMarshalBool(ptr bool) { + ptrStr := "" + if ptr { + ptrStr = "*" + } + err := mapMarshalTpl["bool"].tpl.Execute(g.b, struct { + Ptr string + }{ptrStr}) + if err != nil { + log.Fatal(err) + } +} + +func (g *Gen) mapMarshalInt(intLen string, ptr bool) { + ptrStr := "" + if ptr { + ptrStr = "*" + } + err := mapMarshalTpl["int"].tpl.Execute(g.b, struct { + IntLen string + Ptr string + }{intLen, ptrStr}) + if err != nil { + log.Fatal(err) + } +} + +func (g *Gen) mapMarshalUint(intLen string, ptr bool) { + ptrStr := "" + if ptr { + ptrStr = "*" + } + err := mapMarshalTpl["uint"].tpl.Execute(g.b, struct { + IntLen string + Ptr string + }{intLen, ptrStr}) + if err != nil { + log.Fatal(err) + } +} + +func (g *Gen) mapMarshalFloat(intLen string, ptr bool) { + ptrStr := "" + if ptr { + ptrStr = "*" + } + err := mapMarshalTpl["float"].tpl.Execute(g.b, struct { + IntLen string + Ptr string + }{intLen, ptrStr}) + if err != nil { + log.Fatal(err) + } +} + +func (g *Gen) mapMarshalStruct(st *ast.TypeSpec, ptr bool) { + ptrStr := "" + if ptr { + ptrStr = "*" + } + var err = mapMarshalTpl["struct"].tpl.Execute(g.b, struct { + Ptr string + }{ptrStr}) + if err != nil { + log.Fatal(err) + } +} + +func (g *Gen) mapMarshalArr(st *ast.TypeSpec, ptr bool) { + ptrStr := "" + if ptr { + ptrStr = "*" + } + var err = mapMarshalTpl["arr"].tpl.Execute(g.b, struct { + Ptr string + }{ptrStr}) + if err != nil { + log.Fatal(err) + } +} diff --git a/gojay/gen_map_marshal_tpl.go b/gojay/gen_map_marshal_tpl.go @@ -0,0 +1,60 @@ +package main + +var mapMarshalTpl = templateList{ + "def": &genTpl{ + strTpl: "\n// MarshalJSONObject implements gojay's MarshalerJSONObject" + + "\nfunc (v {{.StructName}}) MarshalJSONObject(enc *gojay.Encoder) {\n", + }, + "isNil": &genTpl{ + strTpl: ` +// IsNil returns wether the structure is nil value or not +func (v {{.StructName}}) IsNil() bool { return v == nil || len(v) == 0 } +`, + }, + "string": &genTpl{ + strTpl: ` for k, s := range v { + enc.StringKey(k, {{.Ptr}}s) + } +`, + }, + "int": &genTpl{ + strTpl: ` for k, s := range v { + enc.Int{{.IntLen}}Key(k, {{.Ptr}}s) + } +`, + }, + "uint": &genTpl{ + strTpl: ` for k, s := range v { + enc.Uint{{.IntLen}}Key(k, {{.Ptr}}s) + } +`, + }, + "float": &genTpl{ + strTpl: ` for k, s := range v { + enc.Float{{.IntLen}}Key(k, {{.Ptr}}s) + } +`, + }, + "bool": &genTpl{ + strTpl: ` for k, s := range v { + enc.BoolKey(k, {{.Ptr}}s) + } +`, + }, + "struct": &genTpl{ + strTpl: ` for k, s := range v { + enc.ObjectKey(k, s) + } +`, + }, + "arr": &genTpl{ + strTpl: ` for k, s := range v { + enc.ArrayKey(k, s) + } +`, + }, +} + +func init() { + parseTemplates(mapMarshalTpl, "mapMarshal") +} diff --git a/gojay/gen_map_test.go b/gojay/gen_map_test.go @@ -0,0 +1,616 @@ +package main + +import ( + "io" + "log" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGenMap(t *testing.T) { + testCases := map[string]struct { + input io.Reader + expectedResult string + }{ + "basicMapStringString": { + input: strings.NewReader(`package test + +//gojay:json +type StrMap map[string]string +`), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (v StrMap) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + var str string + if err := dec.String(&str); err != nil { + return err + } + v[k] = str + return nil +} + +// NKeys returns the number of keys to unmarshal +func (v StrMap) NKeys() int { return 0 } + +// MarshalJSONObject implements gojay's MarshalerJSONObject +func (v StrMap) MarshalJSONObject(enc *gojay.Encoder) { + for k, s := range v { + enc.StringKey(k, s) + } +} + +// IsNil returns wether the structure is nil value or not +func (v StrMap) IsNil() bool { return v == nil || len(v) == 0 } +`, + }, + "basicMapStringStringPtr": { + input: strings.NewReader(`package test + +//gojay:json +type StrMap map[string]*string +`), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (v StrMap) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + var str string + if err := dec.String(&str); err != nil { + return err + } + v[k] = &str + return nil +} + +// NKeys returns the number of keys to unmarshal +func (v StrMap) NKeys() int { return 0 } + +// MarshalJSONObject implements gojay's MarshalerJSONObject +func (v StrMap) MarshalJSONObject(enc *gojay.Encoder) { + for k, s := range v { + enc.StringKey(k, *s) + } +} + +// IsNil returns wether the structure is nil value or not +func (v StrMap) IsNil() bool { return v == nil || len(v) == 0 } +`, + }, + "basicMapStringInt": { + input: strings.NewReader(`package test + +//gojay:json +type IntMap map[string]int +`), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (v IntMap) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + var i int + if err := dec.Int(&i); err != nil { + return err + } + v[k] = i + return nil +} + +// NKeys returns the number of keys to unmarshal +func (v IntMap) NKeys() int { return 0 } + +// MarshalJSONObject implements gojay's MarshalerJSONObject +func (v IntMap) MarshalJSONObject(enc *gojay.Encoder) { + for k, s := range v { + enc.IntKey(k, s) + } +} + +// IsNil returns wether the structure is nil value or not +func (v IntMap) IsNil() bool { return v == nil || len(v) == 0 } +`, + }, + "basicMapStringIntPtr": { + input: strings.NewReader(`package test + +//gojay:json +type IntMap map[string]*int +`), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (v IntMap) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + var i int + if err := dec.Int(&i); err != nil { + return err + } + v[k] = &i + return nil +} + +// NKeys returns the number of keys to unmarshal +func (v IntMap) NKeys() int { return 0 } + +// MarshalJSONObject implements gojay's MarshalerJSONObject +func (v IntMap) MarshalJSONObject(enc *gojay.Encoder) { + for k, s := range v { + enc.IntKey(k, *s) + } +} + +// IsNil returns wether the structure is nil value or not +func (v IntMap) IsNil() bool { return v == nil || len(v) == 0 } +`, + }, + "basicMapStringInt64": { + input: strings.NewReader(`package test + +//gojay:json +type IntMap map[string]int64 +`), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (v IntMap) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + var i int64 + if err := dec.Int64(&i); err != nil { + return err + } + v[k] = i + return nil +} + +// NKeys returns the number of keys to unmarshal +func (v IntMap) NKeys() int { return 0 } + +// MarshalJSONObject implements gojay's MarshalerJSONObject +func (v IntMap) MarshalJSONObject(enc *gojay.Encoder) { + for k, s := range v { + enc.Int64Key(k, s) + } +} + +// IsNil returns wether the structure is nil value or not +func (v IntMap) IsNil() bool { return v == nil || len(v) == 0 } +`, + }, + "basicMapStringInt64Ptr": { + input: strings.NewReader(`package test + +//gojay:json +type IntMap map[string]*int64 +`), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (v IntMap) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + var i int64 + if err := dec.Int64(&i); err != nil { + return err + } + v[k] = &i + return nil +} + +// NKeys returns the number of keys to unmarshal +func (v IntMap) NKeys() int { return 0 } + +// MarshalJSONObject implements gojay's MarshalerJSONObject +func (v IntMap) MarshalJSONObject(enc *gojay.Encoder) { + for k, s := range v { + enc.Int64Key(k, *s) + } +} + +// IsNil returns wether the structure is nil value or not +func (v IntMap) IsNil() bool { return v == nil || len(v) == 0 } +`, + }, + "basicMapStringInt32": { + input: strings.NewReader(`package test + +//gojay:json +type IntMap map[string]int32 +`), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (v IntMap) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + var i int32 + if err := dec.Int32(&i); err != nil { + return err + } + v[k] = i + return nil +} + +// NKeys returns the number of keys to unmarshal +func (v IntMap) NKeys() int { return 0 } + +// MarshalJSONObject implements gojay's MarshalerJSONObject +func (v IntMap) MarshalJSONObject(enc *gojay.Encoder) { + for k, s := range v { + enc.Int32Key(k, s) + } +} + +// IsNil returns wether the structure is nil value or not +func (v IntMap) IsNil() bool { return v == nil || len(v) == 0 } +`, + }, + "basicMapStringInt16": { + input: strings.NewReader(`package test + +//gojay:json +type IntMap map[string]int16 +`), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (v IntMap) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + var i int16 + if err := dec.Int16(&i); err != nil { + return err + } + v[k] = i + return nil +} + +// NKeys returns the number of keys to unmarshal +func (v IntMap) NKeys() int { return 0 } + +// MarshalJSONObject implements gojay's MarshalerJSONObject +func (v IntMap) MarshalJSONObject(enc *gojay.Encoder) { + for k, s := range v { + enc.Int16Key(k, s) + } +} + +// IsNil returns wether the structure is nil value or not +func (v IntMap) IsNil() bool { return v == nil || len(v) == 0 } +`, + }, + "basicMapStringInt8": { + input: strings.NewReader(`package test + +//gojay:json +type IntMap map[string]int8 +`), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (v IntMap) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + var i int8 + if err := dec.Int8(&i); err != nil { + return err + } + v[k] = i + return nil +} + +// NKeys returns the number of keys to unmarshal +func (v IntMap) NKeys() int { return 0 } + +// MarshalJSONObject implements gojay's MarshalerJSONObject +func (v IntMap) MarshalJSONObject(enc *gojay.Encoder) { + for k, s := range v { + enc.Int8Key(k, s) + } +} + +// IsNil returns wether the structure is nil value or not +func (v IntMap) IsNil() bool { return v == nil || len(v) == 0 } +`, + }, + "basicMapStringUint64": { + input: strings.NewReader(`package test + +//gojay:json +type IntMap map[string]uint64 +`), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (v IntMap) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + var i uint64 + if err := dec.Uint64(&i); err != nil { + return err + } + v[k] = i + return nil +} + +// NKeys returns the number of keys to unmarshal +func (v IntMap) NKeys() int { return 0 } + +// MarshalJSONObject implements gojay's MarshalerJSONObject +func (v IntMap) MarshalJSONObject(enc *gojay.Encoder) { + for k, s := range v { + enc.Uint64Key(k, s) + } +} + +// IsNil returns wether the structure is nil value or not +func (v IntMap) IsNil() bool { return v == nil || len(v) == 0 } +`, + }, + "basicMapStringUint64Ptr": { + input: strings.NewReader(`package test + +//gojay:json +type IntMap map[string]*uint64 +`), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (v IntMap) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + var i uint64 + if err := dec.Uint64(&i); err != nil { + return err + } + v[k] = &i + return nil +} + +// NKeys returns the number of keys to unmarshal +func (v IntMap) NKeys() int { return 0 } + +// MarshalJSONObject implements gojay's MarshalerJSONObject +func (v IntMap) MarshalJSONObject(enc *gojay.Encoder) { + for k, s := range v { + enc.Uint64Key(k, *s) + } +} + +// IsNil returns wether the structure is nil value or not +func (v IntMap) IsNil() bool { return v == nil || len(v) == 0 } +`, + }, + "basicMapStringUint32": { + input: strings.NewReader(`package test + +//gojay:json +type IntMap map[string]uint32 +`), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (v IntMap) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + var i uint32 + if err := dec.Uint32(&i); err != nil { + return err + } + v[k] = i + return nil +} + +// NKeys returns the number of keys to unmarshal +func (v IntMap) NKeys() int { return 0 } + +// MarshalJSONObject implements gojay's MarshalerJSONObject +func (v IntMap) MarshalJSONObject(enc *gojay.Encoder) { + for k, s := range v { + enc.Uint32Key(k, s) + } +} + +// IsNil returns wether the structure is nil value or not +func (v IntMap) IsNil() bool { return v == nil || len(v) == 0 } +`, + }, + "basicMapStringUint16": { + input: strings.NewReader(`package test + +//gojay:json +type IntMap map[string]uint16 +`), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (v IntMap) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + var i uint16 + if err := dec.Uint16(&i); err != nil { + return err + } + v[k] = i + return nil +} + +// NKeys returns the number of keys to unmarshal +func (v IntMap) NKeys() int { return 0 } + +// MarshalJSONObject implements gojay's MarshalerJSONObject +func (v IntMap) MarshalJSONObject(enc *gojay.Encoder) { + for k, s := range v { + enc.Uint16Key(k, s) + } +} + +// IsNil returns wether the structure is nil value or not +func (v IntMap) IsNil() bool { return v == nil || len(v) == 0 } +`, + }, + "basicMapStringUint8": { + input: strings.NewReader(`package test + +//gojay:json +type IntMap map[string]uint8 +`), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (v IntMap) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + var i uint8 + if err := dec.Uint8(&i); err != nil { + return err + } + v[k] = i + return nil +} + +// NKeys returns the number of keys to unmarshal +func (v IntMap) NKeys() int { return 0 } + +// MarshalJSONObject implements gojay's MarshalerJSONObject +func (v IntMap) MarshalJSONObject(enc *gojay.Encoder) { + for k, s := range v { + enc.Uint8Key(k, s) + } +} + +// IsNil returns wether the structure is nil value or not +func (v IntMap) IsNil() bool { return v == nil || len(v) == 0 } +`, + }, + "basicMapStringBool": { + input: strings.NewReader(`package test + +//gojay:json +type BoolMap map[string]bool +`), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (v BoolMap) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + var b bool + if err := dec.Bool(&b); err != nil { + return err + } + v[k] = b + return nil +} + +// NKeys returns the number of keys to unmarshal +func (v BoolMap) NKeys() int { return 0 } + +// MarshalJSONObject implements gojay's MarshalerJSONObject +func (v BoolMap) MarshalJSONObject(enc *gojay.Encoder) { + for k, s := range v { + enc.BoolKey(k, s) + } +} + +// IsNil returns wether the structure is nil value or not +func (v BoolMap) IsNil() bool { return v == nil || len(v) == 0 } +`, + }, + "basicMapStringBoolPtr": { + input: strings.NewReader(`package test + +//gojay:json +type BoolMap map[string]*bool +`), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (v BoolMap) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + var b bool + if err := dec.Bool(&b); err != nil { + return err + } + v[k] = &b + return nil +} + +// NKeys returns the number of keys to unmarshal +func (v BoolMap) NKeys() int { return 0 } + +// MarshalJSONObject implements gojay's MarshalerJSONObject +func (v BoolMap) MarshalJSONObject(enc *gojay.Encoder) { + for k, s := range v { + enc.BoolKey(k, *s) + } +} + +// IsNil returns wether the structure is nil value or not +func (v BoolMap) IsNil() bool { return v == nil || len(v) == 0 } +`, + }, + "basicMapStringStruct": { + input: strings.NewReader(`package test + +//gojay:json +type BoolMap map[string]*Test + +type Test struct{} +`), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (v BoolMap) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + var s = &Test{} + if err := dec.Object(s); err != nil { + return err + } + v[k] = s + return nil +} + +// NKeys returns the number of keys to unmarshal +func (v BoolMap) NKeys() int { return 0 } + +// MarshalJSONObject implements gojay's MarshalerJSONObject +func (v BoolMap) MarshalJSONObject(enc *gojay.Encoder) { + for k, s := range v { + enc.ObjectKey(k, s) + } +} + +// IsNil returns wether the structure is nil value or not +func (v BoolMap) IsNil() bool { return v == nil || len(v) == 0 } +`, + }, + } + for n, testCase := range testCases { + t.Run(n, func(t *testing.T) { + g, err := MakeGenFromReader(testCase.input) + if err != nil { + t.Fatal(err) + } + err = g.Gen() + if err != nil { + t.Fatal(err) + } + log.Print(g.b.String()) + assert.Equal( + t, + string(genHeader)+testCase.expectedResult, + g.b.String(), + ) + }) + } +} diff --git a/gojay/gen_map_unmarshal.go b/gojay/gen_map_unmarshal.go @@ -0,0 +1,242 @@ +package main + +import ( + "errors" + "fmt" + "go/ast" + "log" +) + +func (g *Gen) mapGenNKeys(n string, count int) error { + err := mapUnmarshalTpl["nKeys"].tpl.Execute(g.b, struct { + NKeys int + StructName string + }{ + NKeys: count, + StructName: n, + }) + return err +} + +func (g *Gen) mapGenUnmarshalObj(n string, s *ast.MapType) error { + err := mapUnmarshalTpl["def"].tpl.Execute(g.b, struct { + TypeName string + }{ + TypeName: n, + }) + if err != nil { + return err + } + switch t := s.Value.(type) { + case *ast.Ident: + var err error + err = g.mapGenUnmarshalIdent(t, false) + if err != nil { + return err + } + case *ast.StarExpr: + switch ptrExp := t.X.(type) { + case *ast.Ident: + var err error + err = g.mapGenUnmarshalIdent(ptrExp, true) + if err != nil { + return err + } + default: + return fmt.Errorf("Unknown type %s", n) + } + } + _, err = g.b.Write(structUnmarshalClose) + if err != nil { + return err + } + return nil +} + +func (g *Gen) mapGenUnmarshalIdent(i *ast.Ident, ptr bool) error { + switch i.String() { + case "string": + g.mapUnmarshalString(ptr) + case "bool": + g.mapUnmarshalBool(ptr) + case "int": + g.mapUnmarshalInt("", ptr) + case "int64": + g.mapUnmarshalInt("64", ptr) + case "int32": + g.mapUnmarshalInt("32", ptr) + case "int16": + g.mapUnmarshalInt("16", ptr) + case "int8": + g.mapUnmarshalInt("8", ptr) + case "uint64": + g.mapUnmarshalUint("64", ptr) + case "uint32": + g.mapUnmarshalUint("32", ptr) + case "uint16": + g.mapUnmarshalUint("16", ptr) + case "uint8": + g.mapUnmarshalUint("8", ptr) + case "float64": + g.mapUnmarshalFloat("64", ptr) + case "float32": + g.mapUnmarshalFloat("32", ptr) + default: + // if ident is already in our spec list + if sp, ok := g.genTypes[i.Name]; ok { + err := g.mapUnmarshalNonPrim(sp, ptr) + if err != nil { + return err + } + } else if i.Obj != nil { + // else check the obj infos + switch t := i.Obj.Decl.(type) { + case *ast.TypeSpec: + err := g.mapUnmarshalNonPrim(t, ptr) + if err != nil { + return err + } + default: + return errors.New("could not determine what to do with type " + i.String()) + } + } else { + return fmt.Errorf("Unknown type %s", i.Name) + } + } + return nil +} + +func (g *Gen) mapUnmarshalNonPrim(sp *ast.TypeSpec, ptr bool) error { + switch sp.Type.(type) { + case *ast.StructType: + g.mapUnmarshalStruct(sp, ptr) + return nil + case *ast.ArrayType: + g.mapUnmarshalArr(sp, ptr) + return nil + } + return errors.New("Unknown type") +} + +func (g *Gen) mapUnmarshalString(ptr bool) { + if ptr { + err := mapUnmarshalTpl["string"].tpl.Execute(g.b, struct { + Ptr string + }{"&"}) + if err != nil { + log.Fatal(err) + } + } else { + err := mapUnmarshalTpl["string"].tpl.Execute(g.b, struct { + Ptr string + }{""}) + if err != nil { + log.Fatal(err) + } + } +} + +func (g *Gen) mapUnmarshalBool(ptr bool) { + if ptr { + err := mapUnmarshalTpl["bool"].tpl.Execute(g.b, struct { + Ptr string + }{"&"}) + if err != nil { + log.Fatal(err) + } + } else { + err := mapUnmarshalTpl["bool"].tpl.Execute(g.b, struct { + Ptr string + }{""}) + if err != nil { + log.Fatal(err) + } + } +} + +func (g *Gen) mapUnmarshalInt(intLen string, ptr bool) { + if ptr { + err := mapUnmarshalTpl["int"].tpl.Execute(g.b, struct { + IntLen string + Ptr string + }{intLen, "&"}) + if err != nil { + log.Fatal(err) + } + } else { + err := mapUnmarshalTpl["int"].tpl.Execute(g.b, struct { + IntLen string + Ptr string + }{intLen, ""}) + if err != nil { + log.Fatal(err) + } + } +} + +func (g *Gen) mapUnmarshalUint(intLen string, ptr bool) { + if ptr { + err := mapUnmarshalTpl["uint"].tpl.Execute(g.b, struct { + IntLen string + Ptr string + }{intLen, "&"}) + if err != nil { + log.Fatal(err) + } + } else { + err := mapUnmarshalTpl["uint"].tpl.Execute(g.b, struct { + IntLen string + Ptr string + }{intLen, ""}) + if err != nil { + log.Fatal(err) + } + } +} + +func (g *Gen) mapUnmarshalFloat(intLen string, ptr bool) { + if ptr { + err := mapUnmarshalTpl["float"].tpl.Execute(g.b, struct { + IntLen string + Ptr string + }{intLen, "&"}) + if err != nil { + log.Fatal(err) + } + } else { + err := mapUnmarshalTpl["float"].tpl.Execute(g.b, struct { + IntLen string + Ptr string + }{intLen, ""}) + if err != nil { + log.Fatal(err) + } + } +} + +func (g *Gen) mapUnmarshalStruct(st *ast.TypeSpec, ptr bool) { + if ptr { + err := mapUnmarshalTpl["structPtr"].tpl.Execute(g.b, struct { + StructName string + }{st.Name.String()}) + if err != nil { + log.Fatal(err) + } + } else { + err := mapUnmarshalTpl["struct"].tpl.Execute(g.b, struct { + StructName string + }{st.Name.String()}) + if err != nil { + log.Fatal(err) + } + } +} + +func (g *Gen) mapUnmarshalArr(st *ast.TypeSpec, ptr bool) { + err := mapUnmarshalTpl["arr"].tpl.Execute(g.b, struct { + TypeName string + }{st.Name.String()}) + if err != nil { + log.Fatal(err) + } +} diff --git a/gojay/gen_map_unmarshal_tpl.go b/gojay/gen_map_unmarshal_tpl.go @@ -0,0 +1,82 @@ +package main + +var mapUnmarshalTpl = templateList{ + "def": &genTpl{ + strTpl: "\n// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject" + + "\nfunc (v {{.TypeName}}) UnmarshalJSONObject(dec *gojay.Decoder, k string) error {\n", + }, + "nKeys": &genTpl{ + strTpl: ` +// NKeys returns the number of keys to unmarshal +func (v {{.StructName}}) NKeys() int { return {{.NKeys}} } +`, + }, + "string": &genTpl{ + strTpl: ` var str string + if err := dec.String(&str); err != nil { + return err + } + v[k] = {{.Ptr}}str +`, + }, + "int": &genTpl{ + strTpl: ` var i int{{.IntLen}} + if err := dec.Int{{.IntLen}}(&i); err != nil { + return err + } + v[k] = {{.Ptr}}i +`, + }, + "uint": &genTpl{ + strTpl: ` var i uint{{.IntLen}} + if err := dec.Uint{{.IntLen}}(&i); err != nil { + return err + } + v[k] = {{.Ptr}}i +`, + }, + "float": &genTpl{ + strTpl: ` var i float{{.IntLen}} + if err := dec.Float{{.IntLen}}(&i); err != nil { + return err + } + v[k] = {{.Ptr}}i +`, + }, + "bool": &genTpl{ + strTpl: ` var b bool + if err := dec.Bool(&b); err != nil { + return err + } + v[k] = {{.Ptr}}b +`, + }, + "struct": &genTpl{ + strTpl: ` var s = {{.StructName}}{} + if err := dec.Object(&s); err != nil { + return err + } + v[k] = s +`, + }, + "structPtr": &genTpl{ + strTpl: ` var s = &{{.StructName}}{} + if err := dec.Object(s); err != nil { + return err + } + v[k] = s +`, + }, + "arr": &genTpl{ + strTpl: ` var s = &{{.StructName}}{} + if err := dec.Array(s); err != nil { + return err + } + v[k] = s +`, + }, +} + +func init() { + parseTemplates(mapUnmarshalTpl, "mapUnmarshal") +} diff --git a/gojay/gen_parse.go b/gojay/gen_parse.go @@ -0,0 +1,63 @@ +//+build !test + +package main + +import ( + "go/ast" + "go/parser" + "go/token" + "os" +) + +func (g *Gen) parse() error { + var f, err = os.Stat(g.src) + if err != nil { + return err + } + if f.IsDir() { + err = g.parseDir() + } else { + err = g.parseFile() + } + return err +} + +func (g *Gen) parseDir() error { + // parse the given path + fset := token.NewFileSet() + pkgs, err := parser.ParseDir(fset, g.src, nil, parser.ParseComments) + if err != nil { + return err + } + // range across packages + for pkgName, pkg := range pkgs { + v := newVisitor(g, pkgName) + g.pkg = pkgName + // range on files in package + for _, f := range pkg.Files { + ast.Walk(v, f) + if err != nil { + return err + } + } + g.vis = v + } + return nil +} + +func (g *Gen) parseFile() error { + // parse the given path + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, g.src, nil, parser.ParseComments) + if err != nil { + return err + } + g.pkg = f.Name.Name + v := newVisitor(g, g.pkg) + ast.Walk(v, f) + if err != nil { + return err + } + g.vis = v + return nil +} diff --git a/gojay/gen_struct_marshal.go b/gojay/gen_struct_marshal.go @@ -0,0 +1,290 @@ +package main + +import ( + "fmt" + "go/ast" + "log" +) + +func (g *Gen) structGenIsNil(n string) error { + err := structMarshalTpl["isNil"].tpl.Execute(g.b, struct { + StructName string + }{ + StructName: n, + }) + return err +} + +func (g *Gen) structGenMarshalObj(n string, s *ast.StructType) (int, error) { + err := structMarshalTpl["def"].tpl.Execute(g.b, struct { + StructName string + }{ + StructName: n, + }) + if err != nil { + return 0, err + } + keys := 0 + if len(s.Fields.List) > 0 { + // TODO: check tags + for _, field := range s.Fields.List { + // check if has hide tag + var omitEmpty string + if field.Tag != nil { + if hasTagMarshalHide(field.Tag) { + continue + } + if hasTagOmitEmpty(field.Tag) { + omitEmpty = omitEmptyFuncName + } + } + switch t := field.Type.(type) { + case *ast.Ident: + var err error + keys, err = g.structGenMarshalIdent(field, t, keys, omitEmpty, false) + if err != nil { + return 0, err + } + case *ast.StarExpr: + switch ptrExp := t.X.(type) { + case *ast.Ident: + var err error + keys, err = g.structGenMarshalIdent(field, ptrExp, keys, omitEmpty, true) + if err != nil { + return 0, err + } + default: + return 0, fmt.Errorf("Unknown type %s", n) + } + } + } + } + _, err = g.b.Write([]byte("}\n")) + if err != nil { + return 0, err + } + return keys, nil +} + +func (g *Gen) structGenMarshalIdent(field *ast.Field, i *ast.Ident, keys int, omitEmpty string, ptr bool) (int, error) { + var keyV = getStructFieldJSONKey(field) + + switch i.String() { + case "string": + g.structMarshalString(field, keyV, omitEmpty, ptr) + keys++ + case "bool": + g.structMarshalBool(field, keyV, omitEmpty, ptr) + keys++ + case "int": + g.structMarshalInt(field, keyV, "", omitEmpty, ptr) + keys++ + case "int64": + g.structMarshalInt(field, keyV, "64", omitEmpty, ptr) + keys++ + case "int32": + g.structMarshalInt(field, keyV, "32", omitEmpty, ptr) + keys++ + case "int16": + g.structMarshalInt(field, keyV, "16", omitEmpty, ptr) + keys++ + case "int8": + g.structMarshalInt(field, keyV, "8", omitEmpty, ptr) + keys++ + case "uint64": + g.structMarshalUint(field, keyV, "64", omitEmpty, ptr) + keys++ + case "uint32": + g.structMarshalUint(field, keyV, "32", omitEmpty, ptr) + keys++ + case "uint16": + g.structMarshalUint(field, keyV, "16", omitEmpty, ptr) + keys++ + case "uint8": + g.structMarshalUint(field, keyV, "8", omitEmpty, ptr) + keys++ + case "float64": + g.structMarshalFloat(field, keyV, "64", omitEmpty, ptr) + keys++ + case "float32": + g.structMarshalFloat(field, keyV, "32", omitEmpty, ptr) + keys++ + default: + // if ident is already in our spec list + if sp, ok := g.genTypes[i.Name]; ok { + err := g.structMarshalNonPrim(field, keyV, sp, omitEmpty, ptr) + if err != nil { + return 0, err + } + keys++ + } else if i.Obj != nil { + switch t := i.Obj.Decl.(type) { + case *ast.TypeSpec: + var err = g.structMarshalNonPrim(field, keyV, t, omitEmpty, ptr) + if err != nil { + return 0, err + } + keys++ + default: + g.structMarshalAny(field, keyV, sp, ptr) + keys++ + } + } else { + g.structMarshalAny(field, keyV, sp, ptr) + keys++ + } + } + return keys, nil +} + +func (g *Gen) structMarshalNonPrim(field *ast.Field, keyV string, sp *ast.TypeSpec, omitEmpty string, ptr bool) error { + switch sp.Type.(type) { + case *ast.StructType: + g.structMarshalStruct(field, keyV, sp, omitEmpty, ptr) + return nil + case *ast.ArrayType: + g.structMarshalArr(field, keyV, sp, omitEmpty, ptr) + return nil + default: + g.structMarshalAny(field, keyV, sp, ptr) + } + return nil +} + +func (g *Gen) structMarshalString(field *ast.Field, keyV string, omitEmpty string, ptr bool) { + key := field.Names[0].String() + ptrStr := "" + if ptr { + ptrStr = "*" + } + err := structMarshalTpl["string"].tpl.Execute(g.b, struct { + Field string + Key string + OmitEmpty string + Ptr string + }{key, keyV, omitEmpty, ptrStr}) + if err != nil { + log.Fatal(err) + } +} + +func (g *Gen) structMarshalBool(field *ast.Field, keyV string, omitEmpty string, ptr bool) { + key := field.Names[0].String() + ptrStr := "" + if ptr { + ptrStr = "*" + } + err := structMarshalTpl["bool"].tpl.Execute(g.b, struct { + Field string + Key string + OmitEmpty string + Ptr string + }{key, keyV, omitEmpty, ptrStr}) + if err != nil { + log.Fatal(err) + } +} + +func (g *Gen) structMarshalInt(field *ast.Field, keyV string, intLen string, omitEmpty string, ptr bool) { + key := field.Names[0].String() + ptrStr := "" + if ptr { + ptrStr = "*" + } + err := structMarshalTpl["int"].tpl.Execute(g.b, struct { + Field string + IntLen string + Key string + OmitEmpty string + Ptr string + }{key, intLen, keyV, omitEmpty, ptrStr}) + if err != nil { + log.Fatal(err) + } +} + +func (g *Gen) structMarshalUint(field *ast.Field, keyV string, intLen string, omitEmpty string, ptr bool) { + key := field.Names[0].String() + ptrStr := "" + if ptr { + ptrStr = "*" + } + err := structMarshalTpl["uint"].tpl.Execute(g.b, struct { + Field string + IntLen string + Key string + OmitEmpty string + Ptr string + }{key, intLen, keyV, omitEmpty, ptrStr}) + if err != nil { + log.Fatal(err) + } +} + +func (g *Gen) structMarshalFloat(field *ast.Field, keyV string, intLen string, omitEmpty string, ptr bool) { + key := field.Names[0].String() + ptrStr := "" + if ptr { + ptrStr = "*" + } + err := structMarshalTpl["float"].tpl.Execute(g.b, struct { + Field string + IntLen string + Key string + OmitEmpty string + Ptr string + }{key, intLen, keyV, omitEmpty, ptrStr}) + if err != nil { + log.Fatal(err) + } +} + +func (g *Gen) structMarshalStruct(field *ast.Field, keyV string, st *ast.TypeSpec, omitEmpty string, ptr bool) { + key := field.Names[0].String() + ptrStr := "" + if ptr { + ptrStr = "*" + } + var err = structMarshalTpl["struct"].tpl.Execute(g.b, struct { + Key string + Field string + OmitEmpty string + Ptr string + }{keyV, key, omitEmpty, ptrStr}) + if err != nil { + log.Fatal(err) + } +} + +func (g *Gen) structMarshalArr(field *ast.Field, keyV string, st *ast.TypeSpec, omitEmpty string, ptr bool) { + key := field.Names[0].String() + ptrStr := "" + if ptr { + ptrStr = "*" + } + var err = structMarshalTpl["arr"].tpl.Execute(g.b, struct { + Key string + Field string + OmitEmpty string + Ptr string + }{keyV, key, omitEmpty, ptrStr}) + if err != nil { + log.Fatal(err) + } +} + +func (g *Gen) structMarshalAny(field *ast.Field, keyV string, st *ast.TypeSpec, ptr bool) { + key := field.Names[0].String() + ptrStr := "" + if ptr { + ptrStr = "*" + } + var err = structMarshalTpl["any"].tpl.Execute(g.b, struct { + Key string + Field string + Ptr string + }{keyV, key, ptrStr}) + if err != nil { + log.Fatal(err) + } +} diff --git a/gojay/gen_struct_marshal_tpl.go b/gojay/gen_struct_marshal_tpl.go @@ -0,0 +1,42 @@ +package main + +var structMarshalTpl = templateList{ + "def": &genTpl{ + strTpl: "\n// MarshalJSONObject implements gojay's MarshalerJSONObject" + + "\nfunc (v *{{.StructName}}) MarshalJSONObject(enc *gojay.Encoder) {\n", + }, + "isNil": &genTpl{ + strTpl: ` +// IsNil returns wether the structure is nil value or not +func (v *{{.StructName}}) IsNil() bool { return v == nil } +`, + }, + "string": &genTpl{ + strTpl: "\tenc.StringKey{{.OmitEmpty}}(\"{{.Key}}\", {{.Ptr}}v.{{.Field}})\n", + }, + "int": &genTpl{ + strTpl: "\tenc.Int{{.IntLen}}Key{{.OmitEmpty}}(\"{{.Key}}\", {{.Ptr}}v.{{.Field}})\n", + }, + "uint": &genTpl{ + strTpl: "\tenc.Uint{{.IntLen}}Key{{.OmitEmpty}}(\"{{.Key}}\", {{.Ptr}}v.{{.Field}})\n", + }, + "float": &genTpl{ + strTpl: "\tenc.Float{{.IntLen}}Key{{.OmitEmpty}}(\"{{.Key}}\", {{.Ptr}}v.{{.Field}})\n", + }, + "bool": &genTpl{ + strTpl: "\tenc.BoolKey{{.OmitEmpty}}(\"{{.Key}}\", {{.Ptr}}v.{{.Field}})\n", + }, + "struct": &genTpl{ + strTpl: "\tenc.ObjectKey{{.OmitEmpty}}(\"{{.Key}}\", v.{{.Field}})\n", + }, + "arr": &genTpl{ + strTpl: "\tenc.ArrayKey{{.OmitEmpty}}(\"{{.Key}}\", v.{{.Field}})\n", + }, + "any": &genTpl{ + strTpl: "\tenc.AnyKey(\"{{.Key}}\", v.{{.Field}})\n", + }, +} + +func init() { + parseTemplates(structMarshalTpl, "structMarshal") +} diff --git a/gojay/gen_struct_test.go b/gojay/gen_struct_test.go @@ -0,0 +1,433 @@ +package main + +import ( + "io" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGenStruct(t *testing.T) { + testCases := map[string]struct { + input io.Reader + expectedResult string + }{ + "basicStruct": { + input: strings.NewReader(`package test + +//gojay:json +type Struct struct{ + Int int + Int8 int8 + Int16 int16 + Int32 int32 + Int64 int64 + Uint8 uint8 + Uint16 uint16 + Uint32 uint32 + Uint64 uint64 + Float64 float64 + Float32 float32 + Str string + Bool bool + Unknown UnknownType +} + `), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (v *Struct) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + switch k { + case "int": + return dec.Int(&v.Int) + case "int8": + return dec.Int8(&v.Int8) + case "int16": + return dec.Int16(&v.Int16) + case "int32": + return dec.Int32(&v.Int32) + case "int64": + return dec.Int64(&v.Int64) + case "uint8": + return dec.Uint8(&v.Uint8) + case "uint16": + return dec.Uint16(&v.Uint16) + case "uint32": + return dec.Uint32(&v.Uint32) + case "uint64": + return dec.Uint64(&v.Uint64) + case "float64": + return dec.Float64(&v.Float64) + case "float32": + return dec.Float32(&v.Float32) + case "str": + return dec.String(&v.Str) + case "bool": + return dec.Bool(&v.Bool) + case "unknown": + return dec.Any(&v.Unknown) + } + return nil +} + +// NKeys returns the number of keys to unmarshal +func (v *Struct) NKeys() int { return 14 } + +// MarshalJSONObject implements gojay's MarshalerJSONObject +func (v *Struct) MarshalJSONObject(enc *gojay.Encoder) { + enc.IntKey("int", v.Int) + enc.Int8Key("int8", v.Int8) + enc.Int16Key("int16", v.Int16) + enc.Int32Key("int32", v.Int32) + enc.Int64Key("int64", v.Int64) + enc.Uint8Key("uint8", v.Uint8) + enc.Uint16Key("uint16", v.Uint16) + enc.Uint32Key("uint32", v.Uint32) + enc.Uint64Key("uint64", v.Uint64) + enc.Float64Key("float64", v.Float64) + enc.Float32Key("float32", v.Float32) + enc.StringKey("str", v.Str) + enc.BoolKey("bool", v.Bool) + enc.AnyKey("unknown", v.Unknown) +} + +// IsNil returns wether the structure is nil value or not +func (v *Struct) IsNil() bool { return v == nil } +`, + }, + "basicStructPtr": { + input: strings.NewReader(`package test + +//gojay:json +type Struct struct{ + Int *int + Str *string +} + `), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (v *Struct) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + switch k { + case "int": + return dec.Int(v.Int) + case "str": + return dec.String(v.Str) + } + return nil +} + +// NKeys returns the number of keys to unmarshal +func (v *Struct) NKeys() int { return 2 } + +// MarshalJSONObject implements gojay's MarshalerJSONObject +func (v *Struct) MarshalJSONObject(enc *gojay.Encoder) { + enc.IntKey("int", *v.Int) + enc.StringKey("str", *v.Str) +} + +// IsNil returns wether the structure is nil value or not +func (v *Struct) IsNil() bool { return v == nil } +`, + }, + "basicStructTags": { + input: strings.NewReader(`package test + +//gojay:json +type Struct struct{ + Int int ` + "`gojay:\"someInt\"`" + ` + Str string ` + "`gojay:\"someStr\"`" + ` +} + `), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (v *Struct) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + switch k { + case "someInt": + return dec.Int(&v.Int) + case "someStr": + return dec.String(&v.Str) + } + return nil +} + +// NKeys returns the number of keys to unmarshal +func (v *Struct) NKeys() int { return 2 } + +// MarshalJSONObject implements gojay's MarshalerJSONObject +func (v *Struct) MarshalJSONObject(enc *gojay.Encoder) { + enc.IntKey("someInt", v.Int) + enc.StringKey("someStr", v.Str) +} + +// IsNil returns wether the structure is nil value or not +func (v *Struct) IsNil() bool { return v == nil } +`, + }, + "basicStructTagsHideUnmarshal": { + input: strings.NewReader(`package test + +//gojay:json +type Struct struct{ + Int int ` + "`gojay:\"-u\"`" + ` + Str string ` + "`gojay:\"-u\"`" + ` +} + `), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (v *Struct) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + switch k { + } + return nil +} + +// NKeys returns the number of keys to unmarshal +func (v *Struct) NKeys() int { return 0 } + +// MarshalJSONObject implements gojay's MarshalerJSONObject +func (v *Struct) MarshalJSONObject(enc *gojay.Encoder) { + enc.IntKey("int", v.Int) + enc.StringKey("str", v.Str) +} + +// IsNil returns wether the structure is nil value or not +func (v *Struct) IsNil() bool { return v == nil } +`, + }, + "basicStructTagsHideUnmarshal2": { + input: strings.NewReader(`package test + +//gojay:json +type Struct struct{ + Int int ` + "`gojay:\"someInt,-u\"`" + ` + Str string ` + "`gojay:\"someStr,-u\"`" + ` +} + `), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (v *Struct) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + switch k { + } + return nil +} + +// NKeys returns the number of keys to unmarshal +func (v *Struct) NKeys() int { return 0 } + +// MarshalJSONObject implements gojay's MarshalerJSONObject +func (v *Struct) MarshalJSONObject(enc *gojay.Encoder) { + enc.IntKey("someInt", v.Int) + enc.StringKey("someStr", v.Str) +} + +// IsNil returns wether the structure is nil value or not +func (v *Struct) IsNil() bool { return v == nil } +`, + }, + "basicStructTagsHideUnmarshal3": { + input: strings.NewReader(`package test + +//gojay:json +type Struct struct{ + Int int ` + "`gojay:\"someInt,-m\"`" + ` + Str string ` + "`gojay:\"someStr,-m\"`" + ` +} + `), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (v *Struct) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + switch k { + case "someInt": + return dec.Int(&v.Int) + case "someStr": + return dec.String(&v.Str) + } + return nil +} + +// NKeys returns the number of keys to unmarshal +func (v *Struct) NKeys() int { return 2 } + +// MarshalJSONObject implements gojay's MarshalerJSONObject +func (v *Struct) MarshalJSONObject(enc *gojay.Encoder) { +} + +// IsNil returns wether the structure is nil value or not +func (v *Struct) IsNil() bool { return v == nil } +`, + }, + "basicStructTagsHideUnmarshal4": { + input: strings.NewReader(`package test + +//gojay:json +type Struct struct{ + Int int ` + "`gojay:\"-\"`" + ` + Str string ` + "`gojay:\"-\"`" + ` +} + `), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (v *Struct) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + switch k { + } + return nil +} + +// NKeys returns the number of keys to unmarshal +func (v *Struct) NKeys() int { return 0 } + +// MarshalJSONObject implements gojay's MarshalerJSONObject +func (v *Struct) MarshalJSONObject(enc *gojay.Encoder) { +} + +// IsNil returns wether the structure is nil value or not +func (v *Struct) IsNil() bool { return v == nil } +`, + }, + "complexStructStructTag": { + input: strings.NewReader(`package test + +//gojay:json +type Struct struct{ + Struct Struct ` + "`gojay:\"someStruct\"`" + ` +} + `), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (v *Struct) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + switch k { + case "someStruct": + if v.Struct == nil { + v.Struct = Struct{} + } + return dec.Object(v.Struct) + } + return nil +} + +// NKeys returns the number of keys to unmarshal +func (v *Struct) NKeys() int { return 1 } + +// MarshalJSONObject implements gojay's MarshalerJSONObject +func (v *Struct) MarshalJSONObject(enc *gojay.Encoder) { + enc.ObjectKey("someStruct", v.Struct) +} + +// IsNil returns wether the structure is nil value or not +func (v *Struct) IsNil() bool { return v == nil } +`, + }, + "complexStructStructPtrTag": { + input: strings.NewReader(`package test + +//gojay:json +type Struct struct{ + Struct *Struct ` + "`gojay:\"someStruct\"`" + ` +} + `), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (v *Struct) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + switch k { + case "someStruct": + if v.Struct == nil { + v.Struct = &Struct{} + } + return dec.Object(v.Struct) + } + return nil +} + +// NKeys returns the number of keys to unmarshal +func (v *Struct) NKeys() int { return 1 } + +// MarshalJSONObject implements gojay's MarshalerJSONObject +func (v *Struct) MarshalJSONObject(enc *gojay.Encoder) { + enc.ObjectKey("someStruct", v.Struct) +} + +// IsNil returns wether the structure is nil value or not +func (v *Struct) IsNil() bool { return v == nil } +`, + }, + "complexStructStructPtrTagOmitEmpty": { + input: strings.NewReader(`package test + +//gojay:json +type Struct struct{ + Struct *Struct ` + "`gojay:\"someStruct,omitempty\"`" + ` +} + `), + expectedResult: `package + +import "github.com/francoispqt/gojay" + +// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject +func (v *Struct) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { + switch k { + case "someStruct": + if v.Struct == nil { + v.Struct = &Struct{} + } + return dec.Object(v.Struct) + } + return nil +} + +// NKeys returns the number of keys to unmarshal +func (v *Struct) NKeys() int { return 1 } + +// MarshalJSONObject implements gojay's MarshalerJSONObject +func (v *Struct) MarshalJSONObject(enc *gojay.Encoder) { + enc.ObjectKeyOmitEmpty("someStruct", v.Struct) +} + +// IsNil returns wether the structure is nil value or not +func (v *Struct) IsNil() bool { return v == nil } +`, + }, + } + for n, testCase := range testCases { + t.Run(n, func(t *testing.T) { + g, err := MakeGenFromReader(testCase.input) + if err != nil { + t.Fatal(err) + } + err = g.Gen() + if err != nil { + t.Fatal(err) + } + assert.Equal( + t, + string(genHeader)+testCase.expectedResult, + g.b.String(), + ) + }) + } +} diff --git a/gojay/gen_struct_unmarshal.go b/gojay/gen_struct_unmarshal.go @@ -0,0 +1,378 @@ +package main + +import ( + "fmt" + "go/ast" + "log" +) + +var structUnmarshalSwitchOpen = []byte("\tswitch k {\n") +var structUnmarshalClose = []byte("\treturn nil\n}\n") + +func (g *Gen) structGenNKeys(n string, count int) error { + err := structUnmarshalTpl["nKeys"].tpl.Execute(g.b, struct { + NKeys int + StructName string + }{ + NKeys: count, + StructName: n, + }) + return err +} + +func (g *Gen) structGenUnmarshalObj(n string, s *ast.StructType) (int, error) { + err := structUnmarshalTpl["def"].tpl.Execute(g.b, struct { + StructName string + }{ + StructName: n, + }) + if err != nil { + return 0, err + } + keys := 0 + if len(s.Fields.List) > 0 { + // open switch statement + g.b.Write(structUnmarshalSwitchOpen) + // TODO: check tags + // check type of field + // add accordingly + for _, field := range s.Fields.List { + // check if has hide tag + if field.Tag != nil && hasTagUnmarshalHide(field.Tag) { + continue + } + switch t := field.Type.(type) { + case *ast.Ident: + var err error + keys, err = g.structGenUnmarshalIdent(field, t, keys, false) + if err != nil { + return 0, err + } + case *ast.StarExpr: + switch ptrExp := t.X.(type) { + case *ast.Ident: + var err error + keys, err = g.structGenUnmarshalIdent(field, ptrExp, keys, true) + if err != nil { + return 0, err + } + default: + return 0, fmt.Errorf("Unknown type %s", n) + } + } + } + // close switch statement + g.b.Write([]byte("\t}\n")) + } + _, err = g.b.Write(structUnmarshalClose) + if err != nil { + return 0, err + } + return keys, nil +} + +func (g *Gen) structGenUnmarshalIdent(field *ast.Field, i *ast.Ident, keys int, ptr bool) (int, error) { + var keyV = getStructFieldJSONKey(field) + + switch i.String() { + case "string": + g.structUnmarshalString(field, keyV, ptr) + keys++ + case "bool": + g.structUnmarshalBool(field, keyV, ptr) + keys++ + case "int": + g.structUnmarshalInt(field, keyV, "", ptr) + keys++ + case "int64": + g.structUnmarshalInt(field, keyV, "64", ptr) + keys++ + case "int32": + g.structUnmarshalInt(field, keyV, "32", ptr) + keys++ + case "int16": + g.structUnmarshalInt(field, keyV, "16", ptr) + keys++ + case "int8": + g.structUnmarshalInt(field, keyV, "8", ptr) + keys++ + case "uint64": + g.structUnmarshalUint(field, keyV, "64", ptr) + keys++ + case "uint32": + g.structUnmarshalUint(field, keyV, "32", ptr) + keys++ + case "uint16": + g.structUnmarshalUint(field, keyV, "16", ptr) + keys++ + case "uint8": + g.structUnmarshalUint(field, keyV, "8", ptr) + keys++ + case "float64": + g.structUnmarshalFloat(field, keyV, "64", ptr) + keys++ + case "float32": + g.structUnmarshalFloat(field, keyV, "32", ptr) + keys++ + default: + // if ident is already in our spec list + if sp, ok := g.genTypes[i.Name]; ok { + err := g.structUnmarshalNonPrim(field, keyV, sp, ptr) + if err != nil { + return 0, err + } + keys++ + } else if i.Obj != nil { + // else check the obj infos + switch t := i.Obj.Decl.(type) { + case *ast.TypeSpec: + err := g.structUnmarshalNonPrim(field, keyV, t, ptr) + if err != nil { + return 0, err + } + keys++ + default: + g.structUnmarshalAny(field, keyV, sp, ptr) + keys++ + } + } else { + g.structUnmarshalAny(field, keyV, sp, ptr) + keys++ + } + } + return keys, nil +} + +func (g *Gen) structUnmarshalNonPrim(field *ast.Field, keyV string, sp *ast.TypeSpec, ptr bool) error { + switch sp.Type.(type) { + case *ast.StructType: + g.structUnmarshalStruct(field, keyV, sp, ptr) + return nil + case *ast.ArrayType: + g.structUnmarshalArr(field, keyV, sp, ptr) + return nil + default: + g.structUnmarshalAny(field, keyV, sp, ptr) + return nil + } +} + +func (g *Gen) structUnmarshalString(field *ast.Field, keyV string, ptr bool) { + key := field.Names[0].String() + err := structUnmarshalTpl["case"].tpl.Execute(g.b, struct { + Key string + }{keyV}) + if err != nil { + log.Fatal(err) + } + if ptr { + err = structUnmarshalTpl["string"].tpl.Execute(g.b, struct { + Field string + Ptr string + }{key, ""}) + if err != nil { + log.Fatal(err) + } + } else { + err = structUnmarshalTpl["string"].tpl.Execute(g.b, struct { + Field string + Ptr string + }{key, "&"}) + if err != nil { + log.Fatal(err) + } + } +} + +func (g *Gen) structUnmarshalBool(field *ast.Field, keyV string, ptr bool) { + key := field.Names[0].String() + err := structUnmarshalTpl["case"].tpl.Execute(g.b, struct { + Key string + }{keyV}) + if err != nil { + log.Fatal(err) + } + if ptr { + err = structUnmarshalTpl["bool"].tpl.Execute(g.b, struct { + Field string + Ptr string + }{key, ""}) + if err != nil { + log.Fatal(err) + } + } else { + err = structUnmarshalTpl["bool"].tpl.Execute(g.b, struct { + Field string + Ptr string + }{key, "&"}) + if err != nil { + log.Fatal(err) + } + } +} + +func (g *Gen) structUnmarshalInt(field *ast.Field, keyV string, intLen string, ptr bool) { + key := field.Names[0].String() + err := structUnmarshalTpl["case"].tpl.Execute(g.b, struct { + Key string + }{keyV}) + if err != nil { + log.Fatal(err) + } + if ptr { + err = structUnmarshalTpl["int"].tpl.Execute(g.b, struct { + Field string + IntLen string + Ptr string + }{key, intLen, ""}) + if err != nil { + log.Fatal(err) + } + } else { + err = structUnmarshalTpl["int"].tpl.Execute(g.b, struct { + Field string + IntLen string + Ptr string + }{key, intLen, "&"}) + if err != nil { + log.Fatal(err) + } + } +} + +func (g *Gen) structUnmarshalUint(field *ast.Field, keyV string, intLen string, ptr bool) { + key := field.Names[0].String() + err := structUnmarshalTpl["case"].tpl.Execute(g.b, struct { + Key string + }{keyV}) + if err != nil { + log.Fatal(err) + } + if ptr { + err = structUnmarshalTpl["uint"].tpl.Execute(g.b, struct { + Field string + IntLen string + Ptr string + }{key, intLen, ""}) + if err != nil { + log.Fatal(err) + } + } else { + err = structUnmarshalTpl["uint"].tpl.Execute(g.b, struct { + Field string + IntLen string + Ptr string + }{key, intLen, "&"}) + if err != nil { + log.Fatal(err) + } + } +} + +func (g *Gen) structUnmarshalFloat(field *ast.Field, keyV string, intLen string, ptr bool) { + key := field.Names[0].String() + err := structUnmarshalTpl["case"].tpl.Execute(g.b, struct { + Key string + }{keyV}) + if err != nil { + log.Fatal(err) + } + if ptr { + err = structUnmarshalTpl["float"].tpl.Execute(g.b, struct { + Field string + IntLen string + Ptr string + }{key, intLen, ""}) + if err != nil { + log.Fatal(err) + } + } else { + err = structUnmarshalTpl["float"].tpl.Execute(g.b, struct { + Field string + IntLen string + Ptr string + }{key, intLen, "&"}) + if err != nil { + log.Fatal(err) + } + } +} + +func (g *Gen) structUnmarshalStruct(field *ast.Field, keyV string, st *ast.TypeSpec, ptr bool) { + key := field.Names[0].String() + err := structUnmarshalTpl["case"].tpl.Execute(g.b, struct { + Key string + }{keyV}) + if err != nil { + log.Fatal(err) + } + if ptr { + err = structUnmarshalTpl["structPtr"].tpl.Execute(g.b, struct { + Field string + StructName string + }{key, st.Name.String()}) + if err != nil { + log.Fatal(err) + } + } else { + err = structUnmarshalTpl["struct"].tpl.Execute(g.b, struct { + Field string + StructName string + }{key, st.Name.String()}) + if err != nil { + log.Fatal(err) + } + } +} + +func (g *Gen) structUnmarshalArr(field *ast.Field, keyV string, st *ast.TypeSpec, ptr bool) { + key := field.Names[0].String() + err := structUnmarshalTpl["case"].tpl.Execute(g.b, struct { + Key string + }{keyV}) + if err != nil { + log.Fatal(err) + } + if ptr { + err = structUnmarshalTpl["arrPtr"].tpl.Execute(g.b, struct { + Field string + TypeName string + }{key, st.Name.String()}) + if err != nil { + log.Fatal(err) + } + } else { + err = structUnmarshalTpl["arr"].tpl.Execute(g.b, struct { + Field string + TypeName string + }{key, st.Name.String()}) + if err != nil { + log.Fatal(err) + } + } +} + +func (g *Gen) structUnmarshalAny(field *ast.Field, keyV string, st *ast.TypeSpec, ptr bool) { + key := field.Names[0].String() + err := structUnmarshalTpl["case"].tpl.Execute(g.b, struct { + Key string + }{keyV}) + if err != nil { + log.Fatal(err) + } + if ptr { + err = structUnmarshalTpl["anyPtr"].tpl.Execute(g.b, struct { + Field string + }{key}) + if err != nil { + log.Fatal(err) + } + } else { + err = structUnmarshalTpl["any"].tpl.Execute(g.b, struct { + Field string + }{key}) + if err != nil { + log.Fatal(err) + } + } +} diff --git a/gojay/gen_struct_unmarshal_tpl.go b/gojay/gen_struct_unmarshal_tpl.go @@ -0,0 +1,72 @@ +package main + +var structUnmarshalTpl = templateList{ + "def": &genTpl{ + strTpl: "\n// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject" + + "\nfunc (v *{{.StructName}}) UnmarshalJSONObject(dec *gojay.Decoder, k string) error {\n", + }, + "nKeys": &genTpl{ + strTpl: ` +// NKeys returns the number of keys to unmarshal +func (v *{{.StructName}}) NKeys() int { return {{.NKeys}} } +`, + }, + "case": &genTpl{ + strTpl: "\tcase \"{{.Key}}\":\n", + }, + "string": &genTpl{ + strTpl: "\t\treturn dec.String({{.Ptr}}v.{{.Field}})\n", + }, + "int": &genTpl{ + strTpl: "\t\treturn dec.Int{{.IntLen}}({{.Ptr}}v.{{.Field}})\n", + }, + "uint": &genTpl{ + strTpl: "\t\treturn dec.Uint{{.IntLen}}({{.Ptr}}v.{{.Field}})\n", + }, + "float": &genTpl{ + strTpl: "\t\treturn dec.Float{{.IntLen}}({{.Ptr}}v.{{.Field}})\n", + }, + "bool": &genTpl{ + strTpl: "\t\treturn dec.Bool({{.Ptr}}v.{{.Field}})\n", + }, + "struct": &genTpl{ + strTpl: ` if v.{{.Field}} == nil { + v.{{.Field}} = {{.StructName}}{} + } + return dec.Object(v.{{.Field}}) +`, + }, + "structPtr": &genTpl{ + strTpl: ` if v.{{.Field}} == nil { + v.{{.Field}} = &{{.StructName}}{} + } + return dec.Object(v.{{.Field}}) +`, + }, + "arr": &genTpl{ + strTpl: ` if v.{{.Field}} == nil { + arr := make({{.TypeName}}, 0) + v.{{.Field}} = arr + } + return dec.Array(&v.{{.Field}}) +`, + }, + "arrPtr": &genTpl{ + strTpl: ` if v.{{.Field}} == nil { + arr := make({{.TypeName}}, 0) + v.{{.Field}} = &arr + } + return dec.Array(v.{{.Field}}) +`, + }, + "any": &genTpl{ + strTpl: "\t\treturn dec.Any(&v.{{.Field}})\n", + }, + "anyPtr": &genTpl{ + strTpl: "\t\treturn dec.Any(v.{{.Field}})\n", + }, +} + +func init() { + parseTemplates(structUnmarshalTpl, "structUnmarshal") +} diff --git a/gojay/gen_stuct.go b/gojay/gen_stuct.go @@ -0,0 +1,30 @@ +package main + +import ( + "go/ast" + "strings" +) + +func getStructFieldJSONKey(field *ast.Field) string { + var keyV string + if field.Tag != nil { + keyV = tagKeyName(field.Tag) + } + if keyV == "" { + keyV = strings.ToLower(field.Names[0].String()[:1]) + field.Names[0].String()[1:] + } + return keyV +} + +func (g *Gen) genStruct(n string, s *ast.StructType) error { + keys, err := g.structGenUnmarshalObj(n, s) + if err != nil { + return err + } + err = g.structGenNKeys(n, keys) + keys, err = g.structGenMarshalObj(n, s) + if err != nil { + return err + } + return g.structGenIsNil(n) +} diff --git a/gojay/gen_tag.go b/gojay/gen_tag.go @@ -0,0 +1,65 @@ +package main + +import ( + "go/ast" + "log" + + "github.com/fatih/structtag" +) + +const gojayTag = "gojay" +const hideTag = "-" +const unmarshalHideTag = "-u" +const marshalHideTag = "-m" +const omitEmptyTag = "omitempty" + +func getGojayTagValue(tags *ast.BasicLit) (*structtag.Tag, error) { + t, err := structtag.Parse(tags.Value[1 : len(tags.Value)-1]) + if err != nil { + return nil, err + } + v, err := t.Get(gojayTag) + if err != nil { + return nil, err + } + return v, nil +} + +func hasTagUnmarshalHide(tags *ast.BasicLit) bool { + v, err := getGojayTagValue(tags) + if err != nil { + log.Print(err) + return false + } + return (v.Name == unmarshalHideTag || v.Name == hideTag) || v.HasOption(unmarshalHideTag) +} + +func hasTagMarshalHide(tags *ast.BasicLit) bool { + v, err := getGojayTagValue(tags) + if err != nil { + log.Print(err) + return false + } + return (v.Name == marshalHideTag || v.Name == hideTag) || v.HasOption(marshalHideTag) +} + +func hasTagOmitEmpty(tags *ast.BasicLit) bool { + v, err := getGojayTagValue(tags) + if err != nil { + log.Print(err) + return false + } + return v.Name == omitEmptyTag || v.HasOption(omitEmptyTag) +} + +func tagKeyName(tags *ast.BasicLit) string { + v, err := getGojayTagValue(tags) + if err != nil { + log.Print(err) + return "" + } + if v.Name == hideTag || v.Name == unmarshalHideTag || v.Name == marshalHideTag { + return "" + } + return v.Name +} diff --git a/gojay/gen_test.go b/gojay/gen_test.go @@ -0,0 +1,24 @@ +package main + +import ( + "go/ast" + "go/parser" + "go/token" + "io" +) + +func MakeGenFromReader(input io.Reader) (*Gen, error) { + g := NewGen("", []string{}) + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "", input, parser.ParseComments) + if err != nil { + return nil, err + } + v := newVisitor(g, g.pkg) + ast.Walk(v, f) + if err != nil { + return nil, err + } + g.vis = v + return g, nil +} diff --git a/gojay/main.go b/gojay/main.go @@ -0,0 +1,116 @@ +//+build !test + +package main + +import ( + "errors" + "flag" + "io/ioutil" + "log" + "os" + "path/filepath" + "strings" +) + +var dst = flag.String("o", "", "destination file to output generated implementations") +var src = flag.String("s", "", "source dir or file (absolute or relative path)") +var pkg = flag.String("p", "", "go package") +var types = flag.String("t", "", "types to generate") + +var ErrNoPathProvided = errors.New("You must provide a path or a package name") + +type stringWriter interface { + WriteString(string) (int, error) +} + +func hasAnnotation(fP string) bool { + b, err := ioutil.ReadFile(fP) + if err != nil { + log.Fatal(err) + } + return strings.Contains(string(b), gojayAnnotation) +} + +func resolvePath(p string) (string, error) { + if fullPath, err := filepath.Abs(p); err != nil { + return "", err + } else if _, err := os.Stat(fullPath); err != nil { + return "", err + } else { + return fullPath, nil + } +} + +// getPath returns either the path given as argument or current working directory +func getPath() (string, error) { + // if pkg is set, resolve pkg path + if *pkg != "" { + return resolvePath(os.Getenv("GOPATH") + "/src/" + *pkg) + } else if *src != "" { // if src is present parse from src + return resolvePath(*src) + } else if len(os.Args) > 1 { // else if there is a command line arg, use it as path to a package $GOPATH/src/os.Args[1] + return resolvePath(os.Getenv("GOPATH") + "/src/" + os.Args[1]) + } + return "", ErrNoPathProvided +} + +// getTypes returns the types to be parsed +func getTypes() (t []string) { + if *types != "" { // if src is present parse from src + return strings.Split(*types, ",") + } else if *src == "." && *dst == "" && len(os.Args) > 2 { // else if there is a command line arg, use it as path to a package $GOPATH/src/os.Args[1] + return strings.Split(os.Args[2], ",") + } + return t +} + +// getOutput returns the output +func getOutput() (stringWriter, error) { + if *dst != "" { + p, err := filepath.Abs(*dst) + if err != nil { + return nil, err + } + return os.OpenFile(p, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) + } else if len(os.Args) > 3 && *src == "" && *types == "" { + p, err := filepath.Abs(os.Args[3]) + if err != nil { + return nil, err + } + return os.OpenFile(p, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) + } + return os.Stdout, nil +} + +func parseArgs() (p string, t []string, o stringWriter, err error) { + flag.Parse() + p, err = getPath() + if err != nil { + return p, t, o, err + } + t = getTypes() + o, err = getOutput() + return p, t, o, err +} + +func main() { + p, t, o, err := parseArgs() + if err != nil { + log.Fatal(err) + } + // parse source files + g := NewGen(p, t) + err = g.parse() + if err != nil { + log.Fatal(err) + return + } + // generate output + err = g.Gen() + if err != nil { + log.Fatal(err) + return + } + // write content to output + o.WriteString(g.b.String()) +} diff --git a/gojay/tests/maps.go b/gojay/tests/maps.go @@ -0,0 +1,4 @@ +package tests + +//gojay:json +type MapStringInt map[string]int diff --git a/gojay/tests/slices.go b/gojay/tests/slices.go @@ -0,0 +1,16 @@ +package tests + +//gojay:json +type StrSlice []string + +//gojay:json +type IntSlice []int + +//gojay:json +type BoolSlice []bool + +//gojay:json +type StructSlice []*A + +//gojay:json +type SliceSlice []*StrSlice diff --git a/gojay/tests/structs.go b/gojay/tests/structs.go @@ -0,0 +1,44 @@ +package tests + +//gojay:json +type A struct { + Str string `gojay:"string"` + Bool bool + Int int + Int64 int64 + Int32 int32 + Int16 int16 + Int8 int8 + Uint64 uint64 + Uint32 uint32 + Uint16 uint16 + Uint8 uint8 + Bval *B + Arrval *StrSlice +} + +//gojay:json +type B struct { + Str string + Bool bool + Int int + Int64 int64 + Int32 int32 + Int16 int16 + Int8 int8 + Uint64 uint64 + Uint32 uint32 + Uint16 uint16 + Uint8 uint8 + StrPtr *string + BoolPtr *bool + IntPtr *int + Int64Ptr *int64 + Int32Ptr *int32 + Int16Ptr *int16 + Int8Ptr *int8 + Uint64Ptr *uint64 + Uint32Ptr *uint32 + Uint16Ptr *uint16 + Uint8PTr *uint8 +} diff --git a/gojay/vendor/github.com/fatih/structtag/.travis.yml b/gojay/vendor/github.com/fatih/structtag/.travis.yml @@ -0,0 +1,4 @@ +language: go +go: + - 1.7.x + - tip diff --git a/gojay/vendor/github.com/fatih/structtag/LICENSE b/gojay/vendor/github.com/fatih/structtag/LICENSE @@ -0,0 +1,60 @@ +Copyright (c) 2017, Fatih Arslan +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of structtag nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +This software includes some portions from Go. Go is used under the terms of the +BSD like license. + +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The Go gopher was designed by Renee French. http://reneefrench.blogspot.com/ The design is licensed under the Creative Commons 3.0 Attributions license. Read this article for more details: https://blog.golang.org/gopher diff --git a/gojay/vendor/github.com/fatih/structtag/README.md b/gojay/vendor/github.com/fatih/structtag/README.md @@ -0,0 +1,73 @@ +# structtag [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/fatih/structtag) [![Build Status](https://travis-ci.org/fatih/structtag.svg?branch=master)](https://travis-ci.org/fatih/structtag) + +structtag provides an easy way of parsing and manipulating struct tag fields. +Please vendor the library as it might change in future versions. + +# Install + +```bash +go get github.com/fatih/structtag +``` + +# Example + +```go +package main + +import ( + "fmt" + "reflect" + "sort" + + "github.com/fatih/structtag" +) + +func main() { + type t struct { + t string `json:"foo,omitempty,string" xml:"foo"` + } + + // get field tag + tag := reflect.TypeOf(t{}).Field(0).Tag + + // ... and start using structtag by parsing the tag + tags, err := structtag.Parse(string(tag)) + if err != nil { + panic(err) + } + + // iterate over all tags + for _, t := range tags.Tags() { + fmt.Printf("tag: %+v\n", t) + } + + // get a single tag + jsonTag, err := tags.Get("json") + if err != nil { + panic(err) + } + fmt.Println(jsonTag) // Output: json:"foo,omitempty,string" + fmt.Println(jsonTag.Key) // Output: json + fmt.Println(jsonTag.Name) // Output: foo + fmt.Println(jsonTag.Options) // Output: [omitempty string] + + // change existing tag + jsonTag.Name = "foo_bar" + jsonTag.Options = nil + tags.Set(jsonTag) + + // add new tag + tags.Set(&structtag.Tag{ + Key: "hcl", + Name: "foo", + Options: []string{"squash"}, + }) + + // print the tags + fmt.Println(tags) // Output: json:"foo_bar" xml:"foo" hcl:"foo,squash" + + // sort tags according to keys + sort.Sort(tags) + fmt.Println(tags) // Output: hcl:"foo,squash" json:"foo_bar" xml:"foo" +} +``` diff --git a/gojay/vendor/github.com/fatih/structtag/tags.go b/gojay/vendor/github.com/fatih/structtag/tags.go @@ -0,0 +1,303 @@ +package structtag + +import ( + "bytes" + "errors" + "fmt" + "strconv" + "strings" +) + +var ( + errTagSyntax = errors.New("bad syntax for struct tag pair") + errTagKeySyntax = errors.New("bad syntax for struct tag key") + errTagValueSyntax = errors.New("bad syntax for struct tag value") + + errKeyNotSet = errors.New("tag key does not exist") + errTagNotExist = errors.New("tag does not exist") + errTagKeyMismatch = errors.New("mismatch between key and tag.key") +) + +// Tags represent a set of tags from a single struct field +type Tags struct { + tags []*Tag +} + +// Tag defines a single struct's string literal tag +type Tag struct { + // Key is the tag key, such as json, xml, etc.. + // i.e: `json:"foo,omitempty". Here key is: "json" + Key string + + // Name is a part of the value + // i.e: `json:"foo,omitempty". Here name is: "foo" + Name string + + // Options is a part of the value. It contains a slice of tag options i.e: + // `json:"foo,omitempty". Here options is: ["omitempty"] + Options []string +} + +// Parse parses a single struct field tag and returns the set of tags. +func Parse(tag string) (*Tags, error) { + var tags []*Tag + + // NOTE(arslan) following code is from reflect and vet package with some + // modifications to collect all necessary information and extend it with + // usable methods + for tag != "" { + // Skip leading space. + i := 0 + for i < len(tag) && tag[i] == ' ' { + i++ + } + tag = tag[i:] + if tag == "" { + return nil, nil + } + + // Scan to colon. A space, a quote or a control character is a syntax + // error. Strictly speaking, control chars include the range [0x7f, + // 0x9f], not just [0x00, 0x1f], but in practice, we ignore the + // multi-byte control characters as it is simpler to inspect the tag's + // bytes than the tag's runes. + i = 0 + for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f { + i++ + } + + if i == 0 { + return nil, errTagKeySyntax + } + if i+1 >= len(tag) || tag[i] != ':' { + return nil, errTagSyntax + } + if tag[i+1] != '"' { + return nil, errTagValueSyntax + } + + key := string(tag[:i]) + tag = tag[i+1:] + + // Scan quoted string to find value. + i = 1 + for i < len(tag) && tag[i] != '"' { + if tag[i] == '\\' { + i++ + } + i++ + } + if i >= len(tag) { + return nil, errTagValueSyntax + } + + qvalue := string(tag[:i+1]) + tag = tag[i+1:] + + value, err := strconv.Unquote(qvalue) + if err != nil { + return nil, errTagValueSyntax + } + + res := strings.Split(value, ",") + name := res[0] + options := res[1:] + if len(options) == 0 { + options = nil + } + + tags = append(tags, &Tag{ + Key: key, + Name: name, + Options: options, + }) + } + + return &Tags{ + tags: tags, + }, nil +} + +// Get returns the tag associated with the given key. If the key is present +// in the tag the value (which may be empty) is returned. Otherwise the +// returned value will be the empty string. The ok return value reports whether +// the tag exists or not (which the return value is nil). +func (t *Tags) Get(key string) (*Tag, error) { + for _, tag := range t.tags { + if tag.Key == key { + return tag, nil + } + } + + return nil, errTagNotExist +} + +// Set sets the given tag. If the tag key already exists it'll override it +func (t *Tags) Set(tag *Tag) error { + if tag.Key == "" { + return errKeyNotSet + } + + added := false + for i, tg := range t.tags { + if tg.Key == tag.Key { + added = true + t.tags[i] = tag + } + } + + if !added { + // this means this is a new tag, add it + t.tags = append(t.tags, tag) + } + + return nil +} + +// AddOptions adds the given option for the given key. If the option already +// exists it doesn't add it again. +func (t *Tags) AddOptions(key string, options ...string) { + for i, tag := range t.tags { + if tag.Key != key { + continue + } + + for _, opt := range options { + if !tag.HasOption(opt) { + tag.Options = append(tag.Options, opt) + } + } + + t.tags[i] = tag + } +} + +// DeleteOptions deletes the given options for the given key +func (t *Tags) DeleteOptions(key string, options ...string) { + hasOption := func(option string) bool { + for _, opt := range options { + if opt == option { + return true + } + } + return false + } + + for i, tag := range t.tags { + if tag.Key != key { + continue + } + + var updated []string + for _, opt := range tag.Options { + if !hasOption(opt) { + updated = append(updated, opt) + } + } + + tag.Options = updated + t.tags[i] = tag + } +} + +// Delete deletes the tag for the given keys +func (t *Tags) Delete(keys ...string) { + hasKey := func(key string) bool { + for _, k := range keys { + if k == key { + return true + } + } + return false + } + + var updated []*Tag + for _, tag := range t.tags { + if !hasKey(tag.Key) { + updated = append(updated, tag) + } + } + + t.tags = updated +} + +// Tags returns a slice of tags. The order is the original tag order unless it +// was changed. +func (t *Tags) Tags() []*Tag { + return t.tags +} + +// Tags returns a slice of tags. The order is the original tag order unless it +// was changed. +func (t *Tags) Keys() []string { + var keys []string + for _, tag := range t.tags { + keys = append(keys, tag.Key) + } + return keys +} + +// String reassembles the tags into a valid literal tag field representation +func (t *Tags) String() string { + tags := t.Tags() + if len(tags) == 0 { + return "" + } + + var buf bytes.Buffer + for i, tag := range t.Tags() { + buf.WriteString(tag.String()) + if i != len(tags)-1 { + buf.WriteString(" ") + } + } + return buf.String() +} + +// HasOption returns true if the given option is available in options +func (t *Tag) HasOption(opt string) bool { + for _, tagOpt := range t.Options { + if tagOpt == opt { + return true + } + } + + return false +} + +// String reassembles the tag into a valid tag field representation +func (t *Tag) String() string { + options := strings.Join(t.Options, ",") + if options != "" { + return fmt.Sprintf(`%s:"%s,%s"`, t.Key, t.Name, options) + } + return fmt.Sprintf(`%s:"%s"`, t.Key, t.Name) +} + +// GoString implements the fmt.GoStringer interface +func (t *Tag) GoString() string { + template := `{ + Key: '%s', + Name: '%s', + Option: '%s', + }` + + if t.Options == nil { + return fmt.Sprintf(template, t.Key, t.Name, "nil") + } + + options := strings.Join(t.Options, ",") + return fmt.Sprintf(template, t.Key, t.Name, options) +} + +func (t *Tags) Len() int { + return len(t.tags) +} + +func (t *Tags) Less(i int, j int) bool { + return t.tags[i].Key < t.tags[j].Key +} + +func (t *Tags) Swap(i int, j int) { + t.tags[i], t.tags[j] = t.tags[j], t.tags[i] +} diff --git a/gojay/vendor/github.com/fatih/structtag/tags_test.go b/gojay/vendor/github.com/fatih/structtag/tags_test.go @@ -0,0 +1,390 @@ +package structtag + +import ( + "reflect" + "sort" + "testing" +) + +func TestParse(t *testing.T) { + test := []struct { + name string + tag string + exp []*Tag + invalid bool + }{ + { + name: "empty tag", + tag: "", + }, + { + name: "tag with one key (invalid)", + tag: "json", + invalid: true, + }, + { + name: "tag with one key (valid)", + tag: `json:""`, + exp: []*Tag{ + { + Key: "json", + }, + }, + }, + { + name: "tag with one key and dash name", + tag: `json:"-"`, + exp: []*Tag{ + { + Key: "json", + Name: "-", + }, + }, + }, + { + name: "tag with key and name", + tag: `json:"foo"`, + exp: []*Tag{ + { + Key: "json", + Name: "foo", + }, + }, + }, + { + name: "tag with key, name and option", + tag: `json:"foo,omitempty"`, + exp: []*Tag{ + { + Key: "json", + Name: "foo", + Options: []string{"omitempty"}, + }, + }, + }, + { + name: "tag with multiple keys", + tag: `json:"" hcl:""`, + exp: []*Tag{ + { + Key: "json", + }, + { + Key: "hcl", + }, + }, + }, + { + name: "tag with multiple keys and names", + tag: `json:"foo" hcl:"foo"`, + exp: []*Tag{ + { + Key: "json", + Name: "foo", + }, + { + Key: "hcl", + Name: "foo", + }, + }, + }, + { + name: "tag with multiple keys and names", + tag: `json:"foo" hcl:"foo"`, + exp: []*Tag{ + { + Key: "json", + Name: "foo", + }, + { + Key: "hcl", + Name: "foo", + }, + }, + }, + { + name: "tag with multiple keys and different names", + tag: `json:"foo" hcl:"bar"`, + exp: []*Tag{ + { + Key: "json", + Name: "foo", + }, + { + Key: "hcl", + Name: "bar", + }, + }, + }, + { + name: "tag with multiple keys, different names and options", + tag: `json:"foo,omitempty" structs:"bar,omitnested"`, + exp: []*Tag{ + { + Key: "json", + Name: "foo", + Options: []string{"omitempty"}, + }, + { + Key: "structs", + Name: "bar", + Options: []string{"omitnested"}, + }, + }, + }, + { + name: "tag with multiple keys, different names and options", + tag: `json:"foo" structs:"bar,omitnested" hcl:"-"`, + exp: []*Tag{ + { + Key: "json", + Name: "foo", + }, + { + Key: "structs", + Name: "bar", + Options: []string{"omitnested"}, + }, + { + Key: "hcl", + Name: "-", + }, + }, + }, + } + + for _, ts := range test { + t.Run(ts.name, func(t *testing.T) { + tags, err := Parse(ts.tag) + invalid := err != nil + + if invalid != ts.invalid { + t.Errorf("invalid case\n\twant: %+v\n\tgot : %+v\n\terr : %s", ts.invalid, invalid, err) + } + + if invalid { + return + } + + got := tags.Tags() + + if !reflect.DeepEqual(ts.exp, got) { + t.Errorf("parse\n\twant: %#v\n\tgot : %#v", ts.exp, got) + } + }) + } +} + +func TestTags_Get(t *testing.T) { + tag := `json:"foo,omitempty" structs:"bar,omitnested"` + + tags, err := Parse(tag) + if err != nil { + t.Fatal(err) + } + + found, err := tags.Get("json") + if err != nil { + t.Fatal(err) + } + + want := `json:"foo,omitempty"` + + if found.String() != want { + t.Errorf("get\n\twant: %#v\n\tgot : %#v", want, found.String()) + } +} + +func TestTags_Set(t *testing.T) { + tag := `json:"foo,omitempty" structs:"bar,omitnested"` + + tags, err := Parse(tag) + if err != nil { + t.Fatal(err) + } + + err = tags.Set(&Tag{ + Key: "json", + Name: "bar", + Options: []string{}, + }) + if err != nil { + t.Fatal(err) + } + + found, err := tags.Get("json") + if err != nil { + t.Fatal(err) + } + + want := `json:"bar"` + if found.String() != want { + t.Errorf("set\n\twant: %#v\n\tgot : %#v", want, found.String()) + } +} + +func TestTags_Set_Append(t *testing.T) { + tag := `json:"foo,omitempty"` + + tags, err := Parse(tag) + if err != nil { + t.Fatal(err) + } + + err = tags.Set(&Tag{ + Key: "structs", + Name: "bar", + Options: []string{"omitnested"}, + }) + if err != nil { + t.Fatal(err) + } + + found, err := tags.Get("structs") + if err != nil { + t.Fatal(err) + } + + want := `structs:"bar,omitnested"` + if found.String() != want { + t.Errorf("set append\n\twant: %#v\n\tgot : %#v", want, found.String()) + } + + wantFull := `json:"foo,omitempty" structs:"bar,omitnested"` + if tags.String() != wantFull { + t.Errorf("set append\n\twant: %#v\n\tgot : %#v", wantFull, tags.String()) + } +} + +func TestTags_Set_KeyDoesNotExist(t *testing.T) { + tag := `json:"foo,omitempty" structs:"bar,omitnested"` + + tags, err := Parse(tag) + if err != nil { + t.Fatal(err) + } + + err = tags.Set(&Tag{ + Key: "", + Name: "bar", + Options: []string{}, + }) + if err == nil { + t.Fatal("setting tag with a nonexisting key should error") + } + + if err != errKeyNotSet { + t.Errorf("set\n\twant: %#v\n\tgot : %#v", errTagKeyMismatch, err) + } +} + +func TestTags_Delete(t *testing.T) { + tag := `json:"foo,omitempty" structs:"bar,omitnested" hcl:"-"` + + tags, err := Parse(tag) + if err != nil { + t.Fatal(err) + } + + tags.Delete("structs") + if tags.Len() != 2 { + t.Fatalf("tag length should be 2, have %d", tags.Len()) + } + + found, err := tags.Get("json") + if err != nil { + t.Fatal(err) + } + + want := `json:"foo,omitempty"` + if found.String() != want { + t.Errorf("delete\n\twant: %#v\n\tgot : %#v", want, found.String()) + } + + wantFull := `json:"foo,omitempty" hcl:"-"` + if tags.String() != wantFull { + t.Errorf("delete\n\twant: %#v\n\tgot : %#v", wantFull, tags.String()) + } +} + +func TestTags_DeleteOptions(t *testing.T) { + tag := `json:"foo,omitempty" structs:"bar,omitnested,omitempty" hcl:"-"` + + tags, err := Parse(tag) + if err != nil { + t.Fatal(err) + } + + tags.DeleteOptions("json", "omitempty") + + want := `json:"foo" structs:"bar,omitnested,omitempty" hcl:"-"` + if tags.String() != want { + t.Errorf("delete option\n\twant: %#v\n\tgot : %#v", want, tags.String()) + } + + tags.DeleteOptions("structs", "omitnested") + want = `json:"foo" structs:"bar,omitempty" hcl:"-"` + if tags.String() != want { + t.Errorf("delete option\n\twant: %#v\n\tgot : %#v", want, tags.String()) + } +} + +func TestTags_AddOption(t *testing.T) { + tag := `json:"foo" structs:"bar,omitempty" hcl:"-"` + + tags, err := Parse(tag) + if err != nil { + t.Fatal(err) + } + + tags.AddOptions("json", "omitempty") + + want := `json:"foo,omitempty" structs:"bar,omitempty" hcl:"-"` + if tags.String() != want { + t.Errorf("add options\n\twant: %#v\n\tgot : %#v", want, tags.String()) + } + + // this shouldn't change anything + tags.AddOptions("structs", "omitempty") + + want = `json:"foo,omitempty" structs:"bar,omitempty" hcl:"-"` + if tags.String() != want { + t.Errorf("add options\n\twant: %#v\n\tgot : %#v", want, tags.String()) + } + + // this should append to the existing + tags.AddOptions("structs", "omitnested", "flatten") + want = `json:"foo,omitempty" structs:"bar,omitempty,omitnested,flatten" hcl:"-"` + if tags.String() != want { + t.Errorf("add options\n\twant: %#v\n\tgot : %#v", want, tags.String()) + } +} + +func TestTags_String(t *testing.T) { + tag := `json:"foo" structs:"bar,omitnested" hcl:"-"` + + tags, err := Parse(tag) + if err != nil { + t.Fatal(err) + } + + if tags.String() != tag { + t.Errorf("string\n\twant: %#v\n\tgot : %#v", tag, tags.String()) + } +} + +func TestTags_Sort(t *testing.T) { + tag := `json:"foo" structs:"bar,omitnested" hcl:"-"` + + tags, err := Parse(tag) + if err != nil { + t.Fatal(err) + } + + sort.Sort(tags) + + want := `hcl:"-" json:"foo" structs:"bar,omitnested"` + if tags.String() != want { + t.Errorf("string\n\twant: %#v\n\tgot : %#v", want, tags.String()) + } +} diff --git a/gojay/visitor.go b/gojay/visitor.go @@ -0,0 +1,54 @@ +package main + +import ( + "go/ast" + "strings" +) + +func docContains(n *ast.CommentGroup, s string) bool { + for _, d := range n.List { + if strings.Contains(d.Text, s) { + return true + } + } + return false +} + +type vis struct { + pkg string + g *Gen + commentFound bool +} + +func (v *vis) Visit(n ast.Node) (w ast.Visitor) { + switch n := n.(type) { + case *ast.Package: + v.commentFound = false + return v + case *ast.File: + v.commentFound = false + return v + case *ast.GenDecl: + if len(v.g.types) == 0 && n.Doc != nil { + v.commentFound = docContains(n.Doc, gojayAnnotation) + } + return v + case *ast.TypeSpec: + if v.commentFound || v.g.isGenType(n.Name.Name) { + v.g.genTypes[n.Name.Name] = n + } + v.commentFound = false + return v + case *ast.StructType: + v.commentFound = false + return v + } + return v +} + +func newVisitor(g *Gen, pkgName string) *vis { + return &vis{ + g: g, + pkg: pkgName, + } +}