Skip to main content

Overview

The Transform member provides powerful data transformation capabilities using JSONata expressions. Reshape, filter, aggregate, and manipulate data with a concise, declarative syntax. Perfect for data mapping, API response transformation, filtering, aggregation, and complex data operations.

Quick Example

name: transform-data
description: Transform API response

flow:
  - member: fetch-users
    type: Fetch
    config:
      url: "https://api.example.com/users"

  - member: transform
    type: Transform
    input:
      data: ${fetch-users.output.data}
      expression: |
        {
          "users": users[].{
            "id": id,
            "fullName": firstName & " " & lastName,
            "email": email,
            "active": status = "active"
          }
        }

output:
  transformed: ${transform.output}

JSONata Basics

Object Construction

expression: |
  {
    "name": user.name,
    "email": user.email,
    "age": user.age
  }

Array Mapping

expression: |
  users[].{
    "name": name,
    "email": email
  }

Filtering

expression: |
  users[age > 18]

Aggregation

expression: |
  {
    "total": $sum(items.price),
    "count": $count(items),
    "average": $average(items.price)
  }

Common Patterns

API Response Transformation

name: transform-api-response
description: Convert external API format to internal format

flow:
  - member: fetch-external
    type: Fetch
    config:
      url: "https://external-api.com/data"

  - member: transform-response
    type: Transform
    input:
      data: ${fetch-external.output.data}
      expression: |
        {
          "products": data.items[].{
            "id": product_id,
            "name": product_name,
            "price": $number(price_usd),
            "inStock": inventory_count > 0,
            "category": category.name,
            "imageUrl": images[0].url
          }
        }

output:
  products: ${transform-response.output.products}

Data Filtering and Sorting

name: filter-and-sort
description: Filter and sort data

flow:
  - member: transform
    type: Transform
    input:
      data: ${input.users}
      expression: |
        {
          "activeUsers": $sort(
            $filter(users, function($u) { $u.status = "active" }),
            function($a, $b) { $a.name < $b.name }
          )
        }

output:
  activeUsers: ${transform.output.activeUsers}

Data Aggregation

name: aggregate-sales
description: Calculate sales statistics

flow:
  - member: aggregate
    type: Transform
    input:
      data: ${input.orders}
      expression: |
        {
          "summary": {
            "totalRevenue": $sum(orders.total),
            "orderCount": $count(orders),
            "averageOrderValue": $average(orders.total),
            "topProducts": $sort(
              $distinct(orders.items[].productId),
              function($a, $b) {
                $count(orders.items[productId = $a]) >
                $count(orders.items[productId = $b])
              }
            )[0..4]
          }
        }

output:
  summary: ${aggregate.output.summary}

Nested Data Flattening

name: flatten-nested
description: Flatten nested data structure

flow:
  - member: flatten
    type: Transform
    input:
      data: ${input.data}
      expression: |
        {
          "flatItems": data.categories[].products[].{
            "categoryId": $$.id,
            "categoryName": $$.name,
            "productId": id,
            "productName": name,
            "price": price
          }
        }

output:
  flatItems: ${flatten.output.flatItems}

Data Validation and Cleaning

name: clean-data
description: Validate and clean input data

flow:
  - member: clean
    type: Transform
    input:
      data: ${input.users}
      expression: |
        {
          "validUsers": users[
            $exists(email) and
            $match(email, /^[^\s@]+@[^\s@]+\.[^\s@]+$/) and
            $exists(name) and
            $length(name) > 0
          ].{
            "email": $lowercase($trim(email)),
            "name": $trim(name),
            "age": $number(age) ? $number(age) : null
          }
        }

output:
  validUsers: ${clean.output.validUsers}

Combining Multiple Sources

name: combine-data
description: Merge data from multiple sources

flow:
  parallel:
    - member: fetch-users
      type: Fetch
    - member: fetch-orders
      type: Fetch
    - member: fetch-products
      type: Fetch

  - member: combine
    type: Transform
    input:
      data:
        users: ${fetch-users.output.data}
        orders: ${fetch-orders.output.data}
        products: ${fetch-products.output.data}
      expression: |
        {
          "enrichedOrders": data.orders[].{
            "orderId": id,
            "orderDate": date,
            "customer": $lookup(data.users, userId).name,
            "items": items[].{
              "product": $lookup(data.products, productId).name,
              "quantity": quantity,
              "price": $lookup(data.products, productId).price
            },
            "total": $sum(items.(quantity * $lookup(data.products, productId).price))
          }
        }

output:
  enrichedOrders: ${combine.output.enrichedOrders}

JSONata Functions

String Functions

# Concatenation
expression: firstName & " " & lastName

# Uppercase/lowercase
expression: $uppercase(name)
expression: $lowercase(email)

# Substring
expression: $substring(text, 0, 100)

# Replace
expression: $replace(text, "old", "new")

# Split/join
expression: $split(text, ",")
expression: $join(items, ", ")

# Trim
expression: $trim(text)

# Match regex
expression: $match(email, /^[^\s@]+@[^\s@]+\.[^\s@]+$/)

