diff --git a/unmarshaler.go b/unmarshaler.go index b514413..fd2e42f 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -370,6 +370,13 @@ func (d *decoder) handleKeyPart(key ast.Iterator, v reflect.Value, nextFn handle // First, dispatch over v to make sure it is a valid object. // There is no guarantee over what it could be. switch v.Kind() { + case reflect.Ptr: + elem := v.Elem() + if !elem.IsValid() { + v.Set(reflect.New(v.Type().Elem())) + } + elem = v.Elem() + return d.handleKeyPart(key, elem, nextFn, makeFn) case reflect.Map: // Create the key for the map element. For now assume it's a string. mk := reflect.ValueOf(string(key.Node().Data)) diff --git a/unmarshaler_test.go b/unmarshaler_test.go index 35f4ad3..1deb890 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -1,6 +1,7 @@ package toml_test import ( + "encoding/json" "errors" "fmt" "math" @@ -2120,6 +2121,177 @@ bar = 42 } } +func TestUnmarshal_RecursiveTable(t *testing.T) { + type Foo struct { + I int + F *Foo + } + + examples := []struct { + desc string + input string + expected string + err bool + }{ + { + desc: "simplest", + input: ` + I=1 + `, + expected: `{"I":1,"F":null}`, + }, + { + desc: "depth 1", + input: ` + I=1 + [F] + I=2 + `, + expected: `{"I":1,"F":{"I":2,"F":null}}`, + }, + { + desc: "depth 3", + input: ` + I=1 + [F] + I=2 + [F.F] + I=3 + `, + expected: `{"I":1,"F":{"I":2,"F":{"I":3,"F":null}}}`, + }, + { + desc: "depth 4", + input: ` + I=1 + [F] + I=2 + [F.F] + I=3 + [F.F.F] + I=4 + `, + expected: `{"I":1,"F":{"I":2,"F":{"I":3,"F":{"I":4,"F":null}}}}`, + }, + { + desc: "skip mid step", + input: ` + I=1 + [F.F] + I=7 + `, + expected: `{"I":1,"F":{"I":0,"F":{"I":7,"F":null}}}`, + }, + } + + for _, ex := range examples { + e := ex + t.Run(e.desc, func(t *testing.T) { + foo := Foo{} + err := toml.Unmarshal([]byte(e.input), &foo) + if e.err { + require.Error(t, err) + } else { + require.NoError(t, err) + j, err := json.Marshal(foo) + require.NoError(t, err) + assert.Equal(t, e.expected, string(j)) + } + }) + } +} + +func TestUnmarshal_RecursiveTableArray(t *testing.T) { + type Foo struct { + I int + F []*Foo + } + + examples := []struct { + desc string + input string + expected string + err bool + }{ + { + desc: "simplest", + input: ` + I=1 + F=[] + `, + expected: `{"I":1,"F":[]}`, + }, + { + desc: "depth 1", + input: ` + I=1 + [[F]] + I=2 + F=[] + `, + expected: `{"I":1,"F":[{"I":2,"F":[]}]}`, + }, + { + desc: "depth 2", + input: ` + I=1 + [[F]] + I=2 + [[F.F]] + I=3 + F=[] + `, + expected: `{"I":1,"F":[{"I":2,"F":[{"I":3,"F":[]}]}]}`, + }, + { + desc: "depth 3", + input: ` + I=1 + [[F]] + I=2 + [[F.F]] + I=3 + [[F.F.F]] + I=4 + F=[] + `, + expected: `{"I":1,"F":[{"I":2,"F":[{"I":3,"F":[{"I":4,"F":[]}]}]}]}`, + }, + { + desc: "depth 4", + input: ` + I=1 + [[F]] + I=2 + [[F.F]] + I=3 + [[F.F.F]] + I=4 + [[F.F.F.F]] + I=5 + F=[] + `, + expected: `{"I":1,"F":[{"I":2,"F":[{"I":3,"F":[{"I":4,"F":[{"I":5,"F":[]}]}]}]}]}`, + }, + } + + for _, ex := range examples { + e := ex + t.Run(e.desc, func(t *testing.T) { + foo := Foo{} + err := toml.Unmarshal([]byte(e.input), &foo) + if e.err { + require.Error(t, err) + } else { + require.NoError(t, err) + j, err := json.Marshal(foo) + require.NoError(t, err) + assert.Equal(t, e.expected, string(j)) + } + }) + } +} + func ExampleDecoder_SetStrict() { type S struct { Key1 string