Data Navigators
Data Navigators provide a fluent, safe way to navigate and extract data from complex data structures in BoxLang
Data Navigators are a powerful BoxLang feature that provides a fluent, chainable interface for safely navigating and extracting data from complex data structures. Whether you're working with JSON files, API responses, configuration data, or nested structures, Data Navigators eliminate the need for verbose null checking and provide elegant error handling.
🎯 Why Use Data Navigators?
Data Navigators solve common problems when working with complex data:
✅ Safe Navigation - No more "key doesn't exist" errors when traversing nested structures
✅ Fluent Interface - Chainable methods that read like natural language
✅ Dynamic Typing - BoxLang automatically handles type conversions
✅ Multiple Data Sources - Works with JSON strings, files, structures, maps, and more
✅ Flexible Extraction - Get values with defaults, throw on missing data, or check existence
✅ Conditional Processing - Execute code only when values are present
✅ Immutable Navigation - Each navigation returns a new navigator, safe for concurrent use
Traditional vs Navigator Approach
// ❌ Traditional: Verbose null checking
if ( structKeyExists( config, "database" ) ) {
if ( structKeyExists( config.database, "connection" ) ) {
if ( structKeyExists( config.database.connection, "pool" ) ) {
maxSize = config.database.connection.pool.maxSize ?: 10;
} else {
maxSize = 10;
}
} else {
maxSize = 10;
}
} else {
maxSize = 10;
}
// ✅ With Navigator: Clean and safe
maxSize = dataNavigate( config ).get( ["database", "connection", "pool", "maxSize"], 10 );🚀 Getting Started
Creating a Data Navigator
Use the dataNavigate() BIF to create a navigator from various data sources:
Auto-Detection: The dataNavigate() BIF automatically detects the data source type - if it's a valid file path, it loads the JSON file; if it's a string with JSON syntax, it parses it; otherwise, it treats it as a structure.
Basic Navigation
Navigate through nested structures using the fluent interface:
Chaining: Every navigation method returns a navigator, so you can chain operations fluently: dataNavigate(data).from("a").from("b").get("c", default)
JSONPath-Style Path Expressions
Since BoxLang 1.14.0. JSONPath-style expression support is available in get(), getOrDefault(), has(), from(), and query().
In addition to variadic-key navigation, DataNavigator supports JSONPath-style expressions in get(), getOrDefault(), has(), from(), and query(). These expressions let you navigate nested data using a compact string syntax with dot notation, array indexing, recursive descent, wildcards, slices, and filters.
When a single string argument contains . or [, it is treated as a path expression. Plain keys without these characters and multi-argument calls use the original variadic-key behavior unchanged.
Expression Syntax Reference
Dot notation
Navigate nested object keys
boxlang.settings.hello
Value at { boxlang: { settings: { hello: ... } } }
Array index
Access a 1-based array element
keywords[1]
First element of keywords array
Recursive descent
Find the first matching key anywhere in the tree
..hello
Any hello key at any nesting depth
Recursive descent with filtering
..items[?(@.active)].name
All name values inside active items
Wildcard *
Match all struct values
boxlang.settings.*
All values in the settings struct
Match all array elements
keywords[*]
Every element in the keywords array
Slice [n:m]
1-based inclusive range of array elements
keywords[1:2]
First two elements
Open-ended slice
keywords[2:]
From index 2 to end
Filter [?()]
Match array elements by condition
items[?(@.active == true)]
All items where active is true
Filter with nested path
items[?(@.meta.priority > 5)]
Items with priority > 5
Filter with existence check
items[?(@.email)]
Items that have an email field
Combined filter + key
items[?(@.active == true)].name
Names of active items
Whitespace tolerance
Leading/trailing/separator-adjacent whitespace ignored
..hello, keywords [ * ]
Same as ..hello, keywords[*]
Basic Path Expressions
Wildcard Expressions
Array Slicing
Filter Expressions
Filters use @ to reference the current element. Supported operators: ==, !=, >, <, >=, <=.
Recursive Descent Patterns
from() with JSONPath
from() with JSONPathSince BoxLang 1.14.0. The from() method also accepts JSONPath-style path expressions.
🧭 Core Navigation Methods
from( key ):Navigator / from( [key1, key2, ...] ):Navigator
from( key ):Navigator / from( [key1, key2, ...] ):NavigatorNavigate to a specific segment in the data structure. Returns a new navigator scoped to that segment. Since BoxLang 1.14.0, from() also supports JSONPath-style path expressions.
Type Safety: The from() method requires the target to be a struct/map. If you navigate to a non-struct value (like a string or number), it throws a BoxRuntimeException.
has( key ):boolean / has( [key1, key2, ...] ):boolean
has( key ):boolean / has( [key1, key2, ...] ):booleanCheck if a key or nested path exists in the current segment. Since BoxLang 1.14.0, has() supports JSONPath-style path expressions including dot notation, array indexing, recursive descent, wildcards, slices, and filters.
isEmpty():boolean / isPresent():boolean
isEmpty():boolean / isPresent():booleanCheck if the current segment is empty or has data.
🎯 Data Extraction Methods
get( key, [default] ):Object / get( [key1, key2, ...], [default] ):Object
get( key, [default] ):Object / get( [key1, key2, ...], [default] ):ObjectGet a value from the data structure using nested keys. Returns the value or default if not found.
Since BoxLang 1.14.0, when called with a single string containing . or [, it is treated as a JSONPath-style path expression supporting dot notation, array indexing, recursive descent, wildcards, slices, and filters. Plain keys without these characters and multi-argument calls use the original variadic-key behavior unchanged.
Dynamic Typing: BoxLang's dynamic nature means get() automatically handles type conversions. Maps become Structs, Lists become Arrays. You usually don't need the typed getters unless you need explicit casting.
getOrDefault( key, defaultValue ):Object / getOrDefault( [key1, key2, ...], defaultValue ):Object
getOrDefault( key, defaultValue ):Object / getOrDefault( [key1, key2, ...], defaultValue ):ObjectSince BoxLang 1.14.0. Complements getOrThrow() for safe navigation with explicit fallbacks.
Get a value from the data structure or return the provided default if the key doesn't exist. Unlike get() which returns null when no default is supplied, getOrDefault() guarantees a non-null return value by requiring a default.
Why getOrDefault? Use get() when you want to check for null yourself, getOrThrow() when the value must exist, and getOrDefault() when you want a clean one-liner with a guaranteed fallback value.
getOrThrow( key ):Object / getOrThrow( [key1, key2, ...] ):Object
getOrThrow( key ):Object / getOrThrow( [key1, key2, ...] ):ObjectGet a value or throw a BoxRuntimeException if the key doesn't exist. Use for required configuration.
Fail Fast: Use getOrThrow() for critical configuration that must exist. This prevents runtime errors later and makes configuration requirements explicit.
Typed Getter Methods
For explicit type conversion, use typed getters. Each method casts the value to the specified type.
getAsString( key, [default] )
String
Cast to string
getAsString( [key1, key2, ...], [default] )
String
Cast to string (nested path)
getAsBoolean( key, [default] )
Boolean
Cast to boolean
getAsBoolean( [key1, key2, ...], [default] )
Boolean
Cast to boolean (nested path)
getAsInteger( key, [default] )
Integer
Cast to integer (32-bit)
getAsInteger( [key1, key2, ...], [default] )
Integer
Cast to integer (nested path)
getAsLong( key, [default] )
Long
Cast to long (64-bit)
getAsLong( [key1, key2, ...], [default] )
Long
Cast to long (nested path)
getAsDouble( key, [default] )
Double
Cast to double/decimal
getAsDouble( [key1, key2, ...], [default] )
Double
Cast to double/decimal (nested path)
getAsDate( key, [default] )
DateTime
Cast to DateTime object
getAsDate( [key1, key2, ...], [default] )
DateTime
Cast to DateTime (nested path)
getAsStruct( key, [default] )
IStruct
Cast to struct/map
getAsStruct( [key1, key2, ...], [default] )
IStruct
Cast to struct/map (nested path)
getAsArray( key, [default] )
Array
Cast to array
getAsArray( [key1, key2, ...], [default] )
Array
Cast to array (nested path)
getAsKey( key, [default] )
Key
Cast to Key object
getAsKey( [key1, key2, ...], [default] )
Key
Cast to Key object (nested path)
When to Use Typed Getters: Use typed getters when you need guaranteed type conversion (e.g., parsing string config values to numbers) or when working with APIs that expect specific types. Otherwise, get() is usually sufficient thanks to BoxLang's dynamic typing.
query( path ):Array
query( path ):ArrayQuery the data structure using a path expression and return all matching values as an Array. Unlike get(), which returns a single value, query() fans out at wildcards, slices, and filters to collect every match. Null values that are explicitly reachable are preserved in the result.
getByKey( key ):Object
getByKey( key ):ObjectGet a value by exact key lookup — dots and brackets are treated as literal characters, not path separators. Use this when your data contains keys like "value.sep".
hasByKey( key ):boolean
hasByKey( key ):booleanCheck if an exact key exists, treating dots and brackets as literal characters.
🎭 Conditional Processing
ifPresent( key, consumer ):Navigator
ifPresent( key, consumer ):NavigatorExecute a function if a key exists in the current segment. Returns the navigator for chaining.
ifPresentOrElse( key, consumer, orElseAction ):Navigator
ifPresentOrElse( key, consumer, orElseAction ):NavigatorExecute a function if key exists, otherwise execute an alternative action.
Fluent Conditionals: Both methods return the navigator, allowing you to chain multiple conditional operations together for clean, readable code.
Practical Examples
Configuration Management
API Response Processing
Feature Flag Management
Database Configuration
Log Configuration Processing
Error Handling and Validation
Safe Navigation Patterns
Graceful Degradation
💡 Best Practices
1. Use Meaningful Defaults
Always provide sensible defaults for optional configuration values.
2. Validate Critical Configuration
Use getOrThrow() for required configuration that must exist.
3. Leverage Dynamic Typing
BoxLang automatically handles type conversions with get() - use typed getters only when needed.
4. Check Sections Before Processing
Use isPresent() / isEmpty() to check if a section exists before working with it.
5. Use Conditional Methods for Optional Features
Leverage ifPresent() and ifPresentOrElse() for clean conditional logic.
6. Structure Navigation Logically
Navigate to sections once and reuse the navigator.
7. Log Configuration Decisions
Log when using fallbacks or defaults for troubleshooting.
8. Handle Multiple Data Sources
Use try/catch for graceful fallbacks when loading from multiple sources.
9. Validate Before Use
Combine navigators with validation for robust configuration loading.
10. Keep Navigation Chains Readable
Break long navigation chains into logical steps.
📋 Method Reference Summary
Navigation
from( key )
Navigator
Navigate to a single nested segment (supports JSONPath since 1.14.0)
from( [key1, key2, ...] )
Navigator
Navigate to a nested segment using an array path
has( key )
boolean
Check if a single key exists (supports JSONPath expressions since 1.14.0)
has( [key1, key2, ...] )
boolean
Check if a nested path exists (supports JSONPath expressions)
hasByKey( key )
boolean
Check if exact key exists — no path parsing (since 1.14.0)
isEmpty()
boolean
Check if segment is empty
isPresent()
boolean
Check if segment has data
Retrieval
get( key, [default] )
Object
Get single key value with optional default (supports JSONPath expressions since 1.14.0)
get( [key1, key2, ...], [default] )
Object
Get nested value with optional default
getOrDefault( key, defaultValue )
Object
Get single key value or return default — guaranteed non-null (since 1.14.0)
getOrDefault( [key1, key2, ...], defaultValue )
Object
Get nested value or return default (supports JSONPath since 1.14.0)
getOrThrow( key )
Object
Get single key value or throw exception
getOrThrow( [key1, key2, ...] )
Object
Get nested value or throw exception
getByKey( key )
Object
Get value by exact key lookup — no path parsing (since 1.14.0)
query( path )
Array
Get all matching values as array — JSONPath multi-result (since 1.14.0)
getAsString( key, [default] )
String
Get value as string
getAsString( [key1, key2, ...], [default] )
String
Get nested value as string
getAsBoolean( key, [default] )
Boolean
Get value as boolean
getAsBoolean( [key1, key2, ...], [default] )
Boolean
Get nested value as boolean
getAsInteger( key, [default] )
Integer
Get value as integer
getAsInteger( [key1, key2, ...], [default] )
Integer
Get nested value as integer
getAsLong( key, [default] )
Long
Get value as long
getAsLong( [key1, key2, ...], [default] )
Long
Get nested value as long
getAsDouble( key, [default] )
Double
Get value as double
getAsDouble( [key1, key2, ...], [default] )
Double
Get nested value as double
getAsDate( key, [default] )
DateTime
Get value as date
getAsDate( [key1, key2, ...], [default] )
DateTime
Get nested value as date
getAsStruct( key, [default] )
IStruct
Get value as struct
getAsStruct( [key1, key2, ...], [default] )
IStruct
Get nested value as struct
getAsArray( key, [default] )
Array
Get value as array
getAsArray( [key1, key2, ...], [default] )
Array
Get nested value as array
getAsKey( key, [default] )
Key
Get value as Key object
getAsKey( [key1, key2, ...], [default] )
Key
Get nested value as Key object
Conditional
ifPresent( key, consumer )
Navigator
Execute if key exists
ifPresentOrElse( key, consumer, orElse )
Navigator
Execute with fallback
🎓 Summary
Data Navigators offer a robust and fluent way to work with complex data structures in BoxLang. They eliminate common errors, provide excellent defaults, and make configuration management both safe and readable.
Key Benefits:
✅ Safe Navigation - No null pointer exceptions when traversing data ✅ Dynamic Typing - Automatic type conversion for most use cases ✅ Fluent API - Chainable methods that read naturally ✅ Flexible Sources - JSON files, strings, structures, and maps ✅ Error Handling - Graceful fallbacks (getOrDefault()) and strict access (getOrThrow()) ✅ Immutable - Thread-safe navigation operations ✅ Conditional Processing - Execute code only when data is present ✅ JSONPath Expressions - Compact string syntax: dot notation, array indexing, recursive descent (..), wildcards (*), slices ([1:3]), and filters ([?(@.active)]) ✅ Exact-Key Access - getByKey() / hasByKey() for keys containing dots or brackets ✅ Multi-Result Queries - query() returns all matching values as an array ✅ Whitespace Tolerant - Path expressions handle whitespace gracefully
Whether you're processing API responses, managing application configuration, or working with complex data structures, Data Navigators make your code more robust and maintainable.
Pro Tip: Start using Data Navigators for all configuration loading and API response processing. The safe navigation and built-in defaults will prevent entire classes of runtime errors.
🔗 Related Documentation
Structures - Working with BoxLang structs
JSON - JSON parsing and serialization
Null and Nothingness - Understanding null values
Attempts - Another pattern for safe value handling
Operators - Elvis operator and safe navigation
Last updated
Was this helpful?
