Skip to main content

Advanced JSONPath and Golang Templates

Learn how to efficiently retrieve and manipulate data in Torq with advanced JSONPath and Golang templates.

Overview

This tutorial explains how to retrieve, access, and process data in Torq workflows using advanced mechanisms.

Torq uses JSONPath to access data from event triggers and workflow steps. With advanced JSONPath syntax, you can extract specific values from complex structures (such as arrays of objects). For more advanced transformations, you can use Golang templates .

All data processing capabilities described here are also available as no-code steps.

Sample data

All examples in this guide use the following dataset:

{
"hosts": [
{
"name": "host1",
"interfaces": [
{
"name": "Interface 1 of Host 1",
"address": "10.10.10.1",
"weight": 10
},
{
"name": "Interface 2 of Host 1",
"address": "10.10.10.2",
"weight": 20
}
]
},
{
"name": "host2",
"interfaces": [
{
"name": "Interface 1 of Host 2",
"address": "20.20.20.1",
"weight": 10
},
{
"name": "Interface 2 of Host 2",
"address": "20.20.20.2",
"weight": 20
},
{
"name": "Interface 3 of Host 2",
"address": "20.20.20.3",
"weight": 30
}
]
}
]
}

Arrays and collections

Workflow data often includes collections such as users, devices, or vulnerabilities. Torq supports multiple ways to access and manipulate these structures.

Note: Collections in Torq are zero-based (the first item has index 0).

Common template functions

  • len: returns the length (number of items) of the array provided as an argument.
    For the collection above, {{ len $.hosts }} will return 2.

  • index: returns a single item from a map or an array. The provided index can be either a numerical position in an array or a named key inside a map. The function can be used multiple times in a single template to access nested fields within a collection.
    {{ (index (index $.hosts 0).interfaces 0).name }} returns the name of the first interface of the first host. For the collection above, the result will be Interface 1 of Host 1.

  • []: returns either a single item or a range of items. {{ $.hosts[0] }} is equivalent to {{ index $.hosts 0 }}. It can be used for direct field access such as {{ $.hosts[0].name }} or nested access like {{ $.hosts[0].interfaces[0].name }}.
    For the collection above, {{ $.hosts[0].interfaces[0].name }} will return Interface 1 of Host 1.

Access and slice data (JSONPath)

Torq supports the following JSONPath expressions for performing operations on arrays and collections. Indexes start at 0.

Expression

Description

Example

[n]

Returns the n-th element in the array

$.hosts[0].interfaces[0].name will return Interface 1 of Host 1

[x, y]

Returns a list consisting of items at indexes x and y

$.hosts[1].interfaces[1,2][0].name will return Interface 2 of Host 2

[:x]

Returns a list consisting of the first x items of the array

$.hosts[1].interfaces[:2][1].name will return Interface 2 of Host 2

[-x:]

Returns a list consisting of the last x items of the array

$.hosts[1].interfaces[-2:][1].name will return Interface 3 of Host 2

[?(expression)]

Returns a list of elements that match the specified expression

$.hosts[1].interfaces[?(@.weight > 10)][0] will return Interface 2 of Host 2

[:].field_name

Returns a new list where each element is a field extracted from items in the original list

$.get_aws_auto_scaling_groups.AutoScalingGroups[:].AutoScalingGroupName will return a list of auto-scaling group names

Apply filters to arrays and collections

You can filter arrays and collections in JSONPath to return only the elements that match specific conditions, patterns, or combined logic expressions.

  • Filtering complete objects: the expression $.hosts[1].interfaces[?(@.weight > 10)] will return a list of interfaces where the value of the weight key exceeds 10.

  • Retrieving specific fields: the expression $.hosts[1].interfaces[?(@.weight > 10)].name will return a list of names rather than a list of complete interface objects. For the example above, $.hosts[1].interfaces[?(@.weight > 10)][0] will return Interface 2 of Host 2.

  • Matching by JavaScript regular expression: the expression $.hosts[1].interfaces[?(@.name=~ /(Interface [23])/)] will return a list of interfaces that contains interfaces 2 and 3.

  • Matching by multiple expressions: can be performed by combining expressions using && (logical AND) and || (logical OR) operators. For example, the expression $.hosts[1].interfaces[?(@.weight > 20 && @.name=~ /(Interface [23])/)] will return a list containing only Interface 3 of Host 2.

Comparisons

Comparison operators are useful either when defining a conditional execution of a step or when a step argument value depends on a specific condition. Each of the below is a boolean comparison operator:

  • eq - Returns the boolean truth of arg1 == arg2

  • ne - Returns the boolean truth of arg1 != arg2

  • lt - Returns the boolean truth of arg1 < arg2

  • gt - Returns the boolean truth of arg1 > arg2

  • ge - Returns the boolean truth of arg1 >= arg2

  • ieq - Returns the case-insensitive string comparison of arg1 and arg2 (==)

  • ine - Returns the negation of a case-insensitive string comparison of arg1 and arg2 (!=)

  • isNull - Returns true if the operand arg1 is Null (the actual word Null, not an empty argument)

  • empty - Returns true when a JSON path is not defined or present in a step output

For example, if using these comparisons with the collection provided above:

{{ eq (len $.hosts) 2 }} and {{ ge (len $.hosts) 0 }} will return true, whereas {{ isNull $.hosts }} or {{ lt (len $.hosts[0].interfaces) 1 }} will return false.

Logical operators

