Skip to content

Commit 9f31ea6

Browse files
committed
standardize behavior of mapping anonymous fields
the behavior has been defined in a way that is compatible with encoding/json. this behavior is as follows: anonymous fields which are structs will have struct fields get field names as if they were directly in the parent struct. anonymous fields which are not structs, or which are interfaces which may or may not point to structs will get field names that correspond to the name of the type the exception to the rules above is that you can always override this behavior by using a JSON struct tag fixes #101
1 parent 58457e7 commit 9f31ea6

File tree

2 files changed

+132
-2
lines changed

2 files changed

+132
-2
lines changed

mapping_document.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,20 +325,29 @@ func (dm *DocumentMapping) walkDocument(data interface{}, path []string, indexes
325325
for i := 0; i < val.NumField(); i++ {
326326
field := typ.Field(i)
327327
fieldName := field.Name
328+
// anonymous fields of type struct can elide the type name
329+
if field.Anonymous && field.Type.Kind() == reflect.Struct {
330+
fieldName = ""
331+
}
328332

329333
// if the field has a JSON name, prefer that
330334
jsonTag := field.Tag.Get("json")
331335
jsonFieldName := parseJSONTagName(jsonTag)
332336
if jsonFieldName == "-" {
333337
continue
334338
}
335-
if jsonFieldName != "" {
339+
// allow json tag to set field name to empty, only if anonymous
340+
if field.Tag != "" && (jsonFieldName != "" || field.Anonymous) {
336341
fieldName = jsonFieldName
337342
}
338343

339344
if val.Field(i).CanInterface() {
340345
fieldVal := val.Field(i).Interface()
341-
dm.processProperty(fieldVal, append(path, fieldName), indexes, context)
346+
newpath := path
347+
if fieldName != "" {
348+
newpath = append(path, fieldName)
349+
}
350+
dm.processProperty(fieldVal, newpath, indexes, context)
342351
}
343352
}
344353
case reflect.Slice, reflect.Array:

mapping_test.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,3 +603,124 @@ func TestMappingBug353(t *testing.T) {
603603
}
604604
}
605605
}
606+
607+
func TestAnonymousStructFields(t *testing.T) {
608+
609+
type Contact0 string
610+
611+
type Contact1 struct {
612+
Name string
613+
}
614+
615+
type Contact2 interface{}
616+
617+
type Contact3 interface{}
618+
619+
type Thing struct {
620+
Contact0
621+
Contact1
622+
Contact2
623+
Contact3
624+
}
625+
626+
x := Thing{
627+
Contact0: "hello",
628+
Contact1: Contact1{
629+
Name: "marty",
630+
},
631+
Contact2: Contact1{
632+
Name: "will",
633+
},
634+
Contact3: "steve",
635+
}
636+
637+
doc := document.NewDocument("1")
638+
m := NewIndexMapping()
639+
err := m.mapDocument(doc, x)
640+
if err != nil {
641+
t.Fatal(err)
642+
}
643+
644+
if len(doc.Fields) != 4 {
645+
t.Fatalf("expected 4 fields, got %d", len(doc.Fields))
646+
}
647+
if doc.Fields[0].Name() != "Contact0" {
648+
t.Errorf("expected field named 'Contact0', got '%s'", doc.Fields[0].Name())
649+
}
650+
if doc.Fields[1].Name() != "Name" {
651+
t.Errorf("expected field named 'Name', got '%s'", doc.Fields[1].Name())
652+
}
653+
if doc.Fields[2].Name() != "Contact2.Name" {
654+
t.Errorf("expected field named 'Contact2.Name', got '%s'", doc.Fields[2].Name())
655+
}
656+
if doc.Fields[3].Name() != "Contact3" {
657+
t.Errorf("expected field named 'Contact3', got '%s'", doc.Fields[3].Name())
658+
}
659+
660+
type AnotherThing struct {
661+
Contact0 `json:"Alternate0"`
662+
Contact1 `json:"Alternate1"`
663+
Contact2 `json:"Alternate2"`
664+
Contact3 `json:"Alternate3"`
665+
}
666+
667+
y := AnotherThing{
668+
Contact0: "hello",
669+
Contact1: Contact1{
670+
Name: "marty",
671+
},
672+
Contact2: Contact1{
673+
Name: "will",
674+
},
675+
Contact3: "steve",
676+
}
677+
678+
doc2 := document.NewDocument("2")
679+
err = m.mapDocument(doc2, y)
680+
if err != nil {
681+
t.Fatal(err)
682+
}
683+
684+
if len(doc2.Fields) != 4 {
685+
t.Fatalf("expected 4 fields, got %d", len(doc2.Fields))
686+
}
687+
if doc2.Fields[0].Name() != "Alternate0" {
688+
t.Errorf("expected field named 'Alternate0', got '%s'", doc2.Fields[0].Name())
689+
}
690+
if doc2.Fields[1].Name() != "Alternate1.Name" {
691+
t.Errorf("expected field named 'Name', got '%s'", doc2.Fields[1].Name())
692+
}
693+
if doc2.Fields[2].Name() != "Alternate2.Name" {
694+
t.Errorf("expected field named 'Alternate2.Name', got '%s'", doc2.Fields[2].Name())
695+
}
696+
if doc2.Fields[3].Name() != "Alternate3" {
697+
t.Errorf("expected field named 'Alternate3', got '%s'", doc2.Fields[3].Name())
698+
}
699+
}
700+
701+
func TestAnonymousStructFieldWithJSONStructTagEmptString(t *testing.T) {
702+
type InterfaceThing interface{}
703+
type Thing struct {
704+
InterfaceThing `json:""`
705+
}
706+
707+
x := Thing{
708+
InterfaceThing: map[string]interface{}{
709+
"key": "value",
710+
},
711+
}
712+
713+
doc := document.NewDocument("1")
714+
m := NewIndexMapping()
715+
err := m.mapDocument(doc, x)
716+
if err != nil {
717+
t.Fatal(err)
718+
}
719+
720+
if len(doc.Fields) != 1 {
721+
t.Fatalf("expected 1 field, got %d", len(doc.Fields))
722+
}
723+
if doc.Fields[0].Name() != "key" {
724+
t.Errorf("expected field named 'key', got '%s'", doc.Fields[0].Name())
725+
}
726+
}

0 commit comments

Comments
 (0)