Merge pull request #152 from hashicorp/jbardin/undecoded
missing fields when decoding JSON
This commit is contained in:
commit
ef8133da8c
@ -1,3 +1,3 @@
|
|||||||
sudo: false
|
sudo: false
|
||||||
language: go
|
language: go
|
||||||
go: 1.5
|
go: 1.7
|
||||||
|
1
Makefile
1
Makefile
@ -6,6 +6,7 @@ fmt: generate
|
|||||||
go fmt ./...
|
go fmt ./...
|
||||||
|
|
||||||
test: generate
|
test: generate
|
||||||
|
go get -t ./...
|
||||||
go test $(TEST) $(TESTARGS)
|
go test $(TEST) $(TESTARGS)
|
||||||
|
|
||||||
generate:
|
generate:
|
||||||
|
@ -12,5 +12,8 @@ install:
|
|||||||
go version
|
go version
|
||||||
|
|
||||||
go env
|
go env
|
||||||
|
|
||||||
|
go get -t ./...
|
||||||
|
|
||||||
build_script:
|
build_script:
|
||||||
- cmd: go test -v ./...
|
- cmd: go test -v ./...
|
||||||
|
59
decoder.go
59
decoder.go
@ -409,7 +409,6 @@ func (d *decoder) decodeSlice(name string, node ast.Node, result reflect.Value)
|
|||||||
if result.Kind() == reflect.Interface {
|
if result.Kind() == reflect.Interface {
|
||||||
result = result.Elem()
|
result = result.Elem()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the slice if it isn't nil
|
// Create the slice if it isn't nil
|
||||||
resultType := result.Type()
|
resultType := result.Type()
|
||||||
resultElemType := resultType.Elem()
|
resultElemType := resultType.Elem()
|
||||||
@ -443,6 +442,12 @@ func (d *decoder) decodeSlice(name string, node ast.Node, result reflect.Value)
|
|||||||
|
|
||||||
// Decode
|
// Decode
|
||||||
val := reflect.Indirect(reflect.New(resultElemType))
|
val := reflect.Indirect(reflect.New(resultElemType))
|
||||||
|
|
||||||
|
// if item is an object that was decoded from ambiguous JSON and
|
||||||
|
// flattened, make sure it's expanded if it needs to decode into a
|
||||||
|
// defined structure.
|
||||||
|
item := expandObject(item, val)
|
||||||
|
|
||||||
if err := d.decode(fieldName, item, val); err != nil {
|
if err := d.decode(fieldName, item, val); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -455,6 +460,57 @@ func (d *decoder) decodeSlice(name string, node ast.Node, result reflect.Value)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// expandObject detects if an ambiguous JSON object was flattened to a List which
|
||||||
|
// should be decoded into a struct, and expands the ast to properly deocode.
|
||||||
|
func expandObject(node ast.Node, result reflect.Value) ast.Node {
|
||||||
|
item, ok := node.(*ast.ObjectItem)
|
||||||
|
if !ok {
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
elemType := result.Type()
|
||||||
|
|
||||||
|
// our target type must be a struct
|
||||||
|
switch elemType.Kind() {
|
||||||
|
case reflect.Ptr:
|
||||||
|
switch elemType.Elem().Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
//OK
|
||||||
|
default:
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
case reflect.Struct:
|
||||||
|
//OK
|
||||||
|
default:
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
// A list value will have a key and field name. If it had more fields,
|
||||||
|
// it wouldn't have been flattened.
|
||||||
|
if len(item.Keys) != 2 {
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
keyToken := item.Keys[0].Token
|
||||||
|
item.Keys = item.Keys[1:]
|
||||||
|
|
||||||
|
// we need to un-flatten the ast enough to decode
|
||||||
|
newNode := &ast.ObjectItem{
|
||||||
|
Keys: []*ast.ObjectKey{
|
||||||
|
&ast.ObjectKey{
|
||||||
|
Token: keyToken,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Val: &ast.ObjectType{
|
||||||
|
List: &ast.ObjectList{
|
||||||
|
Items: []*ast.ObjectItem{item},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return newNode
|
||||||
|
}
|
||||||
|
|
||||||
func (d *decoder) decodeString(name string, node ast.Node, result reflect.Value) error {
|
func (d *decoder) decodeString(name string, node ast.Node, result reflect.Value) error {
|
||||||
switch n := node.(type) {
|
switch n := node.(type) {
|
||||||
case *ast.LiteralType:
|
case *ast.LiteralType:
|
||||||
@ -606,6 +662,7 @@ func (d *decoder) decodeStruct(name string, node ast.Node, result reflect.Value)
|
|||||||
// match (only object with the field), then we decode it exactly.
|
// match (only object with the field), then we decode it exactly.
|
||||||
// If it is a prefix match, then we decode the matches.
|
// If it is a prefix match, then we decode the matches.
|
||||||
filter := list.Filter(fieldName)
|
filter := list.Filter(fieldName)
|
||||||
|
|
||||||
prefixMatches := filter.Children()
|
prefixMatches := filter.Children()
|
||||||
matches := filter.Elem()
|
matches := filter.Elem()
|
||||||
if len(matches.Items) == 0 && len(prefixMatches.Items) == 0 {
|
if len(matches.Items) == 0 && len(prefixMatches.Items) == 0 {
|
||||||
|
236
decoder_test.go
236
decoder_test.go
@ -6,6 +6,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/hashicorp/hcl/hcl/ast"
|
"github.com/hashicorp/hcl/hcl/ast"
|
||||||
"github.com/hashicorp/hcl/testhelper"
|
"github.com/hashicorp/hcl/testhelper"
|
||||||
)
|
)
|
||||||
@ -851,3 +852,238 @@ func TestDecode_topLevelKeys(t *testing.T) {
|
|||||||
t.Errorf("bad source: %s", templates.Templates[1].Source)
|
t.Errorf("bad source: %s", templates.Templates[1].Source)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDecode_flattenedJSON(t *testing.T) {
|
||||||
|
// make sure we can also correctly extract a Name key too
|
||||||
|
type V struct {
|
||||||
|
Name string `hcl:",key"`
|
||||||
|
Description string
|
||||||
|
Default map[string]string
|
||||||
|
}
|
||||||
|
type Vars struct {
|
||||||
|
Variable []*V
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
JSON string
|
||||||
|
Out interface{}
|
||||||
|
Expected interface{}
|
||||||
|
}{
|
||||||
|
{ // Nested object, no sibling keys
|
||||||
|
JSON: `
|
||||||
|
{
|
||||||
|
"var_name": {
|
||||||
|
"default": {
|
||||||
|
"key1": "a",
|
||||||
|
"key2": "b"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
Out: &[]*V{},
|
||||||
|
Expected: &[]*V{
|
||||||
|
&V{
|
||||||
|
Name: "var_name",
|
||||||
|
Default: map[string]string{"key1": "a", "key2": "b"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{ // Nested object with a sibling key (this worked previously)
|
||||||
|
JSON: `
|
||||||
|
{
|
||||||
|
"var_name": {
|
||||||
|
"description": "Described",
|
||||||
|
"default": {
|
||||||
|
"key1": "a",
|
||||||
|
"key2": "b"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
Out: &[]*V{},
|
||||||
|
Expected: &[]*V{
|
||||||
|
&V{
|
||||||
|
Name: "var_name",
|
||||||
|
Description: "Described",
|
||||||
|
Default: map[string]string{"key1": "a", "key2": "b"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{ // Multiple nested objects, one with a sibling key
|
||||||
|
JSON: `
|
||||||
|
{
|
||||||
|
"variable": {
|
||||||
|
"var_1": {
|
||||||
|
"default": {
|
||||||
|
"key1": "a",
|
||||||
|
"key2": "b"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"var_2": {
|
||||||
|
"description": "Described",
|
||||||
|
"default": {
|
||||||
|
"key1": "a",
|
||||||
|
"key2": "b"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
Out: &Vars{},
|
||||||
|
Expected: &Vars{
|
||||||
|
Variable: []*V{
|
||||||
|
&V{
|
||||||
|
Name: "var_1",
|
||||||
|
Default: map[string]string{"key1": "a", "key2": "b"},
|
||||||
|
},
|
||||||
|
&V{
|
||||||
|
Name: "var_2",
|
||||||
|
Description: "Described",
|
||||||
|
Default: map[string]string{"key1": "a", "key2": "b"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{ // Nested object to maps
|
||||||
|
JSON: `
|
||||||
|
{
|
||||||
|
"variable": {
|
||||||
|
"var_name": {
|
||||||
|
"description": "Described",
|
||||||
|
"default": {
|
||||||
|
"key1": "a",
|
||||||
|
"key2": "b"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
Out: &[]map[string]interface{}{},
|
||||||
|
Expected: &[]map[string]interface{}{
|
||||||
|
{
|
||||||
|
"variable": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"var_name": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"description": "Described",
|
||||||
|
"default": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"key1": "a",
|
||||||
|
"key2": "b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{ // Nested object to maps without a sibling key should decode the same as above
|
||||||
|
JSON: `
|
||||||
|
{
|
||||||
|
"variable": {
|
||||||
|
"var_name": {
|
||||||
|
"default": {
|
||||||
|
"key1": "a",
|
||||||
|
"key2": "b"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
Out: &[]map[string]interface{}{},
|
||||||
|
Expected: &[]map[string]interface{}{
|
||||||
|
{
|
||||||
|
"variable": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"var_name": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"default": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"key1": "a",
|
||||||
|
"key2": "b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{ // Nested objects, one with a sibling key, and one without
|
||||||
|
JSON: `
|
||||||
|
{
|
||||||
|
"variable": {
|
||||||
|
"var_1": {
|
||||||
|
"default": {
|
||||||
|
"key1": "a",
|
||||||
|
"key2": "b"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"var_2": {
|
||||||
|
"description": "Described",
|
||||||
|
"default": {
|
||||||
|
"key1": "a",
|
||||||
|
"key2": "b"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
Out: &[]map[string]interface{}{},
|
||||||
|
Expected: &[]map[string]interface{}{
|
||||||
|
{
|
||||||
|
"variable": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"var_1": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"default": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"key1": "a",
|
||||||
|
"key2": "b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"variable": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"var_2": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"description": "Described",
|
||||||
|
"default": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"key1": "a",
|
||||||
|
"key2": "b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
err := Decode(tc.Out, tc.JSON)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("[%d] err: %s", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(tc.Out, tc.Expected) {
|
||||||
|
t.Fatalf("[%d]\ngot: %s\nexpected: %s\n", i, spew.Sdump(tc.Out), spew.Sdump(tc.Expected))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user