JSONata Deep Dive

Advanced patterns for using JSONata expressions inside {'{{'} {'}}'} template delimiters across your workflow nodes.

JSONata is a lightweight query and transformation language for JSON data. Graph Compose uses it as the expression language inside template delimiters. You can test expressions interactively in the JSONata Playground.

All examples on this page show the JSONata expression as it would appear inside a template value (URL, header, or body field). The expression is always wrapped in {'{{'} {'}}'} delimiters.

String operations

Concatenate strings with the & operator. Do not use $concat() as it does not exist in JSONata.

String concatenation

// Join first and last name
'{{ results.get_user.data.firstName & " " & results.get_user.data.lastName }}'

// Build a greeting
'{{ "Hello, " & results.get_user.data.name }}'

Case conversion and trimming

// Uppercase
'{{ $uppercase(results.get_user.data.email) }}'

// Lowercase
'{{ $lowercase(results.get_user.data.role) }}'

// Trim whitespace
'{{ $trim(results.get_input.data.query) }}'

Substrings and splitting

// Extract domain from email
'{{ $substringAfter(results.get_user.data.email, "@") }}'

// Get first 10 characters
'{{ $substring(results.get_user.data.description, 0, 10) }}'

// Split a comma-separated string into an array
'{{ $split(results.get_tags.data.tags, ",") }}'

// Join an array into a string
'{{ $join(results.get_items.data.names, ", ") }}'

Search and replace

// Check if a string contains a substring
'{{ $contains(results.get_user.data.bio, "developer") }}'

// Replace text
'{{ $replace(results.get_template.data.body, "{name}", results.get_user.data.name) }}'

Array operations

JSONata provides powerful array operations. These work on arrays nested inside node results.

Filtering

// Get active users
'{{ results.get_users.data[status = "active"] }}'

// Get items above a price threshold
'{{ results.get_items.data[price > 50] }}'

// Filter by multiple conditions
'{{ results.get_orders.data[status = "completed" and total > 100] }}'

// Negative filter
'{{ results.get_users.data[role != "admin"] }}'

Extracting fields

// Get all names from an array of objects
'{{ results.get_users.data.name }}'

// Get all email addresses from active users
'{{ results.get_users.data[status = "active"].email }}'

Aggregation

// Sum prices
'{{ $sum(results.get_orders.data.total) }}'

// Count items
'{{ $count(results.get_orders.data) }}'

// Average rating
'{{ $average(results.get_reviews.data.rating) }}'

// Min and max
'{{ $min(results.get_bids.data.amount) }}'
'{{ $max(results.get_bids.data.amount) }}'

Sorting and deduplication

// Sort by price ascending
'{{ $sort(results.get_items.data, function($a, $b) { $a.price > $b.price }) }}'

// Reverse an array
'{{ $reverse(results.get_items.data) }}'

// Remove duplicates
'{{ $distinct(results.get_tags.data) }}'

// Get first and last elements
'{{ $first(results.get_items.data) }}'
'{{ $last(results.get_items.data) }}'

Map, filter, and reduce

// Map: transform each item
'{{ $map(results.get_users.data, function($user) { $user.firstName & " " & $user.lastName }) }}'

// Filter: keep items matching a predicate
'{{ $filter(results.get_items.data, function($item) { $item.inStock = true }) }}'

// Reduce: accumulate a value
'{{ $reduce(results.get_cart.data.items, function($acc, $item) { $acc + $item.price * $item.quantity }, 0) }}'

Numeric operations

Arithmetic

// Calculate total with tax
'{{ results.get_order.data.subtotal * 1.08 }}'

// Percentage
'{{ results.get_stats.data.completed / results.get_stats.data.total * 100 }}'

// Rounding
'{{ $round(results.get_order.data.total, 2) }}'

// Floor and ceiling
'{{ $floor(results.get_calc.data.value) }}'
'{{ $ceil(results.get_calc.data.value) }}'

Math functions

// Absolute value
'{{ $abs(results.get_balance.data.amount) }}'

// Power and square root
'{{ $power(results.get_data.data.base, 2) }}'
'{{ $sqrt(results.get_data.data.variance) }}'

Conditional logic

Use the ternary operator (? :) for conditional values.

Ternary expressions

// Simple condition
'{{ results.get_user.data.age >= 18 ? "adult" : "minor" }}'

// Discount based on order total
'{{ results.get_order.data.total > 100 ? results.get_order.data.total * 0.9 : results.get_order.data.total }}'

