Skip to content

Array Mapping

When you need to return a list of items in GraphQL, you cannot write standard for or map loops. Instead, The Bridge provides a declarative syntax for mapping array elements into your expected output shape, complete with explicit control flow to filter or halt the iteration.

To map an array, you use the [] as <variableName> operator followed by a scoping block.

bridge Query.getJourneys {
with routerApi as router
with output as o
# Iterate over every element in router.journeys
o.journeys <- router.journeys[] as j {
# Map fields for each individual element
.label <- j.label
.departureTime <- j.departure
}
}

When the engine executes an array mapping block, it creates a Shadow Scope (or shadow tree) for every single element in the array.

The variable j represents the current element being processed. Because each element executes in its own isolated scope, you can nest array mappings arbitrarily deep without worrying about variable collisions.

You can also use aliases inside loops purely for readability, without triggering any tools. If your iterator has deeply nested data, bind it to a short variable:

o.list <- api.items[] as it {
# Bind a deep sub-object to a cleaner name
alias it.metadata.authorInfo as author
.name <- author.name
.role <- author.role
}

APIs often return messy arrays containing nulls, missing IDs, or corrupt data. Instead of returning null to the frontend, you can use the explicit control flow keywords continue and break on the right side of any fallback gate (??, ||, catch) to filter the array directly.

The continue keyword instructs the engine to omit this specific item from the final array, but keep looping and processing the rest of the elements.

o.items <- billingApi.items[] as item {
# If the item is missing an ID, skip it entirely.
# The frontend will not receive a null object; the item just won't exist.
.id <- item.id ?? continue
.name <- item.name
}

The break keyword instructs the engine to stop processing the array entirely and return the items processed up to that point.

o.items <- searchApi.results[] as item {
.id <- item.id
# If we hit an item without a price, halt the entire array map.
# Returns only the valid items that came before it.
.price <- item.price ?? break
}

Use Case 1: Filtering an Array Before Mapping

Section titled “Use Case 1: Filtering an Array Before Mapping”

While continue is great for dropping items based on missing structural fields, sometimes you want to pre-filter a massive array based on explicit logic before mapping it. You can do this using the built-in std.filterObject tool.

bridge Query.getActiveAdmins {
with std.filterObject as filter
with usersApi as api
with output as o
# 1. Provide the array to filter
filter.in <- api.users
# 2. Define the exact criteria to match
filter.role = "admin"
filter.isActive = true
# 3. Map over ONLY the filtered results!
o.admins <- filter[] as admin {
.id <- admin.id
.name <- admin.name
}
}

If you have an array of IDs and you need to fetch detailed information from an external API for every single item, you can perform a “Fanout.”

By combining array mapping with a Pipe and an alias, the engine will dynamically fork the tool and fire a parallel API request for every element in the array.

tool getUserDetail from std.httpCall {
.baseUrl = "https://api.example.com/users"
.method = "GET"
# We leave .path blank so we can inject it dynamically!
}
bridge Query.getEnrichedUsers {
with getUserDetail
with input as i
with output as o
o.enrichedUsers <- i.userIds[] as id {
# 1. Dynamically build the path for this specific element
# 2. Pipe the path into the HTTP tool
# 3. Safely swallow any 500 errors so one bad user doesn't break the list
# 4. Cache the result for this element as 'detail'
alias getUserDetail?.path:"/{id}" catch null as detail
# Map the resulting HTTP response back to the array
# If 'detail' is null (from the catch), we skip this user!
.id <- id
.email <- detail.email ?? continue
.status <- detail.status
}
}