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

Commit 8385cfa

Browse files
authored
Merge pull request #225 from SaschaRoland/unset-fields
Add ErrorUnset option to DecoderConfig and Unset array to Metddata
2 parents 17e49ec + d91ccce commit 8385cfa

File tree

2 files changed

+68
-1
lines changed

2 files changed

+68
-1
lines changed

mapstructure.go

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,11 @@ 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).
221+
ErrorUnset bool
222+
218223
// ZeroFields, if set to true, will zero fields before writing them.
219224
// For example, a map will be emptied before decoded values are put in
220225
// it. If this is false, a map will be merged.
@@ -288,6 +293,11 @@ type Metadata struct {
288293
// Unused is a slice of keys that were found in the raw value but
289294
// weren't decoded since there was no matching field in the result interface
290295
Unused []string
296+
297+
// Unset is a slice of field names that were found in the result interface
298+
// but weren't set in the decoding process since there was no matching value
299+
// in the input
300+
Unset []string
291301
}
292302

293303
// Decode takes an input structure and uses reflection to translate it to
@@ -379,6 +389,10 @@ func NewDecoder(config *DecoderConfig) (*Decoder, error) {
379389
if config.Metadata.Unused == nil {
380390
config.Metadata.Unused = make([]string, 0)
381391
}
392+
393+
if config.Metadata.Unset == nil {
394+
config.Metadata.Unset = make([]string, 0)
395+
}
382396
}
383397

384398
if config.TagName == "" {
@@ -1260,6 +1274,7 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
12601274
dataValKeysUnused[dataValKey.Interface()] = struct{}{}
12611275
}
12621276

1277+
targetValKeysUnused := make(map[interface{}]struct{})
12631278
errors := make([]string, 0)
12641279

12651280
// This slice will keep track of all the structs we'll be decoding.
@@ -1364,7 +1379,8 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
13641379

13651380
if !rawMapVal.IsValid() {
13661381
// There was no matching key in the map for the value in
1367-
// the struct. Just ignore.
1382+
// the struct. Remember it for potential errors and metadata.
1383+
targetValKeysUnused[fieldName] = struct{}{}
13681384
continue
13691385
}
13701386
}
@@ -1424,6 +1440,17 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
14241440
errors = appendErrors(errors, err)
14251441
}
14261442

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

14391466
d.config.Metadata.Unused = append(d.config.Metadata.Unused, key)
14401467
}
1468+
for rawKey := range targetValKeysUnused {
1469+
key := rawKey.(string)
1470+
if name != "" {
1471+
key = name + "." + key
1472+
}
1473+
1474+
d.config.Metadata.Unset = append(d.config.Metadata.Unset, key)
1475+
}
14411476
}
14421477

14431478
return nil

mapstructure_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1376,6 +1376,30 @@ func TestDecoder_ErrorUnused_NotSetable(t *testing.T) {
13761376
t.Fatal("expected error")
13771377
}
13781378
}
1379+
func TestDecoder_ErrorUnset(t *testing.T) {
1380+
t.Parallel()
1381+
1382+
input := map[string]interface{}{
1383+
"vstring": "hello",
1384+
"foo": "bar",
1385+
}
1386+
1387+
var result Basic
1388+
config := &DecoderConfig{
1389+
ErrorUnset: true,
1390+
Result: &result,
1391+
}
1392+
1393+
decoder, err := NewDecoder(config)
1394+
if err != nil {
1395+
t.Fatalf("err: %s", err)
1396+
}
1397+
1398+
err = decoder.Decode(input)
1399+
if err == nil {
1400+
t.Fatal("expected error")
1401+
}
1402+
}
13791403

13801404
func TestMap(t *testing.T) {
13811405
t.Parallel()
@@ -2345,6 +2369,14 @@ func TestMetadata(t *testing.T) {
23452369
if !reflect.DeepEqual(md.Unused, expectedUnused) {
23462370
t.Fatalf("bad unused: %#v", md.Unused)
23472371
}
2372+
2373+
expectedUnset := []string{
2374+
"Vbar.Vbool", "Vbar.Vdata", "Vbar.Vextra", "Vbar.Vfloat", "Vbar.Vint",
2375+
"Vbar.VjsonFloat", "Vbar.VjsonInt", "Vbar.VjsonNumber"}
2376+
sort.Strings(md.Unset)
2377+
if !reflect.DeepEqual(md.Unset, expectedUnset) {
2378+
t.Fatalf("bad unset: %#v", md.Unset)
2379+
}
23482380
}
23492381

23502382
func TestMetadata_Embedded(t *testing.T) {

0 commit comments

Comments
 (0)