Skip to content

Commit

Permalink
Fix bug in handling on anon structs which embed other structs
Browse files Browse the repository at this point in the history
If the anon struct embeds another struct _and_ contains fields of its own,
there were cases where the generated code would ignore the anon struct's own
fields.

This happened because when checking if the anon struct implements a marshaling
interface, the reflect package returns true if the anon struct's _embedded
struct_ implements that interface. But using that interface means that the
other fields in the anon struct are ignored.
  • Loading branch information
autarch committed May 10, 2021
1 parent c6c84b5 commit b7d8b50
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 32 deletions.
44 changes: 26 additions & 18 deletions gen/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,26 +61,34 @@ var customDecoders = map[string]string{
func (g *Generator) genTypeDecoder(t reflect.Type, out string, tags fieldTags, indent int) error {
ws := strings.Repeat(" ", indent)

unmarshalerIface := reflect.TypeOf((*easyjson.Unmarshaler)(nil)).Elem()
if reflect.PtrTo(t).Implements(unmarshalerIface) {
fmt.Fprintln(g.out, ws+"("+out+").UnmarshalEasyJSON(in)")
return nil
}
// If the type is unnamed, then this is an anonymous struct. If that anon
// struct embeds another, named struct that implements one of these
// interfaces, then Implements(...) will return true, even if the anon
// struct has other fields. That causes us to skip code gen for those
// extra fields. This addresses
// https://github.com/mailru/easyjson/issues/337.
if t.Name() != "" {
unmarshalerIface := reflect.TypeOf((*easyjson.Unmarshaler)(nil)).Elem()
if reflect.PtrTo(t).Implements(unmarshalerIface) {
fmt.Fprintln(g.out, ws+"("+out+").UnmarshalEasyJSON(in)")
return nil
}

unmarshalerIface = reflect.TypeOf((*json.Unmarshaler)(nil)).Elem()
if reflect.PtrTo(t).Implements(unmarshalerIface) {
fmt.Fprintln(g.out, ws+"if data := in.Raw(); in.Ok() {")
fmt.Fprintln(g.out, ws+" in.AddError( ("+out+").UnmarshalJSON(data) )")
fmt.Fprintln(g.out, ws+"}")
return nil
}
unmarshalerIface = reflect.TypeOf((*json.Unmarshaler)(nil)).Elem()
if reflect.PtrTo(t).Implements(unmarshalerIface) {
fmt.Fprintln(g.out, ws+"if data := in.Raw(); in.Ok() {")
fmt.Fprintln(g.out, ws+" in.AddError( ("+out+").UnmarshalJSON(data) )")
fmt.Fprintln(g.out, ws+"}")
return nil
}

unmarshalerIface = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
if reflect.PtrTo(t).Implements(unmarshalerIface) {
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
unmarshalerIface = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
if reflect.PtrTo(t).Implements(unmarshalerIface) {
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
}
}

err := g.genTypeDecoderNoCheck(t, out, tags, indent)
Expand Down
31 changes: 17 additions & 14 deletions gen/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,22 +94,25 @@ func parseFieldTags(f reflect.StructField) fieldTags {
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()
if reflect.PtrTo(t).Implements(marshalerIface) {
fmt.Fprintln(g.out, ws+"("+in+").MarshalEasyJSON(out)")
return nil
}
// See the comment in genTypeDecoder for why we check the name.
if t.Name() != "" {
marshalerIface := reflect.TypeOf((*easyjson.Marshaler)(nil)).Elem()
if reflect.PtrTo(t).Implements(marshalerIface) {
fmt.Fprintln(g.out, ws+"("+in+").MarshalEasyJSON(out)")
return nil
}

marshalerIface = reflect.TypeOf((*json.Marshaler)(nil)).Elem()
if reflect.PtrTo(t).Implements(marshalerIface) {
fmt.Fprintln(g.out, ws+"out.Raw( ("+in+").MarshalJSON() )")
return nil
}
marshalerIface = reflect.TypeOf((*json.Marshaler)(nil)).Elem()
if reflect.PtrTo(t).Implements(marshalerIface) {
fmt.Fprintln(g.out, ws+"out.Raw( ("+in+").MarshalJSON() )")
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
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, assumeNonEmpty)
Expand Down
1 change: 1 addition & 0 deletions tests/basic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ var testCases = []struct {
{&myTypeDeclaredValue, myTypeDeclaredString},
{&myTypeNotSkippedValue, myTypeNotSkippedString},
{&intern, internString},
{&embedAnonWithEmbedValue, embedAnonWithEmbedString},
}

func TestMarshal(t *testing.T) {
Expand Down
24 changes: 24 additions & 0 deletions tests/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -820,3 +820,27 @@ type MyUInt8Array [2]MyUInt8
var myUInt8ArrayValue = MyUInt8Array{1, 2}

var myUInt8ArrayString = `[1,2]`

type GrandChild struct {
V string `json:"v"`
}

//easyjson:json
type EmbedAnonWithEmbed struct {
Child struct {
GrandChild
Sibling string `json:"sibling"`
} `json:"child"`
}

var embedAnonWithEmbedValue = EmbedAnonWithEmbed{
Child: struct {
GrandChild
Sibling string `json:"sibling"`
}{
GrandChild: GrandChild{V: "val"},
Sibling: "joe",
},
}

var embedAnonWithEmbedString = `{"child":{"sibling":"joe","v":"val"}}`

0 comments on commit b7d8b50

Please sign in to comment.