Skip to content
This repository was archived by the owner on Jul 22, 2024. It is now read-only.

Commit 0bb6a2e

Browse files
authored
Merge branch 'master' into feature/add_or_compose_decode_hook_func
2 parents 477b5e2 + ac10e22 commit 0bb6a2e

File tree

6 files changed

+412
-15
lines changed

6 files changed

+412
-15
lines changed

CHANGELOG.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,26 @@
1+
## 1.4.4
2+
3+
* New option `IgnoreUntaggedFields` to ignore decoding to any fields
4+
without `mapstructure` (or the configured tag name) set [GH-277]
5+
* New option `ErrorUnset` which makes it an error if any fields
6+
in a target struct are not set by the decoding process. [GH-225]
7+
* Decoding to slice from array no longer crashes [GH-265]
8+
* Decode nested struct pointers to map [GH-271]
9+
* Fix issue where `,squash` was ignored if `Squash` option was set. [GH-280]
10+
11+
## 1.4.3
12+
13+
* Fix cases where `json.Number` didn't decode properly [GH-261]
14+
15+
## 1.4.2
16+
17+
* Custom name matchers to support any sort of casing, formatting, etc. for
18+
field names. [GH-250]
19+
* Fix possible panic in ComposeDecodeHookFunc [GH-251]
20+
121
## 1.4.1
222

3-
* Fix regression where `*time.Time` value would be set to empty and not be sent
23+
* Fix regression where `*time.Time` value would be set to empty and not be sent
424
to decode hooks properly [GH-232]
525

626
## 1.4.0

