Skip to content

Commit fd74c75

Browse files
Merge pull request #137 from andreev-fn/opt-root-name
feat: optional root object name
2 parents dee4661 + 8eeb752 commit fd74c75

File tree

2 files changed

+57
-1
lines changed

2 files changed

+57
-1
lines changed

mapstructure.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,10 @@ type DecoderConfig struct {
302302
// matching non-empty tag will be used.
303303
TagName string
304304

305+
// RootName specifies the name to use for the root element in error messages. For example:
306+
// '<rootName>' has unset fields: <fieldName>
307+
RootName string
308+
305309
// The option of the value in the tag that indicates a field should
306310
// be squashed. This defaults to "squash".
307311
SquashTagOption string
@@ -491,7 +495,7 @@ func NewDecoder(config *DecoderConfig) (*Decoder, error) {
491495
// Decode decodes the given raw interface to the target pointer specified
492496
// by the configuration.
493497
func (d *Decoder) Decode(input any) error {
494-
err := d.decode("", input, reflect.ValueOf(d.config.Result).Elem())
498+
err := d.decode(d.config.RootName, input, reflect.ValueOf(d.config.Result).Elem())
495499

496500
// Retain some of the original behavior when multiple errors ocurr
497501
var joinedErr interface{ Unwrap() []error }

mapstructure_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3900,6 +3900,58 @@ func TestDecode_structArrayDeepMap(t *testing.T) {
39003900
}
39013901
}
39023902

3903+
func TestDecoder_RootName(t *testing.T) {
3904+
t.Parallel()
3905+
3906+
input := map[string]any{
3907+
"surname": "green",
3908+
"relation": map[string]any{
3909+
"surname": "black",
3910+
},
3911+
}
3912+
3913+
var result struct {
3914+
Name string `mapstructure:"name"`
3915+
Relation struct {
3916+
Name string `mapstructure:"name"`
3917+
} `mapstructure:"relation"`
3918+
}
3919+
3920+
decoder, err := NewDecoder(&DecoderConfig{
3921+
ErrorUnset: true,
3922+
ErrorUnused: true,
3923+
Result: &result,
3924+
RootName: "root",
3925+
})
3926+
if err != nil {
3927+
t.Fatalf("err: %s", err)
3928+
}
3929+
3930+
err = decoder.Decode(input)
3931+
if err == nil {
3932+
t.Fatal("expected error")
3933+
}
3934+
3935+
expectedErrors := []string{
3936+
`'root.relation' has invalid keys: surname`,
3937+
`'root.relation' has unset fields: name`,
3938+
`'root' has invalid keys: surname`,
3939+
`'root' has unset fields: name`,
3940+
}
3941+
3942+
failed := false
3943+
3944+
for _, expectedErr := range expectedErrors {
3945+
if !strings.Contains(err.Error(), expectedErr) {
3946+
failed = true
3947+
}
3948+
}
3949+
3950+
if failed {
3951+
t.Errorf("unexpected error message, got: %s", err.Error())
3952+
}
3953+
}
3954+
39033955
func stringPtr(v string) *string { return &v }
39043956
func intPtr(v int) *int { return &v }
39053957
func uintPtr(v uint) *uint { return &v }

0 commit comments

Comments
 (0)