diff --git a/nodes.go b/nodes.go index 47a3aee..ea2fd60 100644 --- a/nodes.go +++ b/nodes.go @@ -3,10 +3,12 @@ package hclencoder import ( "errors" "fmt" + "log" "reflect" "sort" "strconv" "strings" + "unsafe" "github.com/hashicorp/hcl/hcl/ast" "github.com/hashicorp/hcl/hcl/token" @@ -27,6 +29,11 @@ const ( // the key for the value. SquashTag string = "squash" + // Optional tag is attached to fields of a struct that are not optional + // when being read by the HCL Parser. If the field is empty, these fields + // can be omitted from the resulting HCL output + OptionalTag string = "optional" + // UnusedKeysTag is a flag that indicates any unused keys found by the // decoder are stored in this field of type []string. This has the same // behavior as the OmitTag and is not encoded. @@ -241,9 +248,17 @@ func encodeStruct(in reflect.Value) (ast.Node, []*ast.ObjectKey, error) { // if the OmitEmptyTag is provided, check if the value is its zero value. rawVal := in.Field(i) + //can only determine emptiness if the field is settable from the current context if meta.omitEmpty { + var rawOut interface{} zeroVal := reflect.Zero(rawVal.Type()).Interface() - if reflect.DeepEqual(rawVal.Interface(), zeroVal) { + if rawVal.CanInterface() { + rawOut = rawVal.Interface() + } else if rawVal.CanAddr() { + rvAddr := reflect.NewAt(rawVal.Type(), unsafe.Pointer(rawVal.UnsafeAddr())) + rawOut = rvAddr.Elem().Interface() + } + if rawOut != nil && reflect.DeepEqual(rawOut, zeroVal) { continue } } @@ -375,6 +390,9 @@ func extractFieldMeta(f reflect.StructField) (meta fieldMeta) { meta.decodedFields = true case UnusedKeysTag: meta.unusedKeys = true + case OptionalTag: + log.Printf("Setting field to empty for field %s %s %s", f.PkgPath, f.Type, f.Name) + meta.omitEmpty = true } } } diff --git a/nodes_test.go b/nodes_test.go index 4c90f52..6f0d53a 100644 --- a/nodes_test.go +++ b/nodes_test.go @@ -375,6 +375,21 @@ func TestEncodeStruct(t *testing.T) { }, }}}, }, + { + ID: "optional field - empty", + Input: reflect.ValueOf(OptionalStruct{}), + Expected: &ast.ObjectType{List: &ast.ObjectList{Items: []*ast.ObjectItem{}}}, + }, + { + ID: "optional field - not empty", + Input: reflect.ValueOf(OptionalStruct{"foo"}), + Expected: &ast.ObjectType{List: &ast.ObjectList{Items: []*ast.ObjectItem{ + &ast.ObjectItem{ + Keys: []*ast.ObjectKey{{Token: token.Token{Type: token.IDENT, Text: "Bar"}}}, + Val: &ast.LiteralType{Token: token.Token{Type: token.STRING, Text: `"foo"`}}, + }, + }}}, + }, { ID: "nil field", Input: reflect.ValueOf(NillableStruct{}), @@ -695,6 +710,10 @@ type OmitEmptyStruct struct { Bar string `hcle:"omitempty"` } +type OptionalStruct struct { + Bar string `hcl:",optional"` +} + type InvalidStruct struct { Chan chan struct{} }