From a56a48ca89251f062badc59568b44d79bd913077 Mon Sep 17 00:00:00 2001 From: DasPoet Date: Tue, 16 Dec 2025 12:02:06 +0100 Subject: [PATCH 1/4] fix: decode into nil struct fields --- pkg/decodini/decode.go | 10 +++++++++- pkg/decodini/decode_test.go | 8 ++++++-- pkg/decodini/encode.go | 2 +- 3 files changed, 16 insertions(+), 4 deletions(-) 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..7b1eeff 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,7 @@ func TestDecode_TwoEmbeddedStructs(t *testing.T) { expected := Outer{EmbeddedA: EmbeddedA{A: "foo"}, EmbeddedB: EmbeddedB{B: 7}} a.Equal(expected, to) } + +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() { From a2c5e9e191c47f6e3d2e1c479c728fb961417b10 Mon Sep 17 00:00:00 2001 From: lukasl-dev Date: Tue, 16 Dec 2025 13:47:31 +0100 Subject: [PATCH 2/4] Add further decode test cases --- pkg/decodini/decode_test.go | 59 +++++++++++++++++++++++++++++++++++++ pkg/decodini/issues_test.go | 2 +- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/pkg/decodini/decode_test.go b/pkg/decodini/decode_test.go index 7b1eeff..f844b98 100644 --- a/pkg/decodini/decode_test.go +++ b/pkg/decodini/decode_test.go @@ -210,6 +210,65 @@ func TestDecode_TwoEmbeddedStructs(t *testing.T) { 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/issues_test.go b/pkg/decodini/issues_test.go index d954ab4..487b3dd 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 { From f7dccc54689c0378e0e3e3fed8a3e722beaed065 Mon Sep 17 00:00:00 2001 From: DasPoet Date: Tue, 16 Dec 2025 13:53:35 +0100 Subject: [PATCH 3/4] test: add regression test --- pkg/decodini/issues_test.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/pkg/decodini/issues_test.go b/pkg/decodini/issues_test.go index 487b3dd..a87af25 100644 --- a/pkg/decodini/issues_test.go +++ b/pkg/decodini/issues_test.go @@ -34,3 +34,25 @@ func TestIssue_2(t *testing.T) { } a.Equal(expected, to) } + +// http://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) +} From 4c6c4910a59167e95e7c4f2c32f375525f7de1be Mon Sep 17 00:00:00 2001 From: lukasl-dev Date: Tue, 16 Dec 2025 13:55:43 +0100 Subject: [PATCH 4/4] Change http to https in issue test url --- pkg/decodini/issues_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/decodini/issues_test.go b/pkg/decodini/issues_test.go index a87af25..edb6338 100644 --- a/pkg/decodini/issues_test.go +++ b/pkg/decodini/issues_test.go @@ -35,7 +35,7 @@ func TestIssue_2(t *testing.T) { a.Equal(expected, to) } -// http://github.com/lukasl-dev/decodini/issues/4 +// https://github.com/lukasl-dev/decodini/issues/4 func TestIssue_4(t *testing.T) { type Payload struct { Value *int `decodini:"value"`