From c3fd9f0541cf59e49d8a8afab73169800b35bf42 Mon Sep 17 00:00:00 2001 From: jmcshane Date: Thu, 21 Jan 2021 19:25:24 -0600 Subject: [PATCH 1/2] Allowing objects that cannot be accessed safely by Interface to be acquired through addressible --- nodes.go | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) 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 } } } From 22bf0c3e1ac0505ad9e6718c2e77a18771d736a8 Mon Sep 17 00:00:00 2001 From: jmcshane Date: Thu, 21 Jan 2021 19:28:12 -0600 Subject: [PATCH 2/2] Adding test for optional fields being ignored in output --- nodes_test.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) 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{} }