Skip to content

indexing and lookup

This structure is good for filtering, but bad for lookup.


The problem we are solving

Each FamilyInstance references its type like this:

"parent": {
  "id": 617464,
  "type": "FamilySymbol"
}

For every instance, we need to answer:

“Give me the FamilySymbol with ID 617464.”

❌ Without an index (slow and noisy)

You would have to search the array every time:

$symbols[id = parent.id]

Problems with this approach:

  • Re-scans the entire symbol list for every instance
  • Becomes slow on large models
  • Makes the rule harder to read
  • Hides intent (“this is a lookup, not a filter”)

What indexing does instead

This expression:

$symbols.{$string(id): $}

Transforms the array into key–value pairs:

{
  "617464": { "id": 617464, "values": { ... } },
  "617670": { "id": 617670, "values": { ... } },
  "617812": { "id": 617812, "values": { ... } }
}

Then:

$merge(...)

Combines those pairs into one object.


The result: $symIndex

{
  "617464": { ...FamilySymbol object... },
  "617670": { ...FamilySymbol object... }
}

Now you can do direct lookup:

$lookup($symIndex, $string(parent.id))

This is:

  • Constant-time lookup
  • Explicit intent
  • Easy to debug
  • Scales well to large models

Why $string(id) is required

JSON object keys must be strings.

FamilySymbol IDs are numbers.

So we convert:

$string(id)

This ensures:

  • Valid object keys
  • Correct matching with parent.id

Rule of thumb for users

Lists are for selection. Maps are for lookup.

If you find yourself repeatedly searching a list by ID, you should probably build an index first.


Why this pattern is preferred in DAQS rules

Indexing:

  • Improves performance
  • Makes rules easier to read
  • Prevents accidental multiple matches
  • Clarifies intent for reviewers

This is not an optimisation trick — it is the correct data structure for the problem.