Skip to content

[go-fan] Go Module Review: gojq (github.com/itchyny/gojq)Β #3111

@github-actions

Description

@github-actions

🐹 Go Fan Report: gojq

Module Overview

github.com/itchyny/gojq is a pure Go implementation of jq β€” the JSON query and transformation language. It provides both a CLI and a Go library with full jq compatibility, plus Go-native extensions like context cancellation, custom functions, and custom types. The project currently uses v0.12.18; v0.12.19 is now available.

Current Usage in gh-aw-mcpg

gojq powers the large payload middleware (internal/middleware/jqschema.go). When a backend MCP tool response exceeds the configured sizeThreshold, the middleware:

  1. Saves the full JSON payload to disk (payload.json)
  2. Runs a jq schema-inference filter that replaces leaf values with their type names ("string", "number", etc.) and collapses arrays to a single representative element
  3. Returns a lightweight metadata response to the client containing the schema, a preview, and the file path
  • Files: 2 (+ 1 bench test file)
  • Key APIs Used: gojq.Parse, gojq.Compile, (*Code).RunWithContext, *gojq.HaltError
  • Pattern: pre-compiled query code (compile once at init(), reuse per request)

Research Findings

The project already follows gojq's recommended best practices quite well. The benchmarks in jqschema_bench_test.go demonstrate a 10–100x speedup from pre-compiling vs parsing on every request, and the implementation correctly uses RunWithContext for cancellation.

Recent Updates (v0.12.18β†’v0.12.19)

  • v0.12.19 is the latest release (observed via GitHub release metadata). An upgrade would bring any bug fixes and potential performance improvements since v0.12.18.

Best Practices (already applied βœ…)

  • Pre-compile with gojq.Compile() at init() β€” fail-fast validation at startup
  • Use RunWithContext() β€” context cancellation/timeout propagated into jq execution
  • Type-assert *gojq.HaltError specifically β€” correct typed error handling
  • UTF-8 safe truncation β€” preview truncation walks back to nearest valid rune boundary

Improvement Opportunities

πŸƒ Quick Wins

1. Upgrade to v0.12.19
The project uses v0.12.18; v0.12.19 is available. Standard bump via go get github.com/itchyny/gojq@latest + go mod tidy.

2. Rename the custom walk(f) def to avoid shadowing the built-in
The jq schema filter defines def walk(f) which shadows gojq's built-in walk/1. While it works and is well-documented in comments, using a distinct name like walk_schema avoids any potential future confusion:

def walk_schema:
  if type == "object" then
    reduce keys[] as $k ({}; . + {($k): (.[$k] | walk_schema)})
  elif type == "array" then
    if length == 0 then [] else [.[0] | walk_schema] end
  else
    type
  end;
walk_schema

3. Fix generateRandomID fallback collision risk
When crypto/rand.Read fails, the fallback is fmt.Sprintf("fallback-%d", os.Getpid()) β€” the same ID for every call within the same process. Concurrent tool calls would collide on payload file paths. A better fallback:

return fmt.Sprintf("fallback-%d-%d", os.Getpid(), time.Now().UnixNano())

4. Tighten payload file permissions (security)
savePayload writes files with 0644 (world-readable). Since payloads may contain sensitive API responses, 0600 (owner read/write only) is more appropriate:

if err := os.WriteFile(filePath, payload, 0600); err != nil {  // was 0644

✨ Feature Opportunities

5. gojq.WithFunction for native Go type helpers
gojq supports registering native Go functions callable from jq filters. For future extensibility (e.g., custom schema annotations, type overrides, field redaction), gojq.WithFunction could inject Go logic directly:

jqSchemaCode, err = gojq.Compile(query, gojq.WithFunction("go_type", 0, 0,
    func(v interface{}, _ []interface{}) interface{} {
        switch v.(type) {
        case map[string]interface{}: return "object"
        case []interface{}:          return "array"
        case string:                 return "string"
        case float64:                return "number"
        case bool:                   return "boolean"
        case nil:                    return "null"
        default:                     return "unknown"
        }
    },
))

6. gojq.WithVariables for configurable filter parameters
Schema filter options (max depth, excluded fields) could be passed as jq variables at compile time, enabling runtime customization without changing the filter string:

gojq.Compile(query, gojq.WithVariables([]string{"$excludeKeys"}))
// At run time:
code.RunWithContext(ctx, data, excludedKeysList)

πŸ“ Best Practice Alignment

7. Assert single-output contract in tests
The walk_schema filter always produces exactly one output value. Unit tests could verify this invariant by asserting iter.Next() returns false on the second call, documenting the contract and catching future filter changes that accidentally produce multiple outputs.

Recommendations

Priority Item Effort
πŸ”΄ High Fix generateRandomID fallback (collision risk on concurrent calls) Trivial
πŸ”΄ High Fix payload file permissions: 0644 β†’ 0600 Trivial
🟑 Medium Upgrade gojq to v0.12.19 Low
🟑 Medium Rename def walk(f) to def walk_schema Low
🟒 Low Explore gojq.WithFunction for future extensibility Medium
🟒 Low Add single-output contract assertion in tests Low

Next Steps

  • Fix generateRandomID fallback: add time.Now().UnixNano() to avoid collisions
  • Change payload file write permission from 0644 to 0600
  • Bump github.com/itchyny/gojq to v0.12.19 and run go mod tidy
  • Rename def walk(f) β†’ def walk_schema in jqSchemaFilter constant

Generated by Go Fan 🐹
Module analysis saved to session artifacts: specs/mods/gojq.md

Note

πŸ”’ Integrity filter blocked 8 items

The following items were blocked because they don't meet the GitHub integrity level.

To allow these resources, lower min-integrity in your GitHub frontmatter:

tools:
  github:
    min-integrity: approved  # merged | approved | unapproved | none

Generated by Go Fan Β· β—·

  • expires on Apr 10, 2026, 7:40 AM UTC

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions