github.com/hashicorp/hcl/v2@v2.20.0/json/public.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package json 5 6 import ( 7 "fmt" 8 "io/ioutil" 9 "os" 10 11 "github.com/hashicorp/hcl/v2" 12 ) 13 14 // Parse attempts to parse the given buffer as JSON and, if successful, returns 15 // a hcl.File for the HCL configuration represented by it. 16 // 17 // This is not a generic JSON parser. Instead, it deals only with the profile 18 // of JSON used to express HCL configuration. 19 // 20 // The returned file is valid only if the returned diagnostics returns false 21 // from its HasErrors method. If HasErrors returns true, the file represents 22 // the subset of data that was able to be parsed, which may be none. 23 func Parse(src []byte, filename string) (*hcl.File, hcl.Diagnostics) { 24 return ParseWithStartPos(src, filename, hcl.Pos{Byte: 0, Line: 1, Column: 1}) 25 } 26 27 // ParseWithStartPos attempts to parse like json.Parse, but unlike json.Parse 28 // you can pass a start position of the given JSON as a hcl.Pos. 29 // 30 // In most cases json.Parse should be sufficient, but it can be useful for parsing 31 // a part of JSON with correct positions. 32 func ParseWithStartPos(src []byte, filename string, start hcl.Pos) (*hcl.File, hcl.Diagnostics) { 33 rootNode, diags := parseFileContent(src, filename, start) 34 35 switch rootNode.(type) { 36 case *objectVal, *arrayVal: 37 // okay 38 default: 39 diags = diags.Append(&hcl.Diagnostic{ 40 Severity: hcl.DiagError, 41 Summary: "Root value must be object", 42 Detail: "The root value in a JSON-based configuration must be either a JSON object or a JSON array of objects.", 43 Subject: rootNode.StartRange().Ptr(), 44 }) 45 46 // Since we've already produced an error message for this being 47 // invalid, we'll return an empty placeholder here so that trying to 48 // extract content from our root body won't produce a redundant 49 // error saying the same thing again in more general terms. 50 fakePos := hcl.Pos{ 51 Byte: 0, 52 Line: 1, 53 Column: 1, 54 } 55 fakeRange := hcl.Range{ 56 Filename: filename, 57 Start: fakePos, 58 End: fakePos, 59 } 60 rootNode = &objectVal{ 61 Attrs: []*objectAttr{}, 62 SrcRange: fakeRange, 63 OpenRange: fakeRange, 64 } 65 } 66 67 file := &hcl.File{ 68 Body: &body{ 69 val: rootNode, 70 }, 71 Bytes: src, 72 Nav: navigation{rootNode}, 73 } 74 return file, diags 75 } 76 77 // ParseExpression parses the given buffer as a standalone JSON expression, 78 // returning it as an instance of Expression. 79 func ParseExpression(src []byte, filename string) (hcl.Expression, hcl.Diagnostics) { 80 return ParseExpressionWithStartPos(src, filename, hcl.Pos{Byte: 0, Line: 1, Column: 1}) 81 } 82 83 // ParseExpressionWithStartPos parses like json.ParseExpression, but unlike 84 // json.ParseExpression you can pass a start position of the given JSON 85 // expression as a hcl.Pos. 86 func ParseExpressionWithStartPos(src []byte, filename string, start hcl.Pos) (hcl.Expression, hcl.Diagnostics) { 87 node, diags := parseExpression(src, filename, start) 88 return &expression{src: node}, diags 89 } 90 91 // ParseFile is a convenience wrapper around Parse that first attempts to load 92 // data from the given filename, passing the result to Parse if successful. 93 // 94 // If the file cannot be read, an error diagnostic with nil context is returned. 95 func ParseFile(filename string) (*hcl.File, hcl.Diagnostics) { 96 f, err := os.Open(filename) 97 if err != nil { 98 return nil, hcl.Diagnostics{ 99 { 100 Severity: hcl.DiagError, 101 Summary: "Failed to open file", 102 Detail: fmt.Sprintf("The file %q could not be opened.", filename), 103 }, 104 } 105 } 106 defer f.Close() 107 108 src, err := ioutil.ReadAll(f) 109 if err != nil { 110 return nil, hcl.Diagnostics{ 111 { 112 Severity: hcl.DiagError, 113 Summary: "Failed to read file", 114 Detail: fmt.Sprintf("The file %q was opened, but an error occured while reading it.", filename), 115 }, 116 } 117 } 118 119 return Parse(src, filename) 120 }