Context and Variables

Understanding how JEXL accesses data through context variables and manages scope

Context and Variables

JEXL expressions are evaluated against a context - a JavaScript object that provides the data and variables available to the expression. Understanding how context works is essential for writing effective JEXL expressions.

What is Context?

Context is the data environment in which a JEXL expression is evaluated. It's a JavaScript object that contains:

  • Variables and their values
  • Objects and their properties
  • Arrays and their elements
  • Functions (when applicable)

Basic Context Example

// Context object
const context = {
  name: "John Doe",
  age: 30,
  email: "john@example.com",
  scores: [85, 92, 78, 96],
  user: {
    profile: {
      displayName: "Johnny",
      preferences: {
        theme: "dark"
      }
    }
  }
};

// JEXL expressions using this context
name                           // "John Doe"
age > 25                       // true
scores | length                // 4
user.profile.displayName       // "Johnny"

Variable Access

Direct Variable Access

Variables in the context root are accessed directly by name:

// Context: { firstName: "John", lastName: "Doe", age: 30 }

firstName              // "John"
lastName               // "Doe"  
age                    // 30
firstName + " " + lastName  // "John Doe"

Case Sensitivity

Variable names are case-sensitive:

// Context: { Name: "John", name: "Jane" }

Name    // "John"
name    // "Jane" 
NAME    // undefined (would cause an error)

Special Variable Names

Variables with special characters require bracket notation:

// Context: { "user-id": 123, "first name": "John", "2023-data": [...] }

user-id          // Error - interpreted as subtraction
["user-id"]      // 123 - correct access
["first name"]   // "John"  
["2023-data"]    // array data

Property Access

Nested Object Access

Access nested properties using dot notation:

// Context
const context = {
  user: {
    personal: {
      name: "John",
      age: 30
    },
    work: {
      company: "Acme Corp",
      position: "Developer"
    }
  }
};

// Property access
user.personal.name           // "John"
user.work.company           // "Acme Corp"
user.personal.age > 25      // true

Dynamic Property Access

Use bracket notation for dynamic property names:

// Context
const context = {
  user: { name: "John", email: "john@example.com" },
  propertyName: "email",
  fieldMap: { userName: "name", userEmail: "email" }
};

// Dynamic access
user[propertyName]           // "john@example.com"
user[fieldMap.userEmail]    // "john@example.com"
user["name"]                // "John"

Array Context

Array Element Access

Access array elements by index:

// Context
const context = {
  scores: [85, 92, 78, 96],
  users: [
    { name: "Alice", age: 28 },
    { name: "Bob", age: 32 },
    { name: "Charlie", age: 24 }
  ]
};

// Array access
scores[0]                   // 85
scores[-1]                  // 96 (last element)
users[1].name              // "Bob"
users[0].age               // 28

Array as Root Context

When the context is an array itself:

// Context is directly an array: [1, 2, 3, 4, 5]

[0]                        // 1 (first element)
length                     // 5
sum                        // 15
filter("value > 3")        // [4, 5]

Context Scope

Global Context

The main context object provides the global scope:

// Global context
const context = {
  appName: "MyApp",
  version: "1.0.0",
  config: { debug: true },
  users: [...]
};

// All expressions can access global context
appName                     // "MyApp"
config.debug               // true
users | length             // Number of users

Transform Context

Within transforms, value refers to the current item being processed:

// Context: { numbers: [1, 2, 3, 4, 5] }

// In map transform, 'value' is each number
numbers | map("value * 2")              // [2, 4, 6, 8, 10]

// In filter transform, 'value' is each number being tested
numbers | filter("value > 3")           // [4, 5]

// Global context still accessible
numbers | map("value * multiplier")     // Uses global 'multiplier'

Nested Transform Context

In nested transforms, inner transforms can access outer context:

// Context: { users: [...], minAge: 21 }

users 
| filter("value.age >= minAge")         // Uses global minAge
| map("value.name | uppercase")         // Inner transform on name

Context Manipulation

Adding Computed Properties

You can reference computed values within expressions:

// Context: { firstName: "John", lastName: "Doe", scores: [85, 92, 78] }

// Computed full name
fullName = firstName + " " + lastName    // Can't assign in JEXL, but conceptually

// Use in expressions  
"Hello " + firstName + " " + lastName   // "Hello John Doe"

Context Merging

Merge objects to extend context:

// Base context
const baseContext = {
  name: "John",
  age: 30
};

// Additional data  
const additionalData = {
  email: "john@example.com",
  role: "admin"
};

// Merged context (done in JavaScript, not JEXL)
const fullContext = { ...baseContext, ...additionalData };

// Now JEXL can access all properties
name + " (" + role + ")"               // "John (admin)"

Context Best Practices

1. Structure Context Logically

// Good - organized structure
const context = {
  user: {
    personal: { name: "John", age: 30 },
    work: { company: "Acme", role: "Dev" },
    preferences: { theme: "dark" }
  },
  app: {
    version: "1.0.0",
    features: ["auth", "reporting"]
  }
};

// Clear access patterns
user.personal.name
app.version
user.preferences.theme

2. Use Consistent Naming

// Good - consistent camelCase
const context = {
  firstName: "John",
  lastName: "Doe", 
  emailAddress: "john@example.com",
  phoneNumber: "555-1234"
};

