Skip to content

Commit 943592a

Browse files
authored
fix: support array-based JSON syntax in WalkExpressions (#422)
1 parent 82edf6a commit 943592a

File tree

2 files changed

+90
-1
lines changed

2 files changed

+90
-1
lines changed

plugin/internal/plugin2host/client.go

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package plugin2host
22

33
import (
44
"context"
5+
stdjson "encoding/json"
56
"errors"
67
"fmt"
78
"os"
@@ -205,6 +206,69 @@ func (w *nativeWalker) Exit(node hclsyntax.Node) hcl.Diagnostics {
205206
return nil
206207
}
207208

209+
// extractJSONKeys extracts attribute names from JSON bytes using encoding/json.
210+
// This works for both object-based JSON {"foo": ...} and array-based JSON [{"foo": ...}].
211+
func extractJSONKeys(bytes []byte) ([]string, error) {
212+
// Try to unmarshal as an object first
213+
var obj map[string]any
214+
if err := stdjson.Unmarshal(bytes, &obj); err == nil {
215+
keys := make([]string, 0, len(obj))
216+
for k := range obj {
217+
keys = append(keys, k)
218+
}
219+
return keys, nil
220+
}
221+
222+
// Try as an array of objects
223+
var arr []map[string]any
224+
if err := stdjson.Unmarshal(bytes, &arr); err != nil {
225+
return nil, err
226+
}
227+
228+
// Collect all unique keys from all objects in the array
229+
keysMap := make(map[string]bool)
230+
for _, obj := range arr {
231+
for k := range obj {
232+
keysMap[k] = true
233+
}
234+
}
235+
236+
keys := make([]string, 0, len(keysMap))
237+
for k := range keysMap {
238+
keys = append(keys, k)
239+
}
240+
return keys, nil
241+
}
242+
243+
// getJSONAttributes gets all attributes from a JSON body, supporting both object
244+
// and array-based syntax. For array-based JSON like [{"import": {...}}], it
245+
// extracts attribute names using encoding/json and builds a schema to extract them.
246+
func getJSONAttributes(body hcl.Body, bytes []byte) (hcl.Attributes, hcl.Diagnostics) {
247+
// First, try JustAttributes (works for object-based JSON)
248+
attrs, diags := body.JustAttributes()
249+
if !diags.HasErrors() {
250+
return attrs, nil
251+
}
252+
253+
// Extract keys using encoding/json
254+
keys, err := extractJSONKeys(bytes)
255+
if err != nil {
256+
return attrs, diags // Return original JustAttributes error
257+
}
258+
259+
// Build a schema with all discovered keys
260+
schema := &hcl.BodySchema{
261+
Attributes: make([]hcl.AttributeSchema, len(keys)),
262+
}
263+
for i, key := range keys {
264+
schema.Attributes[i] = hcl.AttributeSchema{Name: key}
265+
}
266+
267+
// Use PartialContent to get proper *json.expression objects
268+
content, _, partialDiags := body.PartialContent(schema)
269+
return content.Attributes, partialDiags
270+
}
271+
208272
// WalkExpressions traverses expressions in all files by the passed walker.
209273
// Note that it behaves differently in native HCL syntax and JSON syntax.
210274
//
@@ -236,7 +300,7 @@ func (c *GRPCClient) WalkExpressions(walker tflint.ExprWalker) hcl.Diagnostics {
236300
}
237301

238302
// In JSON syntax, everything can be walked as an attribute.
239-
attrs, jsonDiags := file.Body.JustAttributes()
303+
attrs, jsonDiags := getJSONAttributes(file.Body, file.Bytes)
240304
if jsonDiags.HasErrors() {
241305
diags = diags.Extend(jsonDiags)
242306
continue

plugin/internal/plugin2host/plugin2host_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1208,6 +1208,31 @@ data "terraform_remote_state" "remote_state" {
12081208
{Start: hcl.Pos{Line: 8, Column: 17}, End: hcl.Pos{Line: 8, Column: 32}},
12091209
},
12101210
},
1211+
{
1212+
name: "array-based json",
1213+
files: map[string][]byte{
1214+
"main.tf.json": []byte(`[
1215+
{
1216+
"resource": {
1217+
"null_resource": {
1218+
"foo": {}
1219+
}
1220+
}
1221+
},
1222+
{
1223+
"variable": {
1224+
"example": {
1225+
"type": "string"
1226+
}
1227+
}
1228+
}
1229+
]`),
1230+
},
1231+
walked: []hcl.Range{
1232+
{Start: hcl.Pos{Line: 3, Column: 17}, End: hcl.Pos{Line: 7, Column: 6}},
1233+
{Start: hcl.Pos{Line: 10, Column: 17}, End: hcl.Pos{Line: 14, Column: 6}},
1234+
},
1235+
},
12111236
}
12121237

12131238
for _, test := range tests {

0 commit comments

Comments
 (0)