decode_hooks.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ func DecodeHookExec(
6262
func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc {
6363
return func(f reflect.Value, t reflect.Value) (interface{}, error) {
6464
var err error
65-
var data interface{}
65+
data := f.Interface()
66+
6667
newFrom := f
6768
for _, f1 := range fs {
6869
data, err = DecodeHookExec(f1, newFrom, t)

decode_hooks_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,38 @@ func TestOrComposeDecodeHookFunc_err(t *testing.T) {
172172
}
173173
}
174174

175+
func TestComposeDecodeHookFunc_safe_nofuncs(t *testing.T) {
176+
f := ComposeDecodeHookFunc()
177+
type myStruct2 struct {
178+
MyInt int
179+
}
180+
181+
type myStruct1 struct {
182+
Blah map[string]myStruct2
183+
}
184+
185+
src := &myStruct1{Blah: map[string]myStruct2{
186+
"test": {
187+
MyInt: 1,
188+
},
189+
}}
190+
191+
dst := &myStruct1{}
192+
dConf := &DecoderConfig{
193+
Result: dst,
194+
ErrorUnused: true,
195+
DecodeHook: f,
196+
}
197+
d, err := NewDecoder(dConf)
198+
if err != nil {
199+
t.Fatal(err)
200+
}
201+
err = d.Decode(src)
202+
if err != nil {
203+
t.Fatal(err)
204+
}
205+
}
206+
175207
func TestStringToSliceHookFunc(t *testing.T) {
176208
f := StringToSliceHookFunc(",")
177209

mapstructure.go

Lines changed: 88 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@
122122
// field value is zero and a numeric type, the field is empty, and it won't
123123
// be encoded into the destination type.
124124
//
125-
// type Source {
125+
// type Source struct {
126126
// Age int `mapstructure:",omitempty"`
127127
// }
128128
//
@@ -192,7 +192,7 @@ type DecodeHookFuncType func(reflect.Type, reflect.Type, interface{}) (interface
192192
// source and target types.
193193
type DecodeHookFuncKind func(reflect.Kind, reflect.Kind, interface{}) (interface{}, error)
194194

195-
// DecodeHookFuncRaw is a DecodeHookFunc which has complete access to both the source and target
195+
// DecodeHookFuncValue is a DecodeHookFunc which has complete access to both the source and target
196196
// values.
197197
type DecodeHookFuncValue func(from reflect.Value, to reflect.Value) (interface{}, error)
198198

@@ -215,6 +215,12 @@ type DecoderConfig struct {
215215
// (extra keys).
216216
ErrorUnused bool
217217

218+
// If ErrorUnset is true, then it is an error for there to exist
219+
// fields in the result that were not set in the decoding process
220+
// (extra fields). This only applies to decoding to a struct. This
221+
// will affect all nested structs as well.
222+
ErrorUnset bool
223+
218224
// ZeroFields, if set to true, will zero fields before writing them.
219225
// For example, a map will be emptied before decoded values are put in
220226
// it. If this is false, a map will be merged.
@@ -258,6 +264,15 @@ type DecoderConfig struct {
258264
// The tag name that mapstructure reads for field names. This
259265
// defaults to "mapstructure"
260266
TagName string
267+
268+
// IgnoreUntaggedFields ignores all struct fields without explicit
269+
// TagName, comparable to `mapstructure:"-"` as default behaviour.
270+
IgnoreUntaggedFields bool
271+
272+
// MatchName is the function used to match the map key to the struct
273+
// field name or tag. Defaults to `strings.EqualFold`. This can be used
274+
// to implement case-sensitive tag values, support snake casing, etc.
275+
MatchName func(mapKey, fieldName string) bool
261276
}
262277

263278
// A Decoder takes a raw interface value and turns it into structured
@@ -279,6 +294,11 @@ type Metadata struct {
279294
// Unused is a slice of keys that were found in the raw value but
280295
// weren't decoded since there was no matching field in the result interface
281296
Unused []string
297+
298+
// Unset is a slice of field names that were found in the result interface
299+
// but weren't set in the decoding process since there was no matching value
300+
// in the input
301+
Unset []string
282302
}
283303

284304
// Decode takes an input structure and uses reflection to translate it to
@@ -370,12 +390,20 @@ func NewDecoder(config *DecoderConfig) (*Decoder, error) {
370390
if config.Metadata.Unused == nil {
371391
config.Metadata.Unused = make([]string, 0)
372392
}
393+
394+
if config.Metadata.Unset == nil {
395+
config.Metadata.Unset = make([]string, 0)
396+
}
373397
}
374398

375399
if config.TagName == "" {
376400
config.TagName = "mapstructure"
377401
}
378402

403+
if config.MatchName == nil {
404+
config.MatchName = strings.EqualFold
405+
}
406+
379407
result := &Decoder{
380408
config: config,
381409
}
@@ -675,16 +703,12 @@ func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) e
675703
}
676704
case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number":
677705
jn := data.(json.Number)
678-
i, err := jn.Int64()
706+
i, err := strconv.ParseUint(string(jn), 0, 64)
679707
if err != nil {
680708
return fmt.Errorf(
681709
"error decoding json.Number into %s: %s", name, err)
682710
}
683-
if i < 0 && !d.config.WeaklyTypedInput {
684-
return fmt.Errorf("cannot parse '%s', %d overflows uint",
685-
name, i)
686-
}
687-
val.SetUint(uint64(i))
711+
val.SetUint(i)
688712
default:
689713
return fmt.Errorf(
690714
"'%s' expected type '%s', got unconvertible type '%s', value: '%v'",
@@ -901,9 +925,15 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
901925
tagValue := f.Tag.Get(d.config.TagName)
902926
keyName := f.Name
903927

928+
if tagValue == "" && d.config.IgnoreUntaggedFields {
929+
continue
930+
}
931+
904932
// If Squash is set in the config, we squash the field down.
905933
squash := d.config.Squash && v.Kind() == reflect.Struct && f.Anonymous
906934

935+
v = dereferencePtrToStructIfNeeded(v, d.config.TagName)
936+
907937
// Determine the name of the key in the map
908938
if index := strings.Index(tagValue, ","); index != -1 {
909939
if tagValue[:index] == "-" {
@@ -915,7 +945,7 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
915945
}
916946

917947
// If "squash" is specified in the tag, we squash the field down.
918-
squash = !squash && strings.Index(tagValue[index+1:], "squash") != -1
948+
squash = squash || strings.Index(tagValue[index+1:], "squash") != -1
919949
if squash {
920950
// When squashing, the embedded type can be a pointer to a struct.
921951
if v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct {
@@ -1083,7 +1113,7 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value)
10831113
}
10841114

10851115
// If the input value is nil, then don't allocate since empty != nil
1086-
if dataVal.IsNil() {
1116+
if dataValKind != reflect.Array && dataVal.IsNil() {
10871117
return nil
10881118
}
10891119

@@ -1245,6 +1275,7 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
12451275
dataValKeysUnused[dataValKey.Interface()] = struct{}{}
12461276
}
12471277

1278+
targetValKeysUnused := make(map[interface{}]struct{})
12481279
errors := make([]string, 0)
12491280

12501281
// This slice will keep track of all the structs we'll be decoding.
@@ -1340,7 +1371,7 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
13401371
continue
13411372
}
13421373

1343-
if strings.EqualFold(mK, fieldName) {
1374+
if d.config.MatchName(mK, fieldName) {
13441375
rawMapKey = dataValKey
13451376
rawMapVal = dataVal.MapIndex(dataValKey)
13461377
break
@@ -1349,7 +1380,8 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
13491380

13501381
if !rawMapVal.IsValid() {
13511382
// There was no matching key in the map for the value in
1352-
// the struct. Just ignore.
1383+
// the struct. Remember it for potential errors and metadata.
1384+
targetValKeysUnused[fieldName] = struct{}{}
13531385
continue
13541386
}
13551387
}
@@ -1409,6 +1441,17 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
14091441
errors = appendErrors(errors, err)
14101442
}
14111443

1444+
if d.config.ErrorUnset && len(targetValKeysUnused) > 0 {
1445+
keys := make([]string, 0, len(targetValKeysUnused))
1446+
for rawKey := range targetValKeysUnused {
1447+
keys = append(keys, rawKey.(string))
1448+
}
1449+
sort.Strings(keys)
1450+
1451+
err := fmt.Errorf("'%s' has unset fields: %s", name, strings.Join(keys, ", "))
1452+
errors = appendErrors(errors, err)
1453+
}
1454+
14121455
if len(errors) > 0 {
14131456
return &Error{errors}
14141457
}
@@ -1423,6 +1466,14 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
14231466

14241467
d.config.Metadata.Unused = append(d.config.Metadata.Unused, key)
14251468
}
1469+
for rawKey := range targetValKeysUnused {
1470+
key := rawKey.(string)
1471+
if name != "" {
1472+
key = name + "." + key
1473+
}
1474+
1475+
d.config.Metadata.Unset = append(d.config.Metadata.Unset, key)
1476+
}
14261477
}
14271478

14281479
return nil
@@ -1460,3 +1511,28 @@ func getKind(val reflect.Value) reflect.Kind {
14601511
return kind
14611512
}
14621513
}
1514+
1515+
func isStructTypeConvertibleToMap(typ reflect.Type, checkMapstructureTags bool, tagName string) bool {
1516+
for i := 0; i < typ.NumField(); i++ {
1517+
f := typ.Field(i)
1518+
if f.PkgPath == "" && !checkMapstructureTags { // check for unexported fields
1519+
return true
1520+
}
1521+
if checkMapstructureTags && f.Tag.Get(tagName) != "" { // check for mapstructure tags inside
1522+
return true
1523+
}
1524+
}
1525+
return false
1526+
}
1527+
1528+
func dereferencePtrToStructIfNeeded(v reflect.Value, tagName string) reflect.Value {
1529+
if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
1530+
return v
1531+
}
1532+
deref := v.Elem()
1533+
derefT := deref.Type()
1534+
if isStructTypeConvertibleToMap(derefT, true, tagName) {
1535+
return deref
1536+
}
1537+
return v
1538+
}

mapstructure_bugs_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ func TestDecodeBadDataTypeInSlice(t *testing.T) {
479479

480480
// #202 Ensure that intermediate maps in the struct -> struct decode process are settable
481481
// and not just the elements within them.
482-
func TestDecodeIntermeidateMapsSettable(t *testing.T) {
482+
func TestDecodeIntermediateMapsSettable(t *testing.T) {
483483
type Timestamp struct {
484484
Seconds int64
485485
Nanos int32

0 commit comments

Comments
 (0)