Skip to content

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.