Expression System
Object UI includes a powerful expression system that enables dynamic, data-driven UIs. Expressions allow you to reference data, compute values, and create conditional logic directly in your JSON schemas.
Overview
Expressions are JavaScript-like code snippets embedded in schemas using the ${} syntax:
{
"type": "text",
"value": "Hello, ${user.name}!"
}With data:
const data = { user: { name: "Alice" } }This renders: "Hello, Alice!"
Basic Syntax
Simple Property Access
Access data properties using dot notation:
{
"type": "text",
"value": "${user.firstName}"
}Nested Properties
Access nested objects:
{
"type": "text",
"value": "${user.address.city}"
}Array Access
Access array elements:
{
"type": "text",
"value": "${users[0].name}"
}String Interpolation
Mix expressions with static text:
{
"type": "text",
"value": "Welcome, ${user.firstName} ${user.lastName}!"
}Operators
Arithmetic Operators
{
"type": "text",
"value": "Total: ${price * quantity}"
}Supported: +, -, *, /, %
Comparison Operators
{
"type": "badge",
"variant": "${score >= 90 ? 'success' : 'default'}"
}Supported: >, <, >=, <=, ==, ===, !=, !==
Logical Operators
{
"type": "button",
"visibleOn": "${user.isAdmin && user.isActive}"
}Supported: &&, ||, !
Ternary Operator
{
"type": "text",
"value": "${count > 0 ? count + ' items' : 'No items'}"
}Conditional Properties
visibleOn
Show component when expression is true:
{
"type": "button",
"label": "Admin Panel",
"visibleOn": "${user.role === 'admin'}"
}hiddenOn
Hide component when expression is true:
{
"type": "section",
"hiddenOn": "${user.settings.hideSection}"
}disabledOn
Disable component when expression is true:
{
"type": "button",
"label": "Submit",
"disabledOn": "${form.submitting || !form.isValid}"
}Data Context
Accessing Root Data
The root data object is available directly:
const data = {
user: { name: "Alice" },
settings: { theme: "dark" }
}
<SchemaRenderer schema={schema} data={data} />{
"type": "text",
"value": "Theme: ${settings.theme}"
}Scoped Data
Some components provide scoped data:
{
"type": "list",
"items": "${users}",
"itemTemplate": {
"type": "card",
"title": "${item.name}", // 'item' is scoped data
"body": "${item.email}"
}
}Index in Loops
Access the current index in loops:
{
"type": "list",
"items": "${users}",
"itemTemplate": {
"type": "text",
"value": "#${index + 1}: ${item.name}"
}
}Built-in Functions
String Functions
{
"type": "text",
"value": "${user.name.toUpperCase()}"
}Available:
toUpperCase(),toLowerCase()trim(),trimStart(),trimEnd()substring(start, end)replace(search, replace)split(separator)includes(substring)startsWith(prefix),endsWith(suffix)
Array Functions
{
"type": "text",
"value": "Total users: ${users.length}"
}{
"type": "text",
"value": "${users.map(u => u.name).join(', ')}"
}Available:
lengthmap(fn),filter(fn),reduce(fn, initial)join(separator)slice(start, end)includes(item)find(fn),findIndex(fn)some(fn),every(fn)
Number Functions
{
"type": "text",
"value": "Price: ${price.toFixed(2)}"
}Available:
toFixed(decimals)toPrecision(digits)toString()
Math Functions
{
"type": "text",
"value": "${Math.round(average)}"
}Available: All standard Math functions
Math.round(),Math.floor(),Math.ceil()Math.min(),Math.max()Math.abs()Math.random()
Date Functions
{
"type": "text",
"value": "${new Date().toLocaleDateString()}"
}Complex Expressions
Nested Ternary
{
"type": "badge",
"variant": "${
status === 'active' ? 'success' :
status === 'pending' ? 'warning' :
status === 'error' ? 'destructive' :
'default'
}"
}Combining Operators
{
"type": "alert",
"visibleOn": "${
(user.role === 'admin' || user.role === 'moderator') &&
user.isActive &&
!user.isSuspended
}"
}Array Methods
{
"type": "text",
"value": "${
users
.filter(u => u.isActive)
.map(u => u.name)
.join(', ')
}"
}Practical Examples
User Greeting
{
"type": "text",
"value": "${
new Date().getHours() < 12 ? 'Good morning' :
new Date().getHours() < 18 ? 'Good afternoon' :
'Good evening'
}, ${user.firstName}!"
}Status Badge
{
"type": "badge",
"text": "${status}",
"variant": "${
status === 'completed' ? 'success' :
status === 'in_progress' ? 'info' :
status === 'pending' ? 'warning' :
'default'
}"
}Price Formatting
{
"type": "text",
"value": "$${(price * quantity).toFixed(2)}"
}Empty State
{
"type": "empty-state",
"visibleOn": "${items.length === 0}",
"message": "No items to display",
"description": "Start by adding your first item"
}Percentage Bar
{
"type": "progress",
"value": "${(completed / total) * 100}",
"label": "${completed} of ${total} completed"
}Conditional Styling
{
"type": "card",
"className": "${
isPriority ? 'border-red-500 border-2' :
isCompleted ? 'opacity-50' :
'border-gray-200'
}"
}Form Expressions
Dependent Fields
{
"type": "form",
"body": [
{
"type": "select",
"name": "country",
"label": "Country",
"options": ["USA", "Canada", "Mexico"]
},
{
"type": "select",
"name": "state",
"label": "State/Province",
"visibleOn": "${form.country === 'USA'}",
"options": ["CA", "NY", "TX"]
}
]
}Dynamic Validation
{
"type": "input",
"name": "email",
"label": "Email",
"required": true,
"validations": {
"isEmail": true,
"errorMessage": "Please enter a valid email"
}
}Computed Fields
{
"type": "input",
"name": "total",
"label": "Total",
"value": "${form.price * form.quantity}",
"disabled": true
}Performance Considerations
Expensive Computations
Expressions are re-evaluated when data changes. Avoid expensive operations:
// ❌ Bad: Complex computation in expression
{
"type": "text",
"value": "${users.map(u => expensiveOperation(u)).join(', ')}"
}
// ✅ Good: Pre-compute in dataconst data = {
processedUsers: users.map(u => expensiveOperation(u))
}Caching
The expression engine automatically caches results when data doesn't change.
Security
Sandboxed Execution
Expressions run in a sandboxed environment and can only access:
- The data context you provide
- Built-in JavaScript functions (Math, Date, String, Array methods)
They cannot access:
- Browser APIs (window, document, localStorage)
- Node.js APIs (fs, path, etc.)
- Global variables
- Function constructors
Sanitization
All expression outputs are automatically sanitized to prevent XSS attacks.
Debugging Expressions
Expression Errors
Invalid expressions show helpful error messages:
{
"type": "text",
"value": "${user.invalidProperty}"
}Error: "Cannot read property 'invalidProperty' of undefined"
Debug Mode
Enable debug mode to see expression evaluation:
<SchemaRenderer
schema={schema}
data={data}
debug={true}
/>This logs all expression evaluations to the console.
Advanced Usage
Custom Functions
Extend the expression context with custom functions:
import { getExpressionEvaluator } from '@object-ui/core'
const evaluator = getExpressionEvaluator()
evaluator.registerFunction('formatCurrency', (value: number) => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(value)
})Use in schemas:
{
"type": "text",
"value": "${formatCurrency(price)}"
}Custom Operators
Register custom operators for domain-specific logic:
evaluator.registerOperator('contains', (array, item) => {
return array.includes(item)
}){
"type": "button",
"visibleOn": "${user.permissions contains 'admin'}"
}Best Practices
1. Keep Expressions Simple
// ❌ Bad: Too complex
{
"value": "${users.filter(u => u.age > 18).map(u => ({...u, isAdult: true})).reduce((acc, u) => acc + u.score, 0)}"
}
// ✅ Good: Pre-compute complex logic
{
"value": "${adultUsersScore}"
}2. Use Meaningful Variable Names
// ❌ Bad
{
"visibleOn": "${x && y || z}"
}
// ✅ Good
{
"visibleOn": "${isAdmin && isActive || isSuperUser}"
}3. Handle Null/Undefined
// ❌ Bad: Might throw error
{
"value": "${user.address.city}"
}
// ✅ Good: Safe access
{
"value": "${user.address?.city || 'N/A'}"
}4. Use TypeScript
Define your data types:
interface UserData {
user: {
name: string
role: 'admin' | 'user'
isActive: boolean
}
}
const data: UserData = { /* ... */ }
<SchemaRenderer schema={schema} data={data} />Next Steps
- Schema Rendering - Learn the rendering engine
- Component Registry - Understand components
- Protocol Overview - Explore schema specifications
Related Documentation
- Core API - Expression evaluator API
- Form Protocol - Form-specific expressions
- View Protocol - Data view expressions