From bdd93440d82c6b6fc4818758faaee8e8e0f212cc Mon Sep 17 00:00:00 2001 From: James Bardin Date: Thu, 15 Sep 2016 15:36:40 -0400 Subject: [PATCH] Simplify the code to expand objects Now that we know only individual items in a slice need to be expanded, we can simplify the code flow to expand the ast in place while decoding. --- decoder.go | 120 +++++++++++++++++------------------------------- decoder_test.go | 58 +++++++++++++++++++++++ 2 files changed, 100 insertions(+), 78 deletions(-) diff --git a/decoder.go b/decoder.go index f733052..c8a077d 100644 --- a/decoder.go +++ b/decoder.go @@ -418,20 +418,6 @@ func (d *decoder) decodeSlice(name string, node ast.Node, result reflect.Value) resultSliceType, 0, 0) } - // if node is an object that was decoded from ambiguous JSON and flattened - // as a list, decode into a single value in the slice. - if objAsList(node, resultElemType) { - val := reflect.Indirect(reflect.New(resultElemType)) - err := d.decodeFlatObj(name+"[0]", node, val) - if err != nil { - return err - } - - result = reflect.Append(result, val) - set.Set(result) - return nil - } - // Figure out the items we'll be copying into the slice var items []ast.Node switch n := node.(type) { @@ -456,6 +442,12 @@ func (d *decoder) decodeSlice(name string, node ast.Node, result reflect.Value) // Decode 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 { return err } @@ -468,16 +460,40 @@ func (d *decoder) decodeSlice(name string, node ast.Node, result reflect.Value) return nil } -// decode a flattened object into a single struct -func (d decoder) decodeFlatObj(name string, node ast.Node, result reflect.Value) error { - list := node.(*ast.ObjectList) - var keyToken token.Token - // get the key token, and strip it out of the key-val nodes - for _, item := range list.Items { - keyToken = item.Keys[0].Token - item.Keys = item.Keys[1:] +// 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{ @@ -486,65 +502,13 @@ func (d decoder) decodeFlatObj(name string, node ast.Node, result reflect.Value) }, }, Val: &ast.ObjectType{ - List: list, + List: &ast.ObjectList{ + Items: []*ast.ObjectItem{item}, + }, }, } - if err := d.decode(name, newNode, result); err != nil { - return err - } - - return nil -} - -// objAsList detects if an ambiguous JSON object was flattened to a List which -// should be decoded into a struct. -func objAsList(node ast.Node, elemType reflect.Type) bool { - objList, ok := node.(*ast.ObjectList) - if !ok { - return false - } - - // our target type must be a struct - switch elemType.Kind() { - case reflect.Ptr: - switch elemType.Elem().Kind() { - case reflect.Struct: - //OK - default: - return false - } - case reflect.Struct: - //OK - default: - return false - } - - fieldNames := make(map[string]bool) - prevKey := "" - - for _, item := range objList.Items { - // a list value will have a key and field name - if len(item.Keys) != 2 { - return false - } - - key := item.Keys[0].Token.Value().(string) - if prevKey != "" && key != prevKey { - // the same object will have all the same key - return false - } - prevKey = key - - fieldName := item.Keys[1].Token.Value().(string) - - if ok := fieldNames[fieldName]; ok { - // A single struct won't have duplicate fields. - return false - } - } - - return true + return newNode } func (d *decoder) decodeString(name string, node ast.Node, result reflect.Value) error { diff --git a/decoder_test.go b/decoder_test.go index 14abb5c..5a8404c 100644 --- a/decoder_test.go +++ b/decoder_test.go @@ -1016,6 +1016,64 @@ func TestDecode_flattenedJSON(t *testing.T) { }, }, }, + + { // 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 {