Subtracting and Slicing Paths
Relevant source files
The following files were used as context for generating this wiki page:
The data-path library provides a set of algebraic operations to manipulate type-safe object property paths. Two core operations for path manipulation are Subtraction and Slicing. These features allow developers to programmatically derive relative paths from absolute ones or extract specific segments of a path.
Subtraction is primarily used to find the relative difference between two paths when one is a prefix or suffix of the other. Slicing mimics the standard Array.prototype.slice behavior, enabling the creation of sub-paths based on segment indices. Both operations are immutable, returning new path instances without modifying the original objects.
Sources: src/impl/path-impl.ts:318-356, README.md:144-162
Path Subtraction
The .subtract() method allows for the removal of segments of one path from another. This is particularly useful in scenarios involving reusable components that receive a base path and need to calculate their relative position within a data structure.
Sources: src/impl/path-impl.ts:318-337, README.md:158-160
Logic and Constraints
The subtraction logic follows these rules:
- Prefix Removal: If the provided path is a prefix of the current path, the prefix is removed, and the remaining suffix is returned as a new path.
- Suffix Removal: If the provided path is a suffix of the current path, the suffix is removed, and the remaining prefix is returned.
- Identity: If both paths are identical, an empty path is returned.
- Null Results: If the provided path is neither a prefix nor a suffix, or if it is longer than the current path, the method returns
null.
Sources: src/impl/path-impl.ts:318-337, src/tests/algebra.spec.ts:107-160
Workflow of Subtraction
The following diagram illustrates the decision logic within the .subtract() method.
The method determines if the relationship between the two sets of segments allows for a logical subtraction.
Sources: src/impl/path-impl.ts:318-337
Path Slicing
The .slice() method provides a mechanism to extract a subset of segments from a path. It adheres to the same semantics as Array.prototype.slice, accepting optional start and end indices.
Sources: src/impl/path-impl.ts:346-356, src/tests/algebra.spec.ts:162-185
Slicing Characteristics
- Segment Access: It operates directly on the
segmentsarray, which contains the string keys and numeric indices of the path. - Immutability: Like all manipulation methods in
data-path, it returns a newPathImplinstance. - Index Handling: It supports negative indices (counting from the end) and gracefully handles out-of-bounds indices by clamping them to the segment array length.
Sources: src/impl/path-impl.ts:346-356, src/tests/algebra.spec.ts:162-185
Slicing Examples
| Operation | Path Context | Resulting String ($) |
|---|---|---|
.slice(0, 1) | users.0.name | users |
.slice(1) | users.0.name | 0.name |
.slice(-1) | users.0.name | name |
.slice(0, 10) | a.b.c | a.b.c (Clamped) |
Sources: src/tests/algebra.spec.ts:164-177, README.md:162
Component Overview
The manipulation methods are defined in the BasePath interface and implemented in the PathImpl class.
| Method | Parameters | Return Type | Description |
|---|---|---|---|
subtract | other: ResolvablePath<T> | Path<T, V, string> | null | Removes a prefix or suffix matching other. |
slice | start?, end? | Path<T, unknown, string> | Returns a new path with a subset of segments. |
Sources: src/types.ts:219-236, src/impl/path-impl.ts:318-356
Structural Relationships
The relationship between path objects and their manipulation methods is defined through the following class structure:
The PathImpl class encapsulates the logic for segment-based algebra, ensuring that segment-level comparisons and array manipulations are handled consistently.
Sources: src/types.ts:145-246, src/impl/path-impl.ts:32-356
Implementation Details
The implementation of these methods relies on utility functions to resolve path segments from various inputs (lambdas, other path objects, or raw segments).
- resolveSegments: Converts a
ResolvablePath(which might be a lambda(p) => p.a.b) into a flat array ofSegments. - segmentsEqual: Used by
.subtract()to verify if a prefix or suffix matches the target path exactly.
Sources: src/utils.ts:18-28, src/utils.ts:46-52
// Example of subtraction logic implementation
subtract(other: ResolvablePath<T>): Path<T, V, string> | null {
const a = this.segments;
const b = resolveSegments(other);
if (b.length > a.length) return null;
if (segmentsEqual(a, b)) return new PathImpl<T, V, string>([]);
// b prefix of a?
if (segmentsEqual(a.slice(0, b.length), b)) {
return new PathImpl<T, V, string>(a.slice(b.length));
}
// ... suffix check ...
}
Sources: src/impl/path-impl.ts:318-337
Summary
Subtracting and slicing are essential tools for managing complex path structures in the data-path library. By leveraging the underlying segment array, these methods provide a predictable, type-safe, and immutable way to transform paths. This enables developers to create modular code where components can easily calculate relative data locations or isolate specific parts of a deep object structure.
Sources: README.md:144-162, src/impl/path-impl.ts:318-356