Singleton vs Array Issues
JSONata behaves differently depending on whether an expression returns a single object or an array of objects. This difference is invisible in many cases — but it causes subtle bugs that only appear when a model has exactly one matching element.
What the issue is
When a filter matches multiple elements, JSONata returns an array:
[
{ "id": 1, "name": "Door A" },
{ "id": 2, "name": "Door B" }
]
When a filter matches exactly one element, JSONata returns a single object — not an array containing one object:
{ "id": 1, "name": "Door A" }
Why this causes bugs
A rule or downstream expression that expects an array will behave incorrectly when it receives a single object.
The most common place this surfaces is with .id extraction:
$doorSymbolIds := $[type = "FamilySymbol" and values.category.label = "OST_Doors"].id;
If there is only one door type in the model, $doorSymbolIds is a single number — not an array of numbers. The in operator used in the next step may not behave as expected.
How to reproduce it
Test your filter with a dataset that contains exactly one matching element. If the rule behaves differently than with two or more elements, you have a singleton issue.
The fix: wrap with $[] or use array functions
The safest fix is to ensure the result is always treated as an array. Wrap the extraction in []:
$doorSymbolIds := [$[type = "FamilySymbol" and values.category.label = "OST_Doors"].id];
The outer [] ensures the result is always an array — even if only one element matches.
Alternatively, use $append([], ...) or check with $type(...) if you need to handle both cases explicitly.
Where this typically appears
| Pattern | Risk |
|---|---|
.id extraction used with in |
High — single ID becomes a number, not an array |
$merge(...) on a single-element result |
Medium — result shape may differ |
| Scriban loops over a filter output field | Medium — single value vs array |
| Any pattern that uses the result as a list | Check explicitly |
How to test for it
Add this check to your debugging workflow when a rule works on large models but produces unexpected results on small ones:
/* How many symbols match? */
$count($[type = "FamilySymbol" and values.category.label = "OST_Doors"])
If the count is 1, test your filter with the [] wrapper and confirm the result is still an array.
Rule of thumb
Whenever you extract a list of IDs to use with
in, always wrap with[].
/* Safe */
$symbolIds := [$[type = "FamilySymbol" and ...].id];
/* Risky on single-element models */
$symbolIds := $[type = "FamilySymbol" and ...].id;
This one habit prevents the entire class of singleton issues.