mitchellh / mapstructure

Go library for decoding generic map values into native Go structures and vice versa.
https://gist.github.com/mitchellh/90029601268e59a29e64e55bab1c5bdc
MIT License
7.89k stars 669 forks source link

Pointers decoding inconsistency (and discussion) #312

Open mullerch opened 1 year ago

mullerch commented 1 year ago

When decoding a struct containing a string pointer and a struct pointer into a map, the resulting map has a pointer to the string but not a pointer to the struct.

func TestDecode_structToMap_PointersConsistency(t *testing.T) {
    type SourceChild struct {
        String string `mapstructure:"string"`
    }

    type SourceParent struct {
        ChildPtr  *SourceChild `mapstructure:"child-ptr"`
        StringPtr *string      `mapstructure:"string-ptr"`
    }

    var target map[string]interface{}

    var value = "goodbye"

    source := SourceParent{
        ChildPtr: &SourceChild{
            String: "hello",
        },
        StringPtr: &value,
    }

    if err := Decode(source, &target); err != nil {
        t.Fatalf("got error: %s", err)
    }

    expected := map[string]interface{}{
        "child-ptr": map[string]interface{}{
            "string": "hello",
        },
        "string-ptr": "goodbye",
    }

    expectedPointers := map[string]interface{}{
        "child-ptr": &map[string]interface{}{
            "string": "hello",
        },
        "string-ptr": stringPtr("goodbye"),
    }

    if !reflect.DeepEqual(target, expected) && !reflect.DeepEqual(target, expectedPointers) {
        t.Fatalf("bad: \nexpected        : %#v\nexpectedPointers: %#v\nresult          : %#v", expected, expectedPointers, target)
    }
}

Produces:

=== RUN   TestDecode_structToMap_PointersConsistency
    mapstructure_test.go:2941: bad: 
        expected        : map[string]interface {}{"child-ptr":map[string]interface {}{"string":"hello"}, "string-ptr":"goodbye"}
        expectedPointers: map[string]interface {}{"child-ptr":(*map[string]interface {})(0xc0000a8058), "string-ptr":(*string)(0xc00008eec0)}
        result          : map[string]interface {}{"child-ptr":map[string]interface {}{"string":"hello"}, "string-ptr":(*string)(0xc00008ede0)}
--- FAIL: TestDecode_structToMap_PointersConsistency (0.00s)

mapstructure version https://github.com/mitchellh/mapstructure/commit/bf980b35cac4dfd34e05254ee5aba086504c3f96 go version go1.18.7 linux/amd64

I can't see a reason why structures and base types should behave differently. I would expect to either keep the pointers or remove them. Can you please explain to me if this is a design choice or not? My guess is that struct pointers where not supported before PR #271 which made it dereference struct pointers systematically.

Discussion

Should all pointers be represented in the target map or none?

From the perspective of my use case, the pointer representation in the map is not necessary.

Here are the benefits of the pointers usage in this context:

For these reasons, I would suggest to:

As an alternative, the options could be merge in a "ReferencesHandling" option with values legacy/preserve/discard.

Please let me know what you think and if there are any thechnical constraints that would not allow this. I would be happy to contribute.

wuxq commented 1 year ago

I think this is very useful. I'm Looking forward to it.