Skip to content

Commit

Permalink
Add configuration option for tag value that indicates squash
Browse files Browse the repository at this point in the history
  • Loading branch information
triarius committed Apr 3, 2024
1 parent cb699d2 commit 2fbfd79
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 2 deletions.
12 changes: 10 additions & 2 deletions mapstructure.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,10 @@ type DecoderConfig struct {
// defaults to "mapstructure"
TagName string

// The name of the value in the tag that indicates a field should
// be squashed. This defaults to "squash".
SquashName string

// IgnoreUntaggedFields ignores all struct fields without explicit
// TagName, comparable to `mapstructure:"-"` as default behaviour.
IgnoreUntaggedFields bool
Expand Down Expand Up @@ -400,6 +404,10 @@ func NewDecoder(config *DecoderConfig) (*Decoder, error) {
config.TagName = "mapstructure"
}

if config.SquashName == "" {
config.SquashName = "squash"
}

if config.MatchName == nil {
config.MatchName = strings.EqualFold
}
Expand Down Expand Up @@ -969,7 +977,7 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
}

// If "squash" is specified in the tag, we squash the field down.
squash = squash || strings.Index(tagValue[index+1:], "squash") != -1
squash = squash || strings.Contains(tagValue[index+1:], d.config.SquashName)
if squash {
// When squashing, the embedded type can be a pointer to a struct.
if v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct {
Expand Down Expand Up @@ -1356,7 +1364,7 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
// We always parse the tags cause we're looking for other tags too
tagParts := strings.Split(fieldType.Tag.Get(d.config.TagName), ",")
for _, tag := range tagParts[1:] {
if tag == "squash" {
if tag == d.config.SquashName {
squash = true
break
}
Expand Down
60 changes: 60 additions & 0 deletions mapstructure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ type BasicSquash struct {
Test Basic `mapstructure:",squash"`
}

type BasicJSONInline struct {
Test Basic `json:",inline"`
}

type Embedded struct {
Basic
Vunique string
Expand Down Expand Up @@ -488,6 +492,62 @@ func TestDecodeFrom_BasicSquash(t *testing.T) {
}
}

func TestDecode_BasicJSONInline(t *testing.T) {
t.Parallel()

input := map[string]interface{}{
"vstring": "foo",
}

var result BasicJSONInline
d, err := NewDecoder(&DecoderConfig{TagName: "json", SquashName: "inline", Result: &result})
if err != nil {
t.Fatalf("got an err: %s", err.Error())
}

if err := d.Decode(input); err != nil {
t.Fatalf("got an err: %s", err.Error())
}

if result.Test.Vstring != "foo" {
t.Errorf("vstring value should be 'foo': %#v", result.Test.Vstring)
}
}

func TestDecodeFrom_BasicJSONInline(t *testing.T) {
t.Parallel()

var v interface{}
var ok bool

input := BasicJSONInline{
Test: Basic{
Vstring: "foo",
},
}

var result map[string]interface{}
d, err := NewDecoder(&DecoderConfig{TagName: "json", SquashName: "inline", Result: &result})
if err != nil {
t.Fatalf("got an err: %s", err.Error())
}

if err := d.Decode(input); err != nil {
t.Fatalf("got an err: %s", err.Error())
}

if _, ok = result["Test"]; ok {
t.Error("test should not be present in map")
}

v, ok = result["Vstring"]
if !ok {
t.Error("vstring should be present in map")
} else if !reflect.DeepEqual(v, "foo") {
t.Errorf("vstring value should be 'foo': %#v", v)
}
}

func TestDecode_Embedded(t *testing.T) {
t.Parallel()

Expand Down

0 comments on commit 2fbfd79

Please sign in to comment.