A HashiCorp Configuration Language (HCL) extension that enables user-defined functions written in JQ. This package extends HCL with powerful data transformation capabilities using the JQ query language.
- JQ Integration: Write HCL functions using JQ query syntax
- Flexible Input Types: Accept both JSON strings and native cty values
- Smart String Handling: String results from JSON input are returned directly (not JSON-encoded). You can always use
tojsonin your query if you really want a string containing the qujotes json string. - Multi-Result Support: Handle JQ queries that return multiple values
- Parameter Support: Pass HCL function arguments as JQ variables
- Enhanced Error Reporting: Detailed error messages with source location information
go get github.com/tsarna/hcl-jqfuncpackage main
import (
"fmt"
"log"
"github.com/hashicorp/hcl/v2/hclparse"
"github.com/tsarna/hcl-jqfunc"
"github.com/zclconf/go-cty/cty"
)
func main() {
// Define HCL with JQ functions
hclCode := `
jq "get_name" {
params = []
query = ".name"
}
jq "add_tax" {
params = [rate]
query = ".price * (1 + $rate)"
}
`
// Parse and decode functions
parser := hclparse.NewParser()
file, diags := parser.ParseHCL([]byte(hclCode), "config.hcl")
if diags.HasErrors() {
log.Fatal(diags)
}
functions, _, diags := jqfunc.DecodeJqFunctions(file.Body, "jq")
if diags.HasErrors() {
log.Fatal(diags)
}
// Use the functions
getName := functions["get_name"]
result, _ := getName.Call([]cty.Value{
cty.StringVal(`{"name": "Alice", "age": 30}`),
})
fmt.Println(result.AsString()) // Output: Alice
addTax := functions["add_tax"]
result, _ = addTax.Call([]cty.Value{
cty.StringVal(`{"price": 100}`),
cty.NumberFloatVal(0.08),
})
fmt.Println(result.AsString()) // Output: 108
}jq "function_name" {
params = [param1, param2, ...] # Parameter names (bare identifiers)
query = "JQ_QUERY_STRING" # JQ query with $param1, $param2, etc.
}jq "extract_user_info" {
params = []
query = "{name: .name, email: .contact.email}"
}// JSON string input
result, _ := extractUserInfo.Call([]cty.Value{
cty.StringVal(`{"name": "Bob", "contact": {"email": "bob@example.com"}}`),
})
// Returns: {"email":"bob@example.com","name":"Bob"}
// cty object input
result, _ := extractUserInfo.Call([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"name": cty.StringVal("Bob"),
"contact": cty.ObjectVal(map[string]cty.Value{
"email": cty.StringVal("bob@example.com"),
}),
}),
})
// Returns: cty.ObjectVal with name and email fieldsjq "filter_active_users" {
params = []
query = "[.users[] | select(.active == true) | .name]"
}jq "calculate_total" {
params = [tax_rate, discount]
query = ".price * (1 + $tax_rate) * (1 - $discount)"
}- Block Type: Use any name (commonly
jq) - Function Name: Single label after block type
- Parameters: List of bare identifiers in
paramsattribute - Query: JQ query string in
queryattribute
- Parameters become JQ variables prefixed with
$ - Example:
params = [rate, discount]creates$rateand$discountvariables - Parameters can be any cty type and are converted to Go values for JQ processing
- JSON String: Parsed as JSON, processed by JQ, result handling depends on output type
- cty Value: Converted to Go value, processed by JQ, converted back to cty
| Input Type | Result Type | Output |
|---|---|---|
| JSON String | String | Direct string (no JSON encoding) |
| JSON String | Number/Object/Array | JSON-encoded string |
| cty Value | Any | Corresponding cty type |
- Single result: Returned directly
- Multiple results: Returned as array/list
- No results: Returns
null(JSON string) orcty.NullVal(cty input)
The package provides enhanced error reporting with:
- Source Location: Exact HCL block location for compilation errors
- Runtime Context: Function name and query for execution errors
- Error Chaining: Go 1.13+ error unwrapping support
if err != nil {
var jqErr *jqfunc.JqExecutionError
if errors.As(err, &jqErr) {
fmt.Printf("JQ error in %s at %s: %v\n",
jqErr.FunctionName, jqErr.Range, jqErr.Cause)
}
}// Use custom block type instead of "jq"
functions, _, diags := jqfunc.DecodeJqFunctions(body, "transform")jq "process_orders" {
params = [min_amount]
query = """
[.orders[] |
select(.total >= $min_amount) |
{
id: .id,
customer: .customer.name,
total: .total,
items: [.items[].name]
}
]
"""
}jq "categorize_user" {
params = []
query = """
if .age < 18 then "minor"
elif .age < 65 then "adult"
else "senior"
end
"""
}JQ functions integrate seamlessly with HCL's function system:
# In your HCL configuration
locals {
user_data = jsondecode(file("users.json"))
active_users = filter_active_users(user_data)
user_names = [for user in active_users : user.name]
}- JQ queries are compiled once during function creation
- Parameter conversion happens at runtime
- For high-frequency use, prefer cty input over JSON strings
- Complex queries may benefit from breaking into smaller functions
- JQ variables must be prefixed with
$in queries - Queries have no access to variables in the execution context, only the passed in variables.
- Queries cannot call other queries or HCL functions.
- Parameter names must be valid HCL identifiers
- JSON string input/output adds serialization overhead
- Multi-result queries with mixed types may require careful handling
github.com/hashicorp/hcl/v2- HCL parsing and evaluationgithub.com/itchyny/gojq- Pure Go JQ implementationgithub.com/zclconf/go-cty- HCL type systemgithub.com/tsarna/go2cty2go- Enhanced cty value conversion
Special thanks to itchyny for creating and maintaining gojq, the excellent pure Go implementation of JQ that makes this project possible. The gojq library provides the robust JQ query processing that powers all the functionality in this package.