gojay

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

commit 493ce7aea4e9bd569f393766eb51dd7ab9d9d91b
parent 2dc87fb620b38fc93f7bff16ee47e3ec1f6321c5
Author: francoispqt <francois@parquet.ninja>
Date:   Sun, 24 Jun 2018 23:21:59 +0800

add float64 funcs, add omit empty tag handling

Diffstat:
Mdecode.go | 14+++++++++++++-
Mdecode_array.go | 20++++++++++++++++++++
Mdecode_array_test.go | 5+++++
Mdecode_number_float_test.go | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mencode_number_float.go | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Aencode_number_float_test.go | 119+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mencode_stream.go | 9+++++++--
Mgojay/Makefile | 2+-
Mgojay/README.md | 3+--
Mgojay/gen.go | 1+
Mgojay/gen_array_marshal.go | 99++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Mgojay/gen_array_marshal_tpl.go | 5+++++
Mgojay/gen_array_test.go | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mgojay/gen_array_unmarshal.go | 99++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Mgojay/gen_array_unmarshal_tpl.go | 6++++++
Mgojay/gen_map_marshal.go | 9++++++---
Mgojay/gen_map_marshal_tpl.go | 10+++++-----
Mgojay/gen_map_test.go | 170+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mgojay/gen_map_unmarshal.go | 29++++++++++++++++-------------
Mgojay/gen_map_unmarshal_tpl.go | 10+++++-----
Mgojay/gen_struct_marshal.go | 156+++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Mgojay/gen_struct_marshal_tpl.go | 17++++++++++-------
Mgojay/gen_struct_test.go | 54+++++++++++++++++++++++++++++++++++++++++++++++++-----
Mgojay/gen_struct_unmarshal.go | 44++++++++++++++++++++++++++++++++++++++------
Mgojay/gen_struct_unmarshal_tpl.go | 6++++++
Mgojay/gen_tag.go | 10++++++++++
26 files changed, 886 insertions(+), 200 deletions(-)

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 @@ -620,3 +620,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_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_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/Makefile b/gojay/Makefile @@ -4,7 +4,7 @@ build: .PHONY: test test: - go test -race . + go test -race -v . .PHONY: cover cover: diff --git a/gojay/README.md b/gojay/README.md @@ -58,4 +58,4 @@ type A struct { SkipMarshal string `gojay:"skipMarshal,-m"` Skip string `gojay:"-"` } -``` -\ No newline at end of file +``` diff --git a/gojay/gen.go b/gojay/gen.go @@ -9,6 +9,7 @@ import ( 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") diff --git a/gojay/gen_array_marshal.go b/gojay/gen_array_marshal.go @@ -2,7 +2,9 @@ package main import ( "errors" + "fmt" "go/ast" + "log" ) func init() {} @@ -40,7 +42,7 @@ func (g *Gen) arrGenMarshal(n string, s *ast.ArrayType) error { return err } default: - return errors.New("Unknown type") + return fmt.Errorf("Unknown type %s", n) } } _, err = g.b.Write([]byte("}\n")) @@ -53,27 +55,31 @@ func (g *Gen) arrGenMarshal(n string, s *ast.ArrayType) error { func (g *Gen) arrGenMarshalIdent(i *ast.Ident, ptr bool) error { switch i.String() { case "string": - return g.arrMarshalString(ptr) + g.arrMarshalString(ptr) case "bool": - return g.arrMarshalBool(ptr) + g.arrMarshalBool(ptr) case "int": - return g.arrMarshalInt("", ptr) + g.arrMarshalInt("", ptr) case "int64": - return g.arrMarshalInt("64", ptr) + g.arrMarshalInt("64", ptr) case "int32": - return g.arrMarshalInt("32", ptr) + g.arrMarshalInt("32", ptr) case "int16": - return g.arrMarshalInt("16", ptr) + g.arrMarshalInt("16", ptr) case "int8": - return g.arrMarshalInt("8", ptr) + g.arrMarshalInt("8", ptr) case "uint64": - return g.arrMarshalUint("64", ptr) + g.arrMarshalUint("64", ptr) case "uint32": - return g.arrMarshalUint("32", ptr) + g.arrMarshalUint("32", ptr) case "uint16": - return g.arrMarshalUint("16", ptr) + g.arrMarshalUint("16", ptr) case "uint8": - return g.arrMarshalUint("8", ptr) + 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 { @@ -87,66 +93,65 @@ func (g *Gen) arrGenMarshalIdent(i *ast.Ident, ptr bool) error { return errors.New("could not determine what to do with type " + i.String()) } } - return errors.New("Unknown type") + 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: - return g.arrMarshalStruct(sp, ptr) + g.arrMarshalStruct(sp, ptr) case *ast.ArrayType: - return g.arrMarshalArr(sp, ptr) + g.arrMarshalArr(sp, ptr) } return nil } -func (g *Gen) arrMarshalString(ptr bool) error { +func (g *Gen) arrMarshalString(ptr bool) { if ptr { err := arrMarshalTpl["stringPtr"].tpl.Execute(g.b, struct { Ptr string }{""}) if err != nil { - return err + log.Fatal(err) } } else { err := arrMarshalTpl["string"].tpl.Execute(g.b, struct { Ptr string }{"&"}) if err != nil { - return err + log.Fatal(err) } } - return nil } -func (g *Gen) arrMarshalBool(ptr bool) error { +func (g *Gen) arrMarshalBool(ptr bool) { if ptr { err := arrMarshalTpl["bool"].tpl.Execute(g.b, struct { Ptr string }{""}) if err != nil { - return err + log.Fatal(err) } } else { err := arrMarshalTpl["bool"].tpl.Execute(g.b, struct { Ptr string }{"&"}) if err != nil { - return err + log.Fatal(err) } } - return nil } -func (g *Gen) arrMarshalInt(intLen string, ptr bool) error { +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 { - return err + log.Fatal(err) } } else { err := arrMarshalTpl["int"].tpl.Execute(g.b, struct { @@ -154,20 +159,39 @@ func (g *Gen) arrMarshalInt(intLen string, ptr bool) error { Ptr string }{intLen, "&"}) if err != nil { - return err + 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) } } - return nil } -func (g *Gen) arrMarshalUint(intLen string, ptr bool) error { +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 { - return err + log.Fatal(err) } } else { err := arrMarshalTpl["uint"].tpl.Execute(g.b, struct { @@ -175,46 +199,43 @@ func (g *Gen) arrMarshalUint(intLen string, ptr bool) error { Ptr string }{intLen, "&"}) if err != nil { - return err + log.Fatal(err) } } - return nil } -func (g *Gen) arrMarshalStruct(st *ast.TypeSpec, ptr bool) error { +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 { - return err + log.Fatal(err) } } else { err := arrMarshalTpl["struct"].tpl.Execute(g.b, struct { StructName string }{st.Name.String()}) if err != nil { - return err + log.Fatal(err) } } - return nil } -func (g *Gen) arrMarshalArr(st *ast.TypeSpec, ptr bool) error { +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 { - return err + log.Fatal(err) } } else { err := arrMarshalTpl["arr"].tpl.Execute(g.b, struct { StructName string }{st.Name.String()}) if err != nil { - return err + log.Fatal(err) } } - return nil } diff --git a/gojay/gen_array_marshal_tpl.go b/gojay/gen_array_marshal_tpl.go @@ -31,6 +31,11 @@ var arrMarshalTpl = templateList{ "\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" + diff --git a/gojay/gen_array_test.go b/gojay/gen_array_test.go @@ -277,6 +277,72 @@ func (v *IntSlice) IsNil() bool { } `, }, + "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 diff --git a/gojay/gen_array_unmarshal.go b/gojay/gen_array_unmarshal.go @@ -2,7 +2,9 @@ package main import ( "errors" + "fmt" "go/ast" + "log" ) func (g *Gen) arrGenUnmarshal(n string, s *ast.ArrayType) error { @@ -29,7 +31,7 @@ func (g *Gen) arrGenUnmarshal(n string, s *ast.ArrayType) error { return err } default: - return errors.New("Unknown type") + return fmt.Errorf("Unknown type %s", n) } } _, err = g.b.Write(structUnmarshalClose) @@ -42,27 +44,31 @@ func (g *Gen) arrGenUnmarshal(n string, s *ast.ArrayType) error { func (g *Gen) arrGenUnmarshalIdent(i *ast.Ident, ptr bool) error { switch i.String() { case "string": - return g.arrUnmarshalString(ptr) + g.arrUnmarshalString(ptr) case "bool": - return g.arrUnmarshalBool(ptr) + g.arrUnmarshalBool(ptr) case "int": - return g.arrUnmarshalInt("", ptr) + g.arrUnmarshalInt("", ptr) case "int64": - return g.arrUnmarshalInt("64", ptr) + g.arrUnmarshalInt("64", ptr) case "int32": - return g.arrUnmarshalInt("32", ptr) + g.arrUnmarshalInt("32", ptr) case "int16": - return g.arrUnmarshalInt("16", ptr) + g.arrUnmarshalInt("16", ptr) case "int8": - return g.arrUnmarshalInt("8", ptr) + g.arrUnmarshalInt("8", ptr) case "uint64": - return g.arrUnmarshalUint("64", ptr) + g.arrUnmarshalUint("64", ptr) case "uint32": - return g.arrUnmarshalUint("32", ptr) + g.arrUnmarshalUint("32", ptr) case "uint16": - return g.arrUnmarshalUint("16", ptr) + g.arrUnmarshalUint("16", ptr) case "uint8": - return g.arrUnmarshalUint("8", ptr) + 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 { @@ -76,66 +82,65 @@ func (g *Gen) arrGenUnmarshalIdent(i *ast.Ident, ptr bool) error { return errors.New("could not determine what to do with type " + i.String()) } } - return errors.New("Unknown type") + 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: - return g.arrUnmarshalStruct(sp, ptr) + g.arrUnmarshalStruct(sp, ptr) case *ast.ArrayType: - return g.arrUnmarshalArr(sp, ptr) + g.arrUnmarshalArr(sp, ptr) } return nil } -func (g *Gen) arrUnmarshalString(ptr bool) error { +func (g *Gen) arrUnmarshalString(ptr bool) { if ptr { err := arrUnmarshalTpl["stringPtr"].tpl.Execute(g.b, struct { Ptr string }{""}) if err != nil { - return err + log.Fatal(err) } } else { err := arrUnmarshalTpl["string"].tpl.Execute(g.b, struct { Ptr string }{"&"}) if err != nil { - return err + log.Fatal(err) } } - return nil } -func (g *Gen) arrUnmarshalBool(ptr bool) error { +func (g *Gen) arrUnmarshalBool(ptr bool) { if ptr { err := arrUnmarshalTpl["bool"].tpl.Execute(g.b, struct { Ptr string }{""}) if err != nil { - return err + log.Fatal(err) } } else { err := arrUnmarshalTpl["bool"].tpl.Execute(g.b, struct { Ptr string }{"&"}) if err != nil { - return err + log.Fatal(err) } } - return nil } -func (g *Gen) arrUnmarshalInt(intLen string, ptr bool) error { +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 { - return err + log.Fatal(err) } } else { err := arrUnmarshalTpl["int"].tpl.Execute(g.b, struct { @@ -143,20 +148,19 @@ func (g *Gen) arrUnmarshalInt(intLen string, ptr bool) error { Ptr string }{intLen, "&"}) if err != nil { - return err + log.Fatal(err) } } - return nil } -func (g *Gen) arrUnmarshalUint(intLen string, ptr bool) error { +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 { - return err + log.Fatal(err) } } else { err := arrUnmarshalTpl["uint"].tpl.Execute(g.b, struct { @@ -164,46 +168,63 @@ func (g *Gen) arrUnmarshalUint(intLen string, ptr bool) error { Ptr string }{intLen, "&"}) if err != nil { - return err + 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) } } - return nil } -func (g *Gen) arrUnmarshalStruct(st *ast.TypeSpec, ptr bool) error { +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 { - return err + log.Fatal(err) } } else { err := arrUnmarshalTpl["struct"].tpl.Execute(g.b, struct { StructName string }{st.Name.String()}) if err != nil { - return err + log.Fatal(err) } } - return nil } -func (g *Gen) arrUnmarshalArr(st *ast.TypeSpec, ptr bool) error { +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 { - return err + log.Fatal(err) } } else { err := arrUnmarshalTpl["arr"].tpl.Execute(g.b, struct { StructName string }{st.Name.String()}) if err != nil { - return err + log.Fatal(err) } } - return nil } diff --git a/gojay/gen_array_unmarshal_tpl.go b/gojay/gen_array_unmarshal_tpl.go @@ -27,6 +27,12 @@ var arrUnmarshalTpl = templateList{ "\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" + diff --git a/gojay/gen_map_marshal.go b/gojay/gen_map_marshal.go @@ -2,6 +2,7 @@ package main import ( "errors" + "fmt" "go/ast" "log" ) @@ -40,7 +41,7 @@ func (g *Gen) mapGenMarshalObj(n string, s *ast.MapType) error { return err } default: - return errors.New("Unknown type") + return fmt.Errorf("Unknown type %s", n) } } _, err = g.b.Write([]byte("}\n")) @@ -75,7 +76,9 @@ func (g *Gen) mapGenMarshalIdent(i *ast.Ident, ptr bool) error { case "uint8": g.mapMarshalUint("8", ptr) case "float64": - g.mapMarshalFloat("", ptr) + 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 { @@ -88,7 +91,7 @@ func (g *Gen) mapGenMarshalIdent(i *ast.Ident, ptr bool) error { return errors.New("could not determine what to do with type " + i.String()) } } else { - return errors.New("Unknown type") + return fmt.Errorf("Unknown type %s", i.Name) } } return nil diff --git a/gojay/gen_map_marshal_tpl.go b/gojay/gen_map_marshal_tpl.go @@ -13,31 +13,31 @@ func (v {{.StructName}}) IsNil() bool { return v == nil || len(v) == 0 } }, "string": &genTpl{ strTpl: ` for k, s := range v { - enc.StringKey(k, s) + enc.StringKey(k, {{.Ptr}}s) } `, }, "int": &genTpl{ strTpl: ` for k, s := range v { - enc.Int{{.IntLen}}Key(k, s) + enc.Int{{.IntLen}}Key(k, {{.Ptr}}s) } `, }, "uint": &genTpl{ strTpl: ` for k, s := range v { - enc.Uint{{.IntLen}}Key(k, s) + enc.Uint{{.IntLen}}Key(k, {{.Ptr}}s) } `, }, "float": &genTpl{ strTpl: ` for k, s := range v { - enc.Float{{.IntLen}}Key(k, s) + enc.Float{{.IntLen}}Key(k, {{.Ptr}}s) } `, }, "bool": &genTpl{ strTpl: ` for k, s := range v { - enc.BoolKey(k, s) + enc.BoolKey(k, {{.Ptr}}s) } `, }, diff --git a/gojay/gen_map_test.go b/gojay/gen_map_test.go @@ -48,6 +48,40 @@ func (v StrMap) MarshalJSONObject(enc *gojay.Encoder) { 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 @@ -82,6 +116,40 @@ func (v IntMap) MarshalJSONObject(enc *gojay.Encoder) { 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 @@ -116,6 +184,40 @@ func (v IntMap) MarshalJSONObject(enc *gojay.Encoder) { 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 @@ -252,6 +354,40 @@ func (v IntMap) MarshalJSONObject(enc *gojay.Encoder) { 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 @@ -388,6 +524,40 @@ func (v BoolMap) MarshalJSONObject(enc *gojay.Encoder) { 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 diff --git a/gojay/gen_map_unmarshal.go b/gojay/gen_map_unmarshal.go @@ -2,6 +2,7 @@ package main import ( "errors" + "fmt" "go/ast" "log" ) @@ -42,7 +43,7 @@ func (g *Gen) mapGenUnmarshalObj(n string, s *ast.MapType) error { return err } default: - return errors.New("Unknown type") + return fmt.Errorf("Unknown type %s", n) } } _, err = g.b.Write(structUnmarshalClose) @@ -77,7 +78,9 @@ func (g *Gen) mapGenUnmarshalIdent(i *ast.Ident, ptr bool) error { case "uint8": g.mapUnmarshalUint("8", ptr) case "float64": - g.mapUnmarshalFloat("", ptr) + 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 { @@ -97,7 +100,7 @@ func (g *Gen) mapGenUnmarshalIdent(i *ast.Ident, ptr bool) error { return errors.New("could not determine what to do with type " + i.String()) } } else { - return errors.New("Unknown type") + return fmt.Errorf("Unknown type %s", i.Name) } } return nil @@ -119,14 +122,14 @@ 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) } @@ -137,14 +140,14 @@ 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) } @@ -156,7 +159,7 @@ func (g *Gen) mapUnmarshalInt(intLen string, ptr bool) { err := mapUnmarshalTpl["int"].tpl.Execute(g.b, struct { IntLen string Ptr string - }{intLen, ""}) + }{intLen, "&"}) if err != nil { log.Fatal(err) } @@ -164,7 +167,7 @@ func (g *Gen) mapUnmarshalInt(intLen string, ptr bool) { err := mapUnmarshalTpl["int"].tpl.Execute(g.b, struct { IntLen string Ptr string - }{intLen, "&"}) + }{intLen, ""}) if err != nil { log.Fatal(err) } @@ -176,7 +179,7 @@ func (g *Gen) mapUnmarshalUint(intLen string, ptr bool) { err := mapUnmarshalTpl["uint"].tpl.Execute(g.b, struct { IntLen string Ptr string - }{intLen, ""}) + }{intLen, "&"}) if err != nil { log.Fatal(err) } @@ -184,7 +187,7 @@ func (g *Gen) mapUnmarshalUint(intLen string, ptr bool) { err := mapUnmarshalTpl["uint"].tpl.Execute(g.b, struct { IntLen string Ptr string - }{intLen, "&"}) + }{intLen, ""}) if err != nil { log.Fatal(err) } @@ -196,7 +199,7 @@ func (g *Gen) mapUnmarshalFloat(intLen string, ptr bool) { err := mapUnmarshalTpl["float"].tpl.Execute(g.b, struct { IntLen string Ptr string - }{intLen, ""}) + }{intLen, "&"}) if err != nil { log.Fatal(err) } @@ -204,7 +207,7 @@ func (g *Gen) mapUnmarshalFloat(intLen string, ptr bool) { err := mapUnmarshalTpl["float"].tpl.Execute(g.b, struct { IntLen string Ptr string - }{intLen, "&"}) + }{intLen, ""}) if err != nil { log.Fatal(err) } diff --git a/gojay/gen_map_unmarshal_tpl.go b/gojay/gen_map_unmarshal_tpl.go @@ -16,7 +16,7 @@ func (v {{.StructName}}) NKeys() int { return {{.NKeys}} } if err := dec.String(&str); err != nil { return err } - v[k] = str + v[k] = {{.Ptr}}str `, }, "int": &genTpl{ @@ -24,7 +24,7 @@ func (v {{.StructName}}) NKeys() int { return {{.NKeys}} } if err := dec.Int{{.IntLen}}(&i); err != nil { return err } - v[k] = i + v[k] = {{.Ptr}}i `, }, "uint": &genTpl{ @@ -32,7 +32,7 @@ func (v {{.StructName}}) NKeys() int { return {{.NKeys}} } if err := dec.Uint{{.IntLen}}(&i); err != nil { return err } - v[k] = i + v[k] = {{.Ptr}}i `, }, "float": &genTpl{ @@ -40,7 +40,7 @@ func (v {{.StructName}}) NKeys() int { return {{.NKeys}} } if err := dec.Float{{.IntLen}}(&i); err != nil { return err } - v[k] = i + v[k] = {{.Ptr}}i `, }, "bool": &genTpl{ @@ -48,7 +48,7 @@ func (v {{.StructName}}) NKeys() int { return {{.NKeys}} } if err := dec.Bool(&b); err != nil { return err } - v[k] = b + v[k] = {{.Ptr}}b `, }, "struct": &genTpl{ diff --git a/gojay/gen_struct_marshal.go b/gojay/gen_struct_marshal.go @@ -1,7 +1,7 @@ package main import ( - "errors" + "fmt" "go/ast" "log" ) @@ -29,13 +29,19 @@ func (g *Gen) structGenMarshalObj(n string, s *ast.StructType) (int, error) { // TODO: check tags for _, field := range s.Fields.List { // check if has hide tag - if field.Tag != nil && hasTagMarshalHide(field.Tag) { - continue + 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, false) + keys, err = g.structGenMarshalIdent(field, t, keys, omitEmpty, false) if err != nil { return 0, err } @@ -43,12 +49,12 @@ func (g *Gen) structGenMarshalObj(n string, s *ast.StructType) (int, error) { switch ptrExp := t.X.(type) { case *ast.Ident: var err error - keys, err = g.structGenMarshalIdent(field, ptrExp, keys, true) + keys, err = g.structGenMarshalIdent(field, ptrExp, keys, omitEmpty, true) if err != nil { return 0, err } default: - return 0, errors.New("Unknown type") + return 0, fmt.Errorf("Unknown type %s", n) } } } @@ -60,50 +66,53 @@ func (g *Gen) structGenMarshalObj(n string, s *ast.StructType) (int, error) { return keys, nil } -func (g *Gen) structGenMarshalIdent(field *ast.Field, i *ast.Ident, keys int, ptr bool) (int, error) { +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, ptr) + g.structMarshalString(field, keyV, omitEmpty, ptr) keys++ case "bool": - g.structMarshalBool(field, keyV, ptr) + g.structMarshalBool(field, keyV, omitEmpty, ptr) keys++ case "int": - g.structMarshalInt(field, keyV, "", ptr) + g.structMarshalInt(field, keyV, "", omitEmpty, ptr) keys++ case "int64": - g.structMarshalInt(field, keyV, "64", ptr) + g.structMarshalInt(field, keyV, "64", omitEmpty, ptr) keys++ case "int32": - g.structMarshalInt(field, keyV, "32", ptr) + g.structMarshalInt(field, keyV, "32", omitEmpty, ptr) keys++ case "int16": - g.structMarshalInt(field, keyV, "16", ptr) + g.structMarshalInt(field, keyV, "16", omitEmpty, ptr) keys++ case "int8": - g.structMarshalInt(field, keyV, "8", ptr) + g.structMarshalInt(field, keyV, "8", omitEmpty, ptr) keys++ case "uint64": - g.structMarshalUint(field, keyV, "64", ptr) + g.structMarshalUint(field, keyV, "64", omitEmpty, ptr) keys++ case "uint32": - g.structMarshalUint(field, keyV, "32", ptr) + g.structMarshalUint(field, keyV, "32", omitEmpty, ptr) keys++ case "uint16": - g.structMarshalUint(field, keyV, "16", ptr) + g.structMarshalUint(field, keyV, "16", omitEmpty, ptr) keys++ case "uint8": - g.structMarshalUint(field, keyV, "8", ptr) + g.structMarshalUint(field, keyV, "8", omitEmpty, ptr) keys++ case "float64": - g.structMarshalFloat(field, keyV, "", ptr) + 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, ptr) + err := g.structMarshalNonPrim(field, keyV, sp, omitEmpty, ptr) if err != nil { return 0, err } @@ -111,139 +120,166 @@ func (g *Gen) structGenMarshalIdent(field *ast.Field, i *ast.Ident, keys int, pt } else if i.Obj != nil { switch t := i.Obj.Decl.(type) { case *ast.TypeSpec: - var err = g.structMarshalNonPrim(field, keyV, t, ptr) + var err = g.structMarshalNonPrim(field, keyV, t, omitEmpty, ptr) if err != nil { return 0, err } keys++ default: - return 0, errors.New("could not determine what to do with type " + i.String()) + g.structMarshalAny(field, keyV, sp, ptr) + keys++ } } else { - return 0, errors.New("Unknown type") + g.structMarshalAny(field, keyV, sp, ptr) + keys++ } } return keys, nil } -func (g *Gen) structMarshalNonPrim(field *ast.Field, keyV string, sp *ast.TypeSpec, ptr bool) error { +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, ptr) + g.structMarshalStruct(field, keyV, sp, omitEmpty, ptr) return nil case *ast.ArrayType: - g.structMarshalArr(field, keyV, sp, ptr) + 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, ptr bool) { +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 - Ptr string - }{key, keyV, ptrStr}) + 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, ptr bool) { +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 - Ptr string - }{key, keyV, ptrStr}) + 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, ptr bool) { +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 - Ptr string - }{key, intLen, keyV, ptrStr}) + 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, ptr bool) { +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 - Ptr string - }{key, intLen, keyV, ptrStr}) + 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, ptr bool) { +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 - Ptr string - }{key, intLen, keyV, ptrStr}) + 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, ptr bool) { +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 - Ptr string - }{keyV, key, ptrStr}) + 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, ptr bool) { +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 diff --git a/gojay/gen_struct_marshal_tpl.go b/gojay/gen_struct_marshal_tpl.go @@ -12,25 +12,28 @@ func (v *{{.StructName}}) IsNil() bool { return v == nil } `, }, "string": &genTpl{ - strTpl: "\tenc.StringKey(\"{{.Key}}\", {{.Ptr}}v.{{.Field}})\n", + strTpl: "\tenc.StringKey{{.OmitEmpty}}(\"{{.Key}}\", {{.Ptr}}v.{{.Field}})\n", }, "int": &genTpl{ - strTpl: "\tenc.Int{{.IntLen}}Key(\"{{.Key}}\", {{.Ptr}}v.{{.Field}})\n", + strTpl: "\tenc.Int{{.IntLen}}Key{{.OmitEmpty}}(\"{{.Key}}\", {{.Ptr}}v.{{.Field}})\n", }, "uint": &genTpl{ - strTpl: "\tenc.Uint{{.IntLen}}Key(\"{{.Key}}\", {{.Ptr}}v.{{.Field}})\n", + strTpl: "\tenc.Uint{{.IntLen}}Key{{.OmitEmpty}}(\"{{.Key}}\", {{.Ptr}}v.{{.Field}})\n", }, "float": &genTpl{ - strTpl: "\tenc.Float{{.IntLen}}Key(\"{{.Key}}\", {{.Ptr}}v.{{.Field}})\n", + strTpl: "\tenc.Float{{.IntLen}}Key{{.OmitEmpty}}(\"{{.Key}}\", {{.Ptr}}v.{{.Field}})\n", }, "bool": &genTpl{ - strTpl: "\tenc.BoolKey(\"{{.Key}}\", {{.Ptr}}v.{{.Field}})\n", + strTpl: "\tenc.BoolKey{{.OmitEmpty}}(\"{{.Key}}\", {{.Ptr}}v.{{.Field}})\n", }, "struct": &genTpl{ - strTpl: "\tenc.ObjectKey(\"{{.Key}}\", v.{{.Field}})\n", + strTpl: "\tenc.ObjectKey{{.OmitEmpty}}(\"{{.Key}}\", v.{{.Field}})\n", }, "arr": &genTpl{ - strTpl: "\tenc.ArrayKey(\"{{.Key}}\", v.{{.Field}})\n", + strTpl: "\tenc.ArrayKey{{.OmitEmpty}}(\"{{.Key}}\", v.{{.Field}})\n", + }, + "any": &genTpl{ + strTpl: "\tenc.AnyKey(\"{{.Key}}\", v.{{.Field}})\n", }, } diff --git a/gojay/gen_struct_test.go b/gojay/gen_struct_test.go @@ -27,9 +27,11 @@ type Struct struct{ Uint16 uint16 Uint32 uint32 Uint64 uint64 - Float float64 + Float64 float64 + Float32 float32 Str string Bool bool + Unknown UnknownType } `), expectedResult: `package @@ -57,18 +59,22 @@ func (v *Struct) UnmarshalJSONObject(dec *gojay.Decoder, k string) error { return dec.Uint32(&v.Uint32) case "uint64": return dec.Uint64(&v.Uint64) - case "float": - return dec.Float(&v.Float) + 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 12 } +func (v *Struct) NKeys() int { return 14 } // MarshalJSONObject implements gojay's MarshalerJSONObject func (v *Struct) MarshalJSONObject(enc *gojay.Encoder) { @@ -81,9 +87,11 @@ func (v *Struct) MarshalJSONObject(enc *gojay.Encoder) { enc.Uint16Key("uint16", v.Uint16) enc.Uint32Key("uint32", v.Uint32) enc.Uint64Key("uint64", v.Uint64) - enc.FloatKey("float", v.Float) + 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 @@ -368,6 +376,42 @@ func (v *Struct) MarshalJSONObject(enc *gojay.Encoder) { 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) { diff --git a/gojay/gen_struct_unmarshal.go b/gojay/gen_struct_unmarshal.go @@ -1,7 +1,7 @@ package main import ( - "errors" + "fmt" "go/ast" "log" ) @@ -57,7 +57,7 @@ func (g *Gen) structGenUnmarshalObj(n string, s *ast.StructType) (int, error) { return 0, err } default: - return 0, errors.New("Unknown type") + return 0, fmt.Errorf("Unknown type %s", n) } } } @@ -109,7 +109,10 @@ func (g *Gen) structGenUnmarshalIdent(field *ast.Field, i *ast.Ident, keys int, g.structUnmarshalUint(field, keyV, "8", ptr) keys++ case "float64": - g.structUnmarshalFloat(field, keyV, "", ptr) + 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 @@ -129,10 +132,12 @@ func (g *Gen) structGenUnmarshalIdent(field *ast.Field, i *ast.Ident, keys int, } keys++ default: - return 0, errors.New("could not determine what to do with type " + i.String()) + g.structUnmarshalAny(field, keyV, sp, ptr) + keys++ } } else { - return 0, errors.New("Unknown type") + g.structUnmarshalAny(field, keyV, sp, ptr) + keys++ } } return keys, nil @@ -146,8 +151,10 @@ func (g *Gen) structUnmarshalNonPrim(field *ast.Field, keyV string, sp *ast.Type case *ast.ArrayType: g.structUnmarshalArr(field, keyV, sp, ptr) return nil + default: + g.structUnmarshalAny(field, keyV, sp, ptr) + return nil } - return errors.New("Unknown type") } func (g *Gen) structUnmarshalString(field *ast.Field, keyV string, ptr bool) { @@ -344,3 +351,28 @@ func (g *Gen) structUnmarshalArr(field *ast.Field, keyV string, st *ast.TypeSpec } } } + +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 @@ -59,6 +59,12 @@ func (v *{{.StructName}}) NKeys() int { return {{.NKeys}} } 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() { diff --git a/gojay/gen_tag.go b/gojay/gen_tag.go @@ -11,6 +11,7 @@ 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]) @@ -42,6 +43,15 @@ func hasTagMarshalHide(tags *ast.BasicLit) bool { 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 {