NAME
Data::Path::XS - Fast path-based access to nested data structures
SYNOPSIS
use Data::Path::XS qw(path_get path_set path_delete path_exists);
my $data = { foo => { bar => [1, 2, 3] } };
# String path API
path_get($data, '/foo/bar/1'); # 2
path_set($data, '/foo/bar/1', 42); # sets to 42
path_exists($data, '/foo/baz'); # 0
path_delete($data, '/foo/bar/0'); # removes first element
# Array path API - when path is already parsed
use Data::Path::XS qw(patha_get patha_set);
patha_get($data, ['foo', 'bar', 1]); # 42
patha_set($data, ['foo', 'new'], 'value');
# Compiled path API - for repeated access patterns
use Data::Path::XS qw(path_compile pathc_get pathc_set);
my $cp = path_compile('/foo/bar/1');
pathc_get($data, $cp); # 42
pathc_get($other_data, $cp); # reuse on different data
# KEYWORDS - Zero-overhead syntax (requires ':keywords')
use Data::Path::XS ':keywords';
my $val = pathget $data, "/foo/bar/1"; # fast custom op, no autovivification
pathset $data, "/foo/bar/1", 99; # with autovivification
pathdelete $data, "/foo/bar/1"; # delete element
if (pathexists $data, "/foo/bar") { ... } # check existence
DESCRIPTION
Minimal XS module for fast access to deeply nested Perl data structures using slash-separated paths. Provides four APIs optimized for different use cases:
Keywords API - Near-native performance via compile-time optimization
String API (path_*) - Best general-purpose performance
Array API (patha_*) - When path components are already parsed
Compiled API (pathc_*) - Maximum speed for repeated access patterns
KEYWORDS API
The keywords API provides near-native performance through compile-time optimized custom ops (or native Perl ops for pathset with constant paths).
use Data::Path::XS ':keywords';
pathget DATA, PATH
Get a value from a nested data structure. Returns undef for missing paths without autovivifying intermediate levels.
my $val = pathget $data, "/users/0/name";
pathset DATA, PATH, VALUE
Set a value with autovivification support. Returns the value set.
pathset $data, "/users/0/name", "Alice";
Intermediate structures are created automatically (hashes for string keys, arrays for numeric indices). If an existing intermediate value is not a reference, it is silently replaced with the appropriate container type.
pathdelete DATA, PATH
Delete a value and return the deleted value.
my $old = pathdelete $data, "/users/0/name";
pathexists DATA, PATH
Check if a path exists (returns true/false).
if (pathexists $data, "/users/0/name") {
print "User has a name\n";
}
Constant vs Dynamic Paths
For pathset with constant string paths, the keyword compiles directly to native Perl assignment ops with autovivification - zero overhead. Because this uses Perl's native ops, error messages differ from the dynamic-path form (e.g. "Not a HASH reference" vs "Cannot navigate to path"), and attempting to traverse a non-reference intermediate will croak rather than silently replacing it.
All other keywords and dynamic paths use optimized custom ops. pathget, pathexists, and pathdelete never autovivify intermediate structures. Dynamic pathset autovivifies intermediates, including replacing non-reference scalars.
All keyword forms achieve near-native performance.
STRING PATH API
path_get($data, $path)
Returns value at path, or undef if not found. Empty path returns root.
path_get($data, '/foo/bar'); # deep access
path_get($data, ''); # returns $data
path_set($data, $path, $value)
Sets value at path. Creates intermediate hashes/arrays as needed. Numeric keys create arrays, string keys create hashes. Returns the value set.
path_set($data, '/foo/bar', 42);
path_set($data, '/items/0/name', 'first'); # creates array
path_delete($data, $path)
Deletes value at path. Returns deleted value or undef if not found.
my $old = path_delete($data, '/foo/bar');
path_exists($data, $path)
Returns 1 if path exists, 0 otherwise. Empty path always exists.
if (path_exists($data, '/foo/bar')) { ... }
ARRAY PATH API
Functions that accept path as an arrayref of components. Useful when path components are already parsed or contain special characters.
patha_get($data, \@path)
patha_get($data, ['foo', 'bar', 0]);
patha_get($data, []); # returns root
patha_set($data, \@path, $value)
patha_set($data, ['foo', 'bar'], 42);
patha_delete($data, \@path)
patha_delete($data, ['foo', 'bar']);
patha_exists($data, \@path)
patha_exists($data, ['foo', 'bar']);
COMPILED PATH API
Pre-compile paths for maximum performance when accessing the same path repeatedly on different data structures. Compiled paths eliminate parsing overhead and pre-compute array indices.
path_compile($path)
Compiles a path string into a reusable compiled path object.
my $cp = path_compile('/users/0/name');
pathc_get($data, $compiled)
my $cp = path_compile('/deep/path/here');
for my $record (@records) {
my $val = pathc_get($record, $cp); # no parsing each time
}
pathc_set($data, $compiled, $value)
pathc_set($data, $cp, 'new value');
pathc_delete($data, $compiled)
pathc_delete($data, $cp);
pathc_exists($data, $compiled)
pathc_exists($data, $cp);
PATH FORMAT
Leading "/" is optional (e.g., "/foo/bar" and "foo/bar" are equivalent)
Empty string or "/" refers to root
Components are separated by "/"
Numeric components access array elements (including negative indices)
No escaping - keys containing "/" cannot be used with string API; use array API instead
Empty string keys ("") cannot be accessed via string API; use array API:
patha_get($data, [''])
EDGE CASES
Empty Paths
An empty path ("" or "/") refers to the root data structure:
path_get($data, ""); # returns $data
path_get($data, "/"); # returns $data
path_get($data, "foo/bar"); # same as "/foo/bar"
path_exists($data, ""); # returns 1 (root always exists)
path_set($data, "", $v); # croaks - cannot set root
path_delete($data, ""); # croaks - cannot delete root
Negative Array Indices
Negative indices work like Perl's native array access:
my $data = { arr => ['a', 'b', 'c'] };
path_get($data, '/arr/-1'); # 'c' (last element)
path_get($data, '/arr/-2'); # 'b' (second to last)
path_set($data, '/arr/-1', 'z'); # sets last element
Out-of-bounds negative indices return undef (or false for exists).
Leading Zeros
Numeric strings with leading zeros are treated as hash keys, not array indices:
path_get($data, '/arr/007'); # looks up $data->{arr}{007}, not $data->{arr}[7]
path_get($data, '/arr/0'); # accesses $data->{arr}[0] (single zero is valid)
Integer Overflow
Indices longer than 18 digits (9 on 32-bit perls) are treated as hash keys to prevent overflow:
path_get($data, '/arr/12345678901234567890'); # hash key, not array index
THREAD SAFETY
This module is designed for single-threaded use. It does not use any global state and should work correctly in a threaded environment where each thread has its own data structures, but no thread-safety guarantees are made for shared data structures.
Compiled paths hold an internal copy of the path string and should not be shared across threads. Create separate compiled path objects in each thread if needed.
PERFORMANCE
Benchmarks show XS implementation is 10-25x faster than pure Perl for deep paths, and competitive with native Perl hash/array access.
For hot paths accessed repeatedly, the compiled API provides additional 20-35% speedup over the string API by eliminating parsing overhead.
AUTHOR
vividsnow
LICENSE
This is free software; you can redistribute it and/or modify it under the same terms as Perl itself.