diff --git a/decoder.go b/decoder.go index dc9d45ae..5e32563c 100644 --- a/decoder.go +++ b/decoder.go @@ -404,6 +404,11 @@ func (d *decoder) decodeMap(name string, node ast.Node, result reflect.Value) er } func (d *decoder) decodePtr(name string, node ast.Node, result reflect.Value) error { + // if pointer is not nil, decode into existing value + if !result.IsNil() { + return d.decode(name, node, result.Elem()) + } + // Create an element of the concrete (non pointer) type and decode // into that. Then set the value of the pointer to this type. resultType := result.Type() @@ -512,7 +517,7 @@ func expandObject(node ast.Node, result reflect.Value) ast.Node { // we need to un-flatten the ast enough to decode newNode := &ast.ObjectItem{ Keys: []*ast.ObjectKey{ - &ast.ObjectKey{ + { Token: keyToken, }, }, @@ -631,10 +636,19 @@ func (d *decoder) decodeStruct(name string, node ast.Node, result reflect.Value) } } - usedKeys := make(map[string]struct{}) decodedFields := make([]string, 0, len(fields)) decodedFieldsVal := make([]reflect.Value, 0) unusedKeysVal := make([]reflect.Value, 0) + + // fill unusedNodeKeys with keys from the AST + // a slice because we have to do equals case fold to match Filter + unusedNodeKeys := make([]string, 0) + for _, item := range list.Items { + for _, k := range item.Keys { + unusedNodeKeys = append(unusedNodeKeys, k.Token.Value().(string)) + } + } + for _, f := range fields { field, fieldValue := f.field, f.val if !fieldValue.IsValid() { @@ -689,8 +703,8 @@ func (d *decoder) decodeStruct(name string, node ast.Node, result reflect.Value) continue } - // Track the used key - usedKeys[fieldName] = struct{}{} + // Track the used keys + unusedNodeKeys = removeCaseFold(unusedNodeKeys, fieldName) // Create the field name and decode. We range over the elements // because we actually want the value. @@ -723,6 +737,14 @@ func (d *decoder) decodeStruct(name string, node ast.Node, result reflect.Value) } } + if len(unusedNodeKeys) > 0 { + // like decodedFields, populated the unusedKeys field(s) + sort.Strings(unusedNodeKeys) + for _, v := range unusedKeysVal { + v.Set(reflect.ValueOf(unusedNodeKeys)) + } + } + return nil } @@ -734,3 +756,12 @@ func findNodeType() reflect.Type { value := reflect.ValueOf(nodeContainer).FieldByName("Node") return value.Type() } + +func removeCaseFold(xs []string, y string) []string { + for i, x := range xs { + if strings.EqualFold(x, y) { + return append(xs[:i], xs[i+1:]...) + } + } + return xs +} diff --git a/decoder_test.go b/decoder_test.go index 3ffdcfdc..70da72db 100644 --- a/decoder_test.go +++ b/decoder_test.go @@ -616,6 +616,35 @@ func TestDecode_structurePtr(t *testing.T) { } } +func TestDecode_nonNilStructurePtr(t *testing.T) { + type V struct { + Key int + Foo string + DontChange string + } + + actual := &V{ + Key: 42, + Foo: "foo", + DontChange: "don't change me", + } + + err := Decode(&actual, testReadFile(t, "flat.hcl")) + if err != nil { + t.Fatalf("err: %s", err) + } + + expected := &V{ + Key: 7, + Foo: "bar", + DontChange: "don't change me", + } + + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("Actual: %#v\n\nExpected: %#v", actual, expected) + } +} + func TestDecode_structureArray(t *testing.T) { // This test is extracted from a failure in Consul (consul.io), // hence the interesting structure naming. @@ -786,6 +815,36 @@ func TestDecode_structureMapInvalid(t *testing.T) { } } +func TestDecode_structureMapExtraKeys(t *testing.T) { + type hclVariable struct { + A int + B int + Found []string `hcl:",decodedFields"` + Extra []string `hcl:",unusedKeys"` + } + + q := hclVariable{ + A: 1, + B: 2, + Found: []string{"A", "B"}, + Extra: []string{"extra1", "extra2"}, + } + + var p hclVariable + ast, _ := Parse(testReadFile(t, "structure_map_extra_keys.hcl")) + DecodeObject(&p, ast) + if !reflect.DeepEqual(p, q) { + t.Fatal("not equal") + } + + var j hclVariable + ast, _ = Parse(testReadFile(t, "structure_map_extra_keys.json")) + DecodeObject(&j, ast) + if !reflect.DeepEqual(p, j) { + t.Fatal("not equal") + } +} + func TestDecode_interfaceNonPointer(t *testing.T) { var value interface{} err := Decode(value, testReadFile(t, "basic_int_string.hcl")) diff --git a/hcl/parser/parser_test.go b/hcl/parser/parser_test.go index 27021220..1188d09d 100644 --- a/hcl/parser/parser_test.go +++ b/hcl/parser/parser_test.go @@ -240,7 +240,7 @@ func TestListType_lineComment(t *testing.T) { comment := l.comment[i] if (lt.LineComment == nil) != (comment == "") { - t.Fatalf("bad: %s", lt) + t.Fatalf("bad: %s", lt.Token.Value()) } if comment == "" { diff --git a/test-fixtures/structure_map_extra_keys.hcl b/test-fixtures/structure_map_extra_keys.hcl new file mode 100644 index 00000000..b888c769 --- /dev/null +++ b/test-fixtures/structure_map_extra_keys.hcl @@ -0,0 +1,4 @@ +a = 1 +b = 2 +extra1 = 3 +extra2 = 4 diff --git a/test-fixtures/structure_map_extra_keys.json b/test-fixtures/structure_map_extra_keys.json new file mode 100644 index 00000000..2c049720 --- /dev/null +++ b/test-fixtures/structure_map_extra_keys.json @@ -0,0 +1,6 @@ +{ + "a": 1, + "b": 2, + "extra1": 3, + "extra2": 4 +}