d2path¶
d2path is the expression language used inside every d2ql stage (where, select, transform,
order, group by, fold). It navigates and computes over data with dotted path navigation,
operators, and functions, and works the same whether the data is a DHIS2 wire model or any plain
JSON document (so it can read and emit JSON like FHIR resources too).
Try any expression standalone against a JSON file:
echo '{"name":"ANC 1st visit"}' > patient.json
d2w query d2path 'name.upper()' --input patient.json # => ["ANC 1ST VISIT"]
Collection semantics¶
Every expression evaluates to a list — there are no scalars under the hood. A single value is a
one-item list, a missing path is the empty list []. Navigation flattens: items.qty walks into
every items element and collects each qty.
This is why comparisons are existential: items.qty > 2 is true if any collected qty exceeds
2. The CLI/engine collapses a one-item list to that item when building output, so name.upper()
shows as "ANC", not ["ANC"], in a select.
Presence and absence — there is no = null¶
A missing field and an explicit JSON null both evaluate to the empty collection []. There is
therefore no useful field = null: comparing against the empty collection yields
empty (treated as false), so where deleted = null matches nothing and is never pushed down. Test
presence and absence with functions instead:
where field.exists() # keep rows where the field is present (non-null)
where field.empty() # keep rows where the field is absent or null
null is still a writable literal (e.g. transform { note: null }); it just isn't something you
compare with =/!=.
Literals¶
| Form | Example |
|---|---|
| String (double-quoted) | "AGGREGATE" |
| Number | 42, 3.14 |
| Boolean / null | true, false, null |
| Array | ["a", "b", "c"] |
| Object | { code: id, label: name } |
Variables¶
| Variable | Meaning |
|---|---|
$this |
The current row (in a stage) or item (inside a function applied per element). |
$index |
Zero-based position of the current row. |
$rows |
Inside fold, the whole stream of rows. |
$name |
A defined scalar, e.g. define MinLevel: 3 → $MinLevel. |
$param |
A define function's parameter, e.g. define function f(de): $de.name. |
Navigation¶
| Syntax | Meaning | Example |
|---|---|---|
a.b |
Member access (flattens over collections) | categoryCombo.name |
a[0] |
Index into a collection | coding[0] |
a["key"] |
Member access by key, for non-identifier field names | extension["us-core-race"] |
a.b.c |
Chained navigation | name.given.first() |
# coding.where(system = "dhis2").code over {"coding":[{"system":"dhis2","code":"X"},{"system":"loinc","code":"Y"}]}
=> ["X"]
Operators¶
Comparison (existential over collections): =, !=, <, <=, >, >=.
Matching: like (case-insensitive substring; written name like "anc") and ~ (its symbolic
form). matches(regex) for full regular expressions.
Membership: value in ["A", "B"]; collection contains item.
Logical: and, or, xor, implies, and the not() function. and/or short-circuit.
Arithmetic: +, -, *, /, integer div, mod.
Type test: value is Integer (also Decimal, String, Boolean).
where domainType = "AGGREGATE" and (name like "ANC" or name like "BCG")
where level in [2, 3]
transform { ratio: numerator / denominator, ok: value >= 100 }
Function reference¶
Functions are called with method syntax on their input collection — name.upper(),
items.where(qty > 0) — except iif, which is a free function. Every example below is run with
d2w query d2path '<expr>' --input data.json and shows the real result.
Filtering & projection¶
where(predicate)¶
Keep items for which predicate is true. $this is the current item.
coding.where(system = "dhis2").code over {"coding":[{"system":"dhis2","code":"X"},{"system":"loinc","code":"Y"}]}
=> ["X"]
select(expr)¶
Map each item to expr (project). Build a new object per item with { … }.
exists(predicate?)¶
True if any item exists (optionally matching predicate).
all(predicate)¶
True if predicate holds for every item.
empty()¶
True if the collection is empty.
iif(condition, then, else)¶
Conditional expression (free function).
not()¶
Boolean negation of a single-item boolean collection.
Subsetting & set operations¶
first() · last()¶
The first / last item.
tail() · skip(n) · take(n)¶
All but the first (tail); drop the first n (skip); keep the first n (take).
count()¶
Number of items.
distinct() · isDistinct()¶
Unique items; whether all items are already unique.
union(other) · combine(other)¶
Set union (de-duplicated) and plain concatenation (keeps duplicates).
Strings¶
upper() · lower() · length() · trim()¶
name.upper() over {"name":"anc"} => ["ANC"]
name.lower() over {"name":"ANC"} => ["anc"]
name.length() over {"name":"Penta"} => [5]
toChars()¶
Split a string into its characters.
startsWith(s) · endsWith(s) · contains(s)¶
substring(start, length?)¶
Substring from start, optionally length chars.
indexOf(s) · replace(find, replacement)¶
matches(regex)¶
True if the string matches the regular expression.
split(sep) · join(sep)¶
Split a string into a list; join a list into a string.
parts.split(",") over {"parts":"a,b,c"} => ["a", "b", "c"]
name.given.join(" ") over {"name":{"given":["Ada","Lovelace"]}} => ["Ada Lovelace"]
Numbers & aggregates¶
sum() · min() · max() · avg()¶
Reduce a numeric collection.
scores.sum() over {"scores":[3,5,2]} => [10.0]
scores.avg() over {"scores":[3,5,2]} => [3.3333333333333335]
scores.max() over {"scores":[3,5,2]} => [5.0]
group by { total: sum(value) }.
abs() · round(places?)¶
Conversion & temporal¶
toInteger() · toDecimal() · toString()¶
Parse/format scalars.
today() · now()¶
Current date / timestamp (ISO 8601).
See also¶
- d2ql tutorial and d2ql reference — where these expressions are used.
dhis2w_qlAPI — evaluate d2path from Python.