The essential utility for seamless data traversal, powered by an intuitive JPath subset.
- Simplify Parsing: Stop writing complex, multi-level Apex loops.
- Intuitive Syntax: Use familiar JPath syntax to retrieve values quickly.
- High Performance: Efficiently query any JSON structure directly in your Salesforce environment.
- Visualise & Generate Code: Use our free, intuitive, interactive JPath finder tool to visualise messy payloads, evaluate your queries, and generate sample Apex code you can copy and paste directly into your project.
In Apex, while JSON.deserialize() is effective for mapping JSON to strongly-typed Apex objects, it requires a predefined class structure. For dynamic JSON or when you only need a few specific values from a large, complex structure, using JSON.deserializeUntyped() is the alternative. However, this often leads to verbose and hard-to-read code with multiple levels of casting and map lookups, a pattern often referred to as "map-string-object hell."
The apex-jpath library simplifies this by providing a concise, expressive way to query and extract data from a JSON structure using JPath expressions. This avoids the need for boilerplate code and makes the developer's intent much clearer.
- Overview
- Installation
- Features
- → JSONPath Finder Tool ←🆕😍
- Usage Examples
- More Usage Examples
- How?
- Supported Operations
- Limitations and Design Choices
- Testing
- License
- Contributing
- Support
This library provides JSONPath functionality directly within Apex, allowing developers to query and filter JSON data using familiar JSONPath expressions. It's designed specifically for Salesforce environments and supports common JSONPath operations including:
- Property access (
.property) - Array indexing (
[0],[-1]) - Wildcard matching (
[*]) - Filter expressions (
[?(@.property > 10)]) - Recursive descent (
..property) - Slice operations (
[0:2])
Install this package using the following URL in your Salesforce prod org:
Install this package using the following URL in your Salesforce sandbox org:
- Clone this repository
- Deploy to your Salesforce org using sf:
sf force:source:deploy -p force-app
- Native Apex Implementation: Pure Apex code with no external dependencies
- Full JSONPath Support: Implements core JSONPath specification with Salesforce-specific enhancements
- Type Safety: Robust type handling with automatic conversion between numeric types
- Filter Expressions: Complex filtering with comparison operators and logical conditions
- Error Handling: Comprehensive exception handling with descriptive error messages
- Performance Optimized: Efficient parsing and evaluation of JSONPath expressions
🔍 Need help constructing JSONPath expressions?
Use the interactive JSONPath Finder Tool to:
- Paste your JSON payload and instantly visualise its structure
- Navigate hierarchically through nested objects and arrays
- Copy paths with one click - hover over any node and click "Copy"
- Avoid syntax errors - get the exact path you need
Perfect for:
- Building complex queries for deeply nested JSON
- Learning JSONPath syntax interactively
- Quickly testing path expressions before using them in Apex
Pro Tip: Use this tool alongside the example files to rapidly prototype and validate your JSONPath queries!
Quick Testing:
For rapid hands-on evaluation, see the attached example files (examples/example1.txtandexamples/example2.txt).
These contain ready-to-copy Apex code blocks for Execute Anonymous windows, allowing you to quickly test JSONPath queries and verify library functionality in your Salesforce org.
Important: Run each test block individually to avoid hitting governor limits (see comments in the files for details).
String json = '{"name": "John", "age": 30}';
JSONPath jp = new JSONPath(json);
List<Object> result = jp.selectPath('$.name');
// Returns: ["John"]String json = '{"fruits": ["apple", "banana", "orange"]}';
JSONPath jp = new JSONPath(json);
List<Object> result = jp.selectPath('$.fruits[1]');
// Returns: ["banana"]String json = '{
"employees": [
{"name": "John", "salary": 50000},
{"name": "Jane", "salary": 60000},
{"name": "Bob", "salary": 45000}
]
}';
JSONPath jp = new JSONPath(json);
List<Object> result = jp.selectPath('$.employees[?(@.salary > 50000)]');
// Returns employees with salary > 50000String json = '{
"items": [
{"name": "A", "price": "12.99"},
{"name": "B", "price": 12.99},
{"name": "C", "price": 8}
]
}';
JSONPath jp = new JSONPath(json);
List<Object> result = jp.selectPath('$.items[?(@.price >= 12.99)]');
// Correctly matches both items with price >= 12.99Find a series of examples to get you started with apex-jpath. All examples will use the following sample JSON, which represents a simple data structure for a fictional online store.
{
"store": {
"name": "The Awesome Store",
"location": {
"city": "San Francisco",
"state": "CA"
},
"products": [
{
"id": 1,
"name": "Laptop",
"price": 1200,
"tags": ["electronics", "computers"],
"inStock": true
},
{
"id": 2,
"name": "Mouse",
"price": 25,
"tags": ["electronics", "accessories"],
"inStock": true
},
{
"id": 3,
"name": "Book",
"price": 15.5,
"tags": ["reading", "education"],
"inStock": false
}
]
}
}These examples cover the most common use cases for accessing data.
To get the name of the store.
Apex Code:
String jsonString = '...'; // Your JSON string here
JPath path = new JPath(jsonString);
String storeName = (String) path.select('store.name');
// Extracts the store's name: "The Awesome Store"To get the city where the store is located.
Apex Code:
String jsonString = '...'; // Your JSON string here
JPath path = new JPath(jsonString);
String city = (String) path.select('store.location.city');
// Extracts the city: "San Francisco"To get the first product in the products array. Note that the result will be a Map<String, Object> representing the JSON object.
Apex Code:
String jsonString = '...'; // Your JSON string here
JPath path = new JPath(jsonString);
Map<String, Object> firstProduct = (Map<String, Object>) path.select('store.products[0]');
// Retrieves the first product object from the array.
System.assertEquals(1, firstProduct.get('id'));
System.assertEquals('Laptop', firstProduct.get('name'));To get the name of the second product.
Apex Code:
String jsonString = '...'; // Your JSON string here
JPath path = new JPath(jsonString);
String secondProductName = (String) path.select('store.products[1].name');
// Gets the name of the second product: "Mouse"These examples show how to work with collections of data.
To get a list of all product names.
Apex Code:
String jsonString = '...'; // Your JSON string here
JPath path = new JPath(jsonString);
List<Object> productNames = (List<Object>) path.select('store.products[*].name');
// Returns a list of all product names: ["Laptop", "Mouse", "Book"]To get the full list of products.
Apex Code:
String jsonString = '...'; // Your JSON string here
JPath path = new JPath(jsonString);
List<Object> allProducts = (List<Object>) path.select('store.products');
// Returns the entire list of product objects.
System.assertEquals(3, allProducts.size());These examples demonstrate the real power of apex-jpath by using filters to query for specific data.
To find the product with the id of 3.
Apex Code:
String jsonString = '...'; // Your JSON string here
JPath path = new JPath(jsonString);
// Note that the filter returns a list of matching elements
List<Object> results = (List<Object>) path.select('store.products[?(@.id == 3)]');
// Finds the product object where the id is 3.
System.assertEquals(1, results.size());
Map<String, Object> book = (Map<String, Object>) results[0];
System.assertEquals('Book', book.get('name'));
System.assertEquals(15.50, book.get('price'));To get the price of the product named "Laptop".
Apex Code:
String jsonString = '...'; // Your JSON string here
JPath path = new JPath(jsonString);
List<Object> prices = (List<Object>) path.select('store.products[?(@.name == \'Laptop\')].price');
// Finds the price of the product named "Laptop": [1200]
Decimal laptopPrice = (Decimal) prices[0];
System.assertEquals(1, prices.size());
System.assertEquals(1200, laptopPrice);To get the names of all products that are inStock.
Apex Code:
String jsonString = '...';
JPath path = new JPath(jsonString);
List<Object> inStockProducts = (List<Object>) path.select('store.products[?(@.inStock == true)].name');
// Returns the names of all products that are in stock: ["Laptop", "Mouse"]
System.assertEquals(new List<Object>{'Laptop', 'Mouse'}, inStockProducts);To get the names of all products with a price greater than 30.
Apex Code:
String jsonString = '...';
JPath path = new JPath(jsonString);
List<Object> expensiveProducts = (List<Object>) path.select('store.products[?(@.price > 30)].name');
// Gets the names of products with a price greater than 30: ["Laptop"]
System.assertEquals(new List<Object>{'Laptop'}, expensiveProducts);- JSONPath: Main class for querying JSON data
- JSONPathException: Custom exception class for JSONPath-related errors
- JSONPathTest: Comprehensive test suite demonstrating usage
| Operation | Syntax | Description |
|---|---|---|
| Property Access | $.property |
Access object property |
| Array Indexing | $.array[0] |
Access array element by index |
| Wildcard | $.array[*] |
Select all elements |
| Filter | $.array[?(@.prop > 10)] |
Filter elements |
| Recursive Descent | $..property |
Find property at any level |
| Slice | $.array[0:2] |
Extract slice of array |
This library is intentionally focused on the most common and essential JSONPath features that are safe and performant on the Salesforce platform. This Apex version deliberately omits the following complex and platform-problematic features:
1. Script Expressions (...) and eval()
- What it is: The original JSONPath proposal allowed for arbitrary script expressions (typically JavaScript) to be evaluated within brackets, like
$.store.book[(@.length-1)]to get the last book, or$.store.book[?(@.price > 10 && @.category === 'fiction')]. - Why it's omitted:
- Security Risk: Executing arbitrary script expressions is equivalent to
eval(), which is a massive security vulnerability. It is explicitly disallowed in Apex for this reason. - Complexity: Building a safe and efficient expression parser and evaluator from scratch in Apex is a monumental task that would make the library incredibly large and slow.
- Security Risk: Executing arbitrary script expressions is equivalent to
2. Complex Filter Logic (&&, ||, Grouping)
- What it is: The ability to combine filter conditions, such as
[?(@.price < 10 && @.category == 'fiction')]. - Why it's omitted:
- Governor Limits: A full-fledged logical expression parser can become very CPU-intensive, especially with nested logic and large arrays, posing a risk to Salesforce governor limits.
- Pragmatism: While powerful, these queries can often be simplified. A developer can first filter by
[?(@.price < 10)]and then use a simple Apex loop on the smaller result set to check the second condition (category == 'fiction'). This approach is more explicit and often safer from a performance standpoint.
3. JSONPath Functions (.length(), .avg(), .match(), etc.)
- What it is: The IETF RFC standardises functions that can be used within expressions, like
[?(@.title.length() > 10)]. - Why it's omitted:
- Implementation Complexity: Each function would require custom logic to implement. Like complex filters, this adds significant overhead and potential performance issues. The logic for a function like
.match()(regex matching) would be particularly complex in Apex.
- Implementation Complexity: Each function would require custom logic to implement. Like complex filters, this adds significant overhead and potential performance issues. The logic for a function like
4. Accessing the Root ($) within Filters
- What it is: The ability to compare an item against a value from the root of the document, like
[?(@.price > $.expensive)]. - Why it's omitted: The current simple filter parser is not designed to handle expressions that break out of the current context (
@). Supporting this would require a more sophisticated evaluation engine that maintains a reference to the root node throughout the filter evaluation process.
By omitting features that rely on eval() or require highly complex, CPU-intensive parsing, the library remains secure and efficient, staying well within governor limits for all reasonable use cases. It provides a massive leap in functionality over parsing JSON manually with JSON.deserializeUntyped and nested loops, while avoiding the advanced edge cases that offer diminishing returns for their immense implementation cost and risk.
All code in this repository is continuously tested using GitHub Actions. The test suite runs automatically on every push and pull request to ensure code quality and reliability.
The CI/CD pipeline:
- Creates a fresh Salesforce scratch org
- Deploys the
apex-jpathlibrary source code - Runs all Apex tests with code coverage
- Reports pass/fail status via the badge at the top of the repo README
You can view detailed test results and logs in the Actions tab.
MIT License
- Fork the repository
- Create a feature branch
- Commit your changes
- Push to the branch
- Create a Pull Request
Questions and feature requests? Please use the Discussions tab. For bugs, open an issue.
All proceeds from this project support coding education for kids in Africa. Check out Tangible Africa.
