mirror of https://github.com/status-im/consul.git
Update mapstructure to v1.2.3
This release contains a fix to prevent duplicate keys in the Metadata after decoding where the output value contains pointer fields.
This commit is contained in:
parent
137a2c32c6
commit
8c545b5206
2
go.mod
2
go.mod
|
@ -65,7 +65,7 @@ require (
|
|||
github.com/mitchellh/copystructure v1.0.0
|
||||
github.com/mitchellh/go-testing-interface v1.14.0
|
||||
github.com/mitchellh/hashstructure v0.0.0-20170609045927-2bca23e0e452
|
||||
github.com/mitchellh/mapstructure v1.1.2
|
||||
github.com/mitchellh/mapstructure v1.2.3
|
||||
github.com/mitchellh/reflectwalk v1.0.1
|
||||
github.com/pascaldekloe/goe v0.1.0
|
||||
github.com/pkg/errors v0.8.1
|
||||
|
|
2
go.sum
2
go.sum
|
@ -336,6 +336,8 @@ github.com/mitchellh/hashstructure v0.0.0-20170609045927-2bca23e0e452/go.mod h1:
|
|||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.2.3 h1:f/MjBEBDLttYCGfRaKBbKSRVF5aV2O6fnBpzknuE3jU=
|
||||
github.com/mitchellh/mapstructure v1.2.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE=
|
||||
github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- "1.11.x"
|
||||
- "1.14.x"
|
||||
- tip
|
||||
|
||||
script:
|
||||
- go test
|
||||
- go test -bench . -benchmem
|
||||
|
|
|
@ -1,3 +1,26 @@
|
|||
## 1.2.3
|
||||
|
||||
* Fix duplicate entries in Keys list with pointer values. [GH-185]
|
||||
|
||||
## 1.2.2
|
||||
|
||||
* Do not add unsettable (unexported) values to the unused metadata key
|
||||
or "remain" value. [GH-150]
|
||||
|
||||
## 1.2.1
|
||||
|
||||
* Go modules checksum mismatch fix
|
||||
|
||||
## 1.2.0
|
||||
|
||||
* Added support to capture unused values in a field using the `",remain"` value
|
||||
in the mapstructure tag. There is an example to showcase usage.
|
||||
* Added `DecoderConfig` option to always squash embedded structs
|
||||
* `json.Number` can decode into `uint` types
|
||||
* Empty slices are preserved and not replaced with nil slices
|
||||
* Fix panic that can occur in when decoding a map into a nil slice of structs
|
||||
* Improved package documentation for godoc
|
||||
|
||||
## 1.1.2
|
||||
|
||||
* Fix error when decode hook decodes interface implementation into interface
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
module github.com/mitchellh/mapstructure
|
||||
|
||||
go 1.14
|
||||
|
|
|
@ -1,10 +1,109 @@
|
|||
// Package mapstructure exposes functionality to convert an arbitrary
|
||||
// map[string]interface{} into a native Go structure.
|
||||
// Package mapstructure exposes functionality to convert one arbitrary
|
||||
// Go type into another, typically to convert a map[string]interface{}
|
||||
// into a native Go structure.
|
||||
//
|
||||
// The Go structure can be arbitrarily complex, containing slices,
|
||||
// other structs, etc. and the decoder will properly decode nested
|
||||
// maps and so on into the proper structures in the native Go struct.
|
||||
// See the examples to see what the decoder is capable of.
|
||||
//
|
||||
// The simplest function to start with is Decode.
|
||||
//
|
||||
// Field Tags
|
||||
//
|
||||
// When decoding to a struct, mapstructure will use the field name by
|
||||
// default to perform the mapping. For example, if a struct has a field
|
||||
// "Username" then mapstructure will look for a key in the source value
|
||||
// of "username" (case insensitive).
|
||||
//
|
||||
// type User struct {
|
||||
// Username string
|
||||
// }
|
||||
//
|
||||
// You can change the behavior of mapstructure by using struct tags.
|
||||
// The default struct tag that mapstructure looks for is "mapstructure"
|
||||
// but you can customize it using DecoderConfig.
|
||||
//
|
||||
// Renaming Fields
|
||||
//
|
||||
// To rename the key that mapstructure looks for, use the "mapstructure"
|
||||
// tag and set a value directly. For example, to change the "username" example
|
||||
// above to "user":
|
||||
//
|
||||
// type User struct {
|
||||
// Username string `mapstructure:"user"`
|
||||
// }
|
||||
//
|
||||
// Embedded Structs and Squashing
|
||||
//
|
||||
// Embedded structs are treated as if they're another field with that name.
|
||||
// By default, the two structs below are equivalent when decoding with
|
||||
// mapstructure:
|
||||
//
|
||||
// type Person struct {
|
||||
// Name string
|
||||
// }
|
||||
//
|
||||
// type Friend struct {
|
||||
// Person
|
||||
// }
|
||||
//
|
||||
// type Friend struct {
|
||||
// Person Person
|
||||
// }
|
||||
//
|
||||
// This would require an input that looks like below:
|
||||
//
|
||||
// map[string]interface{}{
|
||||
// "person": map[string]interface{}{"name": "alice"},
|
||||
// }
|
||||
//
|
||||
// If your "person" value is NOT nested, then you can append ",squash" to
|
||||
// your tag value and mapstructure will treat it as if the embedded struct
|
||||
// were part of the struct directly. Example:
|
||||
//
|
||||
// type Friend struct {
|
||||
// Person `mapstructure:",squash"`
|
||||
// }
|
||||
//
|
||||
// Now the following input would be accepted:
|
||||
//
|
||||
// map[string]interface{}{
|
||||
// "name": "alice",
|
||||
// }
|
||||
//
|
||||
// DecoderConfig has a field that changes the behavior of mapstructure
|
||||
// to always squash embedded structs.
|
||||
//
|
||||
// Remainder Values
|
||||
//
|
||||
// If there are any unmapped keys in the source value, mapstructure by
|
||||
// default will silently ignore them. You can error by setting ErrorUnused
|
||||
// in DecoderConfig. If you're using Metadata you can also maintain a slice
|
||||
// of the unused keys.
|
||||
//
|
||||
// You can also use the ",remain" suffix on your tag to collect all unused
|
||||
// values in a map. The field with this tag MUST be a map type and should
|
||||
// probably be a "map[string]interface{}" or "map[interface{}]interface{}".
|
||||
// See example below:
|
||||
//
|
||||
// type Friend struct {
|
||||
// Name string
|
||||
// Other map[string]interface{} `mapstructure:",remain"`
|
||||
// }
|
||||
//
|
||||
// Given the input below, Other would be populated with the other
|
||||
// values that weren't used (everything but "name"):
|
||||
//
|
||||
// map[string]interface{}{
|
||||
// "name": "bob",
|
||||
// "address": "123 Maple St.",
|
||||
// }
|
||||
//
|
||||
// Other Configuration
|
||||
//
|
||||
// mapstructure is highly configurable. See the DecoderConfig struct
|
||||
// for other features and options that are supported.
|
||||
package mapstructure
|
||||
|
||||
import (
|
||||
|
@ -80,6 +179,14 @@ type DecoderConfig struct {
|
|||
//
|
||||
WeaklyTypedInput bool
|
||||
|
||||
// Squash will squash embedded structs. A squash tag may also be
|
||||
// added to an individual struct field using a tag. For example:
|
||||
//
|
||||
// type Parent struct {
|
||||
// Child `mapstructure:",squash"`
|
||||
// }
|
||||
Squash bool
|
||||
|
||||
// Metadata is the struct that will contain extra metadata about
|
||||
// the decoding. If this is nil, then no metadata will be tracked.
|
||||
Metadata *Metadata
|
||||
|
@ -271,6 +378,7 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e
|
|||
|
||||
var err error
|
||||
outputKind := getKind(outVal)
|
||||
addMetaKey := true
|
||||
switch outputKind {
|
||||
case reflect.Bool:
|
||||
err = d.decodeBool(name, input, outVal)
|
||||
|
@ -289,7 +397,7 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e
|
|||
case reflect.Map:
|
||||
err = d.decodeMap(name, input, outVal)
|
||||
case reflect.Ptr:
|
||||
err = d.decodePtr(name, input, outVal)
|
||||
addMetaKey, err = d.decodePtr(name, input, outVal)
|
||||
case reflect.Slice:
|
||||
err = d.decodeSlice(name, input, outVal)
|
||||
case reflect.Array:
|
||||
|
@ -303,7 +411,7 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e
|
|||
|
||||
// If we reached here, then we successfully decoded SOMETHING, so
|
||||
// mark the key as used if we're tracking metainput.
|
||||
if d.config.Metadata != nil && name != "" {
|
||||
if addMetaKey && d.config.Metadata != nil && name != "" {
|
||||
d.config.Metadata.Keys = append(d.config.Metadata.Keys, name)
|
||||
}
|
||||
|
||||
|
@ -438,6 +546,7 @@ func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) er
|
|||
func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) error {
|
||||
dataVal := reflect.Indirect(reflect.ValueOf(data))
|
||||
dataKind := getKind(dataVal)
|
||||
dataType := dataVal.Type()
|
||||
|
||||
switch {
|
||||
case dataKind == reflect.Int:
|
||||
|
@ -469,6 +578,18 @@ func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) e
|
|||
} else {
|
||||
return fmt.Errorf("cannot parse '%s' as uint: %s", name, err)
|
||||
}
|
||||
case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number":
|
||||
jn := data.(json.Number)
|
||||
i, err := jn.Int64()
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"error decoding json.Number into %s: %s", name, err)
|
||||
}
|
||||
if i < 0 && !d.config.WeaklyTypedInput {
|
||||
return fmt.Errorf("cannot parse '%s', %d overflows uint",
|
||||
name, i)
|
||||
}
|
||||
val.SetUint(uint64(i))
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
"'%s' expected type '%s', got unconvertible type '%s'",
|
||||
|
@ -689,16 +810,19 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
|
|||
keyName = tagParts[0]
|
||||
}
|
||||
|
||||
// If Squash is set in the config, we squash the field down.
|
||||
squash := d.config.Squash && v.Kind() == reflect.Struct
|
||||
// If "squash" is specified in the tag, we squash the field down.
|
||||
squash := false
|
||||
for _, tag := range tagParts[1:] {
|
||||
if tag == "squash" {
|
||||
squash = true
|
||||
break
|
||||
if !squash {
|
||||
for _, tag := range tagParts[1:] {
|
||||
if tag == "squash" {
|
||||
squash = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if squash && v.Kind() != reflect.Struct {
|
||||
return fmt.Errorf("cannot squash non-struct type '%s'", v.Type())
|
||||
}
|
||||
}
|
||||
if squash && v.Kind() != reflect.Struct {
|
||||
return fmt.Errorf("cannot squash non-struct type '%s'", v.Type())
|
||||
}
|
||||
|
||||
switch v.Kind() {
|
||||
|
@ -738,7 +862,7 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
|
|||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) error {
|
||||
func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) (bool, error) {
|
||||
// If the input data is nil, then we want to just set the output
|
||||
// pointer to be nil as well.
|
||||
isNil := data == nil
|
||||
|
@ -759,7 +883,7 @@ func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) er
|
|||
val.Set(nilValue)
|
||||
}
|
||||
|
||||
return nil
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Create an element of the concrete (non pointer) type and decode
|
||||
|
@ -773,16 +897,16 @@ func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) er
|
|||
}
|
||||
|
||||
if err := d.decode(name, data, reflect.Indirect(realVal)); err != nil {
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
|
||||
val.Set(realVal)
|
||||
} else {
|
||||
if err := d.decode(name, data, reflect.Indirect(val)); err != nil {
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeFunc(name string, data interface{}, val reflect.Value) error {
|
||||
|
@ -805,8 +929,8 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value)
|
|||
valElemType := valType.Elem()
|
||||
sliceType := reflect.SliceOf(valElemType)
|
||||
|
||||
valSlice := val
|
||||
if valSlice.IsNil() || d.config.ZeroFields {
|
||||
// If we have a non array/slice type then we first attempt to convert.
|
||||
if dataValKind != reflect.Array && dataValKind != reflect.Slice {
|
||||
if d.config.WeaklyTypedInput {
|
||||
switch {
|
||||
// Slice and array we use the normal logic
|
||||
|
@ -833,18 +957,17 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value)
|
|||
}
|
||||
}
|
||||
|
||||
// Check input type
|
||||
if dataValKind != reflect.Array && dataValKind != reflect.Slice {
|
||||
return fmt.Errorf(
|
||||
"'%s': source data must be an array or slice, got %s", name, dataValKind)
|
||||
return fmt.Errorf(
|
||||
"'%s': source data must be an array or slice, got %s", name, dataValKind)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// If the input value is empty, then don't allocate since non-nil != nil
|
||||
if dataVal.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
// If the input value is nil, then don't allocate since empty != nil
|
||||
if dataVal.IsNil() {
|
||||
return nil
|
||||
}
|
||||
|
||||
valSlice := val
|
||||
if valSlice.IsNil() || d.config.ZeroFields {
|
||||
// Make a new slice to hold our result, same size as the original data.
|
||||
valSlice = reflect.MakeSlice(sliceType, dataVal.Len(), dataVal.Len())
|
||||
}
|
||||
|
@ -1005,6 +1128,11 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
|
|||
field reflect.StructField
|
||||
val reflect.Value
|
||||
}
|
||||
|
||||
// remainField is set to a valid field set with the "remain" tag if
|
||||
// we are keeping track of remaining values.
|
||||
var remainField *field
|
||||
|
||||
fields := []field{}
|
||||
for len(structs) > 0 {
|
||||
structVal := structs[0]
|
||||
|
@ -1017,13 +1145,21 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
|
|||
fieldKind := fieldType.Type.Kind()
|
||||
|
||||
// If "squash" is specified in the tag, we squash the field down.
|
||||
squash := false
|
||||
squash := d.config.Squash && fieldKind == reflect.Struct
|
||||
remain := false
|
||||
|
||||
// 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" {
|
||||
squash = true
|
||||
break
|
||||
}
|
||||
|
||||
if tag == "remain" {
|
||||
remain = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if squash {
|
||||
|
@ -1036,8 +1172,14 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
|
|||
continue
|
||||
}
|
||||
|
||||
// Normal struct field, store it away
|
||||
fields = append(fields, field{fieldType, structVal.Field(i)})
|
||||
// Build our field
|
||||
fieldCurrent := field{fieldType, structVal.Field(i)}
|
||||
if remain {
|
||||
remainField = &fieldCurrent
|
||||
} else {
|
||||
// Normal struct field, store it away
|
||||
fields = append(fields, field{fieldType, structVal.Field(i)})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1078,9 +1220,6 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
|
|||
}
|
||||
}
|
||||
|
||||
// Delete the key we're using from the unused map so we stop tracking
|
||||
delete(dataValKeysUnused, rawMapKey.Interface())
|
||||
|
||||
if !fieldValue.IsValid() {
|
||||
// This should never happen
|
||||
panic("field is not valid")
|
||||
|
@ -1092,6 +1231,9 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
|
|||
continue
|
||||
}
|
||||
|
||||
// Delete the key we're using from the unused map so we stop tracking
|
||||
delete(dataValKeysUnused, rawMapKey.Interface())
|
||||
|
||||
// If the name is empty string, then we're at the root, and we
|
||||
// don't dot-join the fields.
|
||||
if name != "" {
|
||||
|
@ -1103,6 +1245,25 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
|
|||
}
|
||||
}
|
||||
|
||||
// If we have a "remain"-tagged field and we have unused keys then
|
||||
// we put the unused keys directly into the remain field.
|
||||
if remainField != nil && len(dataValKeysUnused) > 0 {
|
||||
// Build a map of only the unused values
|
||||
remain := map[interface{}]interface{}{}
|
||||
for key := range dataValKeysUnused {
|
||||
remain[key] = dataVal.MapIndex(reflect.ValueOf(key)).Interface()
|
||||
}
|
||||
|
||||
// Decode it as-if we were just decoding this map onto our map.
|
||||
if err := d.decodeMap(name, remain, remainField.val); err != nil {
|
||||
errors = appendErrors(errors, err)
|
||||
}
|
||||
|
||||
// Set the map to nil so we have none so that the next check will
|
||||
// not error (ErrorUnused)
|
||||
dataValKeysUnused = nil
|
||||
}
|
||||
|
||||
if d.config.ErrorUnused && len(dataValKeysUnused) > 0 {
|
||||
keys := make([]string, 0, len(dataValKeysUnused))
|
||||
for rawKey := range dataValKeysUnused {
|
||||
|
|
|
@ -303,7 +303,7 @@ github.com/mitchellh/go-homedir
|
|||
github.com/mitchellh/go-testing-interface
|
||||
# github.com/mitchellh/hashstructure v0.0.0-20170609045927-2bca23e0e452
|
||||
github.com/mitchellh/hashstructure
|
||||
# github.com/mitchellh/mapstructure v1.1.2
|
||||
# github.com/mitchellh/mapstructure v1.2.3
|
||||
github.com/mitchellh/mapstructure
|
||||
# github.com/mitchellh/reflectwalk v1.0.1
|
||||
github.com/mitchellh/reflectwalk
|
||||
|
|
Loading…
Reference in New Issue