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

custom post decoder #284

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions mapstructure.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,10 @@ type DecoderConfig struct {
// field name or tag. Defaults to `strings.EqualFold`. This can be used
// to implement case-sensitive tag values, support snake casing, etc.
MatchName func(mapKey, fieldName string) bool

// If CustomPostDecoder is true, then after standard decoding
// for types that implements CustomDecoder the CustomDecoder.PostDecode function is run.
CustomPostDecoder bool
}

// A Decoder takes a raw interface value and turns it into structured
Expand All @@ -285,6 +289,15 @@ type Decoder struct {
config *DecoderConfig
}

// CustomDecoder is the interface implemented by types that
// can decode themselves.
// This interface does something similar to json Marshaler .
//
// PostDecode is run after main decode implementation for each type.
type CustomDecoder interface {
PostDecode() error
}

// Metadata contains information about decoding a structure that
// is tedious or difficult to get otherwise.
type Metadata struct {
Expand Down Expand Up @@ -495,6 +508,12 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e
return fmt.Errorf("%s: unsupported type: %s", name, outputKind)
}

if d.config.CustomPostDecoder {
if m, ok := outVal.Interface().(CustomDecoder); ok && m != nil {
err = m.PostDecode()
}
}

// If we reached here, then we successfully decoded SOMETHING, so
// mark the key as used if we're tracking metainput.
if addMetaKey && d.config.Metadata != nil && name != "" {
Expand Down
129 changes: 129 additions & 0 deletions mapstructure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2732,6 +2732,135 @@ func TestDecoder_IgnoreUntaggedFields(t *testing.T) {
}
}

type CustomDecoder_PostDecode_Brief struct {
FullName string
PrimaryEmail string
}

type CustomDecoder_PostDecode_Contact struct {
Name string `mapstructure:"name"`
LastName string `mapstructure:"last_name"`
Emails []string `mapstructure:"emails"`
Brief *CustomDecoder_PostDecode_Brief
}

func (t *CustomDecoder_PostDecode_Contact) PostDecode() error {
primaryEmail := ""

if t.Emails != nil && len(t.Emails) > 0 {
primaryEmail = t.Emails[0]
}

t.Brief = &CustomDecoder_PostDecode_Brief{
FullName: t.Name + " " + t.LastName,
PrimaryEmail: primaryEmail,
}

return nil
}

func TestDecoder_CustomDecoder_PostDecode(t *testing.T) {
t.Parallel()

input := []map[string]interface{}{
{
"name": "John",
"last_name": "Doe",
"emails": []string{"[email protected]"},
},
}

expected := []*CustomDecoder_PostDecode_Contact{
{
Name: "John",
LastName: "Doe",
Emails: []string{"[email protected]"},
Brief: &CustomDecoder_PostDecode_Brief{
FullName: "John Doe",
PrimaryEmail: "[email protected]",
},
},
}

var actual []*CustomDecoder_PostDecode_Contact

config := &DecoderConfig{
Result: &actual,
IgnoreUntaggedFields: true,
CustomPostDecoder: true,
}

decoder, err := NewDecoder(config)
if err != nil {
t.Fatalf("err: %s", err)
}

err = decoder.Decode(input)
if err != nil {
t.Fatalf("err: %s", err)
}

if !reflect.DeepEqual(expected, actual) {
t.Fatalf("Decode() expected: %#v, got: %#v", expected, actual)
}
}

type CustomDecoder_PostDecode_PrimaryEmail string

func (s CustomDecoder_PostDecode_PrimaryEmail) ptr() *CustomDecoder_PostDecode_PrimaryEmail {
return &s
}

func (s *CustomDecoder_PostDecode_PrimaryEmail) PostDecode() error {
v := *s

*s = "#1 " + v

return nil
}

func TestDecoder_CustomDecoder_PostDecodeC(t *testing.T) {
t.Parallel()

input := []map[string]interface{}{
{
"primary_email": "[email protected]",
},
}

type Target struct {
PrimaryEmail *CustomDecoder_PostDecode_PrimaryEmail `mapstructure:"primary_email"`
}

expected := []*Target{
{
PrimaryEmail: CustomDecoder_PostDecode_PrimaryEmail("#1 [email protected]").ptr(),
},
}

var actual []*Target

config := &DecoderConfig{
Result: &actual,
IgnoreUntaggedFields: true,
CustomPostDecoder: true,
}

decoder, err := NewDecoder(config)
if err != nil {
t.Fatalf("err: %s", err)
}

err = decoder.Decode(input)
if err != nil {
t.Fatalf("err: %s", err)
}

if !reflect.DeepEqual(expected, actual) {
t.Fatalf("Decode() expected: %#v, got: %#v", expected, actual)
}
}

func testSliceInput(t *testing.T, input map[string]interface{}, expected *Slice) {
var result Slice
err := Decode(input, &result)
Expand Down