Skip to content

Commit aa74cdc

Browse files
committed
Add configuration option for tag value that indicates squash
1 parent cb699d2 commit aa74cdc

File tree

2 files changed

+70
-2
lines changed

2 files changed

+70
-2
lines changed

mapstructure.go

+10-2
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,10 @@ type DecoderConfig struct {
265265
// defaults to "mapstructure"
266266
TagName string
267267

268+
// The option of the value in the tag that indicates a field should
269+
// be squashed. This defaults to "squash".
270+
SquashTagOption string
271+
268272
// IgnoreUntaggedFields ignores all struct fields without explicit
269273
// TagName, comparable to `mapstructure:"-"` as default behaviour.
270274
IgnoreUntaggedFields bool
@@ -400,6 +404,10 @@ func NewDecoder(config *DecoderConfig) (*Decoder, error) {
400404
config.TagName = "mapstructure"
401405
}
402406

407+
if config.SquashTagOption == "" {
408+
config.SquashTagOption = "squash"
409+
}
410+
403411
if config.MatchName == nil {
404412
config.MatchName = strings.EqualFold
405413
}
@@ -969,7 +977,7 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
969977
}
970978

971979
// If "squash" is specified in the tag, we squash the field down.
972-
squash = squash || strings.Index(tagValue[index+1:], "squash") != -1
980+
squash = squash || strings.Contains(tagValue[index+1:], d.config.SquashTagOption)
973981
if squash {
974982
// When squashing, the embedded type can be a pointer to a struct.
975983
if v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct {
@@ -1356,7 +1364,7 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
13561364
// We always parse the tags cause we're looking for other tags too
13571365
tagParts := strings.Split(fieldType.Tag.Get(d.config.TagName), ",")
13581366
for _, tag := range tagParts[1:] {
1359-
if tag == "squash" {
1367+
if tag == d.config.SquashTagOption {
13601368
squash = true
13611369
break
13621370
}

mapstructure_test.go

+60
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ type BasicSquash struct {
5050
Test Basic `mapstructure:",squash"`
5151
}
5252

53+
type BasicJSONInline struct {
54+
Test Basic `json:",inline"`
55+
}
56+
5357
type Embedded struct {
5458
Basic
5559
Vunique string
@@ -488,6 +492,62 @@ func TestDecodeFrom_BasicSquash(t *testing.T) {
488492
}
489493
}
490494

495+
func TestDecode_BasicJSONInline(t *testing.T) {
496+
t.Parallel()
497+
498+
input := map[string]interface{}{
499+
"vstring": "foo",
500+
}
501+
502+
var result BasicJSONInline
503+
d, err := NewDecoder(&DecoderConfig{TagName: "json", SquashTagOption: "inline", Result: &result})
504+
if err != nil {
505+
t.Fatalf("got an err: %s", err.Error())
506+
}
507+
508+
if err := d.Decode(input); err != nil {
509+
t.Fatalf("got an err: %s", err.Error())
510+
}
511+
512+
if result.Test.Vstring != "foo" {
513+
t.Errorf("vstring value should be 'foo': %#v", result.Test.Vstring)
514+
}
515+
}
516+
517+
func TestDecodeFrom_BasicJSONInline(t *testing.T) {
518+
t.Parallel()
519+
520+
var v interface{}
521+
var ok bool
522+
523+
input := BasicJSONInline{
524+
Test: Basic{
525+
Vstring: "foo",
526+
},
527+
}
528+
529+
var result map[string]interface{}
530+
d, err := NewDecoder(&DecoderConfig{TagName: "json", SquashTagOption: "inline", Result: &result})
531+
if err != nil {
532+
t.Fatalf("got an err: %s", err.Error())
533+
}
534+
535+
if err := d.Decode(input); err != nil {
536+
t.Fatalf("got an err: %s", err.Error())
537+
}
538+
539+
if _, ok = result["Test"]; ok {
540+
t.Error("test should not be present in map")
541+
}
542+
543+
v, ok = result["Vstring"]
544+
if !ok {
545+
t.Error("vstring should be present in map")
546+
} else if !reflect.DeepEqual(v, "foo") {
547+
t.Errorf("vstring value should be 'foo': %#v", v)
548+
}
549+
}
550+
491551
func TestDecode_Embedded(t *testing.T) {
492552
t.Parallel()
493553

0 commit comments

Comments
 (0)