Ternary Assignment (?= operator)
July 30, 2026 · View on GitHub
The ?= operator provides conditional (ternary) assignment in assignFrom. The LHS path receives a value chosen based on a condition evaluated from the source object.
Syntax
assignFrom(target, {
'<lhsPath> ?=': [condition, thenValue, elseValue?]
}, { from: source });
The RHS is always an array. The first element is the condition; subsequent elements are the possible result values. Each element can be a ?.-prefixed path (resolved against from), a protocol string, or a plain literal.
Forms
Guard — [condition, thenValue]
Assigns thenValue only when condition is truthy. If falsy, the assignment is skipped entirely (the target property remains unchanged).
const vm = { isLoggedIn: true, greeting: 'Welcome back!' };
assignFrom(header, {
'?.textContent ?=': ['?.isLoggedIn', '?.greeting']
}, { from: vm });
// header.textContent = 'Welcome back!'
// If isLoggedIn were false, header.textContent would remain unchanged.
Ternary — [condition, thenValue, elseValue]
Assigns thenValue when truthy, elseValue when falsy. Always assigns one or the other.
const vm = { isHappy: true, happyMsg: 'I am happy', sadMsg: 'I am sad' };
assignFrom(element, {
'?.textContent ?=': ['?.isHappy', '?.happyMsg', '?.sadMsg']
}, { from: vm });
// element.textContent = 'I am happy'
With literal values (no ?. prefix — passed through as-is):
assignFrom(element, {
'?.textContent ?=': ['?.isHappy', 'Feeling great', 'Not so great']
}, { from: vm });
Three-state — [condition, trueValue, falseValue, neitherValue]
Distinguishes between true, false, and nullish (null/undefined). Useful for data that hasn't loaded yet vs. an explicit negative state.
const vm = { loaded: null }; // not yet determined
assignFrom(statusEl, {
'?.textContent ?=': ['?.loaded', 'Ready', 'Failed', 'Loading...']
}, { from: vm });
// statusEl.textContent = 'Loading...'
Semantics:
condition == null→ assigns element [3] (neither)conditionis truthy → assigns element [1]conditionis falsy (but not nullish) → assigns element [2]
Chain shortcut — [c1, '||', c2, ...] and [c1, '??', c2, ...]
When the second element is the marker '||' or '??', the array is a candidate chain instead of a ternary:
'||'assigns the first truthy candidate (equivalent toc1 || c2 || c3).'??'assigns the first non-nullish candidate (equivalent toc1 ?? c2 ?? c3).
const vm = { nickname: '', name: 'Gingerly' };
assignFrom(element, {
'?.textContent ?=': ['?.nickname', '||', '?.name']
}, { from: vm });
// element.textContent = 'Gingerly' — avoids writing ['?.nickname', '?.nickname', '?.name']
Longer chains work too:
// cond1 || cond2 || cond3
'?.textContent ?=': ['?.cond1', '||', '?.cond2', '||', '?.cond3']
'??' only skips null/undefined — falsy values like 0 or false are returned:
const vm = { count: 0, fallback: 5 };
assignFrom(element, {
'?.textContent ?=': ['?.count', '??', '?.fallback']
}, { from: vm });
// element.textContent = 0 (0 is not nullish — with '||' it would have been 5)
Semantics and edge cases:
- Candidates are resolved lazily — evaluation stops at the first match, and each candidate is resolved at most once.
- When the chain ends with a candidate (
['?.a', '||', '?.b']), that candidate doubles as the fallback and is assigned even when it fails the test (matching JSa || b, which yieldsbregardless). - A trailing element after the last candidate with no marker between (
['?.a', '||', '?.b', 'fallback']) is an explicit fallback, assigned when all candidates fail. - When the chain ends with a dangling marker (
['?.a', '||']or['?.a', '||', '?.b', '||']), it acts as a guard: assigns the first passing candidate, skips otherwise. - The marker check takes precedence over the length-based forms, so a length-4 chain like
['?.a', '||', '?.b', 'fallback']is not misread as the three-state form. - Markers are only recognized in the truthiness form — they are not supported in comparison mode (
[[lhs, rhs], ...]). - Mixing
'||'and'??'in one chain is not supported; the first marker sets the mode. - The literal strings
'||'and'??'can no longer be used as a then-value in position 1.
Equality guard — [[lhs, rhs], result]
When the first element is an array, it's a comparison. Assigns result only when lhs === rhs. Skips otherwise.
const vm = { role: 'admin' };
assignFrom(badge, {
'?.textContent ?=': [['?.role', 'admin'], 'Administrator']
}, { from: vm });
// badge.textContent = 'Administrator'
// If role were 'user', badge.textContent would remain unchanged.
Both sides of the comparison can be paths:
assignFrom(target, {
'?.className ?=': [['?.expected', '?.actual'], 'match', 'mismatch']
}, { from: { expected: 'foo', actual: 'foo' } });
// target.className = 'match'
Equality ternary — [[lhs, rhs], equalResult, notEqualResult]
Always assigns one of the two results based on equality.
const vm = { theme: 'dark' };
assignFrom(body, {
'?.className ?=': [['?.theme', 'dark'], 'dark-mode', 'light-mode']
}, { from: vm });
// body.className = 'dark-mode'
Operator comparison — [[lhs, op, rhs], result, elseResult?]
For comparisons beyond equality, include an operator string as the middle element of the condition array.
Supported operators: ===, !==, >, >=, <, <=
const vm = { score: 95 };
assignFrom(gradeEl, {
'?.textContent ?=': [['?.score', '>=', 90], 'A', 'Below A']
}, { from: vm });
// gradeEl.textContent = 'A'
Without an else (guard form):
assignFrom(alert, {
'?.hidden ?=': [['?.errorCount', '>', 0], false]
}, { from: { errorCount: 3 } });
// alert.hidden = false (shows the alert)
Nested ternaries — [c1, t1, [c2, t2, e2]]
Any result position (then/else/neither, comparison results, chain candidates) can itself be a ternary expression, enabling chained conditions:
// cond1 ? then1 : cond2 ? then2 : else
const vm = { isVip: false, isLoggedIn: true };
assignFrom(element, {
'?.textContent ?=': ['?.isVip', 'Welcome, VIP!', ['?.isLoggedIn', 'Welcome back!', 'Please log in']]
}, { from: vm });
// element.textContent = 'Welcome back!'
A nested array is treated as a ternary when it has at least 2 elements and its first element is either a ?.-prefixed string (truthiness mode) or an array (comparison mode). Any other array is assigned as a literal value.
// Comparison-mode nesting: cond1 ? then1 : (a === b ? 'match' : 'no match')
'?.textContent ?=': ['?.c1', 'then1', [['?.a', '?.b'], 'match', 'no match']]
Semantics and edge cases:
- Nesting recurses to any depth:
[c1, t1, [c2, t2, [c3, t3, e]]]. - A skip from a nested guard propagates outward: in
[c1, [guard, v], else], ifc1is truthy butguardis falsy, the whole assignment is skipped — the outerelseis not assigned. - Chain candidates may be nested ternaries (
['?.a', '||', ['?.b', '?.c', '?.d']]). A nested guard that skips counts as a failed candidate and the chain continues. - Literal/protocol conditions don't trigger nesting — only
?.-prefixed strings and arrays (comparison mode) do. - Consequence of the trigger: a literal array whose first element is a
?.-prefixed string (or an array) cannot be assigned in a result position — it will be interpreted as a nested ternary.
Mixing with other keys
?= works alongside normal assignments, Y=, +=, and handlers in the same call:
assignFrom(element, {
'?.title': '?.pageTitle', // normal path resolution
'?.textContent ?=': ['?.isActive', '?.activeMsg', '?.inactiveMsg'], // ternary
'?.style Y=': { opacity: '1' }, // merge
'?.dataset?.visits +=': 1, // increment
}, { from: vm });
Multiple ternaries
Multiple ?= keys in the same pattern are all evaluated:
assignFrom(element, {
'?.textContent ?=': ['?.showGreeting', '?.greeting', ''],
'?.className ?=': [['?.theme', 'dark'], 'dark-theme', 'light-theme'],
'?.hidden ?=': ['?.isVisible', false, true],
}, { from: vm });
Notes
?=is available inassignFromandassignFromAsync(notassignGingerly— it requires source resolution).- Path resolution uses
getValuesinternally — protocols,withMethods, andakaall work in condition and result values. - Guard forms (2-element) skip assignment entirely when the condition is not met — the target property is untouched.
- The condition array form (
[[...], ...]) is distinguished from truthiness form by checking if the first element of the outer array is itself an array.