// Choose an endpoint based on environment
'{{ context.env = "production" ? "https://api.example.com" : "https://staging.example.com" }}'

Boolean checks

// Check if a field exists
'{{ $exists(results.get_user.data.premium) }}'

// Negate a boolean
'{{ $not(results.get_user.data.blocked) }}'

// Equality check (single = in JSONata, not ==)
'{{ results.get_order.data.status = "paid" }}'

Object transformation

Reshape objects using JSONata's object projection syntax.

Object projection

// Transform each item in an array into a new shape
'{{ results.get_cart.data.items.{ "productId": id, "total": price * quantity } }}'

// Build a summary object
'{{ { "name": results.get_user.data.name, "orderCount": $count(results.get_orders.data), "totalSpent": $sum(results.get_orders.data.total) } }}'

Object utilities

// Get keys of an object
'{{ $keys(results.get_config.data) }}'

// Merge two objects
'{{ $merge([results.get_defaults.data, results.get_overrides.data]) }}'

// Spread object into key-value pairs
'{{ $spread(results.get_config.data) }}'

// Check the type of a value
'{{ $type(results.get_data.data.value) }}'

Combining data from multiple nodes

When a node depends on multiple upstream nodes, you can reference results from all of them in a single expression.

Cross-node references

// Build a request body from two upstream nodes
.withBody({
  userId: '{{ results.get_user.data.id }}',
  orderId: '{{ results.get_order.data.id }}',
  total: '{{ results.get_order.data.subtotal + results.get_shipping.data.cost }}',
  address: '{{ results.get_user.data.address }}'
})

Conditional routing based on upstream data

// Use in a continueTo condition
.withConditions({
  continueTo: [
    {
      to: 'process_premium',
      when: '{{ results.get_user.data.tier = "premium" and results.get_order.data.total > 50 }}'
    },
    {
      to: 'process_standard',
      when: '{{ results.get_user.data.tier != "premium" }}'
    }
  ]
})

Date and time

Timestamps

// Current time as ISO string
'{{ $now() }}'

// Current time as milliseconds since epoch
'{{ $millis() }}'

// Convert milliseconds to formatted string
'{{ $fromMillis($millis(), "[Y]-[M01]-[D01]T[H01]:[m01]:[s01]Z") }}'

// Convert ISO string to milliseconds
'{{ $toMillis(results.get_event.data.createdAt) }}'

Encoding

URL encoding

// Encode a query parameter value
'{{ $encodeUrlComponent(results.get_input.data.query) }}'

// Decode a URL-encoded value
'{{ $decodeUrlComponent(results.get_data.data.encoded) }}'

Base64

// Encode credentials for Basic auth
'{{ $base64encode(context.username & ":" & context.password) }}'

// Decode a base64 string
'{{ $base64decode(results.get_data.data.encoded) }}'

Common recipes

Build a dynamic URL from upstream data

Dynamic URL

graph
  .node('get_user')
    .get('https://api.example.com/users/{{ context.userId }}')
  .end()

  .node('get_orders')
    .get('https://api.example.com/users/{{ results.get_user.data.id }}/orders')
    .withDependencies(['get_user'])
  .end()

Construct an authorization header from a secret

Basic auth header

graph
  .node('call_api')
    .post('https://api.example.com/data')
    .withHeaders({
      'Authorization': 'Basic {{ $base64encode($secret("api_user") & ":" & $secret("api_pass")) }}'
    })
  .end()

Sum items from an upstream response

Aggregate and forward

graph
  .node('get_cart')
    .get('https://api.example.com/cart/{{ context.cartId }}')
  .end()

  .node('create_invoice')
    .post('https://api.example.com/invoices')
    .withBody({
      cartId: '{{ context.cartId }}',
      lineItems: '{{ $count(results.get_cart.data.items) }}',
      subtotal: '{{ $sum(results.get_cart.data.items.(price * quantity)) }}',
      currency: '{{ results.get_cart.data.currency }}'
    })
    .withDependencies(['get_cart'])
  .end()

Filter and reshape an array before sending

Filter and transform

graph
  .node('get_products')
    .get('https://api.example.com/products')
  .end()

  .node('submit_report')
    .post('https://api.example.com/reports')
    .withBody({
      inStockProducts: '{{ results.get_products.data[inStock = true].{ "sku": sku, "name": name, "price": price } }}'
    })
    .withDependencies(['get_products'])
  .end()

Next steps