Working with complex JSON payloads can quickly become a nightmare. You end up chaining .map(), .filter(), and .reduce() calls across multiple lines just to pull out a few nested values. Add optional chaining to avoid crashes and the code becomes nearly unreadable.
There is a cleaner way - JSONata. It is a compact, purpose-built query and transformation language for JSON data. Think of it as XPath for XML, but designed from the ground up to work with JSON objects and arrays.
What is JSONata?
JSONata is an open-source project originally created by Andrew Coleman at IBM. It gives developers a declarative syntax to extract and reshape JSON data without writing procedural JavaScript loops. Where vanilla JS might take 15 lines, a JSONata expression often takes one.
It is available as an npm package and integrates naturally into Node.js and TypeScript projects.
Simple Path Navigation
The foundation of JSONata is its dot-notation path traversal. Given a nested JSON object, you simply trace the path to the value you need:
customer.address.city
This returns the city value without any need for null checks or defensive coding. JSONata handles missing properties gracefully by returning undefined rather than throwing errors.
Automatic Array Mapping
When JSONata encounters an array during path traversal, it automatically maps across all items. There is no need to write an explicit .map() call:
customer.orders.product
This returns an array of all product names from every order in one clean expression.
Inline Filtering
You can filter arrays directly using bracket notation with a condition:
customer.orders[price > 1000].product
This returns only the products from orders where the price exceeds 1000. No .filter() callback required.
Built-in Aggregation Functions
JSONata ships with a solid set of built-in functions for math, strings, and arrays. Aggregating a set of values is straightforward:
$sum(customer.orders.price)
Other useful functions include $count(), $average(), $string(), $round(), and many more.
Restructuring JSON Output
One of JSONata's most powerful features is the ability to declare an entirely new output shape. You define the target structure and map source values into it:
{
"customerName": customer.name,
"totalSpent": $sum(customer.orders.price),
"orderCount": $count(customer.orders)
}
This is especially useful in Backend-For-Frontend (BFF) patterns where you need to slim down a bloated API response before it reaches the client.
JSONata vs Vanilla JavaScript
JavaScript can do everything JSONata does - but at a cost. JSONata wins on:
- Conciseness: Data reshaping expressions shrink dramatically.
- Declarative style: You describe the output shape, not the iteration steps.
- Safety: JSONata expressions are sandboxed, making them safer to expose to non-engineers for custom data extraction.
JSONata vs jq
If you live in the terminal, jq is a great tool. But for Node.js and React applications where you need embedded transformation logic, JSONata offers syntax that feels more natural to JavaScript developers and integrates directly into application code.
Getting Started in Node.js
Installing JSONata takes one command:
npm install jsonata
Here is a basic usage example:
const jsonata = require('jsonata');
const data = {
customer: {
name: "John Doe",
orders: [
{ id: 1, item: "Laptop", price: 1200 },
{ id: 2, item: "Phone", price: 800 }
]
}
};
const expression = jsonata('customer.orders[price > 1000]');
const result = await expression.evaluate(data);
console.log(result);
Real-World Use Case - API Response Formatting
A common pattern is using JSONata in a BFF layer to strip unnecessary fields from a third-party API response before forwarding it to the frontend. Instead of writing custom mapping functions for every endpoint, you define a single JSONata expression that reshapes the response declaratively.
Real-World Use Case - Sales Data Aggregation
JSONata handles grouped aggregations well. You can compute totals and averages across nested transaction arrays using $sum() and $average() in a single expression, without writing complex reduce() logic.
Real-World Use Case - Dynamic Config Parsing
JSONata can dynamically resolve environment-specific values from a shared configuration object. By injecting the environment name into the expression, you get clean, environment-specific output without branching logic in your application code.
Advanced Feature - Custom Functions
JSONata lets you define lambda functions directly inside your expression payload. This is useful for tasks like formatting currency values or applying custom business logic to each item in an array, all within a single self-contained expression.
Advanced Feature - Conditional Fallbacks
Ternary expressions in JSONata make it easy to handle missing or unexpected values from external APIs:
customer.address.zipCode ? customer.address.zipCode : "No ZIP code provided"
This keeps your transformation logic clean and avoids runtime failures.
Advanced Feature - Recursive Processing
JSONata supports recursive self-calling functions, which makes it possible to traverse and process tree structures like file directory hierarchies of arbitrary depth - without any imperative loop logic.
Conclusion
If your work involves API integration, data pipelines, or payload manipulation, JSONata is a tool worth adding to your toolkit. It replaces verbose imperative JavaScript with concise, readable, declarative expressions that are easier to maintain and reason about.
Start with simple path expressions and work up to custom functions and recursive transforms - the learning curve is gentle and the payoff is significant.
References
- Original article: What Is JSONata? Learn How to Query JSON Like a Pro - DevToolLab
- JSONata official site: https://jsonata.org
- JSONata npm package: https://www.npmjs.com/package/jsonata
- JSONata documentation: https://docs.jsonata.org
Top comments (0)