Skip to content

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 id matches
  • .name returns 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

(
  /* 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.