Here is a clean, DAQS-style documentation you can use directly in your course.
Level name lookup
What does this do?
This pattern is used when you have a reference ID (e.g. levelId) and you need to retrieve the name of the referenced element (e.g. the Level name).
This is a very common pattern in Revit data:
- FamilyInstance →
levelId - Doors/Windows →
hostId - Instances →
parent.id(FamilySymbol)
The core problem
Revit extraction data is not relational. There is no built-in join between elements.
So when you have:
"levelId": 1951
You must manually resolve:
{
"id": 1951,
"type": "Level",
"name": "Level 1"
}
Solution 1 — Simple function (readable, but slow)
(
/* Functions */
$getNameById := function($id) {
$[id = $id].name
};
/* Filter */
$[type = "FamilyInstance"].{
"id": id,
"type": type,
"name": name,
"level": $getNameById(values.levelId),
}
)
How it works
$[id = $id]scans the entire dataset- It finds the object where
idmatches .namereturns the name
Important behavior
- If multiple matches exist → returns an array
- If no match exists → returns
null
⚠️ Limitation (important)
This approach performs a full dataset scan for every element.
Complexity
- For n elements → O(n²)
Practical impact
- Works fine for small models
- Becomes slow on large projects (10k+ elements)
- Not suitable for production-level DAQS rules
Solution 2 — Using indexing (recommended)
(
/* INDEX */
$indexById := $reduce($, function($acc, $item) {
$merge([$acc, { $string($item.id): $item }])
}, {});
/* NAME INDEX */
$nameIndex := $reduce($, function($acc, $item) {
$merge([$acc, { $string($item.id): $item.name }])
}, {});
/* FUNCTION */
$getNameById := function($id) {
$lookup($nameIndex, $string($id))
};
/* OUTPUT */
$[type = "FamilyInstance"].{
"id": id,
"type": type,
"name": name,
"level": $getNameById(values.levelId),
}
)
How this works
Step 1 — Build an index
$reduce(...)
Creates a lookup table:
{
"1951": "Level 1",
"4476": "Type 3"
}
Step 2 — Use constant-time lookup
$lookup($nameIndex, $string($id))
- No scanning
- Direct access by key
- Always O(1)
Why $string($id) is required
JSONata object keys must be strings.
Without this:
$lookup($nameIndex, 1951)
You will get errors like:
Object key should be String
Performance comparison
| Approach | Complexity | Suitable for |
|---|---|---|
| Simple filter | O(n²) | Small datasets |
| Indexed lookup | O(n) + O(1) | Large datasets / production |
Best practice (DAQS)
Use indexing when:
- You perform lookups inside a loop
- You work with Revit models of realistic size
- You combine multiple joins (level, host, symbol, etc.)
Avoid:
$[id = ...]
inside mapped expressions.
Common mistakes
1. Using $ incorrectly inside functions
Inside mappings, $ refers to the current element, not the full dataset.
This is why indexing is safer — it removes context ambiguity.
2. Forgetting $string()
Leads to lookup failures or runtime errors.
3. Recomputing the index multiple times
Always define the index once at the top.
When to use which approach
Use simple function when:
- You are learning JSONata
- Dataset is small
- Readability is more important than performance
Use indexing when:
- Building DAQS rules
- Working with real project data
- Combining multiple lookups
Key takeaway
JSONata does not have joins — you must build them yourself.
- Simple approach = scan every time
- Scalable approach = build an index once, reuse it
If you skip indexing in real DAQS rules, you will run into performance issues.