From 1a411a8244e588f34c570a2132a64b706dab2660 Mon Sep 17 00:00:00 2001 From: Vladimir Varankin Date: Mon, 6 Mar 2017 21:23:53 +0300 Subject: [PATCH 1/4] proper encoding.TextMarshaler interfaces handling Currently easyjson doesn't handle [encoding.TextMarshaler][1] interfaces. This leads to a situation where types like [net.IP][2] are handled as Base64Bytes. [1]: https://godoc.org/encoding#TextMarshaler [2]: https://godoc.org/net#IP --- gen/decoder.go | 9 ++++++++ gen/encoder.go | 7 +++++++ jwriter/writer.go | 19 ++++++++++++++++- tests/basic_test.go | 1 + tests/data.go | 50 ++++++++++++++++++++++++++++++++++++++++++--- 5 files changed, 82 insertions(+), 4 deletions(-) diff --git a/gen/decoder.go b/gen/decoder.go index f4c328f..82558a7 100644 --- a/gen/decoder.go +++ b/gen/decoder.go @@ -1,6 +1,7 @@ package gen import ( + "encoding" "encoding/json" "fmt" "reflect" @@ -65,6 +66,14 @@ func (g *Generator) genTypeDecoder(t reflect.Type, out string, tags fieldTags, i return nil } + unmarshalerIface = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() + if reflect.PtrTo(t).Implements(unmarshalerIface) { + fmt.Fprintln(g.out, ws+"if data := in.UnsafeString(); in.Ok() {") + fmt.Fprintln(g.out, ws+" in.AddError( ("+out+").UnmarshalText([]byte(data)) )") + fmt.Fprintln(g.out, ws+"}") + return nil + } + err := g.genTypeDecoderNoCheck(t, out, tags, indent) return err } diff --git a/gen/encoder.go b/gen/encoder.go index 202b9a4..a54f6e2 100644 --- a/gen/encoder.go +++ b/gen/encoder.go @@ -1,6 +1,7 @@ package gen import ( + "encoding" "encoding/json" "fmt" "reflect" @@ -95,6 +96,12 @@ func (g *Generator) genTypeEncoder(t reflect.Type, in string, tags fieldTags, in return nil } + marshalerIface = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() + if reflect.PtrTo(t).Implements(marshalerIface) { + fmt.Fprintln(g.out, ws+"out.RawText( ("+in+").MarshalText() )") + return nil + } + err := g.genTypeEncoderNoCheck(t, in, tags, indent) return err } diff --git a/jwriter/writer.go b/jwriter/writer.go index 6e78466..b4ffe67 100644 --- a/jwriter/writer.go +++ b/jwriter/writer.go @@ -68,7 +68,7 @@ func (w *Writer) RawString(s string) { w.Buffer.AppendString(s) } -// RawByte appends raw binary data to the buffer or sets the error if it is given. Useful for +// Raw appends raw binary data to the buffer or sets the error if it is given. Useful for // calling with results of MarshalJSON-like functions. func (w *Writer) Raw(data []byte, err error) { switch { @@ -83,6 +83,23 @@ func (w *Writer) Raw(data []byte, err error) { } } +// RawText encloses raw binary data in quotes and appends in to the buffer. +// Useful for calling with results of MarshalText-like functions. +func (w *Writer) RawText(data []byte, err error) { + switch { + case w.Error != nil: + return + case err != nil: + w.Error = err + case len(data) > 0: + w.Buffer.AppendByte('"') + w.Buffer.AppendBytes(data) + w.Buffer.AppendByte('"') + default: + w.RawString("null") + } +} + // Base64Bytes appends data to the buffer after base64 encoding it func (w *Writer) Base64Bytes(data []byte) { if data == nil { diff --git a/tests/basic_test.go b/tests/basic_test.go index 4e7c20c..b727c9e 100644 --- a/tests/basic_test.go +++ b/tests/basic_test.go @@ -28,6 +28,7 @@ var testCases = []struct { {&optsValue, optsString}, {&rawValue, rawString}, {&stdMarshalerValue, stdMarshalerString}, + {&userMarshalerValue, userMarshalerString}, {&unexportedStructValue, unexportedStructString}, {&excludedFieldValue, excludedFieldString}, {&sliceValue, sliceString}, diff --git a/tests/data.go b/tests/data.go index b13bcd8..ca8676e 100644 --- a/tests/data.go +++ b/tests/data.go @@ -3,6 +3,7 @@ package tests import ( "fmt" "math" + "net" "time" "github.com/mailru/easyjson" @@ -392,11 +393,54 @@ var rawString = `{` + `}` type StdMarshaler struct { - T time.Time + T time.Time + IP net.IP } -var stdMarshalerValue = StdMarshaler{T: time.Date(2016, 01, 02, 14, 15, 10, 0, time.UTC)} -var stdMarshalerString = `{"T":"2016-01-02T14:15:10Z"}` +var stdMarshalerValue = StdMarshaler{ + T: time.Date(2016, 01, 02, 14, 15, 10, 0, time.UTC), + IP: net.IPv4(192, 168, 0, 1), +} +var stdMarshalerString = `{` + + `"T":"2016-01-02T14:15:10Z",` + + `"IP":"192.168.0.1"` + + `}` + +type UserMarshaler struct { + V vMarshaler + T tMarshaler +} + +type vMarshaler net.IP + +func (v vMarshaler) MarshalJSON() ([]byte, error) { + return []byte(`"0::0"`), nil +} + +func (v *vMarshaler) UnmarshalJSON([]byte) error { + *v = vMarshaler(net.IPv6zero) + return nil +} + +type tMarshaler net.IP + +func (v tMarshaler) MarshalText() ([]byte, error) { + return []byte(`[0::0]`), nil +} + +func (v *tMarshaler) UnmarshalText([]byte) error { + *v = tMarshaler(net.IPv6zero) + return nil +} + +var userMarshalerValue = UserMarshaler{ + V: vMarshaler(net.IPv6zero), + T: tMarshaler(net.IPv6zero), +} +var userMarshalerString = `{` + + `"V":"0::0",` + + `"T":"[0::0]"` + + `}` type unexportedStruct struct { Value string From 29907909d5bb894ca6a166cc958df034b98eb6df Mon Sep 17 00:00:00 2001 From: Vladimir Varankin Date: Tue, 7 Mar 2017 12:26:41 +0300 Subject: [PATCH 2/4] escape TextMarshaler's results --- jwriter/writer.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/jwriter/writer.go b/jwriter/writer.go index b4ffe67..7b55293 100644 --- a/jwriter/writer.go +++ b/jwriter/writer.go @@ -92,9 +92,7 @@ func (w *Writer) RawText(data []byte, err error) { case err != nil: w.Error = err case len(data) > 0: - w.Buffer.AppendByte('"') - w.Buffer.AppendBytes(data) - w.Buffer.AppendByte('"') + w.String(string(data)) default: w.RawString("null") } From d1142d3549731282ac9ed5f1dc7acdcff9290dca Mon Sep 17 00:00:00 2001 From: Vladimir Varankin Date: Tue, 7 Mar 2017 12:49:04 +0300 Subject: [PATCH 3/4] avoid data convertion during encodig --- gen/decoder.go | 4 ++-- jlexer/lexer.go | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/gen/decoder.go b/gen/decoder.go index 82558a7..80f8d2c 100644 --- a/gen/decoder.go +++ b/gen/decoder.go @@ -68,8 +68,8 @@ func (g *Generator) genTypeDecoder(t reflect.Type, out string, tags fieldTags, i unmarshalerIface = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() if reflect.PtrTo(t).Implements(unmarshalerIface) { - fmt.Fprintln(g.out, ws+"if data := in.UnsafeString(); in.Ok() {") - fmt.Fprintln(g.out, ws+" in.AddError( ("+out+").UnmarshalText([]byte(data)) )") + fmt.Fprintln(g.out, ws+"if data := in.UnsafeBytes(); in.Ok() {") + fmt.Fprintln(g.out, ws+" in.AddError( ("+out+").UnmarshalText(data) )") fmt.Fprintln(g.out, ws+"}") return nil } diff --git a/jlexer/lexer.go b/jlexer/lexer.go index ae8344e..d4c16f1 100644 --- a/jlexer/lexer.go +++ b/jlexer/lexer.go @@ -618,6 +618,12 @@ func (r *Lexer) UnsafeString() string { return ret } +// UnsafeBytes returns the byte slice if the token is a string literal. +func (r *Lexer) UnsafeBytes() []byte { + _, ret := r.unsafeString() + return ret +} + // String reads a string literal. func (r *Lexer) String() string { if r.token.kind == tokenUndef && r.Ok() { From 51187256a19452ded163bbd0c31475b3b32214d6 Mon Sep 17 00:00:00 2001 From: Alexey Baranov Date: Fri, 10 Mar 2017 14:45:27 +0700 Subject: [PATCH 4/4] replace dash with empty character in import alias name --- gen/generator.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/gen/generator.go b/gen/generator.go index 630f938..1257b44 100644 --- a/gen/generator.go +++ b/gen/generator.go @@ -212,6 +212,11 @@ func fixPkgPathVendoring(pkgPath string) string { return pkgPath } +func fixAliasName(alias string) string { + alias = strings.Replace(alias, "-", "", -1) + return alias +} + // pkgAlias creates and returns and import alias for a given package. func (g *Generator) pkgAlias(pkgPath string) string { pkgPath = fixPkgPathVendoring(pkgPath) @@ -220,7 +225,7 @@ func (g *Generator) pkgAlias(pkgPath string) string { } for i := 0; ; i++ { - alias := path.Base(pkgPath) + alias := fixAliasName(path.Base(pkgPath)) if i > 0 { alias += fmt.Sprint(i) }