package json import ( "sync" "github.com/hashicorp/go-multierror" "github.com/hashicorp/hcl/hcl/ast" ) // jsonErrors are the errors built up from parsing. These should not // be accessed directly. var jsonErrors []error var jsonLock sync.Mutex var jsonResult *ast.File // Parse parses the given string and returns the result. func Parse(v string) (*ast.File, error) { jsonLock.Lock() defer jsonLock.Unlock() jsonErrors = nil jsonResult = nil // Parse lex := &jsonLex{Input: v} jsonParse(lex) // If we have an error in the lexer itself, return it if lex.err != nil { return nil, lex.err } // If we have a result, flatten it. This is an operation we take on // to make our AST look more like traditional HCL. This makes parsing // it a lot easier later. if jsonResult != nil { flattenObjects(jsonResult) } // Build up the errors var err error if len(jsonErrors) > 0 { err = &multierror.Error{Errors: jsonErrors} jsonResult = nil } return jsonResult, err } // flattenObjects takes an AST node, walks it, and flattens func flattenObjects(node ast.Node) { ast.Walk(jsonResult, func(n ast.Node) bool { // We only care about lists, because this is what we modify list, ok := n.(*ast.ObjectList) if !ok { return true } // Rebuild the item list items := make([]*ast.ObjectItem, 0, len(list.Items)) frontier := make([]*ast.ObjectItem, len(list.Items)) copy(frontier, list.Items) for len(frontier) > 0 { // Pop the current item n := len(frontier) item := frontier[n-1] frontier = frontier[:n-1] switch v := item.Val.(type) { case *ast.ObjectType: items, frontier = flattenObjectType(v, item, items, frontier) case *ast.ListType: items, frontier = flattenListType(v, item, items, frontier) default: items = append(items, item) } } // Reverse the list since the frontier model runs things backwards for i := len(items)/2 - 1; i >= 0; i-- { opp := len(items) - 1 - i items[i], items[opp] = items[opp], items[i] } // Done! Set the original items list.Items = items return true }) } func flattenListType( ot *ast.ListType, item *ast.ObjectItem, items []*ast.ObjectItem, frontier []*ast.ObjectItem) ([]*ast.ObjectItem, []*ast.ObjectItem) { // All the elements of this object must also be objects! for _, subitem := range ot.List { if _, ok := subitem.(*ast.ObjectType); !ok { items = append(items, item) return items, frontier } } // Great! We have a match go through all the items and flatten for _, elem := range ot.List { // This won't fail since we verified it earlier ot := elem.(*ast.ObjectType) // Go over all the subitems and merge them in for _, subitem := range ot.List.Items { // Copy the new key keys := make([]*ast.ObjectKey, len(item.Keys)+len(subitem.Keys)) copy(keys, item.Keys) copy(keys[len(item.Keys):], subitem.Keys) // Add it to the frontier so that we can recurse frontier = append(frontier, &ast.ObjectItem{ Keys: keys, Assign: item.Assign, Val: subitem.Val, LeadComment: item.LeadComment, LineComment: item.LineComment, }) } } return items, frontier } func flattenObjectType( ot *ast.ObjectType, item *ast.ObjectItem, items []*ast.ObjectItem, frontier []*ast.ObjectItem) ([]*ast.ObjectItem, []*ast.ObjectItem) { // All the elements of this object must also be objects! for _, subitem := range ot.List.Items { if _, ok := subitem.Val.(*ast.ObjectType); !ok { items = append(items, item) return items, frontier } } // Great! We have a match go through all the items and flatten for _, subitem := range ot.List.Items { // Copy the new key keys := make([]*ast.ObjectKey, len(item.Keys)+len(subitem.Keys)) copy(keys, item.Keys) copy(keys[len(item.Keys):], subitem.Keys) // Add it to the frontier so that we can recurse frontier = append(frontier, &ast.ObjectItem{ Keys: keys, Assign: item.Assign, Val: subitem.Val, LeadComment: item.LeadComment, LineComment: item.LineComment, }) } return items, frontier }