// Avoid mixed naming styles
const badContext = {
  first_name: "John",      // snake_case
  LastName: "Doe",         // PascalCase  
  "email-address": "...",  // kebab-case
  phoneNumber: "..."       // camelCase
};

3. Provide Safe Defaults

// Good - handle missing data
const context = {
  user: user || {},
  settings: settings || { theme: "light" },
  permissions: permissions || []
};

// JEXL expressions can safely access
user.name || "Anonymous"
settings.theme
permissions | length > 0

4. Avoid Deep Nesting

// Good - reasonable depth
const context = {
  user: {
    profile: { name: "John", email: "..." },
    settings: { theme: "dark" }
  }
};

// Harder to work with - too deep
const deepContext = {
  app: {
    modules: {
      user: {
        management: {
          profile: {
            personal: {
              details: {
                name: "John"  // Too deep!
              }
            }
          }
        }
      }
    }
  }
};

Advanced Context Patterns

Context with Functions

While JEXL Extended provides built-in functions, you can add custom functions to context:

// Context with custom functions (JavaScript setup)
const context = {
  data: [1, 2, 3],
  customMultiplier: 5,
  // Custom functions would be added at the JEXL level, not in context
};

// Use JEXL Extended's built-in functions instead
data | map("value * customMultiplier")

Dynamic Context Building

Build context dynamically based on conditions:

// JavaScript context preparation
const buildContext = (user, permissions) => {
  const context = {
    user: user || { name: "Guest" },
    isAuthenticated: !!user,
    permissions: permissions || []
  };
  
  // Add computed properties
  if (user) {
    context.displayName = user.firstName + " " + user.lastName;
    context.initials = (user.firstName[0] + user.lastName[0]).toUpperCase();
  }
  
  return context;
};

// JEXL expressions using dynamic context
isAuthenticated ? displayName : "Please log in"
permissions | contains("admin") ? "Full Access" : "Limited Access"

Context Validation

Validate context structure before evaluation:

// JavaScript validation
const validateContext = (context) => {
  const required = ['user', 'settings', 'data'];
  const missing = required.filter(key => !(key in context));
  
  if (missing.length > 0) {
    throw new Error(`Missing required context: ${missing.join(', ')}`);
  }
  
  return context;
};

// Safe JEXL evaluation with validated context
const safeContext = validateContext(rawContext);

Context in Different Scenarios

API Response Processing

// API response as context
const apiResponse = {
  status: "success",
  data: {
    users: [...],
    pagination: { page: 1, total: 100 }
  },
  meta: {
    timestamp: "2023-01-01T00:00:00Z",
    version: "v1"
  }
};

// JEXL expressions for API processing
status == "success" && data.users | length > 0
data.pagination.page * 10 <= data.pagination.total
meta.timestamp | dateTimeFormat("YYYY-MM-DD")

Form Validation Context

// Form data as context
const formContext = {
  firstName: "John",
  lastName: "Doe",
  email: "john@example.com",
  age: 25,
  agreedToTerms: true,
  preferences: {
    newsletter: true,
    notifications: false
  }
};

// Validation expressions
firstName | trim | length > 0
email | contains("@") && email | contains(".")
age >= 18
agreedToTerms == true

Configuration Context

// App configuration as context
const configContext = {
  environment: "production",
  features: {
    darkMode: true,
    analytics: true,
    debugging: false
  },
  limits: {
    maxUsers: 1000,
    maxFileSize: 10485760  // 10MB
  },
  endpoints: {
    api: "https://api.example.com",
    auth: "https://auth.example.com"
  }
};

// Configuration-based expressions
features.darkMode ? "dark-theme" : "light-theme"
environment == "development" && features.debugging
limits.maxFileSize / 1048576 + "MB"  // Convert to MB

Error Handling with Context

Safe Property Access

// Safe access patterns
user && user.profile && user.profile.name || "Unknown"
"email" in user ? user.email : "No email provided"
typeof user.age == "number" ? user.age : 0

Context Existence Checks

// Check if context properties exist
typeof users != "undefined" && users | length > 0
settings != null && "theme" in settings
data && Array.isArray(data) && data | length > 0

Default Context Values

// Provide defaults for missing context
name || "Anonymous"
settings.timeout || 5000
permissions || []
config.maxRetries || 3

Context Debugging

When debugging JEXL expressions, understanding the context is crucial:

Context Inspection

// JavaScript debugging
console.log('Context:', JSON.stringify(context, null, 2));

// Check specific paths
console.log('User exists:', 'user' in context);
console.log('User name:', context.user?.name);
console.log('Data type:', typeof context.data);

Expression Testing

// Test expressions with different context values
const testContexts = [
  { user: { name: "John" } },
  { user: {} },
  { user: null },
  {}
];

testContexts.forEach(ctx => {
  try {
    const result = jexl.evalSync('user.name || "Unknown"', ctx);
    console.log('Result:', result);
  } catch (error) {
    console.error('Error:', error.message);
  }
});

Understanding context and variables is fundamental to mastering JEXL. The context provides the data foundation for all expressions, and proper context design makes expressions more powerful and maintainable.

Next: Explore practical Usage Guides to see how context works in real applications.

Cookie Notice

We use cookies to enhance your browsing experience.