Truthiness Modes in EXPRESSO
When writing portable business logic rules, truthiness (how values are converted to boolean) varies significantly across programming languages. EXPRESSO provides configurable truthiness modes to handle these differences.
Why Truthiness Matters
Different languages have different rules for what counts as "truthy" (true in boolean context):
| Value | JavaScript | Python | JsonLogic | Ruby |
|---|---|---|---|---|
true | ✅ Truthy | ✅ Truthy | ✅ Truthy | ✅ Truthy |
false | ❌ Falsy | ❌ Falsy | ❌ Falsy | ❌ Falsy |
0 | ❌ Falsy | ❌ Falsy | ❌ Falsy | ✅ Truthy |
1 | ✅ Truthy | ✅ Truthy | ✅ Truthy | ✅ Truthy |
"" | ❌ Falsy | ❌ Falsy | ❌ Falsy | ✅ Truthy |
"hello" | ✅ Truthy | ✅ Truthy | ✅ Truthy | ✅ Truthy |
null | ❌ Falsy | ❌ Falsy | ❌ Falsy | ❌ Falsy |
undefined | ❌ Falsy | ❌ Falsy | ❌ Falsy | N/A |
[] | ✅ Truthy | ❌ Falsy | ✅ Truthy | ✅ Truthy |
{} | ✅ Truthy | ❌ Falsy | ✅ Truthy | ✅ Truthy |
NaN | ❌ Falsy | ❌ Falsy | ❌ Falsy | ✅ Truthy |
Available Modes
default (JavaScript Semantics)
Uses JavaScript's native truthiness rules. This is the default mode and maintains backward compatibility.
Falsy values: false, 0, "", null, undefined, NaN
Truthy values: Everything else (including [] and {})
import { apply } from '/expresso';
apply({ '!': [0] }, {}, { truthinessMode: 'default' }); // true
apply({ '!': [[]] }, {}, { truthinessMode: 'default' }); // false
apply({ if: [[], 'yes', 'no'] }, {}, { truthinessMode: 'default' }); // 'yes'
Use case: When your rules will only run in JavaScript environments or when you want standard JavaScript behavior.
jsonlogic (JsonLogic Specification)
Follows the official JsonLogic specification for boolean operations.
Falsy values: false, 0, "", null, undefined
Truthy values: Everything else (including [] and {})
Similar to default mode but excludes NaN from explicit falsy values.
import { apply } from '/expresso';
apply({ '!': [0] }, {}, { truthinessMode: 'jsonlogic' }); // true
apply({ '!': [NaN] }, {}, { truthinessMode: 'jsonlogic' }); // false
Use case: When you need strict JsonLogic specification compliance.
python (Python Semantics)
Follows Python's truthiness rules for portability to Python-based systems.
Falsy values: false, 0, 0.0, "", null, undefined, NaN, [],
{} (empty objects)
Truthy values: Everything else
import { apply } from '/expresso';
apply({ '!': [0] }, {}, { truthinessMode: 'python' }); // true
apply({ '!': [[]] }, {}, { truthinessMode: 'python' }); // true
apply({ '!': [[1, 2]] }, {}, { truthinessMode: 'python' }); // false
apply({ '!': [{}] }, {}, { truthinessMode: 'python' }); // true
apply({ '!': [{ a: 1 }] }, {}, { truthinessMode: 'python' }); // false
apply({ if: [[], 'yes', 'no'] }, {}, { truthinessMode: 'python' }); // 'no'
Use case: When porting rules to Python or when you need Python-like empty collection handling.
strict (Boolean-Only Mode)
Only true is truthy, only false is falsy. All other values throw or are
treated as falsy.
Falsy values: false and everything except true
Truthy values: Only true
import { apply } from '/expresso';
apply({ '!': [true] }, {}, { truthinessMode: 'strict' }); // false
apply({ '!': [1] }, {}, { truthinessMode: 'strict' }); // true
apply({ '!': ['hello'] }, {}, { truthinessMode: 'strict' }); // true
apply({ '!': [[]] }, {}, { truthinessMode: 'strict' }); // true
apply({ if: [1, 'yes', 'no'] }, {}, { truthinessMode: 'strict' }); // 'no'
apply({ if: [true, 'yes', 'no'] }, {}, { truthinessMode: 'strict' }); // 'yes'
Use case: When you want explicit boolean checking and avoid type coercion.
Usage
With apply()
import { apply } from '/expresso';
const rule = { if: [{ var: 'items' }, 'has-items', 'no-items'] };
const data = { items: [] };
// Default mode: empty arrays are truthy
apply(rule, data); // 'has-items'
// Python mode: empty arrays are falsy
apply(rule, data, { truthinessMode: 'python' }); // 'no-items'
With applyAsync()
import { applyAsync } from '/expresso';
const result = await applyAsync(rule, data, { truthinessMode: 'python' });
With compile()
import { compile } from '/expresso';
const fn = compile(rule, { truthinessMode: 'python' });
fn(data); // 'no-items'
Common Portability Scenarios
Scenario 1: Array Empty Checks
Problem: { "!": { "var": "data" } } where data is []
// JavaScript/default: ![] → false
apply({ '!': { var: 'data' } }, { data: [] }); // false
// Python: ![] → true
apply({ '!': { var: 'data' } }, { data: [] }, { truthinessMode: 'python' }); // true
Solution: Use Python mode for Python targets.
Scenario 2: Numeric Zero Checks
Problem: Zero behaves differently in Ruby vs others
// JavaScript/default: !0 → true
apply({ '!': { var: 'value' } }, { value: 0 }); // true
// Ruby: !0 → false (zero is truthy in Ruby)
// No Ruby mode exists, use strict mode for explicit boolean handling
Solution: Use strict mode when you need explicit boolean values and want to avoid numeric coercion.
Scenario 3: Empty String vs Empty Collection
Problem: Distinguishing "" from [] in boolean context
// Both are falsy in most languages
apply({ if: ['', 'is-empty', 'not-empty'] }); // 'is-empty'
apply({ if: [[], 'is-empty', 'not-empty'] }); // 'is-empty' (default mode)
Solution: Use Python mode to treat empty collections as falsy while keeping non-empty strings truthy:
apply({ if: [[], 'is-empty', 'not-empty'] }, {}, { truthinessMode: 'python' }); // 'is-empty'
apply(
{ if: [['a'], 'is-empty', 'not-empty'] },
{},
{ truthinessMode: 'python' }
); // 'not-empty'
Operators Affected by Truthiness Mode
The following operators use truthiness evaluation:
!- Logical NOT!!- Boolean conversionor- Logical ORand- Logical ANDif- Conditionalswitch- Switch/casesome- Array predicate (some elements match)all- Array predicate (all elements match)none- Array predicate (no elements match)filter- Array filteringfind- Find first matching elementfind_index- Find index of first matching element
Best Practices
-
Choose the right mode for your target language
- Python →
pythonmode - JavaScript/Node.js →
defaultmode - Multiple language support → Document the mode and be consistent
- Python →
-
Be explicit with
!!for boolean conversionconst rule = { '==': [{ '!!': { var: 'value' } }, true] }; -
Test with your target language Always verify rule behavior in your target language(s), especially edge cases.
-
Document your mode choice Include the truthiness mode in your rule documentation or comments.
-
Use strict mode for explicit boolean checks When you want to avoid type coercion and only accept actual boolean values.
Migration Guide
If you're migrating existing rules to use truthiness modes:
-
Test with default mode first
const result = apply(rule, data, { truthinessMode: 'default' }); -
Identify edge cases Look for array empty checks, zero values, and empty objects in your rules.
-
Update to appropriate mode
// If targeting Python
apply(rule, data, { truthinessMode: 'python' }); -
Verify against target language Test your rules in the target language to ensure consistent behavior.
Future Extensibility
The truthiness mode system is designed to be extensible. New modes can be added
to support additional language semantics (e.g., ruby, php, etc.) without
breaking existing functionality.
For more information or to request additional modes, please open an issue on GitHub.