Path expressions
This page describes path expressions functionality.
Overview
A path expression is a programmatic, type-safe mechanism used to traverse
and manipulate collection data types (CDTs). Expressions are used by Path
expression operations (selectByPath and modifyByPath) to navigate and
interact with nested Map and
List elements.
Path expressions enhance Aerospike’s capability to retrieve and manipulate data from JSON-like documents. For a tutorial that builds on path expressions with other CDT techniques, see Working with nested collection data types.
How path expressions relate to other CDT APIs
Aerospike provides three complementary ways to work with nested collection data.
CDT operations (ListOperation, MapOperation) act in-place on the record,
reading or modifying a single element at a known position. CDT expressions
(ListExp, MapExp) evaluate to a typed value and can be composed with other
expressions for filtering and computed reads. Path expressions extend both
models by selecting or modifying multiple elements at once using
expression-based contexts such as allChildren() and
allChildrenWithFilter(). For a tutorial that builds on all three techniques,
see
Working with nested collection data types.
Operations
Path expressions use two server-side operations to interact with nested CDTs. Both execute directly on database nodes, minimizing data transfer between client and server.
-
selectByPath: reads and selects multiple nested elements matching the developer-defined paths in a single operation. Returns specific subtrees or values rather than the entire record, reducing network traffic and latency. See the bookstore examples below. -
modifyByPath: removes or updates the value of matched elements, directly on the server.
Navigation and metadata
Navigation through hierarchical CDT structures is managed through a context list (CTX). The list defines element selection and filtering logic at each level of the document.
Path selectors
Paths are defined by progressively applying a list of element selectors from the root bin down to the target element(s). Every step must be contiguous.
-
Index, Key, Rank, Value: standard single-element selectors that navigate to a specific address within a List or Map.
-
allChildren(): matches all elements in a List or Map, enabling multi-element selection and retrieval. See the bookstore examples and advanced usage for worked examples. -
allChildrenWithFilter(exp): matches elements that satisfy a filter expression. See filtering books by price for an example.
Loop variables
During path resolution with allChildrenWithFilter, a typed loop variable
acts as a contextual placeholder that is filled with an aspect of the
current CDT element being evaluated:
MAP_KEY: when the current element is a Map, assign its key to the loop var.VALUE: assign the value of the current (Map or List) element to the loop var.LIST_INDEX: when the current element is a List, assign its index position to the loop var.
For worked examples using loop variables, see the advanced usage page.
Return modes
The selectFlags parameter determines the format and content of the elements
matched by a selectByPath operation.
-
MATCHING_TREE: Returns a hierarchical tree starting from the root down to the target nodes, preserving parent-child relationships. Only branches that satisfied the filter criteria are included. This provides the highest level of structural control. -
VALUE/VALUES: Returns a flat list of values from the final context level. -
MAP_KEY/MAP_KEYS: Returns only the keys of the selected map elements. -
NO_FAIL: Ignores elements with incompatible types rather than failing the entire operation. Useful when matching across heterogeneous collections where not every element has the expected data type.
Indexing and constraints
-
Nesting depth: Aerospike supports up to 15 levels of CDT nesting.
-
Expression indexes: Path expressions can be combined with expression indexes to support high-speed querying into nested documents. See the expression index example for a walkthrough.
-
Batch inlining: For in-memory namespaces with records exceeding about 1KiB, consider setting
allowInlinetofalse. See performance considerations for details.
Language support
Path expressions are supported in Java, Python, C#, C, Go, and Node.js. See the client feature compatibility matrix for version details.
Bookstore example
The examples below use the classic JSONPath bookstore dataset
to demonstrate path expression queries. The data is stored as a single map bin named "bookstore".
Data structure
{ "store": { "book": [ { "category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95 }, { "category": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99 }, { "category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "isbn": "0-553-21311-3", "price": 8.99 }, { "category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "isbn": "0-395-19395-8", "price": 22.99 } ], "bicycle": { "color": "red", "price": 19.95 } }, "expensive": 10}Filtering dimensions
bin "bookstore" (map) ├── store (map) │ ├── book (list of 4 maps) │ │ ├── {category:"reference", author:"Nigel Rees", title:"Sayings of the Century", price:8.95} │ │ ├── {category:"fiction", author:"Evelyn Waugh", title:"Sword of Honour", price:12.99} │ │ ├── {category:"fiction", author:"Herman Melville", title:"Moby Dick", isbn:"0-553-21311-3", price:8.99} │ │ └── {category:"fiction", author:"J. R. R. Tolkien", title:"The Lord of the Rings", isbn:"0-395-19395-8", price:22.99} │ └── bicycle (map) │ ├── color: "red" │ └── price: 19.95 └── expensive: 10- Store level: select
bookvsbicycle(map key navigation) - Book level: filter on
category,author,title,price, presence ofisbn - Cross-level: compare book
priceagainst the top-levelexpensivethreshold (10)
The authors of all books in the store
JSONPath equivalent: $.store.book[*].author
Navigate to the book list, match all children, and select just the "author" key from each book map.
CdtOperation.selectByPath("bookstore", SelectFlags.VALUE, CTX.mapKey(Value.get("store")), CTX.mapKey(Value.get("book")), CTX.allChildren(), CTX.mapKeysIn("author"));ctx = [ cdt_ctx.cdt_ctx_map_key("store"), cdt_ctx.cdt_ctx_map_key("book"), cdt_ctx.cdt_ctx_all_children(), cdt_ctx.cdt_ctx_map_key("author"),]ops = [ operations.select_by_path( bin_name="bookstore", ctx=ctx, flags=aerospike.EXP_PATH_SELECT_VALUE)]_, _, bins = client.operate(key, ops)as_cdt_ctx ctx;as_cdt_ctx_inita(&ctx, 4);as_cdt_ctx_add_map_key(&ctx, (as_val*)as_string_new("store", false));as_cdt_ctx_add_map_key(&ctx, (as_val*)as_string_new("book", false));as_cdt_ctx_add_all_children(&ctx);as_cdt_ctx_add_map_key(&ctx, (as_val*)as_string_new("author", false));
as_operations ops;as_operations_inita(&ops, 1);as_operations_select_by_path(&err, &ops, "bookstore", &ctx, AS_EXP_PATH_SELECT_VALUE);
as_record* rec = NULL;aerospike_key_operate(&as, &err, NULL, &key, &ops, &rec);record, err := client.Operate(nil, key, as.SelectByPath("bookstore", as.EXP_PATH_SELECT_VALUE, as.CtxMapKey(as.NewStringValue("store")), as.CtxMapKey(as.NewStringValue("book")), as.CtxAllChildren(), as.CtxMapKey(as.NewStringValue("author"))),)Record record = client.Operate(null, key, CDTOperation.SelectByPath("bookstore", SelectFlag.VALUE, CTX.MapKey(Value.Get("store")), CTX.MapKey(Value.Get("book")), CTX.AllChildren(), CTX.MapKey(Value.Get("author"))));const context = new Context() .addMapKey('store') .addMapKey('book') .addAllChildren() .addMapKey('author')
const ops = [ op.selectByPath('bookstore', exp.pathSelectFlags.VALUE, context)]const result = await client.operate(key, ops)Output:
["Nigel Rees", "Evelyn Waugh", "Herman Melville", "J. R. R. Tolkien"]Filter all books with an ISBN number
JSONPath equivalent: $.store.book[?(@.isbn)]
Filter books where the isbn key exists using MapReturnType.EXISTS.
Exp hasIsbn = MapExp.getByKey(MapReturnType.EXISTS, Exp.Type.BOOL, Exp.val("isbn"), Exp.mapLoopVar(LoopVarPart.VALUE));
CdtOperation.selectByPath("bookstore", SelectFlags.VALUE, CTX.mapKey(Value.get("store")), CTX.mapKey(Value.get("book")), CTX.allChildrenWithFilter(hasIsbn));has_isbn = exp.MapGetByKey( ctx=None, return_type=aerospike.MAP_RETURN_EXISTS, value_type=exp.ResultType.BOOLEAN, key=exp.Val("isbn"), bin=exp.LoopVarMap(aerospike.EXP_LOOPVAR_VALUE)).compile()
ctx = [ cdt_ctx.cdt_ctx_map_key("store"), cdt_ctx.cdt_ctx_map_key("book"), cdt_ctx.cdt_ctx_all_children_with_filter(has_isbn),]ops = [ operations.select_by_path( bin_name="bookstore", ctx=ctx, flags=aerospike.EXP_PATH_SELECT_VALUE)]_, _, bins = client.operate(key, ops)as_exp_build(has_isbn, as_exp_map_get_by_key(NULL, AS_MAP_RETURN_EXISTS, AS_EXP_TYPE_BOOL, as_exp_str("isbn"), as_exp_loopvar_map(AS_EXP_LOOPVAR_VALUE)));
as_cdt_ctx ctx;as_cdt_ctx_inita(&ctx, 3);as_cdt_ctx_add_map_key(&ctx, (as_val*)as_string_new("store", false));as_cdt_ctx_add_map_key(&ctx, (as_val*)as_string_new("book", false));as_cdt_ctx_add_all_children_with_filter(&ctx, has_isbn);
as_operations ops;as_operations_inita(&ops, 1);as_operations_select_by_path(&err, &ops, "bookstore", &ctx, AS_EXP_PATH_SELECT_VALUE);
as_record* rec = NULL;aerospike_key_operate(&as, &err, NULL, &key, &ops, &rec);hasIsbn := as.ExpMapGetByKey( as.MapReturnType.EXISTS, as.ExpTypeBOOL, as.ExpStringVal("isbn"), as.ExpMapLoopVar(as.VALUE),)
record, err := client.Operate(nil, key, as.SelectByPath("bookstore", as.EXP_PATH_SELECT_VALUE, as.CtxMapKey(as.NewStringValue("store")), as.CtxMapKey(as.NewStringValue("book")), as.CtxAllChildrenWithFilter(hasIsbn)),)Exp hasIsbn = MapExp.GetByKey(MapReturnType.EXISTS, Exp.Type.BOOL, Exp.Val("isbn"), Exp.MapLoopVar(LoopVarPart.VALUE));
Record record = client.Operate(null, key, CDTOperation.SelectByPath("bookstore", SelectFlag.VALUE, CTX.MapKey(Value.Get("store")), CTX.MapKey(Value.Get("book")), CTX.AllChildrenWithFilter(hasIsbn)));const hasIsbn = exp.maps.getByKey( exp.loopVarMap(exp.loopVarPart.VALUE), exp.str('isbn'), exp.type.BOOL, maps.returnType.EXISTS)
const context = new Context() .addMapKey('store') .addMapKey('book') .addAllChildrenWithFilter(hasIsbn)
const ops = [ op.selectByPath('bookstore', exp.pathSelectFlags.VALUE, context)]const result = await client.operate(key, ops)Output:
[ {"category":"fiction", "author":"Herman Melville", "title":"Moby Dick", "isbn":"0-553-21311-3", "price":8.99}, {"category":"fiction", "author":"J. R. R. Tolkien", "title":"The Lord of the Rings", "isbn":"0-395-19395-8", "price":22.99}]Filter all books cheaper than 10
JSONPath equivalent: $.store.book[?(@.price < 10)]
Exp cheapBooks = Exp.lt( MapExp.getByKey(MapReturnType.VALUE, Exp.Type.FLOAT, Exp.val("price"), Exp.mapLoopVar(LoopVarPart.VALUE)), Exp.val(10.0));
CdtOperation.selectByPath("bookstore", SelectFlags.VALUE, CTX.mapKey(Value.get("store")), CTX.mapKey(Value.get("book")), CTX.allChildrenWithFilter(cheapBooks));cheap_books = exp.LT( exp.MapGetByKey( ctx=None, return_type=aerospike.MAP_RETURN_VALUE, value_type=exp.ResultType.FLOAT, key=exp.Val("price"), bin=exp.LoopVarMap(aerospike.EXP_LOOPVAR_VALUE) ), exp.Val(10.0)).compile()
ctx = [ cdt_ctx.cdt_ctx_map_key("store"), cdt_ctx.cdt_ctx_map_key("book"), cdt_ctx.cdt_ctx_all_children_with_filter(cheap_books),]ops = [ operations.select_by_path( bin_name="bookstore", ctx=ctx, flags=aerospike.EXP_PATH_SELECT_VALUE)]_, _, bins = client.operate(key, ops)as_exp_build(cheap_books, as_exp_cmp_lt( as_exp_map_get_by_key(NULL, AS_MAP_RETURN_VALUE, AS_EXP_TYPE_FLOAT, as_exp_str("price"), as_exp_loopvar_map(AS_EXP_LOOPVAR_VALUE)), as_exp_float(10.0)));
as_cdt_ctx ctx;as_cdt_ctx_inita(&ctx, 3);as_cdt_ctx_add_map_key(&ctx, (as_val*)as_string_new("store", false));as_cdt_ctx_add_map_key(&ctx, (as_val*)as_string_new("book", false));as_cdt_ctx_add_all_children_with_filter(&ctx, cheap_books);
as_operations ops;as_operations_inita(&ops, 1);as_operations_select_by_path(&err, &ops, "bookstore", &ctx, AS_EXP_PATH_SELECT_VALUE);
as_record* rec = NULL;aerospike_key_operate(&as, &err, NULL, &key, &ops, &rec);cheapBooks := as.ExpLess( as.ExpMapGetByKey( as.MapReturnType.VALUE, as.ExpTypeFLOAT, as.ExpStringVal("price"), as.ExpMapLoopVar(as.VALUE), ), as.ExpFloatVal(10.0),)
record, err := client.Operate(nil, key, as.SelectByPath("bookstore", as.EXP_PATH_SELECT_VALUE, as.CtxMapKey(as.NewStringValue("store")), as.CtxMapKey(as.NewStringValue("book")), as.CtxAllChildrenWithFilter(cheapBooks)),)Exp cheapBooks = Exp.LT( MapExp.GetByKey(MapReturnType.VALUE, Exp.Type.FLOAT, Exp.Val("price"), Exp.MapLoopVar(LoopVarPart.VALUE)), Exp.Val(10.0));
Record record = client.Operate(null, key, CDTOperation.SelectByPath("bookstore", SelectFlag.VALUE, CTX.MapKey(Value.Get("store")), CTX.MapKey(Value.Get("book")), CTX.AllChildrenWithFilter(cheapBooks)));const cheapBooks = exp.lt( exp.maps.getByKey( exp.loopVarMap(exp.loopVarPart.VALUE), exp.str('price'), exp.type.FLOAT, maps.returnType.VALUE ), exp.float(10.0))
const context = new Context() .addMapKey('store') .addMapKey('book') .addAllChildrenWithFilter(cheapBooks)
const ops = [ op.selectByPath('bookstore', exp.pathSelectFlags.VALUE, context)]const result = await client.operate(key, ops)Output:
[ {"category":"reference", "author":"Nigel Rees", "title":"Sayings of the Century", "price":8.95}, {"category":"fiction", "author":"Herman Melville", "title":"Moby Dick", "isbn":"0-553-21311-3", "price":8.99}]Filter all fiction books costing less than 10
JSONPath equivalent: $.store.book[?(@.category=='fiction' && @.price < 10)]
Compound filter combining two conditions with Exp.and().
Exp isFiction = Exp.eq( MapExp.getByKey(MapReturnType.VALUE, Exp.Type.STRING, Exp.val("category"), Exp.mapLoopVar(LoopVarPart.VALUE)), Exp.val("fiction"));
Exp cheapBooks = Exp.lt( MapExp.getByKey(MapReturnType.VALUE, Exp.Type.FLOAT, Exp.val("price"), Exp.mapLoopVar(LoopVarPart.VALUE)), Exp.val(10.0));
CdtOperation.selectByPath("bookstore", SelectFlags.VALUE, CTX.mapKey(Value.get("store")), CTX.mapKey(Value.get("book")), CTX.allChildrenWithFilter(Exp.and(isFiction, cheapBooks)));is_fiction = exp.Eq( exp.MapGetByKey( ctx=None, return_type=aerospike.MAP_RETURN_VALUE, value_type=exp.ResultType.STRING, key=exp.Val("category"), bin=exp.LoopVarMap(aerospike.EXP_LOOPVAR_VALUE) ), exp.Val("fiction"))
cheap_books = exp.LT( exp.MapGetByKey( ctx=None, return_type=aerospike.MAP_RETURN_VALUE, value_type=exp.ResultType.FLOAT, key=exp.Val("price"), bin=exp.LoopVarMap(aerospike.EXP_LOOPVAR_VALUE) ), exp.Val(10.0))
combined = exp.And(is_fiction, cheap_books).compile()
ctx = [ cdt_ctx.cdt_ctx_map_key("store"), cdt_ctx.cdt_ctx_map_key("book"), cdt_ctx.cdt_ctx_all_children_with_filter(combined),]ops = [ operations.select_by_path( bin_name="bookstore", ctx=ctx, flags=aerospike.EXP_PATH_SELECT_VALUE)]_, _, bins = client.operate(key, ops)as_exp_build(filter, as_exp_and( as_exp_cmp_eq( as_exp_map_get_by_key(NULL, AS_MAP_RETURN_VALUE, AS_EXP_TYPE_STR, as_exp_str("category"), as_exp_loopvar_map(AS_EXP_LOOPVAR_VALUE)), as_exp_str("fiction")), as_exp_cmp_lt( as_exp_map_get_by_key(NULL, AS_MAP_RETURN_VALUE, AS_EXP_TYPE_FLOAT, as_exp_str("price"), as_exp_loopvar_map(AS_EXP_LOOPVAR_VALUE)), as_exp_float(10.0))));
as_cdt_ctx ctx;as_cdt_ctx_inita(&ctx, 3);as_cdt_ctx_add_map_key(&ctx, (as_val*)as_string_new("store", false));as_cdt_ctx_add_map_key(&ctx, (as_val*)as_string_new("book", false));as_cdt_ctx_add_all_children_with_filter(&ctx, filter);
as_operations ops;as_operations_inita(&ops, 1);as_operations_select_by_path(&err, &ops, "bookstore", &ctx, AS_EXP_PATH_SELECT_VALUE);
as_record* rec = NULL;aerospike_key_operate(&as, &err, NULL, &key, &ops, &rec);isFiction := as.ExpEq( as.ExpMapGetByKey( as.MapReturnType.VALUE, as.ExpTypeSTR, as.ExpStringVal("category"), as.ExpMapLoopVar(as.VALUE), ), as.ExpStringVal("fiction"),)
cheapBooks := as.ExpLess( as.ExpMapGetByKey( as.MapReturnType.VALUE, as.ExpTypeFLOAT, as.ExpStringVal("price"), as.ExpMapLoopVar(as.VALUE), ), as.ExpFloatVal(10.0),)
record, err := client.Operate(nil, key, as.SelectByPath("bookstore", as.EXP_PATH_SELECT_VALUE, as.CtxMapKey(as.NewStringValue("store")), as.CtxMapKey(as.NewStringValue("book")), as.CtxAllChildrenWithFilter(as.ExpAnd(isFiction, cheapBooks))),)Exp isFiction = Exp.EQ( MapExp.GetByKey(MapReturnType.VALUE, Exp.Type.STRING, Exp.Val("category"), Exp.MapLoopVar(LoopVarPart.VALUE)), Exp.Val("fiction"));
Exp cheapBooks = Exp.LT( MapExp.GetByKey(MapReturnType.VALUE, Exp.Type.FLOAT, Exp.Val("price"), Exp.MapLoopVar(LoopVarPart.VALUE)), Exp.Val(10.0));
Record record = client.Operate(null, key, CDTOperation.SelectByPath("bookstore", SelectFlag.VALUE, CTX.MapKey(Value.Get("store")), CTX.MapKey(Value.Get("book")), CTX.AllChildrenWithFilter(Exp.And(isFiction, cheapBooks))));const isFiction = exp.eq( exp.maps.getByKey( exp.loopVarMap(exp.loopVarPart.VALUE), exp.str('category'), exp.type.STR, maps.returnType.VALUE ), exp.str('fiction'))
const cheapBooks = exp.lt( exp.maps.getByKey( exp.loopVarMap(exp.loopVarPart.VALUE), exp.str('price'), exp.type.FLOAT, maps.returnType.VALUE ), exp.float(10.0))
const context = new Context() .addMapKey('store') .addMapKey('book') .addAllChildrenWithFilter(exp.and(isFiction, cheapBooks))
const ops = [ op.selectByPath('bookstore', exp.pathSelectFlags.VALUE, context)]const result = await client.operate(key, ops)Output:
[ {"category":"fiction", "author":"Herman Melville", "title":"Moby Dick", "isbn":"0-553-21311-3", "price":8.99}]Next steps
-
Check out the Quickstart: Path expressions.
-
Dive deeper with advanced usage.
-
Find more information in the FAQ.