Accessors

Learn how to access properties and attributes in CQL.

When writing queries, you will often use property access expressions to extract values from your data.

CQL provides several ways to access properties that you can choose depending on your use case and preferred style.

Ownership notation

The ownership notation is a more natural way to access properties in CQL. It is complementary to the possessive notation and can be used interchangeably or in combination with it.

The syntax follows the same pattern as the possessive form in English. It consists of the variable name followed by an apostrophe and an "s" ('s) and the property name.

Here is a basic example that accesses the interests property of the user variable:

user's interests
Try in Playground

You can also chain multiple property accesses together to access nested properties:

user's address's city
Try in Playground

Alternatively, you can use the possessive form for an even more natural sentence:

property city of user's address
Try in Playground

To combine the ownership notation with other technical notations, such as the bracket notation, you can use parentheses:

(user's interests)[0]
Try in Playground

In English, it is common to drop the "s" after the apostrophe when the word ends with an "s". You can do the same in CQL to make the queries more natural:

let status = ["code": "active"];
status' code
Try in Playground

Keep in mind that dropping the extra "s" after an apostrophe is just syntax sugar. It is not a requirement, so you can keep the "s" if you prefer.

Consider using the ownership notation when:

  • The property name is a valid identifier and is known in advance.
  • The property is not deeply nested.
  • You prefer writing queries using a more natural language style.
  • You want to combine it with the possessive notation for a more natural sentence.

Possessive notation

The possessive notation is another way to access properties in CQL that emphasizes natural language. It is complementary to the ownership notation and can be used interchangeably or in combination with it.

The syntax for the possessive notation consists of the keyword property followed by the property name, the keyword of, and the value or variable name.

Here is an example that accesses the interests property of the user variable:

property interests of user
Try in Playground

You can access nested properties by chaining multiple property accesses together.

property city of property address of user
Try in Playground

As mentioned earlier, you can combine the possessive notation with the ownership notation for an even more natural sentence:

property city of user's address
Try in Playground

You can optionally add the word the before the property name to make the sentence more readable depending on the context:

the property city of user's address is equal to 'New York'
Try in Playground

To combine the possessive notation with other technical notations, such as the bracket notation, you can use parentheses:

(property interests of user)[0]
Try in Playground

While the possessive notation can provide a more natural way to access properties in some scenarios, it may not always be the most suitable choice for all situations.

Consider using the possessive notation when:

  • The property name is a valid identifier and is known in advance.
  • The property is not deeply nested.
  • You prefer writing queries using a more natural language style.
  • You want to combine it with the ownership notation for a more natural sentence.

Dot notation

This is the simplest and most concise way to access properties.

It follows the same syntax as some programming languages, such as JavaScript and Python. As a result, it may be more familiar to developers than to those who are not involved in programming.

The syntax consists of the variable name followed by a dot (.) and the property name.

Here is a basic example that accesses the interests property of the user variable:

user.interests
Try in Playground

You can also chain multiple property accesses together to access nested properties:

user.address.city
Try in Playground

Or even combine them with other notations, such as the bracket notation:

user.interests[0]
Try in Playground

Each notation has its own advantages and tradeoffs, so you should choose the one that best fits your scenario.

Consider using the dot notation when:

  • The property name is a valid identifier and is known in advance.
  • You are accessing a deeply nested property.
  • Anyone reading the queries is familiar with this notation.

Bracket notation

The bracket notation is another way to access properties in CQL, and it is particularly useful when the property name is not a valid identifier or when you want to use selectors.

Unlike the dot notation, the bracket notation supports any property name, including those that are not a valid identifier.

The syntax consists of the variable name followed by a selector enclosed in square brackets ([ and ]):

subject[/*<selector>*/]
Try in Playground

Here is a basic example that accesses the interests property of the user variable:

user['interests']
Try in Playground

You can also use numeric indexes to access items from a list:

user.interests[0]
Try in Playground

Although this notation supports arbitrary property names and selectors, it may still not be the best choice for all scenarios.

Consider using the bracket notation when:

  • The property name is not a valid identifier or is not known in advance.
  • You need to access items from a list using numeric indexes.
  • You want to leverage the power of selectors.
  • Anyone reading the queries is familiar with this notation.
  • You prefer to stick to a single notation throughout your queries.

Selectors

We call selectors the different specifiers you can use inside brackets to access properties. They are shorthands for common operations that you can use to access values in a more concise way.

Here is a summary of the currently available selectors:

SelectorDescriptionExample
First selectorGet the first item of a list.cart.items[:first]
Last selectorGet the last item of a list.cart.items[:last]
Ordinal selectorGet the item at a specific position.cart.items[1st]
Expression selectorGet a value using an expression.cart.items['price']
Wildcard selectorGet multiple values at once.cart[*].name
Scan selectorGet values recursively.cart[...].name

First selector

The first selector provides a convenient, more intuitive way to access the first items of a list.

For example, to access the first item in a list, you can use:

let transport = ["🚀", "🚗", "🚆"];
transport[:first] // 🚀
Try in Playground

You can also use it to access a specific number of items from the beginning of a list:

transport[:first(/*<count>*/)]

For example, to access the first two items of a list, you can use:

transport[:first(2)] // ["🚀", "🚗"]

In the real world, you can use it to access the first item a user added to their shopping cart, for example:

cart.items[:first]

And you can also use it to get the first items of a map:

let beings = ["aliens": "👽", "robots": "🤖", "humans": "👨‍🚀"];
beings[:first] // 👽
Try in Playground

This works because the :first selector does not use the index or key to find the first element, but the order in which they appear in the collection.

Last selector

The last selector is the counterpart of the first selector. It allows you to access the last elements of a collection without knowing the index or key.

For example, to access the last item in a list, you can use:

let transport = ["🚀", "🚗", "🚆️"];
transport[:last] // 🚆
Try in Playground

You can also use it to access a specific number of items from the end of a list:

transport[:last(/*<count>*/)]

For example, to access the last two items of a list, you can use:

transport[:last(2)] // ["🚗", "🚆"]

In the real world, you can use it to access the last item a user added to their shopping cart:

cart.items[:last]

And you can also use it to access the last element of a map:

let beings = ["aliens": "👽", "robots": "🤖", "humans": "👨‍🚀"];
beings[:last] // 👨‍🚀
Try in Playground

Similar to the :first selector, :last does not use the index or key to find the last element but the order in which they appear in the collection.

Ordinal selector

The ordinal selector allows you to access an element at a specific position in a list using its ordinal number instead of its index or key.

Croct's mascot amazed
So can I finally count like a human?

That's right! With the ordinal selector, you can use natural counting, starting at 1, instead of the typical 0-based indexing in programming.

For example, to access the second item in a list, you can simply use:

let transport = ["🚀", "🚗", "🚆"];
transport[2nd] // 🚗
Try in Playground

For a real-world example, let's say you want to access the second item added to a shopping cart:

cart.items[2nd] or else null
Try in Playground

Here, we use the or else operator to return null if the cart is empty. Otherwise, the query would fail with an error.

Expression selector

The expression selector is the most versatile, as it allows you to use any expression to access a property or an item in a list.

This selector is especially useful when the property name is not a valid identifier or when you want to dynamically compute the index or key.

For example, say you want to get the value of a query string parameter named referral code. Because the property name contains spaces, you can only access it using the expression selector:

page.query['referral code']
Try in Playground

Because you can use any expression, you could store the property name in a variable and use it in the expression:

let param = 'referral code';
page.query[param]
Try in Playground

You can also use numeric indexes to access items in a list:

let transport = ["🚀", "🚗", "🚆"];
transport[0] // 🚀
Try in Playground

Notice that the index is zero-based, so the first item has an index of 0, the second item has an index of 1, and so on. For a more natural way to access items in a list, see the ordinal selector.

Wildcard selector

The wildcard selector allows you to access multiple properties or elements at once, which can be useful when you want to retrieve or modify several items in a list, for example.

It iterates over all the items in a list and applies the selector to each item, much like a for loop in programming languages.

Before we explore its use, let's first define a wishlist of must-have items:

1let whishlist = [
2 ["name": "🔮 Crystal ball", "price": 50],
3 ["name": "🎩 Invisible hat", "price": 75],
4 ["name": "👻 Ghost repellent spray", "price": 30],
5 ["name": "🚀 DIY rocket ship kit", "price": 900],
6 ["name": "🦖 Dinosaur egg incubator", "price": 200],
7];

Now, let's say you want to calculate the total price of all the items on your wishlist. In this scenario, the wildcard selector comes in handy:

sum of whishlist[*].price

Here the wildcard selector iterates over all the items in the list and creates a new list containing the price of each item. Then, the sum macro calculates and returns the total by summing all the prices in the list.

This also comes in handy when you want to check if a list satisfies a condition. For example, suppose you want to check if at least one item in the user's shopping cart has a price greater than $100. You can use the wildcard selector and the some quantifier to answer this question:

some price in cart.items[*] satisfies price > 100
Try in Playground

This will check if any price in the cart.items list satisfies the given condition. And because the some quantifier returns a boolean, you have your answer.

Scan selector

The scan selector allows you to scan and retrieve values from nested data structures. It recursively iterates through the data structure and returns a list of all corresponding values.

For example, let's say you have the following data structure representing an e-commerce store's inventory:

1let inventory = [
2 [
3 "category": "📱 Electronics",
4 "items": [
5 ["name": "Smartphone", "price": 500],
6 ["name": "Tablet", "price": 400],
7 ]
8 ],
9 [
10 "category": "👗 Fashion",
11 "items": [
12 ["name": "T-shirt", "price": 25],
13 ["name": "Jeans", "price": 60],
14 ],
15 ],
16 [
17 "category": "🍳 Home & Kitchen",
18 "items": [
19 ["name": "Toaster", "price": 30],
20 ["name": "Blender", "price": 75]
21 ],
22 ],
23];

Using the scan selector, you easily find the cheapest item in the inventory:

min of inventory[...].price

Here, the scan selector iterates through the inventory and creates a list of all the prices. Then, the min macro returns the lowest price in the list, which is $25.

You can use the same idea to check whether the user has added an Apple product to their cart:

some brand in cart[...].brand satisfies brand is "Apple"

This time, the scan selector iterates through the cart and creates a list of all the brands. Then, the some quantifier checks if Apple is in the list.

The scan selector also supports an optional maximum depth parameter, which allows you to limit the depth of the search and potentially speed up the query.

For example, let's update the inventory to include subcategories and see how this works:

1let inventory = [ // Level 1
2 [ // Level 2
3 "category": "📱 Electronics",
4 "items": [ // Level 3
5 ["name": "Desktop computer", "price": 900], // Level 4
6 ],
7 "subcategories": [
8 [
9 "category": "🎮 Gaming",
10 "items": [
11 ["name": "Gaming console", "price": 500],
12 ["name": "Gaming headset", "price": 100]
13 ],
14 ],
15 ],
16 ],
17];

Now, let's say you want to sum the prices, but only for the root categories. You can use the scan selector with a maximum depth of 4 to achieve this:

sum of inventory[...4].price

Since the desktop computer is the only item in a root category, the result is $900 instead of $2500.