Mutations Guide
Learn how to modify JSON data using JSONPath queries with jsonpathx's powerful Mutation API. Mutations allow you to set, update, delete, insert, and transform values in your JSON data based on JSONPath expressions.
Table of Contents
- Overview
- Mutation Options
- Basic Mutations
- Array Mutations
- Object Mutations
- Numeric Mutations
- Boolean Mutations
- Advanced Patterns
- Error Handling
- Best Practices
Overview
The Mutation API provides methods to modify JSON data based on JSONPath queries. All mutation operations:
- Support JSONPath queries - Use any valid JSONPath expression to target values
- Return mutation results - Get modified data, count, and paths
- Are immutable by default - Create copies instead of mutating in place
- Support batch operations - Modify multiple matches in one call
Basic Usage
import { Mutation } from '@jsonpathx/jsonpathx';
const data = {
users: [
{ id: 1, name: 'Alice', score: 100 },
{ id: 2, name: 'Bob', score: 150 }
]
};
// Set a value
const result = await Mutation.set(data, '$.users[0].name', 'Alicia');
console.log(result.data); // Modified data
console.log(result.modified); // Number of changes
console.log(result.paths); // Modified pathsMutation Result
All mutation operations return a MutationResult:
interface MutationResult {
data: unknown; // The mutated data
modified: number; // Number of values modified
paths: string[]; // Paths that were modified
}Mutation Options
Immutable vs Mutable
By default, mutations are immutable - they create a copy of your data before making changes.
interface MutationOptions {
immutable?: boolean; // Default: true
createPath?: boolean; // Default: false
// ...plus all QueryOptions
}Immutable Operations (Default)
const data = { value: 10 };
const result = await Mutation.set(data, '$.value', 20, {
immutable: true // Default behavior
});
console.log(data.value); // 10 (original unchanged)
console.log(result.data.value); // 20 (new copy)Use when:
- You need to preserve the original data
- Working with React state or Redux
- Creating undo/redo functionality
- Avoiding side effects
Mutable Operations
const data = { value: 10 };
const result = await Mutation.set(data, '$.value', 20, {
immutable: false // Mutate in place
});
console.log(data.value); // 20 (original modified)
console.log(result.data.value); // 20 (same reference)
console.log(data === result.data); // trueUse when:
- Performance is critical
- Working with large datasets
- Data is already isolated (local scope)
- Creating temporary transformations
Path Creation
Enable automatic creation of intermediate paths that don't exist.
const data = { };
const result = await Mutation.set(data, '$.user.profile.name', 'Alice', {
createPath: true // Create user.profile if missing
});
console.log(result.data);
// { user: { profile: { name: 'Alice' } } }Without createPath:
// Will not modify anything if path doesn't exist
const result = await Mutation.set({}, '$.user.name', 'Alice');
console.log(result.modified); // 0Basic Mutations
Set Values
Set values at a JSONPath to a specific value.
Signature:
Mutation.set(
data: unknown,
path: string,
value: unknown,
options?: MutationOptions
): Promise<MutationResult>Examples:
const data = {
users: [
{ id: 1, name: 'Alice', active: false },
{ id: 2, name: 'Bob', active: false }
]
};
// Set single value
await Mutation.set(data, '$.users[0].name', 'Alicia');
// Set multiple values (all matches)
await Mutation.set(data, '$.users[*].active', true);
// Set with filter
await Mutation.set(
data,
'$.users[?(@.id === 1)].active',
true
);
// Create nested path
await Mutation.set(
data,
'$.users[0].profile.avatar',
'avatar.png',
{ createPath: true }
);Delete Values
Delete values at a JSONPath.
Signature:
Mutation.delete(
data: unknown,
path: string,
options?: MutationOptions
): Promise<MutationResult>Examples:
const data = {
users: [
{ id: 1, name: 'Alice', temp: true },
{ id: 2, name: 'Bob', temp: false }
]
};
// Delete property from objects
await Mutation.delete(data, '$.users[*].temp');
// Delete array element
await Mutation.delete(data, '$.users[0]');
// Delete with filter
await Mutation.delete(data, '$.users[?(@.temp === true)]');Array Deletion:
const data = { items: [1, 2, 3, 4, 5] };
// Remove specific elements
await Mutation.delete(data, '$.items[1]');
// Result: [1, 3, 4, 5]
// Remove multiple elements (deletes deepest first)
await Mutation.delete(data, '$.items[1,3]');
// Result: [1, 3, 5]Update Values
Update values using a transform function.
Signature:
Mutation.update(
data: unknown,
path: string,
transform: (value: unknown, index?: number) => unknown,
options?: MutationOptions
): Promise<MutationResult>Examples:
const data = {
products: [
{ name: 'Widget', price: 100 },
{ name: 'Gadget', price: 200 }
]
};
// Apply discount to all prices
await Mutation.update(
data,
'$.products[*].price',
price => price * 0.9 // 10% off
);
// Update with index
await Mutation.update(
data,
'$.products[*].name',
(name, index) => `${index + 1}. ${name}`
);
// Complex transformation
await Mutation.update(
data,
'$.products[*]',
product => ({
...product,
displayPrice: `$${product.price.toFixed(2)}`,
onSale: product.price < 150
})
);Array Mutations
Insert
Insert a value into an array at a specific position.
Signature:
Mutation.insert(
data: unknown,
path: string,
value: unknown,
options?: MutationOptions & { position?: 'start' | 'end' | number }
): Promise<MutationResult>Examples:
const data = { items: [1, 2, 3] };
// Insert at end (default)
await Mutation.insert(data, '$.items', 4);
// Result: [1, 2, 3, 4]
// Insert at start
await Mutation.insert(data, '$.items', 0, { position: 'start' });
// Result: [0, 1, 2, 3]
// Insert at specific index
await Mutation.insert(data, '$.items', 999, { position: 2 });
// Result: [1, 2, 999, 3]Multiple Arrays:
const data = {
lists: [[1, 2], [3, 4], [5, 6]]
};
// Insert into all arrays
await Mutation.insert(data, '$.lists[*]', 999, { position: 'end' });
// Result: [[1, 2, 999], [3, 4, 999], [5, 6, 999]]Push
Append a value to the end of arrays.
Signature:
Mutation.push(
data: unknown,
path: string,
value: unknown,
options?: MutationOptions
): Promise<MutationResult>Examples:
const data = {
lists: [[1, 2], [3, 4]]
};
// Push to all arrays
await Mutation.push(data, '$.lists[*]', 999);
// Result: [[1, 2, 999], [3, 4, 999]]
// Push object to array
const data2 = { users: [] };
await Mutation.push(data2, '$.users', { id: 1, name: 'Alice' });Unshift
Prepend a value to the start of arrays.
Signature:
Mutation.unshift(
data: unknown,
path: string,
value: unknown,
options?: MutationOptions
): Promise<MutationResult>Examples:
const data = {
lists: [[1, 2], [3, 4]]
};
// Unshift to all arrays
await Mutation.unshift(data, '$.lists[*]', 0);
// Result: [[0, 1, 2], [0, 3, 4]]Object Mutations
Merge
Merge an object into objects at a JSONPath.
Signature:
Mutation.merge(
data: unknown,
path: string,
mergeValue: Record<string, unknown>,
options?: MutationOptions
): Promise<MutationResult>Examples:
const data = {
users: [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
]
};
// Add property to all users
await Mutation.merge(data, '$.users[*]', { active: true });
// Result: users have active: true
// Merge multiple properties
await Mutation.merge(data, '$.users[*]', {
verified: true,
role: 'user',
createdAt: new Date().toISOString()
});
// Merge at root
await Mutation.merge(data, '$', { version: '1.0' });Overwriting Properties:
const data = {
user: { name: 'Alice', age: 30 }
};
// Merge overwrites existing properties
await Mutation.merge(data, '$.user', { age: 31, city: 'NYC' });
// Result: { name: 'Alice', age: 31, city: 'NYC' }Numeric Mutations
Increment
Increment numeric values by a specified amount.
Signature:
Mutation.increment(
data: unknown,
path: string,
amount?: number, // Default: 1
options?: MutationOptions
): Promise<MutationResult>Examples:
const data = {
counters: [
{ count: 5, views: 100 },
{ count: 10, views: 200 }
]
};
// Increment by 1 (default)
await Mutation.increment(data, '$.counters[*].count');
// Counts: 6, 11
// Increment by custom amount
await Mutation.increment(data, '$.counters[*].views', 10);
// Views: 110, 210
// Increment with filter
await Mutation.increment(
data,
'$.counters[?(@.count < 10)].count',
5
);Decrement
Decrement numeric values by a specified amount.
Signature:
Mutation.decrement(
data: unknown,
path: string,
amount?: number, // Default: 1
options?: MutationOptions
): Promise<MutationResult>Examples:
const data = {
inventory: [
{ item: 'Widget', stock: 100 },
{ item: 'Gadget', stock: 50 }
]
};
// Decrement by 1
await Mutation.decrement(data, '$.inventory[*].stock');
// Stock: 99, 49
// Decrement by custom amount
await Mutation.decrement(data, '$.inventory[0].stock', 10);
// Stock: 90Boolean Mutations
Toggle
Toggle boolean values (true ↔ false).
Signature:
Mutation.toggle(
data: unknown,
path: string,
options?: MutationOptions
): Promise<MutationResult>Examples:
const data = {
flags: [
{ active: true, visible: false },
{ active: false, visible: true }
]
};
// Toggle all active flags
await Mutation.toggle(data, '$.flags[*].active');
// Active: false, true
// Toggle with filter
await Mutation.toggle(
data,
'$.flags[?(@.active === true)].visible'
);Advanced Patterns
Chaining Mutations
Since mutations are immutable by default, you can chain them:
let result = await Mutation.set(data, '$.users[*].active', false);
result = await Mutation.merge(result.data, '$.users[*]', {
verified: false
});
result = await Mutation.update(result.data, '$.users[*].score', s => s * 2);
const finalData = result.data;Conditional Updates
Use filters for conditional mutations:
// Update only items matching criteria
await Mutation.update(
data,
'$.products[?(@.price > 100)].price',
price => price * 0.9 // Discount expensive items
);
// Merge only into active users
await Mutation.merge(
data,
'$.users[?(@.active === true)]',
{ lastLogin: Date.now() }
);Batch Operations
Mutations automatically handle multiple matches:
const data = {
categories: [
{ name: 'Electronics', items: [] },
{ name: 'Clothing', items: [] },
{ name: 'Books', items: [] }
]
};
// Push to all categories at once
await Mutation.push(
data,
'$.categories[*].items',
{ id: 1, name: 'New Item' }
);Complex Transformations
Combine mutations with QueryBuilder for complex workflows:
import { JSONPath, Mutation } from '@jsonpathx/jsonpathx';
// Get IDs of inactive users
const inactiveIds = await JSONPath.create(data)
.query('$.users[?(@.active === false)]')
.map(user => user.id)
.execute();
// Delete inactive users
for (const id of inactiveIds) {
await Mutation.delete(data, `$.users[?(@.id === ${id})]`);
}Nested Mutations
Create complex nested structures:
const data = {};
// Create nested structure
let result = await Mutation.set(
data,
'$.app.settings.theme.colors.primary',
'#007bff',
{ createPath: true }
);
result = await Mutation.set(
result.data,
'$.app.settings.theme.colors.secondary',
'#6c757d',
{ createPath: true }
);Atomic Updates
For mutable operations, you can perform atomic updates:
async function atomicIncrement(data: any, path: string) {
// Mutate in place for atomicity
return await Mutation.increment(data, path, 1, {
immutable: false
});
}
// Use in concurrent scenarios
await atomicIncrement(sharedData, '$.counter');Error Handling
Type Errors
Operations validate types before mutation:
try {
// Cannot increment string
await Mutation.increment(data, '$.name');
} catch (error) {
// Error: Cannot increment non-number value
}
try {
// Cannot toggle number
await Mutation.toggle(data, '$.count');
} catch (error) {
// Error: Cannot toggle non-boolean value
}
try {
// Cannot insert into non-array
await Mutation.insert(data, '$.user', 'value');
} catch (error) {
// Error: Cannot insert into non-array
}Safe Mutations
Handle errors gracefully:
async function safeMutation<T>(
operation: () => Promise<MutationResult>,
fallback: T
): Promise<T> {
try {
const result = await operation();
return result.data as T;
} catch (error) {
console.error('Mutation failed:', error);
return fallback;
}
}
// Usage
const result = await safeMutation(
() => Mutation.set(data, '$.invalid.path', 'value'),
data // Return original on error
);Validation Before Mutation
async function validatedMutation(data: any, path: string, value: any) {
// Check if path exists
const matches = await JSONPath.query(path, data);
if (matches.length === 0) {
throw new Error(`Path ${path} not found`);
}
// Perform mutation
return await Mutation.set(data, path, value);
}Best Practices
1. Use Immutable by Default
Always use immutable mutations unless you have a specific reason not to:
// ✓ Good: Immutable (default)
const result = await Mutation.set(data, '$.value', 10);
// ✗ Avoid: Mutable (unless necessary)
const result = await Mutation.set(data, '$.value', 10, {
immutable: false
});2. Check Mutation Results
Always check how many items were modified:
const result = await Mutation.set(data, '$.users[*].active', true);
if (result.modified === 0) {
console.warn('No users were modified');
} else {
console.log(`Updated ${result.modified} users`);
console.log('Paths:', result.paths);
}3. Use Specific Paths
Use specific JSONPath expressions to avoid unintended modifications:
// ✓ Good: Specific
await Mutation.set(data, '$.users[0].name', 'Alice');
// ✗ Risky: Too broad
await Mutation.set(data, '$..*', 'value');4. Validate Types
Validate data types before type-specific mutations:
// Check before incrementing
const value = await JSONPath.query('$.count', data);
if (typeof value[0] === 'number') {
await Mutation.increment(data, '$.count');
}5. Use createPath Carefully
Only use createPath when you intend to create new structures:
// ✓ Good: Intentional path creation
await Mutation.set(data, '$.new.nested.path', 'value', {
createPath: true
});
// ✗ Risky: May mask typos
await Mutation.set(data, '$.usrs[0].name', 'Alice', {
createPath: true // Creates "usrs" instead of using "users"
});6. Chain Related Mutations
Chain mutations that work on the same data:
async function updateUser(data: any, userId: number) {
const path = `$.users[?(@.id === ${userId})]`;
let result = await Mutation.merge(data, path, {
updatedAt: new Date().toISOString()
});
result = await Mutation.increment(result.data, `${path}.version`);
return result.data;
}7. Document Side Effects
When using mutable operations, document them clearly:
/**
* Updates user score IN PLACE (mutates data)
* @param data - User data (will be modified)
*/
async function updateScore(data: any, score: number) {
return await Mutation.set(data, '$.score', score, {
immutable: false // Documented side effect
});
}8. Use TypeScript
Leverage TypeScript for type safety:
interface User {
id: number;
name: string;
active: boolean;
}
interface AppData {
users: User[];
}
async function activateUser(data: AppData, userId: number) {
const result = await Mutation.set(
data,
`$.users[?(@.id === ${userId})].active`,
true
);
return result.data as AppData;
}See Also
- Query API - Core query methods
- QueryBuilder - Fluent query API
- Mutation Examples - Practical examples
- Error Handling - Error handling strategies