---
title: "Working with nested collection data types"
description: "Guide to writing, reading, filtering, indexing, and querying nested List and Map structures in Aerospike."
---

# Working with nested collection data types

> For the complete documentation index see: [llms.txt](https://aerospike.com/docs/llms.txt)
> 
> All documentation pages available in markdown.

This page demonstrates how to write, read, filter, index, and query data within nested [List](https://aerospike.com/docs/develop/data-types/collections/list) and [Map](https://aerospike.com/docs/develop/data-types/collections/map) structures. The examples build on each other, using three complementary techniques:

-   **CDT operate API** with [context](https://aerospike.com/docs/develop/data-types/collections/context/) selectors for single-element writes and reads at known positions.
-   **Expression composition** (nesting `ListExp` / `MapExp` calls) for reading or filtering nested values within record-level expressions.
-   **[Path expressions](https://aerospike.com/docs/develop/expressions/path/)** for selecting or filtering across multiple nested elements at once.

The examples on this page use a record with a bin named `vehicles` and a string bin named `username`. The `vehicles` bin contains a List of Maps, where each Map represents a vehicle with `color`, `license`, `make`, and `model` fields. The map keys are shown in key order (K-order), which is how the application stores them.

```json
[

  { "color": "white",  "license": "8PAJ017", "make": "Toyota", "model": "RAV4"    },

  { "color": "blue",   "license": "6ABC123", "make": "Tesla",  "model": "Model 3" },

  { "color": "silver", "license": "7XYZ789", "make": "Honda",  "model": "Civic"   }

]
```

We can visualize the data another way:

```text
List

├── [0] Map

│       ├── "color"   -> "white"

│       ├── "license" -> "8PAJ017"

│       ├── "make"    -> "Toyota"

│       └── "model"   -> "RAV4"

├── [1] Map

│       ├── "color"   -> "blue"

│       ├── "license" -> "6ABC123"

│       ├── "make"    -> "Tesla"

│       └── "model"   -> "Model 3"

└── [2] Map

        ├── "color"   -> "silver"

        ├── "license" -> "7XYZ789"

        ├── "make"    -> "Honda"

        └── "model"   -> "Civic"
```

## Why the list is unordered and the maps are key-ordered

This data model makes two distinct ordering choices, each for a specific reason.

**The list is unordered.** Index 0 means “default vehicle” — the application assigns semantic meaning to position. An ordered list would sort the vehicles by their [map comparison value](https://aerospike.com/docs/develop/data-types/collections/ordering#map), which would rearrange the entries and break the positional contract. You keep the list unordered so that index 0 stays the default, index 1 is the second choice, and so on.

**The maps are key-ordered.** In this example the maps use the [K-ordered](https://aerospike.com/docs/develop/data-types/collections/map#element-ordering) subtype, which stores elements in key order. You construct the maps with keys in K-order on the client side for two reasons:

1.  **Correct `ADD_UNIQUE` comparison.** When you use the [`ADD_UNIQUE`](https://aerospike.com/docs/develop/data-types/collections/list/operations#append) write flag to prevent duplicate vehicles in the list, the server [compares map elements](https://aerospike.com/docs/develop/data-types/collections/ordering#map) by element count, then keys in stored order, then values. The incoming map must already be K-ordered for the comparison to match the stored form. If a client sends the same vehicle data with keys in a different order, the wire representation differs, comparison fails, and the duplicate slips through. Only [ordered maps](https://aerospike.com/docs/develop/data-types/collections/map#element-ordering) (K-ordered or KV-ordered) can be reliably compared for equality.
2.  **Predictable read results.** When you read the data back, map entries come back in key order. If the application constructs maps with keys in an arbitrary order, the displayed result differs from what was sent, which makes debugging harder.

How you construct a K-ordered map depends on the client language:

-   **Java** — use `TreeMap` (sorts keys by natural order).
-   **Python** — use `aerospike.KeyOrderedDict`. It maps to `as_orderedmap` in the C layer, which sorts keys and includes the K-ordered flag on the wire. A standard `dict` maps to `as_hashmap`, which sorts keys internally but omits the K-ordered wire flag, so the server treats it as unordered.
-   **C** — use `as_orderedmap` (as the example does). It keeps keys sorted and includes the K-ordered flag on the wire. `as_hashmap` also sorts internally but omits the K-ordered wire flag.
-   **Go** — use a `[]as.MapPair` slice with keys in sorted order.
-   **C#** — use `SortedDictionary<string, object>`.
-   **Node.js** — use a plain object or `Map`. The Node.js binding creates an `as_orderedmap` internally, which sorts keys and includes the K-ordered wire flag. Key order in the JavaScript source does not matter.

The vehicle maps in this example are small (four keys each), so the per-operation index rebuild cost is negligible. For maps with many keys, consider using [`PERSIST_INDEX`](https://aerospike.com/docs/develop/data-types/collections/map/operations#persisting-the-map-index) to store the offset index on disk. See [Map performance](https://aerospike.com/docs/develop/data-types/collections/map/performance) for operational complexity by subtype and index configuration.

## Add a new vehicle as the default

The following example inserts a new vehicle at index 0, making it the default. The list policy uses three flags:

-   `UNORDERED` — the list is unordered so that index 0 keeps its “default vehicle” meaning.
-   `ADD_UNIQUE` — prevents inserting a vehicle that already exists in the list, compared as a full map value.
-   `NO_FAIL` — the operation succeeds silently if the vehicle is already present, so the caller does not need to handle a duplicate error.

-   [Java](#tab-panel-2196)
-   [Python](#tab-panel-2197)
-   [Go](#tab-panel-2198)
-   [C](#tab-panel-2199)
-   [C#](#tab-panel-2200)
-   [Node.js](#tab-panel-2201)

```java
TreeMap<String, Object> vehicle = new TreeMap<>();

vehicle.put("color", "red");

vehicle.put("license", "5DEF456");

vehicle.put("make", "Ford");

vehicle.put("model", "Mustang");

ListPolicy policy = new ListPolicy(ListOrder.UNORDERED,

    ListWriteFlags.ADD_UNIQUE | ListWriteFlags.NO_FAIL);

Record record = client.operate(null, key,

    ListOperation.insert(policy, "vehicles", 0, Value.get(vehicle)),

    Operation.get("vehicles"));
```

```python
from aerospike import KeyOrderedDict

from aerospike_helpers.operations import list_operations

vehicle = KeyOrderedDict({

    "color": "red",

    "license": "5DEF456",

    "make": "Ford",

    "model": "Mustang"

})

policy = {

    "list_order": aerospike.LIST_UNORDERED,

    "write_flags": aerospike.LIST_WRITE_ADD_UNIQUE | aerospike.LIST_WRITE_NO_FAIL

}

ops = [

    list_operations.list_insert("vehicles", 0, vehicle, policy)

]

(key, meta, bins) = client.operate(key, ops)
```

```go
vehicle := []as.MapPair{

    {Key: "color", Value: "red"},

    {Key: "license", Value: "5DEF456"},

    {Key: "make", Value: "Ford"},

    {Key: "model", Value: "Mustang"},

}

policy := as.NewListPolicy(as.ListOrderUnordered,

    as.ListWriteFlagsAddUnique|as.ListWriteFlagsNoFail)

record, err := client.Operate(nil, key,

    as.ListInsertWithPolicyOp(policy, "vehicles", 0, vehicle),

    as.GetBinOp("vehicles"),

)
```

```c
as_orderedmap vehicle;

as_orderedmap_init(&vehicle, 4);

as_orderedmap_set(&vehicle,

    (as_val*)as_string_new("color", false),

    (as_val*)as_string_new("red", false));

as_orderedmap_set(&vehicle,

    (as_val*)as_string_new("license", false),

    (as_val*)as_string_new("5DEF456", false));

as_orderedmap_set(&vehicle,

    (as_val*)as_string_new("make", false),

    (as_val*)as_string_new("Ford", false));

as_orderedmap_set(&vehicle,

    (as_val*)as_string_new("model", false),

    (as_val*)as_string_new("Mustang", false));

as_list_policy policy;

as_list_policy_set(&policy, AS_LIST_UNORDERED,

    AS_LIST_WRITE_ADD_UNIQUE | AS_LIST_WRITE_NO_FAIL);

as_operations ops;

as_operations_inita(&ops, 2);

as_operations_list_insert(&ops, "vehicles", NULL, &policy, 0,

    (as_val*)&vehicle);

as_operations_add_read(&ops, "vehicles");

as_record* rec = NULL;

aerospike_key_operate(&as, &err, NULL, &key, &ops, &rec);
```

```csharp
var vehicle = new SortedDictionary<string, object>

{

    { "color", "red" },

    { "license", "5DEF456" },

    { "make", "Ford" },

    { "model", "Mustang" }

};

ListPolicy policy = new ListPolicy(ListOrder.UNORDERED,

    ListWriteFlags.ADD_UNIQUE | ListWriteFlags.NO_FAIL);

Record record = client.Operate(null, key,

    ListOperation.Insert(policy, "vehicles", 0, Value.Get(vehicle)),

    Operation.Get("vehicles"));
```

```js
const vehicle = {

  color: 'red',

  license: '5DEF456',

  make: 'Ford',

  model: 'Mustang'

}

const policy = new Aerospike.ListPolicy({

  order: Aerospike.lists.order.UNORDERED,

  writeFlags: Aerospike.lists.writeFlags.ADD_UNIQUE | Aerospike.lists.writeFlags.NO_FAIL

})

const result = await client.operate(key, [

  Aerospike.lists.insert('vehicles', 0, vehicle, policy),

  Aerospike.operations.read('vehicles')

])
```

After this operation the list contains four vehicles, with the Ford Mustang at index 0 (the new default). Running the same operation again has no effect because `ADD_UNIQUE` detects the duplicate and `NO_FAIL` suppresses the error.

## Read the license plate of the default vehicle

The first vehicle in the list (index 0) acts as the default vehicle. The following expression read extracts its license plate by nesting `ListExp.getByIndex` and `MapExp.getByKey`:

1.  `ListExp.getByIndex` extracts the map at index 0 from the `vehicles` bin.
2.  `MapExp.getByKey` extracts the `"license"` value from that map.
3.  `ExpOperation.read` returns the result under the name `"defaultLicense"`.

-   [Java](#tab-panel-2202)
-   [Python](#tab-panel-2203)
-   [Go](#tab-panel-2204)
-   [C](#tab-panel-2205)
-   [C#](#tab-panel-2206)
-   [Node.js](#tab-panel-2207)

```java
Record record = client.operate(null, key,

    ExpOperation.read("defaultLicense",

        Exp.build(

            MapExp.getByKey(MapReturnType.VALUE, Exp.Type.STRING,

                Exp.val("license"),

                ListExp.getByIndex(ListReturnType.VALUE, Exp.Type.MAP,

                    Exp.val(0), Exp.listBin("vehicles")))),

        ExpReadFlags.DEFAULT));

String plate = record.getString("defaultLicense");
```

```python
from aerospike_helpers import expressions as exp

from aerospike_helpers.operations import expression_operations as exp_ops

read_exp = exp.MapGetByKey(

    ctx=None,

    return_type=aerospike.MAP_RETURN_VALUE,

    value_type=exp.ResultType.STRING,

    key=exp.Val("license"),

    bin=exp.ListGetByIndex(

        ctx=None,

        return_type=aerospike.LIST_RETURN_VALUE,

        value_type=exp.ResultType.MAP,

        index=exp.Val(0),

        bin=exp.ListBin("vehicles")

    )

).compile()

ops = [exp_ops.expression_read("defaultLicense", read_exp)]

(key, meta, bins) = client.operate(key, ops)

print(bins["defaultLicense"])
```

```go
readExp := aero.ExpMapGetByKey(

    aero.MapReturnType.VALUE,

    aero.ExpTypeSTRING,

    aero.ExpStringVal("license"),

    aero.ExpListGetByIndex(

        aero.ListReturnTypeValue,

        aero.ExpTypeMAP,

        aero.ExpIntVal(0),

        aero.ExpListBin("vehicles"),

    ),

)

record, err := client.Operate(nil, key,

    aero.ExpReadOp("defaultLicense", readExp, aero.ExpReadFlagDefault),

)

fmt.Println(record.Bins["defaultLicense"])
```

```c
as_exp_build(read_exp,

    as_exp_map_get_by_key(NULL, AS_MAP_RETURN_VALUE, AS_EXP_TYPE_STR,

        as_exp_str("license"),

        as_exp_list_get_by_index(NULL, AS_LIST_RETURN_VALUE, AS_EXP_TYPE_MAP,

            as_exp_int(0),

            as_exp_bin_list("vehicles"))));

as_operations ops;

as_operations_inita(&ops, 1);

as_operations_exp_read(&ops, "defaultLicense", read_exp, AS_EXP_READ_DEFAULT);

as_record* rec = NULL;

aerospike_key_operate(&as, &err, NULL, &key, &ops, &rec);

char* plate = as_record_get_str(rec, "defaultLicense");
```

```csharp
Record record = client.Operate(null, key,

    ExpOperation.Read("defaultLicense",

        Exp.Build(

            MapExp.GetByKey(MapReturnType.VALUE, Exp.Type.STRING,

                Exp.Val("license"),

                ListExp.GetByIndex(ListReturnType.VALUE, Exp.Type.MAP,

                    Exp.Val(0), Exp.ListBin("vehicles")))),

        ExpReadFlags.DEFAULT));

string plate = record.GetString("defaultLicense");
```

```js
const readExp = exp.maps.getByKey(

  exp.lists.getByIndex(

    exp.binList('vehicles'),

    exp.int(0),

    exp.type.MAP,

    lists.returnType.VALUE

  ),

  exp.str('license'),

  exp.type.STR,

  maps.returnType.VALUE

)

const result = await client.operate(key, [

  exp.operations.read('defaultLicense', readExp, exp.expReadFlags.DEFAULT)

])

console.log(result.bins.defaultLicense)
```

Expected output: `"8PAJ017"`

## Check a license plate against all vehicles

The previous example accessed a single vehicle at a known index. To check a value across **all** vehicles regardless of list size, you can use a [path expression](https://aerospike.com/docs/develop/expressions/path/) to extract values from every element, then test the resulting list.

The following filter expression checks whether **any** vehicle in the list has a license plate matching `"7XYZ789"`. A [path expression](https://aerospike.com/docs/develop/expressions/path/) extracts all `"license"` values regardless of list size, then `ListExp.getByValue` with `EXISTS` checks whether the target plate is present. This filter expression can be used for record selection with a [query](https://aerospike.com/docs/develop/learn/queries/), similar to a relational database’s “WHERE” clause.

-   [Java](#tab-panel-2208)
-   [Python](#tab-panel-2209)
-   [Go](#tab-panel-2210)
-   [C](#tab-panel-2211)
-   [C#](#tab-panel-2212)
-   [Node.js](#tab-panel-2213)

```java
String targetPlate = "7XYZ789";

QueryPolicy policy = client.copyQueryPolicyDefault();

policy.filterExp = Exp.build(

    ListExp.getByValue(ListReturnType.EXISTS,

        Exp.val(targetPlate),

        CdtExp.selectByPath(Exp.Type.LIST, SelectFlags.VALUE,

            Exp.listBin("vehicles"),

            CTX.allChildren(),

            CTX.mapKey(Value.get("license"))))

);
```

```python
from aerospike_helpers import expressions as exp

from aerospike_helpers.expressions.base import SelectByPath, ListBin, ResultType

from aerospike_helpers import cdt_ctx

target_plate = "7XYZ789"

all_licenses = SelectByPath(

    ctx=[

        cdt_ctx.cdt_ctx_all_children(),

        cdt_ctx.cdt_ctx_map_key("license")

    ],

    value_type=ResultType.LIST,

    flags=aerospike.EXP_PATH_SELECT_VALUE,

    bin=ListBin("vehicles")

)

query.policy = {

    "expressions": exp.ListGetByValue(

        ctx=None,

        return_type=aerospike.LIST_RETURN_EXISTS,

        value=exp.Val(target_plate),

        bin=all_licenses

    ).compile()

}
```

```go
targetPlate := "7XYZ789"

allLicenses := aero.ExpSelectByPath(

    aero.ExpTypeLIST,

    aero.EXP_PATH_SELECT_VALUE,

    aero.ExpListBin("vehicles"),

    aero.CtxAllChildren(),

    aero.CtxMapKey(aero.NewValue("license")),

)

queryPolicy := aero.NewQueryPolicy()

queryPolicy.FilterExpression = aero.ExpListGetByValue(

    aero.ListReturnTypeExists,

    aero.ExpStringVal(targetPlate),

    allLicenses,

)
```

```c
as_cdt_ctx ctx;

as_cdt_ctx_inita(&ctx, 2);

as_cdt_ctx_add_all_children(&ctx);

as_cdt_ctx_add_map_key(&ctx, (as_val*)as_string_new("license", false));

as_exp_build(filter,

    as_exp_list_get_by_value(NULL, AS_LIST_RETURN_EXISTS,

        as_exp_str("7XYZ789"),

        as_exp_select_by_path(&ctx, AS_EXP_TYPE_LIST,

            AS_EXP_PATH_SELECT_VALUE,

            as_exp_bin_list("vehicles"))));

as_policy_query policy;

as_policy_query_init(&policy);

policy.base.filter_exp = filter;
```

```csharp
string targetPlate = "7XYZ789";

QueryPolicy policy = new QueryPolicy(client.QueryPolicyDefault);

policy.filterExp = Exp.Build(

    ListExp.GetByValue(ListReturnType.EXISTS,

        Exp.Val(targetPlate),

        CDTExp.SelectByPath(Exp.Type.LIST, SelectFlag.VALUE,

            Exp.ListBin("vehicles"),

            CTX.AllChildren(),

            CTX.MapKey(Value.Get("license"))))

);
```

```js
const targetPlate = '7XYZ789'

const ctx = new Context()

  .addAllChildren()

  .addMapKey('license')

const allLicenses = exp.selectByPath(

  exp.binList('vehicles'),

  exp.type.LIST,

  exp.pathSelectFlags.VALUE,

  ctx

)

const policy = {

  filterExpression: exp.lists.getByValue(

    allLicenses,

    exp.str(targetPlate),

    lists.returnType.EXISTS

  )

}
```

Only records where at least one vehicle has a matching license plate pass this filter.

## Create an expression index on license plates

The filter expression in the previous section evaluates against every record during a query. Instead of scanning every record, you can create a [secondary index](https://aerospike.com/docs/develop/learn/queries/secondary-index) on the license plate values. A secondary index on the extracted values lets the server skip non-matching records entirely, which is significantly faster for large datasets.

A [path expression](https://aerospike.com/docs/develop/expressions/path/) extracts all `"license"` values into a list, and the index is created with collection type `LIST` so each license string is indexed individually.

### Step 1: Create the index

The expression uses `selectByPath` to walk every element in the `vehicles` list and extract the `"license"` value from each map.

-   [Java](#tab-panel-2214)
-   [Python](#tab-panel-2215)
-   [Go](#tab-panel-2216)
-   [C](#tab-panel-2217)
-   [C#](#tab-panel-2218)
-   [Node.js](#tab-panel-2219)

```java
Expression licensesExp = Exp.build(

    CdtExp.selectByPath(Exp.Type.LIST, SelectFlags.VALUE,

        Exp.listBin("vehicles"),

        CTX.allChildren(),

        CTX.mapKey(Value.get("license"))));

IndexTask task = client.createIndex(null, "test", "demo",

    "idx_vehicle_license", IndexType.STRING,

    IndexCollectionType.LIST, licensesExp);

task.waitTillComplete();
```

```python
from aerospike_helpers import cdt_ctx

from aerospike_helpers.expressions.base import SelectByPath, ListBin, ResultType

licenses_exp = SelectByPath(

    ctx=[

        cdt_ctx.cdt_ctx_all_children(),

        cdt_ctx.cdt_ctx_map_key("license")

    ],

    value_type=ResultType.LIST,

    flags=aerospike.EXP_PATH_SELECT_VALUE,

    bin=ListBin("vehicles")

).compile()

client.index_expr_create("test", "demo",

    aerospike.INDEX_TYPE_LIST, aerospike.INDEX_STRING,

    licenses_exp, "idx_vehicle_license")
```

```go
licensesExp := aero.ExpSelectByPath(

    aero.ExpTypeLIST,

    aero.EXP_PATH_SELECT_VALUE,

    aero.ExpListBin("vehicles"),

    aero.CtxAllChildren(),

    aero.CtxMapKey(aero.NewValue("license")),

)

task, err := client.CreateIndexWithExpression(nil, "test", "demo",

    "idx_vehicle_license", aero.STRING, aero.ICT_LIST, licensesExp)

task.OnComplete()
```

```c
as_cdt_ctx ctx;

as_cdt_ctx_inita(&ctx, 2);

as_cdt_ctx_add_all_children(&ctx);

as_cdt_ctx_add_map_key(&ctx, (as_val*)as_string_new("license", false));

as_exp_build(licenses_exp,

    as_exp_select_by_path(&ctx, AS_EXP_TYPE_LIST,

        AS_EXP_PATH_SELECT_VALUE,

        as_exp_bin_list("vehicles")));

as_index_task task;

aerospike_index_create_exp(&as, &err, &task, NULL,

    "test", "demo", "idx_vehicle_license",

    AS_INDEX_TYPE_LIST, AS_INDEX_STRING, licenses_exp);

aerospike_index_create_wait(&err, &task, 0);
```

```csharp
Expression licensesExp = Exp.Build(

    CDTExp.SelectByPath(Exp.Type.LIST, SelectFlag.VALUE,

        Exp.ListBin("vehicles"),

        CTX.AllChildren(),

        CTX.MapKey(Value.Get("license"))));

IndexTask task = client.CreateIndex(null, "test", "demo",

    "idx_vehicle_license", IndexType.STRING,

    IndexCollectionType.LIST, licensesExp);

task.Wait();
```

```js
const ctx = new Context()

  .addAllChildren()

  .addMapKey('license')

const licensesExp = exp.selectByPath(

  exp.binList('vehicles'),

  exp.type.LIST,

  exp.pathSelectFlags.VALUE,

  ctx

)

await client.createExpIndex({

  ns: 'test',

  set: 'demo',

  index: 'idx_vehicle_license',

  exp: licensesExp,

  type: Aerospike.indexType.LIST,

  datatype: Aerospike.indexDataType.STRING

})
```

### Step 2: Query the index

Once the index exists, query for records that contain a specific license plate. Because the index is built on an expression (not a bin), the query filter references the same expression.

Instead of returning the full record, this query uses [operation projection](https://aerospike.com/docs/develop/learn/queries/projection) to return only the matching vehicle and the `username` bin. A `selectByPath` operation with `allChildrenWithFilter` extracts the vehicle whose license matches `"7XYZ789"`, and a plain bin read returns the username.

-   [Java](#tab-panel-2220)
-   [Python](#tab-panel-2221)
-   [Go](#tab-panel-2222)
-   [C](#tab-panel-2223)
-   [C#](#tab-panel-2224)
-   [Node.js](#tab-panel-2225)

```java
Exp matchPlate = Exp.eq(

    MapExp.getByKey(MapReturnType.VALUE, Exp.Type.STRING,

        Exp.val("license"), Exp.mapLoopVar(LoopVarPart.VALUE)),

    Exp.val("7XYZ789"));

Statement stmt = new Statement();

stmt.setNamespace("test");

stmt.setSetName("demo");

stmt.setFilter(Filter.contains(licensesExp,

    IndexCollectionType.LIST, "7XYZ789"));

stmt.setOperations(new Operation[] {

    CdtOperation.selectByPath("vehicles", SelectFlags.VALUE,

        CTX.allChildrenWithFilter(matchPlate)),

    Operation.get("username")

});

RecordSet rs = client.query(null, stmt);
```

```python
from aerospike import predicates as p

from aerospike_helpers.operations import operations

match_plate = exp.Eq(

    exp.MapGetByKey(

        ctx=None,

        return_type=aerospike.MAP_RETURN_VALUE,

        value_type=exp.ResultType.STRING,

        key=exp.Val("license"),

        bin=exp.LoopVarMap(aerospike.EXP_LOOPVAR_VALUE)

    ),

    exp.Val("7XYZ789")

).compile()

query = client.query("test", "demo")

query.where_with_expr(licenses_exp,

    p.contains(None, aerospike.INDEX_TYPE_LIST, "7XYZ789"))

query.add_ops([

    operations.select_by_path(

        bin_name="vehicles",

        ctx=[cdt_ctx.cdt_ctx_all_children_with_filter(match_plate)],

        flags=aerospike.EXP_PATH_SELECT_VALUE),

    operations.read("username")

])

results = query.results()
```

```go
matchPlate := aero.ExpEq(

    aero.ExpMapGetByKey(

        aero.MapReturnType.VALUE, aero.ExpTypeSTRING,

        aero.ExpStringVal("license"),

        aero.ExpMapLoopVar(aero.VALUE),

    ),

    aero.ExpStringVal("7XYZ789"),

)

stmt := aero.NewStatement("test", "demo")

stmt.SetFilter(aero.NewContainsWithExpressionFilter(

    licensesExp, aero.ICT_LIST, "7XYZ789"))

stmt.Operations = []*aero.Operation{

    aero.SelectByPath("vehicles", aero.EXP_PATH_SELECT_VALUE,

        aero.CtxAllChildrenWithFilter(matchPlate)),

    aero.GetBinOp("username"),

}

rs, err := client.Query(nil, stmt)
```

```c
as_exp_build(match_plate,

    as_exp_cmp_eq(

        as_exp_map_get_by_key(NULL, AS_MAP_RETURN_VALUE, AS_EXP_TYPE_STR,

            as_exp_str("license"),

            as_exp_loopvar_map(AS_EXP_LOOPVAR_VALUE)),

        as_exp_str("7XYZ789")));

as_cdt_ctx proj_ctx;

as_cdt_ctx_inita(&proj_ctx, 1);

as_cdt_ctx_add_all_children_with_filter(&proj_ctx, match_plate);

as_operations proj_ops;

as_operations_inita(&proj_ops, 2);

as_operations_select_by_path(&err, &proj_ops, "vehicles", &proj_ctx,

    AS_EXP_PATH_SELECT_VALUE);

as_operations_add_read(&proj_ops, "username");

as_query query;

as_query_init(&query, "test", "demo");

as_query_where_inita(&query, 1);

as_query_where_with_exp(&query, licenses_exp,

    as_contains(LIST, STRING, "7XYZ789"));

query.ops = &proj_ops;

aerospike_query_foreach(&as, &err, NULL, &query, callback, NULL);
```

```csharp
Exp matchPlate = Exp.EQ(

    MapExp.GetByKey(MapReturnType.VALUE, Exp.Type.STRING,

        Exp.Val("license"), Exp.MapLoopVar(LoopVarPart.VALUE)),

    Exp.Val("7XYZ789"));

Statement stmt = new Statement();

stmt.SetNamespace("test");

stmt.SetSetName("demo");

stmt.SetFilter(Filter.Contains(licensesExp,

    IndexCollectionType.LIST, "7XYZ789"));

stmt.Operations = new Operation[] {

    CDTOperation.SelectByPath("vehicles", SelectFlag.VALUE,

        CTX.AllChildrenWithFilter(matchPlate)),

    Operation.Get("username")

};

RecordSet rs = client.Query(null, stmt);
```

```js
const matchPlate = exp.eq(

  exp.maps.getByKey(

    exp.loopVarMap(exp.loopVarPart.VALUE),

    exp.str('license'),

    exp.type.STR,

    maps.returnType.VALUE

  ),

  exp.str('7XYZ789')

)

const projCtx = new Context()

  .addAllChildrenWithFilter(matchPlate)

const query = client.query('test', 'demo')

const sifter = Aerospike.filter.contains(null, '7XYZ789',

  Aerospike.indexType.LIST)

sifter.exp = licensesExp

query.where(sifter)

query.ops = [

  op.selectByPath('vehicles', exp.pathSelectFlags.VALUE, projCtx),

  Aerospike.operations.read('username')

]

const stream = query.foreach()
```

Each matching record now returns only the projected data: the vehicle with license `"7XYZ789"` and the username.

**Expected output per record:**

```json
{"vehicles": [{"color": "silver", "license": "7XYZ789", "make": "Honda", "model": "Civic"}], "username": "thomasanderson"}
```

### Alternatively: query by index name

Once an expression index exists, you can reference it by name instead of passing the expression to the query filter. This is typically the simpler production pattern because it avoids rebuilding the expression on the client side. The same operation projection applies: a `selectByPath` extracts the matching vehicle and a bin read returns the username.

-   [Java](#tab-panel-2226)
-   [Python](#tab-panel-2227)
-   [Go](#tab-panel-2228)
-   [C](#tab-panel-2229)
-   [C#](#tab-panel-2230)
-   [Node.js](#tab-panel-2231)

```java
Exp matchPlate = Exp.eq(

    MapExp.getByKey(MapReturnType.VALUE, Exp.Type.STRING,

        Exp.val("license"), Exp.mapLoopVar(LoopVarPart.VALUE)),

    Exp.val("7XYZ789"));

Statement stmt = new Statement();

stmt.setNamespace("test");

stmt.setSetName("demo");

stmt.setFilter(Filter.containsByIndex("idx_vehicle_license",

    IndexCollectionType.LIST, "7XYZ789"));

stmt.setOperations(new Operation[] {

    CdtOperation.selectByPath("vehicles", SelectFlags.VALUE,

        CTX.allChildrenWithFilter(matchPlate)),

    Operation.get("username")

});

RecordSet rs = client.query(null, stmt);
```

```python
from aerospike import predicates as p

from aerospike_helpers.operations import operations

match_plate = exp.Eq(

    exp.MapGetByKey(

        ctx=None,

        return_type=aerospike.MAP_RETURN_VALUE,

        value_type=exp.ResultType.STRING,

        key=exp.Val("license"),

        bin=exp.LoopVarMap(aerospike.EXP_LOOPVAR_VALUE)

    ),

    exp.Val("7XYZ789")

).compile()

query = client.query("test", "demo")

query.where_with_index_name("idx_vehicle_license",

    p.contains(None, aerospike.INDEX_TYPE_LIST, "7XYZ789"))

query.add_ops([

    operations.select_by_path(

        bin_name="vehicles",

        ctx=[cdt_ctx.cdt_ctx_all_children_with_filter(match_plate)],

        flags=aerospike.EXP_PATH_SELECT_VALUE),

    operations.read("username")

])

results = query.results()
```

```go
matchPlate := aero.ExpEq(

    aero.ExpMapGetByKey(

        aero.MapReturnType.VALUE, aero.ExpTypeSTRING,

        aero.ExpStringVal("license"),

        aero.ExpMapLoopVar(aero.VALUE),

    ),

    aero.ExpStringVal("7XYZ789"),

)

stmt := aero.NewStatement("test", "demo")

stmt.SetFilter(aero.NewContainsWithIndexNameFilter(

    "idx_vehicle_license", aero.ICT_LIST, "7XYZ789"))

stmt.Operations = []*aero.Operation{

    aero.SelectByPath("vehicles", aero.EXP_PATH_SELECT_VALUE,

        aero.CtxAllChildrenWithFilter(matchPlate)),

    aero.GetBinOp("username"),

}

rs, err := client.Query(nil, stmt)
```

```c
as_exp_build(match_plate,

    as_exp_cmp_eq(

        as_exp_map_get_by_key(NULL, AS_MAP_RETURN_VALUE, AS_EXP_TYPE_STR,

            as_exp_str("license"),

            as_exp_loopvar_map(AS_EXP_LOOPVAR_VALUE)),

        as_exp_str("7XYZ789")));

as_cdt_ctx proj_ctx;

as_cdt_ctx_inita(&proj_ctx, 1);

as_cdt_ctx_add_all_children_with_filter(&proj_ctx, match_plate);

as_operations proj_ops;

as_operations_inita(&proj_ops, 2);

as_operations_select_by_path(&err, &proj_ops, "vehicles", &proj_ctx,

    AS_EXP_PATH_SELECT_VALUE);

as_operations_add_read(&proj_ops, "username");

as_query query;

as_query_init(&query, "test", "demo");

as_query_where_inita(&query, 1);

as_query_where_with_index_name(&query, "idx_vehicle_license",

    as_contains(LIST, STRING, "7XYZ789"));

query.ops = &proj_ops;

aerospike_query_foreach(&as, &err, NULL, &query, callback, NULL);
```

```csharp
Exp matchPlate = Exp.EQ(

    MapExp.GetByKey(MapReturnType.VALUE, Exp.Type.STRING,

        Exp.Val("license"), Exp.MapLoopVar(LoopVarPart.VALUE)),

    Exp.Val("7XYZ789"));

Statement stmt = new Statement();

stmt.SetNamespace("test");

stmt.SetSetName("demo");

stmt.SetFilter(Filter.ContainsByIndex("idx_vehicle_license",

    IndexCollectionType.LIST, "7XYZ789"));

stmt.Operations = new Operation[] {

    CDTOperation.SelectByPath("vehicles", SelectFlag.VALUE,

        CTX.AllChildrenWithFilter(matchPlate)),

    Operation.Get("username")

};

RecordSet rs = client.Query(null, stmt);
```

```js
const matchPlate = exp.eq(

  exp.maps.getByKey(

    exp.loopVarMap(exp.loopVarPart.VALUE),

    exp.str('license'),

    exp.type.STR,

    maps.returnType.VALUE

  ),

  exp.str('7XYZ789')

)

const projCtx = new Context()

  .addAllChildrenWithFilter(matchPlate)

const query = client.query('test', 'demo')

const sifter = Aerospike.filter.contains(null, '7XYZ789',

  Aerospike.indexType.LIST)

query.whereWithIndexName(sifter, 'idx_vehicle_license')

query.ops = [

  op.selectByPath('vehicles', exp.pathSelectFlags.VALUE, projCtx),

  Aerospike.operations.read('username')

]

const stream = query.foreach()
```

**Expected output per record:**

```json
{"vehicles": [{"color": "silver", "license": "7XYZ789", "make": "Honda", "model": "Civic"}], "username": "thomasanderson"}
```

For more on operation projection in queries, see [Projection](https://aerospike.com/docs/develop/learn/queries/projection). For more on path expressions, see the [path expressions quickstart](https://aerospike.com/docs/develop/expressions/path/quickstart).