Numeric Functions

# Sum
expression: $sum(items.price)

# Average
expression: $average(values)

# Min/max
expression: $min(values)
expression: $max(values)

# Round
expression: $round(value, 2)

# Floor/ceiling
expression: $floor(value)
expression: $ceil(value)

# Number conversion
expression: $number(string)

Array Functions

# Count
expression: $count(items)

# Filter
expression: $filter(items, function($i) { $i.price > 100 })

# Map
expression: $map(items, function($i) { $i.price * 1.1 })

# Sort
expression: $sort(items, function($a, $b) { $a.price < $b.price })

# Distinct
expression: $distinct(items.category)

# Reverse
expression: $reverse(items)

# Append
expression: $append(array1, array2)

# Flatten
expression: $flatten(nestedArray)

Object Functions

# Keys
expression: $keys(object)

# Lookup
expression: $lookup(users, userId)

# Merge
expression: $merge([object1, object2])

# Exists
expression: $exists(object.field)

Boolean Functions

# Boolean
expression: $boolean(value)

# Not
expression: $not(condition)

# And/or (operators)
expression: condition1 and condition2
expression: condition1 or condition2

Date/Time Functions

# Now
expression: $now()

# From millis
expression: $fromMillis(timestamp)

# To millis
expression: $toMillis(dateString)

Advanced Patterns

Conditional Transformation

expression: |
  {
    "status": age >= 18 ? "adult" : "minor",
    "discount": isPremium ? price * 0.9 : price,
    "category": $eval(
      score >= 90 ? '"excellent"' :
      score >= 70 ? '"good"' :
      score >= 50 ? '"average"' :
      '"poor"'
    )
  }

Grouping and Aggregation

expression: |
  {
    "byCategory": $reduce(
      $distinct(items.category),
      function($acc, $cat) {
        $merge([
          $acc,
          {
            $cat: {
              "count": $count(items[category = $cat]),
              "total": $sum(items[category = $cat].price)
            }
          }
        ])
      },
      {}
    )
  }

Recursive Transformation

expression: |
  {
    "tree": $transformTree := function($node) {
      {
        "id": $node.id,
        "name": $node.name,
        "children": $map($node.children, $transformTree)
      }
    };
    $transformTree(data)
  }

Performance Tips

Cache Transformed Data

- member: transform
  type: Transform
  cache:
    ttl: 3600
  input:
    data: ${input.data}
    expression: ${input.expression}

Minimize Expression Complexity

# ✅ Good - simple, focused transformation
expression: |
  items[].{ "name": name, "price": price }

# ❌ Slow - complex nested operations
expression: |
  $reduce($map($filter(items, ...), ...), ...)

Use Parallel Transformations

parallel:
  - member: transform-users
    type: Transform
  - member: transform-orders
    type: Transform
  - member: transform-products
    type: Transform

Testing

import { describe, it, expect } from 'vitest';
import { TestConductor } from '@ensemble-edge/conductor/testing';

describe('transform member', () => {
  it('should transform data', async () => {
    const conductor = await TestConductor.create();

    const result = await conductor.executeMember('transform', {
      data: {
        users: [
          { id: 1, firstName: 'Alice', lastName: 'Smith' },
          { id: 2, firstName: 'Bob', lastName: 'Jones' }
        ]
      },
      expression: 'users[].{ "fullName": firstName & " " & lastName }'
    });

    expect(result).toBeSuccessful();
    expect(result.output).toEqual([
      { fullName: 'Alice Smith' },
      { fullName: 'Bob Jones' }
    ]);
  });

  it('should filter and sort', async () => {
    const conductor = await TestConductor.create();

    const result = await conductor.executeMember('transform', {
      data: {
        users: [
          { name: 'Charlie', age: 25 },
          { name: 'Alice', age: 30 },
          { name: 'Bob', age: 20 }
        ]
      },
      expression: '$sort(users[age >= 25], function($a, $b) { $a.name < $b.name })'
    });

    expect(result.output).toEqual([
      { name: 'Alice', age: 30 },
      { name: 'Charlie', age: 25 }
    ]);
  });

  it('should aggregate data', async () => {
    const conductor = await TestConductor.create();

    const result = await conductor.executeMember('transform', {
      data: {
        orders: [
          { id: 1, total: 100 },
          { id: 2, total: 200 },
          { id: 3, total: 150 }
        ]
      },
      expression: '{ "sum": $sum(orders.total), "avg": $average(orders.total) }'
    });

    expect(result.output).toEqual({
      sum: 450,
      avg: 150
    });
  });
});

Best Practices

  1. Keep expressions simple - Break complex transformations into multiple steps
  2. Use meaningful variable names - $user instead of $u
  3. Test expressions - Verify transformations with unit tests
  4. Handle nulls gracefully - Use $exists() and default values
  5. Cache when possible - Transform operations can be expensive
  6. Document complex expressions - Add comments explaining logic
  7. Validate input data - Check data structure before transforming
  8. Use type coercion - $number(), $string(), $boolean()