From 2a4ed8adb079569ec101577e3540ae7153c499c5 Mon Sep 17 00:00:00 2001 From: Carlo Alberto Ferraris Date: Tue, 22 Aug 2017 10:13:56 +0900 Subject: [PATCH 01/22] Optimize encoder https://github.com/mailru/easyjson/issues/136 --- gen/encoder.go | 74 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 29 deletions(-) diff --git a/gen/encoder.go b/gen/encoder.go index a54f6e2..1bdb7d4 100644 --- a/gen/encoder.go +++ b/gen/encoder.go @@ -81,7 +81,7 @@ func parseFieldTags(f reflect.StructField) fieldTags { } // genTypeEncoder generates code that encodes in of type t into the writer, but uses marshaler interface if implemented by t. -func (g *Generator) genTypeEncoder(t reflect.Type, in string, tags fieldTags, indent int) error { +func (g *Generator) genTypeEncoder(t reflect.Type, in string, tags fieldTags, indent int, assumeNonEmpty bool) error { ws := strings.Repeat(" ", indent) marshalerIface := reflect.TypeOf((*easyjson.Marshaler)(nil)).Elem() @@ -102,12 +102,12 @@ func (g *Generator) genTypeEncoder(t reflect.Type, in string, tags fieldTags, in return nil } - err := g.genTypeEncoderNoCheck(t, in, tags, indent) + err := g.genTypeEncoderNoCheck(t, in, tags, indent, assumeNonEmpty) return err } // genTypeEncoderNoCheck generates code that encodes in of type t into the writer. -func (g *Generator) genTypeEncoderNoCheck(t reflect.Type, in string, tags fieldTags, indent int) error { +func (g *Generator) genTypeEncoderNoCheck(t reflect.Type, in string, tags fieldTags, indent int, assumeNonEmpty bool) error { ws := strings.Repeat(" ", indent) // Check whether type is primitive, needs to be done after interface check. @@ -128,16 +128,20 @@ func (g *Generator) genTypeEncoderNoCheck(t reflect.Type, in string, tags fieldT if t.Elem().Kind() == reflect.Uint8 { fmt.Fprintln(g.out, ws+"out.Base64Bytes("+in+")") } else { - fmt.Fprintln(g.out, ws+"if "+in+" == nil && (out.Flags & jwriter.NilSliceAsEmpty) == 0 {") - fmt.Fprintln(g.out, ws+` out.RawString("null")`) - fmt.Fprintln(g.out, ws+"} else {") + if !assumeNonEmpty { + fmt.Fprintln(g.out, ws+"if "+in+" == nil && (out.Flags & jwriter.NilSliceAsEmpty) == 0 {") + fmt.Fprintln(g.out, ws+` out.RawString("null")`) + fmt.Fprintln(g.out, ws+"} else {") + } else { + fmt.Fprintln(g.out, ws+"{") + } fmt.Fprintln(g.out, ws+" out.RawByte('[')") fmt.Fprintln(g.out, ws+" for "+iVar+", "+vVar+" := range "+in+" {") fmt.Fprintln(g.out, ws+" if "+iVar+" > 0 {") fmt.Fprintln(g.out, ws+" out.RawByte(',')") fmt.Fprintln(g.out, ws+" }") - g.genTypeEncoder(elem, vVar, tags, indent+2) + g.genTypeEncoder(elem, vVar, tags, indent+2, false) fmt.Fprintln(g.out, ws+" }") fmt.Fprintln(g.out, ws+" out.RawByte(']')") @@ -157,7 +161,7 @@ func (g *Generator) genTypeEncoderNoCheck(t reflect.Type, in string, tags fieldT fmt.Fprintln(g.out, ws+" out.RawByte(',')") fmt.Fprintln(g.out, ws+" }") - g.genTypeEncoder(elem, in+"["+iVar+"]", tags, indent+1) + g.genTypeEncoder(elem, in+"["+iVar+"]", tags, indent+1, false) fmt.Fprintln(g.out, ws+"}") fmt.Fprintln(g.out, ws+"out.RawByte(']')") @@ -170,13 +174,17 @@ func (g *Generator) genTypeEncoderNoCheck(t reflect.Type, in string, tags fieldT fmt.Fprintln(g.out, ws+enc+"(out, "+in+")") case reflect.Ptr: - fmt.Fprintln(g.out, ws+"if "+in+" == nil {") - fmt.Fprintln(g.out, ws+` out.RawString("null")`) - fmt.Fprintln(g.out, ws+"} else {") + if !assumeNonEmpty { + fmt.Fprintln(g.out, ws+"if "+in+" == nil {") + fmt.Fprintln(g.out, ws+` out.RawString("null")`) + fmt.Fprintln(g.out, ws+"} else {") + } - g.genTypeEncoder(t.Elem(), "*"+in, tags, indent+1) + g.genTypeEncoder(t.Elem(), "*"+in, tags, indent+1, false) - fmt.Fprintln(g.out, ws+"}") + if !assumeNonEmpty { + fmt.Fprintln(g.out, ws+"}") + } case reflect.Map: key := t.Key() @@ -185,18 +193,21 @@ func (g *Generator) genTypeEncoderNoCheck(t reflect.Type, in string, tags fieldT } tmpVar := g.uniqueVarName() - fmt.Fprintln(g.out, ws+"if "+in+" == nil && (out.Flags & jwriter.NilMapAsEmpty) == 0 {") - fmt.Fprintln(g.out, ws+" out.RawString(`null`)") - fmt.Fprintln(g.out, ws+"} else {") + if !assumeNonEmpty { + fmt.Fprintln(g.out, ws+"if "+in+" == nil && (out.Flags & jwriter.NilMapAsEmpty) == 0 {") + fmt.Fprintln(g.out, ws+" out.RawString(`null`)") + fmt.Fprintln(g.out, ws+"} else {") + } else { + fmt.Fprintln(g.out, ws+"{") + } fmt.Fprintln(g.out, ws+" out.RawByte('{')") fmt.Fprintln(g.out, ws+" "+tmpVar+"First := true") fmt.Fprintln(g.out, ws+" for "+tmpVar+"Name, "+tmpVar+"Value := range "+in+" {") - fmt.Fprintln(g.out, ws+" if !"+tmpVar+"First { out.RawByte(',') }") - fmt.Fprintln(g.out, ws+" "+tmpVar+"First = false") + fmt.Fprintln(g.out, ws+" if "+tmpVar+"First { "+tmpVar+"First = false } else { out.RawByte(',') }") fmt.Fprintln(g.out, ws+" out.String(string("+tmpVar+"Name))") fmt.Fprintln(g.out, ws+" out.RawByte(':')") - g.genTypeEncoder(t.Elem(), tmpVar+"Value", tags, indent+2) + g.genTypeEncoder(t.Elem(), tmpVar+"Value", tags, indent+2, false) fmt.Fprintln(g.out, ws+" }") fmt.Fprintln(g.out, ws+" out.RawByte('}')") @@ -255,18 +266,23 @@ func (g *Generator) genStructFieldEncoder(t reflect.Type, f reflect.StructField) return nil } if !tags.omitEmpty && !g.omitEmpty || tags.noOmitEmpty { - fmt.Fprintln(g.out, " if !first { out.RawByte(',') }") - fmt.Fprintln(g.out, " first = false") - fmt.Fprintf(g.out, " out.RawString(%q)\n", strconv.Quote(jsonName)+":") - return g.genTypeEncoder(f.Type, "in."+f.Name, tags, 1) + fmt.Fprintln(g.out, " if first {") + fmt.Fprintln(g.out, " first = false") + fmt.Fprintf(g.out, " out.RawString(%q)\n", strconv.Quote(jsonName)+":") + fmt.Fprintln(g.out, " } else {") + fmt.Fprintf(g.out, " out.RawString(%q)\n", ","+strconv.Quote(jsonName)+":") + fmt.Fprintln(g.out, " }") + return g.genTypeEncoder(f.Type, "in."+f.Name, tags, 1, false) } fmt.Fprintln(g.out, " if", g.notEmptyCheck(f.Type, "in."+f.Name), "{") - fmt.Fprintln(g.out, " if !first { out.RawByte(',') }") - fmt.Fprintln(g.out, " first = false") - - fmt.Fprintf(g.out, " out.RawString(%q)\n", strconv.Quote(jsonName)+":") - if err := g.genTypeEncoder(f.Type, "in."+f.Name, tags, 2); err != nil { + fmt.Fprintln(g.out, " if first {") + fmt.Fprintln(g.out, " first = false") + fmt.Fprintf(g.out, " out.RawString(%q)\n", strconv.Quote(jsonName)+":") + fmt.Fprintln(g.out, " } else {") + fmt.Fprintf(g.out, " out.RawString(%q)\n", ","+strconv.Quote(jsonName)+":") + fmt.Fprintln(g.out, " }") + if err := g.genTypeEncoder(f.Type, "in."+f.Name, tags, 2, true); err != nil { return err } fmt.Fprintln(g.out, " }") @@ -293,7 +309,7 @@ func (g *Generator) genSliceArrayMapEncoder(t reflect.Type) error { typ := g.getType(t) fmt.Fprintln(g.out, "func "+fname+"(out *jwriter.Writer, in "+typ+") {") - err := g.genTypeEncoderNoCheck(t, "in", fieldTags{}, 1) + err := g.genTypeEncoderNoCheck(t, "in", fieldTags{}, 1, false) if err != nil { return err } From 464126aa46e5a527d1e4b6baffdfeb5ea4e6f3b6 Mon Sep 17 00:00:00 2001 From: Carlo Alberto Ferraris Date: Sat, 21 Oct 2017 14:07:15 +0530 Subject: [PATCH 02/22] dedup logic in genStructFieldEncoder --- gen/encoder.go | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/gen/encoder.go b/gen/encoder.go index 1bdb7d4..19239bc 100644 --- a/gen/encoder.go +++ b/gen/encoder.go @@ -265,24 +265,21 @@ func (g *Generator) genStructFieldEncoder(t reflect.Type, f reflect.StructField) if tags.omit { return nil } - if !tags.omitEmpty && !g.omitEmpty || tags.noOmitEmpty { - fmt.Fprintln(g.out, " if first {") - fmt.Fprintln(g.out, " first = false") - fmt.Fprintf(g.out, " out.RawString(%q)\n", strconv.Quote(jsonName)+":") - fmt.Fprintln(g.out, " } else {") - fmt.Fprintf(g.out, " out.RawString(%q)\n", ","+strconv.Quote(jsonName)+":") - fmt.Fprintln(g.out, " }") - return g.genTypeEncoder(f.Type, "in."+f.Name, tags, 1, false) + noOmitEmpty := (!tags.omitEmpty && !g.omitEmpty) || tags.noOmitEmpty + if noOmitEmpty { + fmt.Fprintln(g.out, " {") + } else { + fmt.Fprintln(g.out, " if", g.notEmptyCheck(f.Type, "in."+f.Name), "{") } - - fmt.Fprintln(g.out, " if", g.notEmptyCheck(f.Type, "in."+f.Name), "{") + fmt.Fprintf(g.out, " const prefix string = %q\n", ","+strconv.Quote(jsonName)+":") fmt.Fprintln(g.out, " if first {") fmt.Fprintln(g.out, " first = false") - fmt.Fprintf(g.out, " out.RawString(%q)\n", strconv.Quote(jsonName)+":") + fmt.Fprintln(g.out, " out.RawString(prefix[1:])") fmt.Fprintln(g.out, " } else {") - fmt.Fprintf(g.out, " out.RawString(%q)\n", ","+strconv.Quote(jsonName)+":") + fmt.Fprintln(g.out, " out.RawString(prefix)") fmt.Fprintln(g.out, " }") - if err := g.genTypeEncoder(f.Type, "in."+f.Name, tags, 2, true); err != nil { + + if err := g.genTypeEncoder(f.Type, "in."+f.Name, tags, 2, !noOmitEmpty); err != nil { return err } fmt.Fprintln(g.out, " }") From 4f31de893471819dbe6e6c1e3b621acef21bf0aa Mon Sep 17 00:00:00 2001 From: Levi Gross Date: Sun, 26 Nov 2017 18:36:47 -0500 Subject: [PATCH 03/22] Add float string functions when requested Signed-off-by: Levi Gross --- gen/decoder.go | 6 ++++-- gen/encoder.go | 2 ++ jlexer/lexer.go | 32 ++++++++++++++++++++++++++++++++ tests/data.go | 4 ++-- 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/gen/decoder.go b/gen/decoder.go index 021933a..184b229 100644 --- a/gen/decoder.go +++ b/gen/decoder.go @@ -48,10 +48,12 @@ var primitiveStringDecoders = map[reflect.Kind]string{ reflect.Uint32: "in.Uint32Str()", reflect.Uint64: "in.Uint64Str()", reflect.Uintptr: "in.UintptrStr()", + reflect.Float32: "in.Float32()Str", + reflect.Float64: "in.Float64()Str", } var customDecoders = map[string]string{ - "json.Number": "in.JsonNumber()", + "json.Number": "in.JsonNumber()", } // genTypeDecoder generates decoding code for the type t, but uses unmarshaler interface if implemented by t. @@ -88,7 +90,7 @@ func (g *Generator) genTypeDecoder(t reflect.Type, out string, tags fieldTags, i func (g *Generator) genTypeDecoderNoCheck(t reflect.Type, out string, tags fieldTags, indent int) error { ws := strings.Repeat(" ", indent) // Check whether type is primitive, needs to be done after interface check. - if dec := customDecoders[t.String()]; dec != "" { + if dec := customDecoders[t.String()]; dec != "" { fmt.Fprintln(g.out, ws+out+" = "+dec) return nil } else if dec := primitiveStringDecoders[t.Kind()]; dec != "" && tags.asString { diff --git a/gen/encoder.go b/gen/encoder.go index 48cba15..3a26d72 100644 --- a/gen/encoder.go +++ b/gen/encoder.go @@ -45,6 +45,8 @@ var primitiveStringEncoders = map[reflect.Kind]string{ reflect.Uint32: "out.Uint32Str(uint32(%v))", reflect.Uint64: "out.Uint64Str(uint64(%v))", reflect.Uintptr: "out.UintptrStr(uintptr(%v))", + reflect.Float32: "out.Float32Str(float32(%v))", + reflect.Float64: "out.Float64Str(float64(%v))", } // fieldTags contains parsed version of json struct field tags. diff --git a/jlexer/lexer.go b/jlexer/lexer.go index e5558ae..b37c5be 100644 --- a/jlexer/lexer.go +++ b/jlexer/lexer.go @@ -997,6 +997,22 @@ func (r *Lexer) Float32() float32 { return float32(n) } +func (r *Lexer) Float32Str() float32 { + s, b := r.unsafeString() + if !r.Ok() { + return 0 + } + n, err := strconv.ParseFloat(s, 32) + if err != nil { + r.addNonfatalError(&LexerError{ + Offset: r.start, + Reason: err.Error(), + Data: string(b), + }) + } + return float32(n) +} + func (r *Lexer) Float64() float64 { s := r.number() if !r.Ok() { @@ -1014,6 +1030,22 @@ func (r *Lexer) Float64() float64 { return n } +func (r *Lexer) Float64Str() float64 { + s, b := r.unsafeString() + if !r.Ok() { + return 0 + } + n, err := strconv.ParseFloat(s, 64) + if err != nil { + r.addNonfatalError(&LexerError{ + Offset: r.start, + Reason: err.Error(), + Data: string(b), + }) + } + return float64(n) +} + func (r *Lexer) Error() error { return r.fatalError } diff --git a/tests/data.go b/tests/data.go index 145f093..a6e294c 100644 --- a/tests/data.go +++ b/tests/data.go @@ -38,8 +38,8 @@ type PrimitiveTypes struct { Uint32String uint32 `json:",string"` Uint64String uint64 `json:",string"` - Float32 float32 - Float64 float64 + Float32 float32 `json:", string"` + Float64 float64 `json:", string"` Ptr *string PtrNil *string From 56cec8d3487cb84f64150f513b76bc430fb9ec91 Mon Sep 17 00:00:00 2001 From: Levi Gross Date: Sun, 26 Nov 2017 18:47:04 -0500 Subject: [PATCH 04/22] Ensure decoder is set as well Signed-off-by: Levi Gross --- jwriter/writer.go | 17 +++++++++++++++-- tests/data.go | 4 ++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/jwriter/writer.go b/jwriter/writer.go index e5a5ddf..b9ed7cc 100644 --- a/jwriter/writer.go +++ b/jwriter/writer.go @@ -240,11 +240,25 @@ func (w *Writer) Float32(n float32) { w.Buffer.Buf = strconv.AppendFloat(w.Buffer.Buf, float64(n), 'g', -1, 32) } +func (w *Writer) Float32Str(n float32) { + w.Buffer.EnsureSpace(20) + w.Buffer.Buf = append(w.Buffer.Buf, '"') + w.Buffer.Buf = strconv.AppendFloat(w.Buffer.Buf, float64(n), 'g', -1, 32) + w.Buffer.Buf = append(w.Buffer.Buf, '"') +} + func (w *Writer) Float64(n float64) { w.Buffer.EnsureSpace(20) w.Buffer.Buf = strconv.AppendFloat(w.Buffer.Buf, n, 'g', -1, 64) } +func (w *Writer) Float64Str(n float64) { + w.Buffer.EnsureSpace(20) + w.Buffer.Buf = append(w.Buffer.Buf, '"') + w.Buffer.Buf = strconv.AppendFloat(w.Buffer.Buf, float64(n), 'g', -1, 64) + w.Buffer.Buf = append(w.Buffer.Buf, '"') +} + func (w *Writer) Bool(v bool) { w.Buffer.EnsureSpace(5) if v { @@ -340,12 +354,11 @@ func (w *Writer) base64(in []byte) { return } - w.Buffer.EnsureSpace(((len(in) - 1) / 3 + 1) * 4) + w.Buffer.EnsureSpace(((len(in)-1)/3 + 1) * 4) si := 0 n := (len(in) / 3) * 3 - for si < n { // Convert 3x 8bit source bytes into 4 bytes val := uint(in[si+0])<<16 | uint(in[si+1])<<8 | uint(in[si+2]) diff --git a/tests/data.go b/tests/data.go index a6e294c..5f18a98 100644 --- a/tests/data.go +++ b/tests/data.go @@ -38,8 +38,8 @@ type PrimitiveTypes struct { Uint32String uint32 `json:",string"` Uint64String uint64 `json:",string"` - Float32 float32 `json:", string"` - Float64 float64 `json:", string"` + Float32 float32 `json:",string"` + Float64 float64 `json:",string"` Ptr *string PtrNil *string From b5dedd1b9efc7a41be93ea048beaad2ea201712e Mon Sep 17 00:00:00 2001 From: Levi Gross Date: Sun, 26 Nov 2017 18:55:54 -0500 Subject: [PATCH 05/22] We now include and pass tests Signed-off-by: Levi Gross --- gen/decoder.go | 4 ++-- tests/data.go | 13 +++++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/gen/decoder.go b/gen/decoder.go index 184b229..f7e415f 100644 --- a/gen/decoder.go +++ b/gen/decoder.go @@ -48,8 +48,8 @@ var primitiveStringDecoders = map[reflect.Kind]string{ reflect.Uint32: "in.Uint32Str()", reflect.Uint64: "in.Uint64Str()", reflect.Uintptr: "in.UintptrStr()", - reflect.Float32: "in.Float32()Str", - reflect.Float64: "in.Float64()Str", + reflect.Float32: "in.Float32Str()", + reflect.Float64: "in.Float64Str()", } var customDecoders = map[string]string{ diff --git a/tests/data.go b/tests/data.go index 5f18a98..f6d6653 100644 --- a/tests/data.go +++ b/tests/data.go @@ -38,8 +38,11 @@ type PrimitiveTypes struct { Uint32String uint32 `json:",string"` Uint64String uint64 `json:",string"` - Float32 float32 `json:",string"` - Float64 float64 `json:",string"` + Float32 float32 + Float64 float64 + + Float32String float32 `json:",string"` + Float64String float64 `json:",string"` Ptr *string PtrNil *string @@ -77,6 +80,9 @@ var primitiveTypesValue = PrimitiveTypes{ Float32: 1.5, Float64: math.MaxFloat64, + Float32String: 1.5, + Float64String: math.MaxFloat64, + Ptr: &str, } @@ -110,6 +116,9 @@ var primitiveTypesString = "{" + `"Float32":` + fmt.Sprint(1.5) + `,` + `"Float64":` + fmt.Sprint(math.MaxFloat64) + `,` + + `"Float32String":"` + fmt.Sprint(1.5) + `",` + + `"Float64String":"` + fmt.Sprint(math.MaxFloat64) + `",` + + `"Ptr":"bla",` + `"PtrNil":null` + From 5fb2687db09307a2b6f21ecda85a608c5ea2292a Mon Sep 17 00:00:00 2001 From: Levi Gross Date: Mon, 27 Nov 2017 20:29:13 -0500 Subject: [PATCH 06/22] No need to mark as float Signed-off-by: Levi Gross --- jlexer/lexer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jlexer/lexer.go b/jlexer/lexer.go index b37c5be..18d65cd 100644 --- a/jlexer/lexer.go +++ b/jlexer/lexer.go @@ -1043,7 +1043,7 @@ func (r *Lexer) Float64Str() float64 { Data: string(b), }) } - return float64(n) + return n } func (r *Lexer) Error() error { From 95baeb8ee770a428103416af6e4bb347df181307 Mon Sep 17 00:00:00 2001 From: Anthony Regeda Date: Tue, 19 Dec 2017 13:06:15 +0300 Subject: [PATCH 07/22] invalid_indirect_of_pointer_on_array fix invalid indirect of a pointer on a array --- gen/decoder.go | 6 +++--- gen/encoder.go | 2 +- tests/basic_test.go | 1 + tests/data.go | 18 ++++++++++++++++++ 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/gen/decoder.go b/gen/decoder.go index 021933a..b394636 100644 --- a/gen/decoder.go +++ b/gen/decoder.go @@ -51,7 +51,7 @@ var primitiveStringDecoders = map[reflect.Kind]string{ } var customDecoders = map[string]string{ - "json.Number": "in.JsonNumber()", + "json.Number": "in.JsonNumber()", } // genTypeDecoder generates decoding code for the type t, but uses unmarshaler interface if implemented by t. @@ -88,7 +88,7 @@ func (g *Generator) genTypeDecoder(t reflect.Type, out string, tags fieldTags, i func (g *Generator) genTypeDecoderNoCheck(t reflect.Type, out string, tags fieldTags, indent int) error { ws := strings.Repeat(" ", indent) // Check whether type is primitive, needs to be done after interface check. - if dec := customDecoders[t.String()]; dec != "" { + if dec := customDecoders[t.String()]; dec != "" { fmt.Fprintln(g.out, ws+out+" = "+dec) return nil } else if dec := primitiveStringDecoders[t.Kind()]; dec != "" && tags.asString { @@ -170,7 +170,7 @@ func (g *Generator) genTypeDecoderNoCheck(t reflect.Type, out string, tags field fmt.Fprintln(g.out, ws+" for !in.IsDelim(']') {") fmt.Fprintln(g.out, ws+" if "+iterVar+" < "+fmt.Sprint(length)+" {") - if err := g.genTypeDecoder(elem, out+"["+iterVar+"]", tags, indent+3); err != nil { + if err := g.genTypeDecoder(elem, "("+out+")["+iterVar+"]", tags, indent+3); err != nil { return err } diff --git a/gen/encoder.go b/gen/encoder.go index 48cba15..77cdab5 100644 --- a/gen/encoder.go +++ b/gen/encoder.go @@ -165,7 +165,7 @@ func (g *Generator) genTypeEncoderNoCheck(t reflect.Type, in string, tags fieldT fmt.Fprintln(g.out, ws+" out.RawByte(',')") fmt.Fprintln(g.out, ws+" }") - if err := g.genTypeEncoder(elem, in+"["+iVar+"]", tags, indent+1, false); err != nil { + if err := g.genTypeEncoder(elem, "("+in+")["+iVar+"]", tags, indent+1, false); err != nil { return err } diff --git a/tests/basic_test.go b/tests/basic_test.go index 0186784..9731e36 100644 --- a/tests/basic_test.go +++ b/tests/basic_test.go @@ -47,6 +47,7 @@ var testCases = []struct { {&mapUint64StringValue, mapUint64StringValueString}, {&mapUintptrStringValue, mapUintptrStringValueString}, {&intKeyedMapStructValue, intKeyedMapStructValueString}, + {&intArrayStructValue, intArrayStructValueString}, } func TestMarshal(t *testing.T) { diff --git a/tests/data.go b/tests/data.go index 145f093..09607e3 100644 --- a/tests/data.go +++ b/tests/data.go @@ -757,3 +757,21 @@ var intKeyedMapStructValueString = `{` + `"foo":{"42":"life"},` + `"bar":{"32":{"354634382":"life"}}` + `}` + +type IntArray [2]int + +//easyjson:json +type IntArrayStruct struct { + Pointer *IntArray `json:"pointer"` + Value IntArray `json:"value"` +} + +var intArrayStructValue = IntArrayStruct{ + Pointer: &IntArray{1, 2}, + Value: IntArray{1, 2}, +} + +var intArrayStructValueString = `{` + + `"pointer":[1,2],` + + `"value":[1,2]` + + `}` From 20f1e341b0ac99106820337605906fcd712f61c1 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Wed, 24 Jan 2018 13:41:38 -0800 Subject: [PATCH 08/22] Make json.Number handling similar to encoding/json This change modified the `json.Number` unmarshaling to handle `null` values without error, similar to `encoding/json`. These values are become `json.Number("")`. This also modifies the json.Number value returned on error to be `json.Number("")` as that is the zero value. See https://play.golang.org/p/knZLugaqnni --- jlexer/lexer.go | 6 ++++-- jlexer/lexer_test.go | 21 ++++++++++++--------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/jlexer/lexer.go b/jlexer/lexer.go index e5558ae..8af3d2f 100644 --- a/jlexer/lexer.go +++ b/jlexer/lexer.go @@ -1056,7 +1056,7 @@ func (r *Lexer) JsonNumber() json.Number { } if !r.Ok() { r.errInvalidToken("json.Number") - return json.Number("0") + return json.Number("") } switch r.token.kind { @@ -1064,9 +1064,11 @@ func (r *Lexer) JsonNumber() json.Number { return json.Number(r.String()) case tokenNumber: return json.Number(r.Raw()) + case tokenNull: + return json.Number("") default: r.errSyntax() - return json.Number("0") + return json.Number("") } } diff --git a/jlexer/lexer_test.go b/jlexer/lexer_test.go index 4ce4abe..529a270 100644 --- a/jlexer/lexer_test.go +++ b/jlexer/lexer_test.go @@ -25,9 +25,9 @@ func TestString(t *testing.T) { {toParse: `"test"junk`, want: "test"}, - {toParse: `5`, wantError: true}, // not a string - {toParse: `"\x"`, wantError: true}, // invalid escape - {toParse: `"\ud800"`, want: "�"}, // invalid utf-8 char; return replacement char + {toParse: `5`, wantError: true}, // not a string + {toParse: `"\x"`, wantError: true}, // invalid escape + {toParse: `"\ud800"`, want: "�"}, // invalid utf-8 char; return replacement char } { l := Lexer{Data: []byte(test.toParse)} @@ -269,16 +269,19 @@ func TestJsonNumber(t *testing.T) { {toParse: `"0.12"`, want: json.Number("0.12"), wantValue: 0.12}, {toParse: `"25E-4"`, want: json.Number("25E-4"), wantValue: 25E-4}, - {toParse: `"a""`, wantValueError: true}, + {toParse: `"foo"`, want: json.Number("foo"), wantValueError: true}, + {toParse: `null`, want: json.Number(""), wantValueError: true}, - {toParse: `[1]`, wantLexerError: true}, - {toParse: `{}`, wantLexerError: true}, - {toParse: `a`, wantLexerError: true}, + {toParse: `"a""`, want: json.Number("a"), wantValueError: true}, + + {toParse: `[1]`, want: json.Number(""), wantLexerError: true, wantValueError: true}, + {toParse: `{}`, want: json.Number(""), wantLexerError: true, wantValueError: true}, + {toParse: `a`, want: json.Number(""), wantLexerError: true, wantValueError: true}, } { l := Lexer{Data: []byte(test.toParse)} got := l.JsonNumber() - if got != test.want && !test.wantLexerError && !test.wantValueError { + if got != test.want { t.Errorf("[%d, %q] JsonNumber() = %v; want %v", i, test.toParse, got, test.want) } @@ -303,7 +306,7 @@ func TestJsonNumber(t *testing.T) { } if valueErr != nil && !test.wantValueError { - t.Errorf("[%d, %q] JsonNumber() value error: %v", i, test.toParse, err) + t.Errorf("[%d, %q] JsonNumber() value error: %v", i, test.toParse, valueErr) } else if valueErr == nil && test.wantValueError { t.Errorf("[%d, %q] JsonNumber() ok; want value error", i, test.toParse) } From 6334c0a320471ea282ca8eab07407214a35b53b0 Mon Sep 17 00:00:00 2001 From: "Nicolas S. Dade" Date: Tue, 6 Mar 2018 13:23:15 -0800 Subject: [PATCH 09/22] add unit test of embedded types which generates broken code which cannot be compile --- Makefile | 4 +++- tests/basic_test.go | 1 + tests/embedded_type.go | 24 ++++++++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 tests/embedded_type.go diff --git a/Makefile b/Makefile index f877ab2..7823499 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,8 @@ generate: root build .root/src/$(PKG)/tests/data.go \ .root/src/$(PKG)/tests/omitempty.go \ .root/src/$(PKG)/tests/nothing.go \ - .root/src/$(PKG)/tests/named_type.go + .root/src/$(PKG)/tests/named_type.go \ + .root/src/$(PKG)/tests/embedded_type.go .root/bin/easyjson -all .root/src/$(PKG)/tests/data.go .root/bin/easyjson -all .root/src/$(PKG)/tests/nothing.go @@ -33,6 +34,7 @@ generate: root build .root/bin/easyjson -build_tags=use_easyjson .root/src/$(PKG)/benchmark/data.go .root/bin/easyjson .root/src/$(PKG)/tests/nested_easy.go .root/bin/easyjson .root/src/$(PKG)/tests/named_type.go + .root/bin/easyjson .root/src/$(PKG)/tests/embedded_type.go test: generate root go test \ diff --git a/tests/basic_test.go b/tests/basic_test.go index 0186784..511e43a 100644 --- a/tests/basic_test.go +++ b/tests/basic_test.go @@ -38,6 +38,7 @@ var testCases = []struct { {&IntsValue, IntsString}, {&mapStringStringValue, mapStringStringString}, {&namedTypeValue, namedTypeValueString}, + {&embeddedTypeValue, embeddedTypeValueString}, {&mapMyIntStringValue, mapMyIntStringValueString}, {&mapIntStringValue, mapIntStringValueString}, {&mapInt32StringValue, mapInt32StringValueString}, diff --git a/tests/embedded_type.go b/tests/embedded_type.go new file mode 100644 index 0000000..66470b6 --- /dev/null +++ b/tests/embedded_type.go @@ -0,0 +1,24 @@ +package tests + +//easyjson:json +type EmbeddedType struct { + EmbeddedInnerType + Inner struct { + EmbeddedInnerType + } + Field2 int +} + +type EmbeddedInnerType struct { + Field1 int +} + +var embeddedTypeValue EmbeddedType + +func init() { + embeddedTypeValue.Field1 = 1 + embeddedTypeValue.Field2 = 2 + embeddedTypeValue.Inner.Field1 = 3 +} + +var embeddedTypeValueString = `{"Inner":{"Field1":3},"Field2":2,"Field1":1}` From 19a1ce64c0ee5af0015320da155dddec32a30315 Mon Sep 17 00:00:00 2001 From: "Nicolas S. Dade" Date: Tue, 6 Mar 2018 13:23:41 -0800 Subject: [PATCH 10/22] fix handling of embedded types inside unnamed types --- gen/generator.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gen/generator.go b/gen/generator.go index eb0d70b..4f1eb04 100644 --- a/gen/generator.go +++ b/gen/generator.go @@ -284,7 +284,11 @@ func (g *Generator) getType(t reflect.Type) string { lines := make([]string, 0, nf) for i := 0; i < nf; i++ { f := t.Field(i) - line := f.Name + " " + g.getType(f.Type) + var line string + if !f.Anonymous { + line = f.Name + " " + } // else the field is anonymous (an embedded type) + line += g.getType(f.Type) t := f.Tag if t != "" { line += " " + escapeTag(t) From 699d6f0801ccb31acf3ccc6cc67c27ac4d2bdfa5 Mon Sep 17 00:00:00 2001 From: "Nicolas S. Dade" Date: Tue, 6 Mar 2018 13:43:16 -0800 Subject: [PATCH 11/22] add test of map with key with custom marshaler --- Makefile | 4 +++- tests/basic_test.go | 1 + tests/custom_map_key_type.go | 30 ++++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 tests/custom_map_key_type.go diff --git a/Makefile b/Makefile index f877ab2..9c76392 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,8 @@ generate: root build .root/src/$(PKG)/tests/data.go \ .root/src/$(PKG)/tests/omitempty.go \ .root/src/$(PKG)/tests/nothing.go \ - .root/src/$(PKG)/tests/named_type.go + .root/src/$(PKG)/tests/named_type.go \ + .root/src/$(PKG)/tests/custom_map_key_type.go .root/bin/easyjson -all .root/src/$(PKG)/tests/data.go .root/bin/easyjson -all .root/src/$(PKG)/tests/nothing.go @@ -33,6 +34,7 @@ generate: root build .root/bin/easyjson -build_tags=use_easyjson .root/src/$(PKG)/benchmark/data.go .root/bin/easyjson .root/src/$(PKG)/tests/nested_easy.go .root/bin/easyjson .root/src/$(PKG)/tests/named_type.go + .root/bin/easyjson .root/src/$(PKG)/tests/custom_map_key_type.go test: generate root go test \ diff --git a/tests/basic_test.go b/tests/basic_test.go index 0186784..9476752 100644 --- a/tests/basic_test.go +++ b/tests/basic_test.go @@ -38,6 +38,7 @@ var testCases = []struct { {&IntsValue, IntsString}, {&mapStringStringValue, mapStringStringString}, {&namedTypeValue, namedTypeValueString}, + {&customMapKeyTypeValue, customMapKeyTypeValueString}, {&mapMyIntStringValue, mapMyIntStringValueString}, {&mapIntStringValue, mapIntStringValueString}, {&mapInt32StringValue, mapInt32StringValueString}, diff --git a/tests/custom_map_key_type.go b/tests/custom_map_key_type.go new file mode 100644 index 0000000..e5cc32e --- /dev/null +++ b/tests/custom_map_key_type.go @@ -0,0 +1,30 @@ +package tests + +import fmt "fmt" + +//easyjson:json +type CustomMapKeyType struct { + Map map[customKeyType]int +} + +type customKeyType [2]byte + +func (k customKeyType) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%02x"`, k)), nil +} + +func (k *customKeyType) UnmarshalJSON(b []byte) error { + _, err := fmt.Sscanf(string(b), `"%02x%02x"`, &k[0], &k[1]) + return err +} + +var customMapKeyTypeValue CustomMapKeyType + +func init() { + customMapKeyTypeValue.Map = map[customKeyType]int{ + customKeyType{0x01, 0x01}: 1, + customKeyType{0x02, 0x02}: 2, + } +} + +var customMapKeyTypeValueString = `{"Map":{"0101":1,"0202":2}}` From a06183da62cb3e02e05eb265809164c91ca7b16b Mon Sep 17 00:00:00 2001 From: "Nicolas S. Dade" Date: Tue, 6 Mar 2018 13:29:46 -0800 Subject: [PATCH 12/22] support maps with key types which have custom marshler/unmarshalers by assuming the caller knows what they are doing and that the custom marshler will generate JSON appropriate for a key. The standard library's encoding/json supports these. --- gen/decoder.go | 24 ++++++++++++++++++++---- gen/encoder.go | 23 +++++++++++++++++++---- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/gen/decoder.go b/gen/decoder.go index 021933a..f740c68 100644 --- a/gen/decoder.go +++ b/gen/decoder.go @@ -84,6 +84,14 @@ func (g *Generator) genTypeDecoder(t reflect.Type, out string, tags fieldTags, i return err } +// returns true of the type t implements one of the custom unmarshaler interfaces +func hasCustomUnmarshaler(t reflect.Type) bool { + t = reflect.PtrTo(t) + return t.Implements(reflect.TypeOf((*easyjson.Unmarshaler)(nil)).Elem()) || + t.Implements(reflect.TypeOf((*json.Unmarshaler)(nil)).Elem()) || + t.Implements(reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()) +} + // genTypeDecoderNoCheck generates decoding code for the type t. func (g *Generator) genTypeDecoderNoCheck(t reflect.Type, out string, tags fieldTags, indent int) error { ws := strings.Repeat(" ", indent) @@ -208,9 +216,9 @@ func (g *Generator) genTypeDecoderNoCheck(t reflect.Type, out string, tags field case reflect.Map: key := t.Key() keyDec, ok := primitiveStringDecoders[key.Kind()] - if !ok { - return fmt.Errorf("map type %v not supported: only string and integer keys are allowed", key) - } + if !ok && !hasCustomUnmarshaler(key) { + return fmt.Errorf("map type %v not supported: only string and integer keys and types implementing json.Unmarshaler are allowed", key) + } // else assume the caller knows what they are doing and that the custom unmarshaler performs the translation from string or integer keys to the key type elem := t.Elem() tmpVar := g.uniqueVarName() @@ -225,7 +233,15 @@ func (g *Generator) genTypeDecoderNoCheck(t reflect.Type, out string, tags field fmt.Fprintln(g.out, ws+" }") fmt.Fprintln(g.out, ws+" for !in.IsDelim('}') {") - fmt.Fprintln(g.out, ws+" key := "+g.getType(key)+"("+keyDec+")") + if keyDec != "" { + fmt.Fprintln(g.out, ws+" key := "+g.getType(key)+"("+keyDec+")") + } else { + fmt.Fprintln(g.out, ws+" var key "+g.getType(key)) + if err := g.genTypeDecoder(key, "key", tags, indent+2); err != nil { + return err + } + } + fmt.Fprintln(g.out, ws+" in.WantColon()") fmt.Fprintln(g.out, ws+" var "+tmpVar+" "+g.getType(elem)) diff --git a/gen/encoder.go b/gen/encoder.go index 48cba15..4eca160 100644 --- a/gen/encoder.go +++ b/gen/encoder.go @@ -108,6 +108,14 @@ func (g *Generator) genTypeEncoder(t reflect.Type, in string, tags fieldTags, in return err } +// returns true of the type t implements one of the custom marshaler interfaces +func hasCustomMarshaler(t reflect.Type) bool { + t = reflect.PtrTo(t) + return t.Implements(reflect.TypeOf((*easyjson.Marshaler)(nil)).Elem()) || + t.Implements(reflect.TypeOf((*json.Marshaler)(nil)).Elem()) || + t.Implements(reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()) +} + // genTypeEncoderNoCheck generates code that encodes in of type t into the writer. func (g *Generator) genTypeEncoderNoCheck(t reflect.Type, in string, tags fieldTags, indent int, assumeNonEmpty bool) error { ws := strings.Repeat(" ", indent) @@ -197,9 +205,9 @@ func (g *Generator) genTypeEncoderNoCheck(t reflect.Type, in string, tags fieldT case reflect.Map: key := t.Key() keyEnc, ok := primitiveStringEncoders[key.Kind()] - if !ok { - return fmt.Errorf("map key type %v not supported: only string and integer keys are allowed", key) - } + if !ok && !hasCustomMarshaler(key) { + return fmt.Errorf("map key type %v not supported: only string and integer keys and types implementing Marshaler interfaces are allowed", key) + } // else assume the caller knows what they are doing and that the custom marshaler performs the translation from the key type to a string or integer tmpVar := g.uniqueVarName() if !assumeNonEmpty { @@ -213,7 +221,14 @@ func (g *Generator) genTypeEncoderNoCheck(t reflect.Type, in string, tags fieldT fmt.Fprintln(g.out, ws+" "+tmpVar+"First := true") fmt.Fprintln(g.out, ws+" for "+tmpVar+"Name, "+tmpVar+"Value := range "+in+" {") fmt.Fprintln(g.out, ws+" if "+tmpVar+"First { "+tmpVar+"First = false } else { out.RawByte(',') }") - fmt.Fprintln(g.out, ws+" "+fmt.Sprintf(keyEnc, tmpVar+"Name")) + if keyEnc != "" { + fmt.Fprintln(g.out, ws+" "+fmt.Sprintf(keyEnc, tmpVar+"Name")) + } else { + if err := g.genTypeEncoder(key, tmpVar+"Name", tags, indent+2, false); err != nil { + return err + } + } + fmt.Fprintln(g.out, ws+" out.RawByte(':')") if err := g.genTypeEncoder(t.Elem(), tmpVar+"Value", tags, indent+2, false); err != nil { From 042f8eb204ff322798e9f0ab66115a358c5b96ac Mon Sep 17 00:00:00 2001 From: "Nicolas S. Dade" Date: Wed, 7 Mar 2018 03:26:27 -0800 Subject: [PATCH 13/22] reduce the unit test map to 1 element so the encoding is consistent otherwise, since the map iterates in randomish order, some of the time the element order does not match the unit test expected result, causing the unit test to fail unnecessarily. --- tests/custom_map_key_type.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/custom_map_key_type.go b/tests/custom_map_key_type.go index e5cc32e..099bd06 100644 --- a/tests/custom_map_key_type.go +++ b/tests/custom_map_key_type.go @@ -22,9 +22,8 @@ var customMapKeyTypeValue CustomMapKeyType func init() { customMapKeyTypeValue.Map = map[customKeyType]int{ - customKeyType{0x01, 0x01}: 1, - customKeyType{0x02, 0x02}: 2, + customKeyType{0x01, 0x02}: 3, } } -var customMapKeyTypeValueString = `{"Map":{"0101":1,"0202":2}}` +var customMapKeyTypeValueString = `{"Map":{"0102":3}}` From 90d1db1043bb4b10e388a2f330ecc337edb103f0 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Fri, 23 Mar 2018 08:08:36 -0700 Subject: [PATCH 14/22] Correctly consume the null value --- jlexer/lexer.go | 1 + 1 file changed, 1 insertion(+) diff --git a/jlexer/lexer.go b/jlexer/lexer.go index d6110a7..0fd9b12 100644 --- a/jlexer/lexer.go +++ b/jlexer/lexer.go @@ -1097,6 +1097,7 @@ func (r *Lexer) JsonNumber() json.Number { case tokenNumber: return json.Number(r.Raw()) case tokenNull: + r.Null() return json.Number("") default: r.errSyntax() From 09f3bc3a8fe471337029b6b3897458c1f22a6eae Mon Sep 17 00:00:00 2001 From: IncSW Date: Fri, 27 Apr 2018 01:46:49 +0300 Subject: [PATCH 15/22] fix marshaling for uint8 custom types --- Makefile | 4 ++-- gen/decoder.go | 4 ++-- gen/encoder.go | 4 ++-- tests/basic_test.go | 2 ++ tests/data.go | 16 ++++++++++++++++ 5 files changed, 24 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 49c80f3..99367e0 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ all: test .root/src/$(PKG): mkdir -p $@ - for i in $$PWD/* ; do ln -s $$i $@/`basename $$i` ; done + for i in $$PWD/* ; do ln -s $$i $@/`basename $$i` ; done root: .root/src/$(PKG) @@ -27,7 +27,7 @@ generate: root build .root/src/$(PKG)/tests/custom_map_key_type.go \ .root/src/$(PKG)/tests/embedded_type.go - .root/bin/easyjson -all .root/src/$(PKG)/tests/data.go + .root/bin/easyjson -all .root/src/$(PKG)/tests/data.go .root/bin/easyjson -all .root/src/$(PKG)/tests/nothing.go .root/bin/easyjson -all .root/src/$(PKG)/tests/errors.go .root/bin/easyjson -snake_case .root/src/$(PKG)/tests/snake.go diff --git a/gen/decoder.go b/gen/decoder.go index 3c8f8f8..4203625 100644 --- a/gen/decoder.go +++ b/gen/decoder.go @@ -114,7 +114,7 @@ func (g *Generator) genTypeDecoderNoCheck(t reflect.Type, out string, tags field tmpVar := g.uniqueVarName() elem := t.Elem() - if elem.Kind() == reflect.Uint8 { + if elem.Kind() == reflect.Uint8 && elem.Name() == "uint8" { fmt.Fprintln(g.out, ws+"if in.IsNull() {") fmt.Fprintln(g.out, ws+" in.Skip()") fmt.Fprintln(g.out, ws+" "+out+" = nil") @@ -161,7 +161,7 @@ func (g *Generator) genTypeDecoderNoCheck(t reflect.Type, out string, tags field iterVar := g.uniqueVarName() elem := t.Elem() - if elem.Kind() == reflect.Uint8 { + if elem.Kind() == reflect.Uint8 && elem.Name() == "uint8" { fmt.Fprintln(g.out, ws+"if in.IsNull() {") fmt.Fprintln(g.out, ws+" in.Skip()") fmt.Fprintln(g.out, ws+"} else {") diff --git a/gen/encoder.go b/gen/encoder.go index 293a66a..b2be743 100644 --- a/gen/encoder.go +++ b/gen/encoder.go @@ -137,7 +137,7 @@ func (g *Generator) genTypeEncoderNoCheck(t reflect.Type, in string, tags fieldT iVar := g.uniqueVarName() vVar := g.uniqueVarName() - if t.Elem().Kind() == reflect.Uint8 { + if t.Elem().Kind() == reflect.Uint8 && elem.Name() == "uint8" { fmt.Fprintln(g.out, ws+"out.Base64Bytes("+in+")") } else { if !assumeNonEmpty { @@ -166,7 +166,7 @@ func (g *Generator) genTypeEncoderNoCheck(t reflect.Type, in string, tags fieldT elem := t.Elem() iVar := g.uniqueVarName() - if t.Elem().Kind() == reflect.Uint8 { + if t.Elem().Kind() == reflect.Uint8 && elem.Name() == "uint8" { fmt.Fprintln(g.out, ws+"out.Base64Bytes("+in+"[:])") } else { fmt.Fprintln(g.out, ws+"out.RawByte('[')") diff --git a/tests/basic_test.go b/tests/basic_test.go index 28f0fdf..ecc8cc9 100644 --- a/tests/basic_test.go +++ b/tests/basic_test.go @@ -50,6 +50,8 @@ var testCases = []struct { {&mapUintptrStringValue, mapUintptrStringValueString}, {&intKeyedMapStructValue, intKeyedMapStructValueString}, {&intArrayStructValue, intArrayStructValueString}, + {&myUInt8SliceValue, myUInt8SliceString}, + {&myUInt8ArrayValue, myUInt8ArrayString}, } func TestMarshal(t *testing.T) { diff --git a/tests/data.go b/tests/data.go index 8d5132d..6ae90a0 100644 --- a/tests/data.go +++ b/tests/data.go @@ -784,3 +784,19 @@ var intArrayStructValueString = `{` + `"pointer":[1,2],` + `"value":[1,2]` + `}` + +type MyUInt8 uint8 + +//easyjson:json +type MyUInt8Slice []MyUInt8 + +var myUInt8SliceValue = MyUInt8Slice{1, 2, 3, 4, 5} + +var myUInt8SliceString = `[1,2,3,4,5]` + +//easyjson:json +type MyUInt8Array [2]MyUInt8 + +var myUInt8ArrayValue = MyUInt8Array{1, 2} + +var myUInt8ArrayString = `[1,2]` From c63cf99c78d24fe8f5484aab05defd6daeb89f12 Mon Sep 17 00:00:00 2001 From: Aleksandr Petrukhin Date: Tue, 29 May 2018 16:19:21 +0000 Subject: [PATCH 16/22] [Generator] implement DisallowUnknownFields from go 1.10 --- bootstrap/bootstrap.go | 12 ++++++++---- easyjson/main.go | 26 ++++++++++++++------------ gen/decoder.go | 10 +++++++++- gen/generator.go | 12 +++++++++--- 4 files changed, 40 insertions(+), 20 deletions(-) diff --git a/bootstrap/bootstrap.go b/bootstrap/bootstrap.go index 3c20e09..95e5d1e 100644 --- a/bootstrap/bootstrap.go +++ b/bootstrap/bootstrap.go @@ -22,10 +22,11 @@ type Generator struct { PkgPath, PkgName string Types []string - NoStdMarshalers bool - SnakeCase bool - LowerCamelCase bool - OmitEmpty bool + NoStdMarshalers bool + SnakeCase bool + LowerCamelCase bool + OmitEmpty bool + DisallowUnknownFields bool OutName string BuildTags string @@ -120,6 +121,9 @@ func (g *Generator) writeMain() (path string, err error) { if g.NoStdMarshalers { fmt.Fprintln(f, " g.NoStdMarshalers()") } + if g.DisallowUnknownFields { + fmt.Fprintln(f, " g.DisallowUnknownFields()") + } sort.Strings(g.Types) for _, v := range g.Types { diff --git a/easyjson/main.go b/easyjson/main.go index 1cd30bb..df180ea 100644 --- a/easyjson/main.go +++ b/easyjson/main.go @@ -27,6 +27,7 @@ var stubs = flag.Bool("stubs", false, "only generate stubs for marshaler/unmarsh var noformat = flag.Bool("noformat", false, "do not run 'gofmt -w' on output file") var specifiedName = flag.String("output_filename", "", "specify the filename of the output") var processPkg = flag.Bool("pkg", false, "process the whole package instead of just the given file") +var disallowUnknownFields = flag.Bool("disallow_unknown_fields", false, "return error if any unknown field in json found") func generate(fname string) (err error) { fInfo, err := os.Stat(fname) @@ -60,18 +61,19 @@ func generate(fname string) (err error) { } g := bootstrap.Generator{ - BuildTags: trimmedBuildTags, - PkgPath: p.PkgPath, - PkgName: p.PkgName, - Types: p.StructNames, - SnakeCase: *snakeCase, - LowerCamelCase: *lowerCamelCase, - NoStdMarshalers: *noStdMarshalers, - OmitEmpty: *omitEmpty, - LeaveTemps: *leaveTemps, - OutName: outName, - StubsOnly: *stubs, - NoFormat: *noformat, + BuildTags: trimmedBuildTags, + PkgPath: p.PkgPath, + PkgName: p.PkgName, + Types: p.StructNames, + SnakeCase: *snakeCase, + LowerCamelCase: *lowerCamelCase, + NoStdMarshalers: *noStdMarshalers, + DisallowUnknownFields: *disallowUnknownFields, + OmitEmpty: *omitEmpty, + LeaveTemps: *leaveTemps, + OutName: outName, + StubsOnly: *stubs, + NoFormat: *noformat, } if err := g.Run(); err != nil { diff --git a/gen/decoder.go b/gen/decoder.go index 3c8f8f8..5dc1249 100644 --- a/gen/decoder.go +++ b/gen/decoder.go @@ -461,7 +461,15 @@ func (g *Generator) genStructDecoder(t reflect.Type) error { } fmt.Fprintln(g.out, " default:") - fmt.Fprintln(g.out, " in.SkipRecursive()") + if g.disallowUnknownFields { + fmt.Fprintln(g.out, ` in.AddError(&jlexer.LexerError{ + Offset: in.GetPos(), + Reason: "unknown field", + Data: key, + })`) + } else { + fmt.Fprintln(g.out, " in.SkipRecursive()") + } fmt.Fprintln(g.out, " }") fmt.Fprintln(g.out, " in.WantComma()") fmt.Fprintln(g.out, " }") diff --git a/gen/generator.go b/gen/generator.go index 4f1eb04..a34a852 100644 --- a/gen/generator.go +++ b/gen/generator.go @@ -33,9 +33,10 @@ type Generator struct { varCounter int - noStdMarshalers bool - omitEmpty bool - fieldNamer FieldNamer + noStdMarshalers bool + omitEmpty bool + disallowUnknownFields bool + fieldNamer FieldNamer // package path to local alias map for tracking imports imports map[string]string @@ -110,6 +111,11 @@ func (g *Generator) NoStdMarshalers() { g.noStdMarshalers = true } +// DisallowUnknownFields instructs not to skip unknown fields in json and return error. +func (g *Generator) DisallowUnknownFields() { + g.disallowUnknownFields = true +} + // OmitEmpty triggers `json=",omitempty"` behaviour by default. func (g *Generator) OmitEmpty() { g.omitEmpty = true From 31e0226908ff016d8ba91a190809827975248941 Mon Sep 17 00:00:00 2001 From: Aleksandr Petrukhin Date: Tue, 29 May 2018 16:32:28 +0000 Subject: [PATCH 17/22] [Tests] add tests for disallow_unknown_fields --- .gitignore | 1 + Makefile | 1 + README.md | 2 ++ tests/basic_test.go | 8 ++++++++ tests/disallow_unknown.go | 8 ++++++++ 5 files changed, 20 insertions(+) create mode 100644 tests/disallow_unknown.go diff --git a/.gitignore b/.gitignore index db8c66e..26156fb 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ *_easyjson.go *.iml .idea +*.swp diff --git a/Makefile b/Makefile index 49c80f3..7717c1e 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,7 @@ generate: root build .root/bin/easyjson .root/src/$(PKG)/tests/named_type.go .root/bin/easyjson .root/src/$(PKG)/tests/custom_map_key_type.go .root/bin/easyjson .root/src/$(PKG)/tests/embedded_type.go + .root/bin/easyjson -disallow_unknown_fields .root/src/$(PKG)/tests/disallow_unknown.go test: generate root go test \ diff --git a/README.md b/README.md index 9366e3f..b59d3ad 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,8 @@ Usage of easyjson: use lowerCamelCase instead of CamelCase by default -stubs only generate stubs for marshaler/unmarshaler funcs + -disallow_unknown_fields + return error if some unknown field in json occured ``` Using `-all` will generate marshalers/unmarshalers for all Go structs in the diff --git a/tests/basic_test.go b/tests/basic_test.go index 28f0fdf..ab166f3 100644 --- a/tests/basic_test.go +++ b/tests/basic_test.go @@ -232,3 +232,11 @@ func TestUnmarshalStructWithEmbeddedPtrStruct(t *testing.T) { t.Errorf("easyjson.Unmarshal() = %#v; want %#v", s, structWithInterfaceValueFilled) } } + +func TestDisallowUnknown(t *testing.T) { + var d DisallowUnknown + err := easyjson.Unmarshal([]byte(disallowUnknownString), &d) + if err == nil { + t.Error("want error, got nil") + } +} diff --git a/tests/disallow_unknown.go b/tests/disallow_unknown.go new file mode 100644 index 0000000..5b884c6 --- /dev/null +++ b/tests/disallow_unknown.go @@ -0,0 +1,8 @@ +package tests + +//easyjson:json +type DisallowUnknown struct { + FieldOne string `json:"field_one"` +} + +var disallowUnknownString = `{"field_one": "one", "field_two": "two"}` From 1df2e963608a01ba264fc317ba44cae08db2dd06 Mon Sep 17 00:00:00 2001 From: Aleksandr Petrukhin Date: Tue, 29 May 2018 16:34:20 +0000 Subject: [PATCH 18/22] [README] fix --- README.md | 2 +- easyjson/main.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b59d3ad..7fd7686 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Usage of easyjson: -stubs only generate stubs for marshaler/unmarshaler funcs -disallow_unknown_fields - return error if some unknown field in json occured + return error if some unknown field in json appeared ``` Using `-all` will generate marshalers/unmarshalers for all Go structs in the diff --git a/easyjson/main.go b/easyjson/main.go index df180ea..d4035f7 100644 --- a/easyjson/main.go +++ b/easyjson/main.go @@ -27,7 +27,7 @@ var stubs = flag.Bool("stubs", false, "only generate stubs for marshaler/unmarsh var noformat = flag.Bool("noformat", false, "do not run 'gofmt -w' on output file") var specifiedName = flag.String("output_filename", "", "specify the filename of the output") var processPkg = flag.Bool("pkg", false, "process the whole package instead of just the given file") -var disallowUnknownFields = flag.Bool("disallow_unknown_fields", false, "return error if any unknown field in json found") +var disallowUnknownFields = flag.Bool("disallow_unknown_fields", false, "return error if any unknown field in json appeared") func generate(fname string) (err error) { fInfo, err := os.Stat(fname) From fa2eed830eee763edafcab29f2f95bf44ff1ce94 Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 6 Jun 2018 15:56:57 +0200 Subject: [PATCH 19/22] refactor: remove CRLF --- parser/parser_windows.go | 98 ++++++++++++++++++++-------------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/parser/parser_windows.go b/parser/parser_windows.go index 90d3a78..7c26f14 100644 --- a/parser/parser_windows.go +++ b/parser/parser_windows.go @@ -1,49 +1,49 @@ -package parser - -import ( - "fmt" - "os" - "path" - "path/filepath" - "strings" -) - -func normalizePath(path string) string { - // use lower case, as Windows file systems will almost always be case insensitive - return strings.ToLower(strings.Replace(path, "\\", "/", -1)) -} - -func getPkgPath(fname string, isDir bool) (string, error) { - // path.IsAbs doesn't work properly on Windows; use filepath.IsAbs instead - if !filepath.IsAbs(fname) { - pwd, err := os.Getwd() - if err != nil { - return "", err - } - fname = path.Join(pwd, fname) - } - - fname = normalizePath(fname) - - gopath := os.Getenv("GOPATH") - if gopath == "" { - var err error - gopath, err = getDefaultGoPath() - if err != nil { - return "", fmt.Errorf("cannot determine GOPATH: %s", err) - } - } - - for _, p := range strings.Split(os.Getenv("GOPATH"), ";") { - prefix := path.Join(normalizePath(p), "src") + "/" - if rel := strings.TrimPrefix(fname, prefix); rel != fname { - if !isDir { - return path.Dir(rel), nil - } else { - return path.Clean(rel), nil - } - } - } - - return "", fmt.Errorf("file '%v' is not in GOPATH", fname) -} +package parser + +import ( + "fmt" + "os" + "path" + "path/filepath" + "strings" +) + +func normalizePath(path string) string { + // use lower case, as Windows file systems will almost always be case insensitive + return strings.ToLower(strings.Replace(path, "\\", "/", -1)) +} + +func getPkgPath(fname string, isDir bool) (string, error) { + // path.IsAbs doesn't work properly on Windows; use filepath.IsAbs instead + if !filepath.IsAbs(fname) { + pwd, err := os.Getwd() + if err != nil { + return "", err + } + fname = path.Join(pwd, fname) + } + + fname = normalizePath(fname) + + gopath := os.Getenv("GOPATH") + if gopath == "" { + var err error + gopath, err = getDefaultGoPath() + if err != nil { + return "", fmt.Errorf("cannot determine GOPATH: %s", err) + } + } + + for _, p := range strings.Split(os.Getenv("GOPATH"), ";") { + prefix := path.Join(normalizePath(p), "src") + "/" + if rel := strings.TrimPrefix(fname, prefix); rel != fname { + if !isDir { + return path.Dir(rel), nil + } else { + return path.Clean(rel), nil + } + } + } + + return "", fmt.Errorf("file '%v' is not in GOPATH", fname) +} From 93f3cb8741d2afe73c8e2ce6d27d9005391111cd Mon Sep 17 00:00:00 2001 From: Irioth Date: Mon, 16 Jul 2018 17:15:30 +0300 Subject: [PATCH 20/22] #162 use default GOPATH annoying bug --- parser/parser_unix.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parser/parser_unix.go b/parser/parser_unix.go index 09b20a2..cc0686e 100644 --- a/parser/parser_unix.go +++ b/parser/parser_unix.go @@ -27,7 +27,7 @@ func getPkgPath(fname string, isDir bool) (string, error) { } } - for _, p := range strings.Split(os.Getenv("GOPATH"), ":") { + for _, p := range strings.Split(gopath, ":") { prefix := path.Join(p, "src") + "/" if rel := strings.TrimPrefix(fname, prefix); rel != fname { if !isDir { From 48f134c4619cfbecc99a603d1fe6961ab0c1447f Mon Sep 17 00:00:00 2001 From: Irioth Date: Mon, 16 Jul 2018 19:07:20 +0300 Subject: [PATCH 21/22] #162 using default GOPATH for win and trim last newline character --- parser/parser.go | 2 +- parser/parser_windows.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/parser/parser.go b/parser/parser.go index 5bd06e9..babb84c 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -93,5 +93,5 @@ func (p *Parser) Parse(fname string, isDir bool) error { func getDefaultGoPath() (string, error) { output, err := exec.Command("go", "env", "GOPATH").Output() - return string(output), err + return strings.TrimSpace(string(output)), err } diff --git a/parser/parser_windows.go b/parser/parser_windows.go index 7c26f14..c6f36d5 100644 --- a/parser/parser_windows.go +++ b/parser/parser_windows.go @@ -9,7 +9,7 @@ import ( ) func normalizePath(path string) string { - // use lower case, as Windows file systems will almost always be case insensitive + // use lower case, as Windows file systems will almost always be case insensitive return strings.ToLower(strings.Replace(path, "\\", "/", -1)) } @@ -34,7 +34,7 @@ func getPkgPath(fname string, isDir bool) (string, error) { } } - for _, p := range strings.Split(os.Getenv("GOPATH"), ";") { + for _, p := range strings.Split(gopath, ";") { prefix := path.Join(normalizePath(p), "src") + "/" if rel := strings.TrimPrefix(fname, prefix); rel != fname { if !isDir { From c33a78ba6e89e5476d9f0b101ebe7eee0f922e69 Mon Sep 17 00:00:00 2001 From: Dmitry Dorogin Date: Tue, 17 Jul 2018 13:41:31 +0300 Subject: [PATCH 22/22] Update lexer.go --- jlexer/lexer.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jlexer/lexer.go b/jlexer/lexer.go index 0fd9b12..90525e6 100644 --- a/jlexer/lexer.go +++ b/jlexer/lexer.go @@ -649,7 +649,7 @@ func (r *Lexer) Bytes() []byte { return nil } ret := make([]byte, base64.StdEncoding.DecodedLen(len(r.token.byteValue))) - len, err := base64.StdEncoding.Decode(ret, r.token.byteValue) + n, err := base64.StdEncoding.Decode(ret, r.token.byteValue) if err != nil { r.fatalError = &LexerError{ Reason: err.Error(), @@ -658,7 +658,7 @@ func (r *Lexer) Bytes() []byte { } r.consume() - return ret[:len] + return ret[:n] } // Bool reads a true or false boolean keyword.