Back to all posts
Tutorial
6 min read

What Is JSONata? Learn How to Query JSON Like a Pro

DevToolLab Team

DevToolLab Team

April 25, 2025 (Updated: March 10, 2026)

What Is JSONata? Learn How to Query JSON Like a Pro

If you've ever had to extract a single nested value from a massive, unpredictable JSON payload, you know the pain. You end up writing brittle chains of data.map().filter().reduce(), adding endless ?. optional chaining just to prevent your app from crashing.

This is where JSONata comes in. It's a lightweight, incredibly powerful query and transformation language built specifically for JSON. Think of it as XPath or GraphQL, but heavily optimized for standard JSON objects.

What Exactly is JSONata?

Created by Andrew Coleman at IBM, JSONata is an open-source query language that lets you extract, transform, and map data from JSON documents using insanely concise syntax. Instead of writing 15 lines of JavaScript to reshape an API response, you can do it in two lines of JSONata.

Why Devs Love JSONata (Key Features)

1. Dead-Simple Path Navigation

The most basic feature of JSONata is navigating through deeply nested JSON structures without worrying about undefined errors:

JSON
// Sample JSON data
{
  "customer": {
    "name": "John Smith",
    "address": {
      "city": "New York",
      "zipCode": "10001"
    },
    "orders": [
      { "id": "1001", "product": "Laptop", "price": 1200 },
      { "id": "1002", "product": "Smartphone", "price": 800 }
    ]
  }
}

To access the customer's city, you just trace the path:

customer.address.city

Result: "New York"

Path Navigation in JSONata
Path Navigation in JSONata

2. Painless Array Processing

Stop writing .map((order) => order.product). JSONata understands when it hits an array and automatically maps the properties for you:

customer.orders.product

Result: [ "Laptop", "Smartphone" ]

Array Processing in JSONata
Array Processing in JSONata

3. Inline Filtering

You can filter arrays natively right inside the bracket notation:

customer.orders[price > 1000].product

Result: "Laptop"

Filtering and Conditions in JSONata
Filtering and Conditions in JSONata

4. Built-in Functions

JSONata ships with powerful math and string manipulation functions right out of the box:

$sum(customer.orders.price)

Result: 2000

Functions and Transformations in JSONata
Functions and Transformations in JSONata

5. Instant Data Restructuring

Need to reshape the JSON to fit your frontend component? Just declare the object structure you want and map the variables directly inside it:

{
  "customerName": customer.name,
  "totalSpent": $sum(customer.orders.price),
  "orderCount": $count(customer.orders)
}

Result:

JSON
{
  "customerName": "John Smith",
  "totalSpent": 2000,
  "orderCount": 2
}
Data Restructuring in JSONata
Data Restructuring in JSONata

JSONata vs. The Alternatives

JSONata vs. Vanilla JavaScript

Yes, you can do all of this in plain JS. But JSONata gives you:

  1. Conciseness: It shrinks data reshaping code by 80%.
  2. Declarative style: You declare what shape you want, not how to iterate over it.
  3. Safety: Perfect for allowing end-users or product managers to write custom data-extraction scripts without risking arbitrary JS code injection.

JSONata vs. jq

If you operate entirely in the bash terminal, jq is king. But if you are building Node.js/React applications and need to embed complex transformation logic natively, JSONata provides a much more intuitive syntax for JavaScript developers.

Getting Started

Integrating JSONata into your Node/TypeScript project takes seconds:

npm install jsonata

Here's how easily it evaluates:

JavaScript
const data = {
  customer: {
    name: "John Doe",
    orders: [
      { id: 1, item: "Laptop", price: 1200 },
      { id: 2, item: "Phone", price: 800 },
      { id: 3, item: "Tablet", price: 1500 }
    ]
  }
};

// Compile the expression
const expression = jsonata('customer.orders[price > 1000]');

// Evaluate against your dataset
const result = await expression.evaluate(data);  
console.log(result);
Basic Usage of JSONata
Basic Usage of JSONata

Real World Developer Use Cases

1. API Response Formatting (BFF Layer)

When your backend returns way too much garbage data, use JSONata in your "Backend-For-Frontend" layer to instantly strip it down before sending it to the client:

JavaScript
const apiResponse = {
    "records": [
      { "id": 1, "firstName": "John", "lastName": "Doe", "age": 28, "email": "john@example.com" },
      { "id": 2, "firstName": "Jane", "lastName": "Smith", "age": 32, "email": "jane@example.com" }
    ],
    "pagination": { "total": 200, "page": 1, "perPage": 2 }
  };
  
  // Transform straight to the frontend requirement
  const expression = jsonata(`{
    "users": records.{
      "fullName": firstName & " " & lastName,
      "contactInfo": {
        "email": email,
        "age": age
      }
    },
    "totalUsers": pagination.total
  }`);
  
  const result = await expression.evaluate(apiResponse);
  console.dir(result,{depth:null})
API Response Transformation with JSONata
API Response Transformation with JSONata

2. Aggregating Sales Data

JSONata easily handles complex $sum and $average mapping across nested transactions:

JavaScript
const salesData = {
  "transactions": [
    { "category": "Electronics", "amount": 1200 },
    { "category": "Clothing", "amount": 90 },
    { "category": "Electronics", "amount": 500 },
    { "category": "Groceries", "amount": 125 }
  ]
};

