From c63fee0297e61dcace87246eeadcfc79be710cbb Mon Sep 17 00:00:00 2001 From: Derrick Hammer Date: Mon, 3 Jun 2024 18:49:33 -0400 Subject: [PATCH] feat: variation of PR https://github.com/mitchellh/mapstructure/pull/294 --- mapstructure.go | 23 +++++++++++++++++------ mapstructure_examples_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/mapstructure.go b/mapstructure.go index c8619fa..e30cecd 100644 --- a/mapstructure.go +++ b/mapstructure.go @@ -3,9 +3,9 @@ // into a native Go structure. // // The Go structure can be arbitrarily complex, containing slices, -// other structs, etc. and the decoder will properly decode nested +// other structs, etc. and the Unmarshaler 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. +// See the examples to see what the Unmarshaler is capable of. // // The simplest function to start with is Decode. // @@ -129,7 +129,7 @@ // # Unexported fields // // Since unexported (private) struct fields cannot be set outside the package -// where they are defined, the decoder will simply skip them. +// where they are defined, the Unmarshaler will simply skip them. // // For this output type definition: // @@ -196,7 +196,7 @@ type DecodeHookFuncKind func(reflect.Kind, reflect.Kind, interface{}) (interface // values. type DecodeHookFuncValue func(from reflect.Value, to reflect.Value) (interface{}, error) -// DecoderConfig is the configuration that is used to create a new decoder +// DecoderConfig is the configuration that is used to create a new Unmarshaler // and allows customization of various aspects of decoding. type DecoderConfig struct { // DecodeHook, if set, will be called before any decoding and any @@ -368,8 +368,8 @@ func WeakDecodeMetadata(input interface{}, output interface{}, metadata *Metadat return decoder.Decode(input) } -// NewDecoder returns a new decoder for the given configuration. Once -// a decoder has been returned, the same configuration must not be used +// NewDecoder returns a new Unmarshaler for the given configuration. Once +// a Unmarshaler has been returned, the same configuration must not be used // again. func NewDecoder(config *DecoderConfig) (*Decoder, error) { val := reflect.ValueOf(config.Result) @@ -462,6 +462,13 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e } } + if outVal.CanAddr() { + v := outVal.Addr() + if u, ok := v.Interface().(Unmarshaler); ok { + return u.DecodeMapstructure(input) + } + } + var err error outputKind := getKind(outVal) addMetaKey := true @@ -1580,3 +1587,7 @@ func dereferencePtrToStructIfNeeded(v reflect.Value, tagName string) reflect.Val } return v } + +type Unmarshaler interface { + DecodeMapstructure(interface{}) error +} diff --git a/mapstructure_examples_test.go b/mapstructure_examples_test.go index 31cff15..b648efa 100644 --- a/mapstructure_examples_test.go +++ b/mapstructure_examples_test.go @@ -293,6 +293,35 @@ func ExampleDecode_omitempty() { // &map[Age:0 FirstName:Somebody] } +type Someone struct { + Name string +} + +func (p *Someone) DecodeMapstructure(v interface{}) error { + if name, ok := v.(string); ok { + p.Name = name + return nil + } + return fmt.Errorf("undecipherable name") +} + +func ExampleDecode_custom_decoder_type() { + type Profile struct { + Friends []Someone + } + input := map[string]interface{}{ + "friends": []string{"Mitchell"}, + } + var result Profile + err := Decode(input, &result) + if err != nil { + panic(err) + } + fmt.Printf("%#v", result) + // Output: + // mapstructure.Profile{Friends:[]mapstructure.Someone{mapstructure.Someone{Name:"Mitchell"}}} +} + func ExampleDecode_decodeHookFunc() { type PersonLocation struct { Latitude float64