For the complete documentation index, see llms.txt. This page is also available as Markdown.

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:

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

Syntax
Description
Example
Matches

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

Recursive Descent Patterns

from() with JSONPath

Since BoxLang 1.14.0. The from() method also accepts JSONPath-style path expressions.

🧭 Core Navigation Methods

from( key ):Navigator / from( [key1, key2, ...] ):Navigator

Navigate 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.

has( key ):boolean / has( [key1, key2, ...] ):boolean

Check 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

Check if the current segment is empty or has data.

🎯 Data Extraction Methods

get( key, [default] ):Object / get( [key1, key2, ...], [default] ):Object

Get 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.

getOrDefault( key, defaultValue ):Object / getOrDefault( [key1, key2, ...], defaultValue ):Object

Since 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.

getOrThrow( key ):Object / getOrThrow( [key1, key2, ...] ):Object

Get a value or throw a BoxRuntimeException if the key doesn't exist. Use for required configuration.

Typed Getter Methods

For explicit type conversion, use typed getters. Each method casts the value to the specified type.

Method
Return Type
Purpose

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 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

Get 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

Check if an exact key exists, treating dots and brackets as literal characters.

🎭 Conditional Processing

ifPresent( key, consumer ):Navigator

Execute a function if a key exists in the current segment. Returns the navigator for chaining.

ifPresentOrElse( key, consumer, orElseAction ):Navigator

Execute a function if key exists, otherwise execute an alternative action.

Practical Examples

Configuration Management

API Response Processing

Feature Flag Management

Database Configuration

Log Configuration Processing

Error Handling and Validation

Safe Navigation Patterns

Graceful Degradation

Advanced Patterns

Configuration Inheritance

Dynamic Configuration Validation

💡 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

Category
Method
Returns
Description

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.


Last updated

Was this helpful?