// Calculate totals perfectly grouped by category
const expression = jsonata(`{
  "total": $sum(transactions.amount),
  "average": $round($average(transactions.amount), 2),
  "categories": {
    "Electronics": $sum(transactions[category="Electronics"].amount),
    "Clothing": $sum(transactions[category="Clothing"].amount),
    "Groceries": $sum(transactions[category="Groceries"].amount)
  }
}`);

expression.evaluate(salesData)
  .then(result => console.log(JSON.stringify(result, null, 2)))
  .catch(err => console.error('Error:', err));
Data Aggregation and Analysis with JSONata
Data Aggregation and Analysis with JSONata

3. Dynamic Environment Configuration

Inject JSONata scripts to parse generic config templates into exact environment URLs without writing complex JS logic:

JavaScript
const baseConfig = {
    "app": {
      "name": "DevTools App",
      "environments": {
        "development": { "apiUrl": "http://localhost:3000/api", "debug": true },
        "staging": { "apiUrl": "https://staging-api.example.com", "debug": true },
        "production": { "apiUrl": "https://api.example.com", "debug": false }
      },
      "defaults": { "timeout": 5000, "retries": 3 }
    }
  };
  
  async function getConfig(env) {
    const expression = jsonata(`{
      "name": app.name,
      "apiUrl": app.environments.${env}.apiUrl,
      "debug": app.environments.${env}.debug,
      "timeout": app.defaults.timeout,
      "retries": app.defaults.retries
    }`);
    
    const result = await expression.evaluate(baseConfig);
    return result;
  }
  
  getConfig('development').then(result => console.log(result)).catch(error => console.error(error));
  getConfig('staging').then(result => console.log(result)).catch(error => console.error(error));
  getConfig('production').then(result => console.log(result)).catch(error => console.error(error));
Configuration Management with JSONata
Configuration Management with JSONata

Advanced JSONata Features

1. Writing Custom Functions

You aren't restricted to built-ins. You can declare fully custom functions directly inside your JSONata script payload:

JavaScript
const apiResponse = {
    "order": {
        "items": [
            {"name": "Apple","price": 0.99,"quantity": 3},
            {"name": "Banana","price": 0.59,"quantity": 5},
            {"name": "Orange","price": 1.29,"quantity": 2}
        ]
    }
}
const expression = jsonata(`(
    $formatCurrency := function($amount) {
      "$" & $string($round($amount, 2))
    };
    {
      "items": order.items.{
        "product": name,
        "price": $formatCurrency(price),
        "total": $formatCurrency(price * quantity)
      },
      "orderTotal": $formatCurrency($sum(order.items.(price * quantity)))
    }
  )`);
try {
    const result = await expression.evaluate(apiResponse);
    console.dir(result, { depth: null, colors: true });
} catch (err) {
    console.error('JSONata evaluation failed:\n', err);
}
Custom Functions in JSONata
Custom Functions in JSONata

2. Clean Error Handling

Ternary operators let you aggressively fallback to defaults when third-party APIs fail to send the data you expect:

JavaScript
const apiResponse = {
  customer: {
    address: {}
  }
};

// Falls back safely if zipCode is entirely missing
const expression = jsonata(
  `customer.address.zipCode ? customer.address.zipCode : "No ZIP code provided"`
);

try {
  const result = await expression.evaluate(apiResponse);
  console.dir(result, { depth: null, colors: true });
} catch (err) {
  console.error('JSONata evaluation failed:\n', err);
}
Error Handling in JSONata
Error Handling in JSONata

3. Recursive Processing

Need to map a complex file directory tree? JSONata allows self-calling recursion to parse infinite depths effortlessly:

JavaScript
const apiResponse = {
    "rootNode": {
        "name": "root",
        "type": "folder",
        "children": [
            {"name": "file1.txt","type": "file","size": 123},
            {"name": "subfolder","type": "folder",
            "children": [{"name": "file2.txt","type": "file","size": 456}]
            }
        ]
    }
};

// Watch $processNode elegantly call itself
const expression = jsonata(`(
        $processNode := function($node) {
          $node.type = "folder" ? 
            {
              "name": $node.name,
              "type": "folder",
              "children": $node.children.$processNode($)
            } : 
            {
              "name": $node.name,
              "type": "file",
              "size": $node.size
            }
        };
        
        $processNode(rootNode)
      )`);
try {
    const result = await expression.evaluate(apiResponse);
    console.dir(result, { depth: null, colors: true });
} catch (err) {
    console.error('JSONata evaluation failed:\n', err);
}
Recursive Processing in JSONata
Recursive Processing in JSONata

Conclusion

If you spend your days mutating APIs, scraping payloads, or building data pipes, JSONata is fundamentally a superpower. It strips away the imperative boilerplate of JavaScript arrays yielding clean, readable, configuration-driven transformations.

Stop dreading data reshaping grids. Add JSONata to your project and let its elegant syntax do the heavy lifting for you!

Related Posts

Are Claude Code Mods Safe? We Checked 22

Claude Code mods run unsandboxed with your permissions. claude plugin validate shows what one can do; 9 of 22 public mods reach files, programs or the network.

By DevToolLab Team•

npm Supply Chain Attack Mitigation in 2026

Block dependency install scripts and add a one-day release-age gate. npm 12, npm 11, pnpm, Yarn and Bun settings, tested against the tensorlake worm.

By DevToolLab Team•

How to Identify a Device by MAC Address

The first 3 bytes of a MAC address name the chip maker in the IEEE registry. How to look one up, spot randomized phone addresses, and script it in Python or JS.

By DevToolLab Team•