diff --git a/pkg/decodini/decode.go b/pkg/decodini/decode.go index f5b05fa..a5db427 100644 --- a/pkg/decodini/decode.go +++ b/pkg/decodini/decode.go @@ -51,7 +51,15 @@ func Decode[T any](dec *Decoding, tr *Tree) (T, error) { } func (dec *Decoding) into(node *Tree, target DecodeTarget) error { - if target.Value.Kind() == reflect.Ptr { + if target.Value.Kind() == reflect.Pointer { + if target.Value.IsNil() { + if !target.Value.CanSet() { + return newDecodeErrorf(node, target, "cannot decode into unsettable value") + } + + target.Value.Set(reflect.New(target.Value.Type().Elem())) + } + target.Value = target.Value.Elem() return dec.into(node, target) } diff --git a/pkg/decodini/decode_test.go b/pkg/decodini/decode_test.go index 392d25c..f844b98 100644 --- a/pkg/decodini/decode_test.go +++ b/pkg/decodini/decode_test.go @@ -85,7 +85,7 @@ func TestDecode_ShallowStruct_to_ShallowMap(t *testing.T) { func TestDecode_ShallowMap_to_ShallowStruct(t *testing.T) { type toStruct struct { A string `decodini:"a"` - B int + B *int C int `decodini:"-"` x bool } @@ -104,7 +104,7 @@ func TestDecode_ShallowMap_to_ShallowStruct(t *testing.T) { expected := toStruct{ A: "foo", - B: 42, + B: ptr(42), C: 0, x: false, } @@ -209,3 +209,66 @@ func TestDecode_TwoEmbeddedStructs(t *testing.T) { expected := Outer{EmbeddedA: EmbeddedA{A: "foo"}, EmbeddedB: EmbeddedB{B: 7}} a.Equal(expected, to) } + +func TestDecode_Slice_to_SliceOfPointers(t *testing.T) { + a := assert.New(t) + + from := []int{1, 2, 3} + tr := Encode(nil, from) + + to, err := Decode[[]*int](nil, tr) + a.NoError(err) + + a.Equal(len(from), len(to)) + for i, v := range from { + if a.NotNil(to[i]) { + a.Equal(v, *to[i]) + } + } +} + +func TestDecode_Map_to_MapOfPointers(t *testing.T) { + a := assert.New(t) + + from := map[string]int{ + "a": 1, + "b": 2, + } + tr := Encode(nil, from) + + to, err := Decode[map[string]*int](nil, tr) + a.NoError(err) + + a.Equal(len(from), len(to)) + for k, v := range from { + ptrVal, ok := to[k] + a.True(ok) + if a.NotNil(ptrVal) { + a.Equal(v, *ptrVal) + } + } +} + +func TestDecode_PointerToPointerStructField(t *testing.T) { + type toStruct struct { + V **int `decodini:"v"` + } + + a := assert.New(t) + + from := map[string]any{ + "v": 42, + } + tr := Encode(nil, from) + + to, err := Decode[toStruct](nil, tr) + a.NoError(err) + + a.NotNil(to.V) + a.NotNil(*to.V) + a.Equal(42, **to.V) +} + +func ptr[T any](value T) *T { + return &value +} diff --git a/pkg/decodini/encode.go b/pkg/decodini/encode.go index 09792aa..c20bbc6 100644 --- a/pkg/decodini/encode.go +++ b/pkg/decodini/encode.go @@ -33,7 +33,7 @@ func Encode(enc *Encoding, val any) *Tree { func encode(enc *Encoding, parent *Tree, name any, val reflect.Value) *Tree { switch val.Kind() { - case reflect.Ptr: + case reflect.Pointer: return encode(enc, parent, name, val.Elem()) case reflect.Interface: if !val.IsNil() { diff --git a/pkg/decodini/issues_test.go b/pkg/decodini/issues_test.go index d954ab4..edb6338 100644 --- a/pkg/decodini/issues_test.go +++ b/pkg/decodini/issues_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/assert" ) -// http://github.com/lukasl-dev/decodini/issues/2 +// https://github.com/lukasl-dev/decodini/issues/2 func TestIssue_2(t *testing.T) { type ( Embedded struct { @@ -34,3 +34,25 @@ func TestIssue_2(t *testing.T) { } a.Equal(expected, to) } + +// https://github.com/lukasl-dev/decodini/issues/4 +func TestIssue_4(t *testing.T) { + type Payload struct { + Value *int `decodini:"value"` + } + + a := assert.New(t) + + from := map[string]any{ + "value": 42, + } + tr := Encode(nil, from) + + to, err := Decode[Payload](nil, tr) + a.NoError(err) + + expected := Payload{ + Value: ptr(42), + } + a.Equal(expected, to) +}