Logical operators allow executing boolean logic using execution context fields or variables as arguments.

  • and: returns the boolean AND of its arguments by returning the first empty argument or the last argument; that is, "and x y" behaves as "if x then y else x". All the arguments are evaluated.
    For example: {{ and (eq $.event.Scope_Action "Firing") (eq $.event.Scope_Env "Production") }}

  • or: returns the boolean OR of its arguments by returning the first non-empty argument or the last argument; that is, "or x y" behaves as "if x then x else y". All the arguments are evaluated.
    For example: {{ or (eq $.event.Scope_Action "Firing") (eq $.event.Scope_Action "Warning") }}

  • not: returns the boolean negation of its single argument.
    For example: {{ not (eq $.event.Scope_Action "Firing") }}

Short-circuit evaluation

Do not rely on short-circuit evaluation. If one of the conditions can be in a state that prevents its evaluation, advanced conditional structures should be used. For example, in the case of an array that can be empty, the following clause will not work: {{ and (gt (len $.array) 0) (eq $.array[1] 8) }}, because when the array is empty, the index evaluation will fail.

Useful template functions

Torq supports a broad set of Sprig functions (Golang templates), providing built-in tools for string manipulation, data formatting, logic, and date/time handling. Below are the most commonly used template functions; for the full list, see the Sprig Functions: Add Dynamic Data Logic to Your Workflows article.

Function

Description

Example > Output

default

If the evaluated value is empty, the given default will be returned

{{ default "N/A" $.user.email }} > N/A (if user email is missing) or john.doe@mail.com (if this email exists)

jsonEscape

Escapes a string so it can be safely embedded in JSON (adds backslashes for quotes, newlines, etc.)

{{ jsonEscape Obi Wan said, "Hello there!" }} > Obi Wan said, "Hello there!"

b64enc, b64dec

Encodes or decodes the provided data using Base64 encoding

{{ b64enc "hello world" }} > aGVsbG8gd29ybGQ= {{ b64dec "aGVsbG8gd29ybGQ=" }} > hello world

upper, lower

Converts a string to uppercase or lowercase

{{ upper "torq" }} > TORQ {{ lower "TORQ" }} > torq

len

Returns the length of a string, array, map, or object

{{ len "Torq" }} > 4

Advanced templates

Torq supports advanced templates that generate dynamic output (text, markdown, or scripts) populated with data from the workflow context.

The syntax from the Golang Template package allows iterating over collections of data and objects using the range function.

For example, you can build a template that displays the number of hosts and lists their names.

Examples

Basic range usage

Template

We currently have {{ len $.hosts }} hosts and their names are:
{{ range $index, $host := $.hosts }} {{ $host.name }} {{ end }}

Output

We currently have 2 hosts and their names are: host1 host2

The range function performs the following actions:

  • Iterates over each item in $.hosts (no output is generated if the array is empty)

  • Defines two temporary variables available inside the loop:

    • $index > index of the current item (zero-based)

    • $host > the current object

  • Outputs the name field for each host

If / else

Another example is using conditional logic in a template:

{{ if (gt (len $.hosts) 1) -}} Multiple hosts found {{- else -}} Single host found {{- end }}
  • If more than one host exists, the output will be: Multiple hosts found

  • If only one host exists, the output will be: Single host found

Render lists with range

The range function can also generate dynamic lists, for example in an Interact Markdown widget:

{{ range $item := $.interact.answers.use_cases }}
- {{ $item }}
{{ end }}

This iterates over the array and renders each item as a separate bullet point.

Improve template verbosity with $index

You can enhance templates by including the $index variable to produce more detailed and readable output.

Template with improved verbosity

We currently have {{ len $.hosts }} hosts and their names are:
{{ range $index, $host := $.hosts }} The name of host {{ $index }} is {{ $host.name }} {{ end }}

Output

We currently have 2 hosts and their names are: The name of host 0 is host1 The name of host 1 is host2

As seen in the example, the index is zero-based.

Nested range constructs

The range construct can be nested to iterate over arrays within arrays, such as interfaces inside hosts.

Template using nested range constructs

We currently have {{ len $.hosts }} hosts and their names are:
{{ range $index, $host := $.hosts }}
----------------------------------------------
The name of host {{ $index }} is {{ $host.name }}
It has {{ len $host.interfaces }} interfaces:
{{ range $interfaceidx, $interface := $host.interfaces }}
Interface number {{ $interfaceidx }} is named "{{$interface.name}}" and has an IP address of {{$interface.address}}
{{ end }}
----------------------------------------------
{{ end }}

Output

We currently have 2 hosts and their names are:
----------------------------------------------
The name of host 0 is host1
It has 2 interfaces:
Interface number 0 is named "Interface 1 of Host 1" and has an IP address of 10.10.10.1
Interface number 1 is named "Interface 2 of Host 1" and has an IP address of 10.10.10.2
----------------------------------------------
----------------------------------------------
The name of host 1 is host2
It has 3 interfaces:
Interface number 0 is named "Interface 1 of Host 2" and has an IP address of 20.20.20.1
Interface number 1 is named "Interface 2 of Host 2" and has an IP address of 20.20.20.2
Interface number 2 is named "Interface 3 of Host 2" and has an IP address of 20.20.20.3
----------------------------------------------

Combine range variables with other data

Template variables such as $index can be combined with other workflow data to reference related items in different arrays.

{{ index $.some_other_step.list_of_responses $idx }}

In this example:

  • index retrieves an element from the array $.some_other_step.list_of_responses

  • $idx represents the current index from a range loop (0, 1, 2, ...)

Troubleshooting

Escaping URLs

Use the urlquery function to safely escape URLs directly inside templates, without requiring a separate step.

Did this answer your question?