> For the complete documentation index, see [llms.txt](https://boxlang.ortusbooks.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://boxlang.ortusbooks.com/boxlang-language/syntax/data-navigators.md).

# Data Navigators

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

```js
// ❌ 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:

```mermaid
graph LR
    A[Data Sources] --> B[dataNavigate]
    B --> C[Navigator]

    D[Struct/Map] --> B
    E[JSON String] --> B
    F[JSON File Path] --> B
    G[Java Map] --> B

    style B fill:#e1f5ff
    style C fill:#e1ffe1
```

```js
// From a structure
config = {
    "app": {
        "name": "MyApp",
        "version": "1.0.0",
        "database": {
            "host": "localhost",
            "port": 5432,
            "ssl": true
        }
    }
};
nav = dataNavigate( config );

// From a JSON string
jsonData = '{"users": [{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}]}';
nav = dataNavigate( jsonData );

// From a JSON file
nav = dataNavigate( "/path/to/config.json" );

// From a Java Map
javaMap = createObject( "java", "java.util.HashMap" ).init();
javaMap.put( "key", "value" );
nav = dataNavigate( javaMap );
```

{% hint style="info" %}
**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.
{% endhint %}

### Basic Navigation

Navigate through nested structures using the fluent interface:

```mermaid
graph TD
    A[Create Navigator] --> B[from: Navigate to Section]
    B --> C{Data Exists?}
    C -->|Yes| D[Get Values]
    C -->|No| E[Empty Navigator]
    D --> F[has: Check Keys]
    D --> G[get: Retrieve with Default]
    E --> G
    G --> H[Continue Chain]

    style A fill:#e1f5ff
    style B fill:#fff4e1
    style C fill:#ffe1e1
    style D fill:#e1ffe1
    style E fill:#ffcccc
```

```js
// Setup test data
appConfig = {
    "application": {
        "name": "BoxLang App",
        "version": "2.0.0",
        "features": {
            "caching": true,
            "logging": {
                "level": "INFO",
                "appenders": [ "console", "file" ]
            }
        }
    }
};

nav = dataNavigate( appConfig );

// Navigate to a specific section
loggingNav = nav.from( ["application", "features", "logging"] );

// Get values with defaults
logLevel = loggingNav.get( "level", "DEBUG" );
println( "Log Level: #logLevel#" ); // "INFO"

// Check if values exist
if ( loggingNav.has( "appenders" ) ) {
    appenders = loggingNav.get( "appenders" );
    println( "Appenders: #appenders.toList()#" );
}
```

{% hint style="success" %}
**Chaining**: Every navigation method returns a navigator, so you can chain operations fluently: `dataNavigate(data).from("a").from("b").get("c", default)`
{% endhint %}

## JSONPath-Style Path Expressions

{% hint style="info" %}
**Since BoxLang 1.14.0**. JSONPath-style expression support is available in `get()`, `getOrDefault()`, `has()`, `from()`, and `query()`.
{% endhint %}

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

```js
nav = dataNavigate( config );

// Dot notation for nested structs
nav.get( "boxlang.settings.hello" );   // "luis"

// 1-based array indexing
nav.get( "keywords[1]" );              // "test"
nav.get( "keywords[2]" );              // "example"

// Recursive descent — finds first match anywhere in the tree
nav.get( "..hello" );                  // "luis"
nav.has( "..hello" );                  // true

// Whitespace tolerant — all equivalent
nav.get( "..hello" );                  // "luis"
nav.get( "   ..hello" );               // "luis"
nav.get( "..hello   " );               // "luis"
nav.get( "boxlang . settings . hello" ); // "luis"
```

### Wildcard Expressions

```js
nav = dataNavigate( config );

// Wildcard on struct values — returns all values in the struct
nav.get( "boxlang.settings.*" );       // [ "luis" ] — single match, still an array
nav.query( "boxlang.settings.*" );     // [ "luis" ]

// Wildcard on array — returns every element
nav.query( "keywords[*]" );            // [ "test", "example" ]

// Wildcard combined with deeper keys
nav.query( "modules[*].name" );        // all module name values
```

### Array Slicing

```js
nav = dataNavigate( {
    primes: [ 2, 3, 5, 7, 11, 13, 17, 19 ]
} );

// 1-based inclusive slice
nav.get( "primes[1:3]" );            // [ 2, 3, 5 ]
nav.query( "primes[1:3]" );          // [ 2, 3, 5 ]

// Single-element slice
nav.get( "primes[2:2]" );            // [ 3 ]

// Open-ended slice (from index to end)
nav.get( "primes[5:]" );             // [ 11, 13, 17, 19 ]

// Slice with JSONPath wildcard after
nav.query( "orders[*].items[1:2]" ); // first 2 items from every order
```

### Filter Expressions

{% hint style="success" %}
Filters use `@` to reference the current element. Supported operators: `==`, `!=`, `>`, `<`, `>=`, `<=`.
{% endhint %}

```js
nav = dataNavigate( {
    items: [
        { name: "alpha",   active: true,  priority: 1, tags: [ "a", "b" ] },
        { name: "beta",    active: false, priority: 3, tags: [ "c" ] },
        { name: "gamma",   active: true,  priority: 5, tags: [ "a", "d" ] },
        { name: "delta",   active: false, priority: 2, tags: [] }
    ]
} );

// Equality filter
nav.query( "items[?(@.active == true)]" );
// [ { name: "alpha", ... }, { name: "gamma", ... } ]

// Numeric comparison
nav.query( "items[?(@.priority > 2)]" );
// [ { name: "beta", ... }, { name: "gamma", ... } ]

// Combined conditions with AND
nav.query( "items[?(@.active == true && @.priority > 2)]" );
// [ { name: "gamma", ... } ]

// Combined conditions with OR
nav.query( "items[?(@.priority == 1 || @.priority == 5)]" );
// [ { name: "alpha", ... }, { name: "gamma", ... } ]

// Negation
nav.query( "items[?(!@.active)]" );
// [ { name: "beta", ... }, { name: "delta", ... } ]

// Existence check — matches if field exists and is truthy
nav.query( "items[?(@.tags)]" );
// items with non-empty tags array

// Filter + key extraction
nav.query( "items[?(@.active == true)].name" );
// [ "alpha", "gamma" ]

// Nested path in filter
nav.query( "items[?(@.meta.priority >= 3)].name" );

// get() always returns the first match
nav.get( "items[?(@.active == true)]" );
// { name: "alpha", active: true, priority: 1, ... }
```

### Recursive Descent Patterns

```js
nav = dataNavigate( {
    api: {
        v1: {
            endpoints: {
                users: { id: "users-v1", active: true },
                orders: { id: "orders-v1", active: false }
            }
        },
        v2: {
            endpoints: {
                users: { id: "users-v2", active: true },
                products: { id: "products-v2", active: true }
            }
        }
    }
} );

// Find first matching key anywhere in the tree
nav.get( "..users" );
// { id: "users-v1", active: true }  — first match (v1)

// Find ALL matching keys anywhere in the tree
nav.query( "..users" );
// [ { id: "users-v1", ... }, { id: "users-v2", ... } ]

// Recursive descent with filter — find all active endpoints
nav.query( "..active" );
// [ true, false, true, true ]

// Find all endpoints that are active
nav.query( "..endpoints[?(@.active == true)]" );
// nested endpoint objects where active is true

// Deep search for a specific key
nav.has( "..products" );    // true
nav.get( "..products" );    // { id: "products-v2", active: true }
```

### `from()` with JSONPath

{% hint style="info" %}
**Since BoxLang 1.14.0**. The `from()` method also accepts JSONPath-style path expressions.
{% endhint %}

```js
nav = dataNavigate( {
    app: {
        modules: {
            auth: { enabled: true, version: "2.1" },
            cache: { enabled: false, version: "1.0" }
        }
    }
} );

// Navigate with a dot-path expression
authNav = nav.from( "app.modules.auth" );
authNav.get( "enabled" );   // true
authNav.get( "version" );   // "2.1"

// Equivalent to variadic keys
authNav = nav.from( ["app", "modules", "auth"] );

// JSONPath from() is scoped — further navigation is relative
cacheNav = nav.from( "app.modules.cache" );
cacheNav.get( "enabled" );  // false
```

## 🧭 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.

```js
// Single-level navigation
userNav = dataNavigate( userData ).from( "profile" );
dbNav = dataNavigate( config ).from( "database" );

// Multi-level navigation (variadic keys)
loggingNav = dataNavigate( config ).from( ["application", "features", "logging"] );

// JSONPath-style dot-path navigation (since 1.14.0)
loggingNav = dataNavigate( config ).from( "application.features.logging" );

// Both styles are equivalent and can be mixed
nav.from( "api.v1" ).from( ["endpoints", "users"] );

// Chained navigation (multiple calls)
deepNav = dataNavigate( complexData )
    .from( "api" )
    .from( "v1" )
    .from( "endpoints" )
    .from( "users" );

// Safe navigation - returns empty navigator if path doesn't exist
missingNav = dataNavigate( config ).from( ["nonexistent", "path"] );
println( missingNav.isEmpty() ); // true
```

{% hint style="warning" %}
**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`.
{% endhint %}

### `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.

```js
nav = dataNavigate( config );

// Check single key
hasDatabase = nav.has( "database" );        // true/false

// Check nested path (variadic)
hasSSLConfig = nav.has( ["database", "ssl", "enabled"] );

// JSONPath-style path expressions
nav.has( "boxlang.settings.hello" );            // true
nav.has( "..hello" );                           // true - recursive descent
nav.has( "keywords[*]" );                       // true - wildcard
nav.has( "keywords[1:2]" );                     // true - slice
nav.has( "items[?(@.active == true)]" );        // true - filter
nav.has( "items[?(@.active == true)].name" );   // true - filter + key
nav.has( "settings.nullable" );                 // true, even when value is null

// Use in conditionals
if ( nav.has( ["features", "caching"] ) ) {
    setupCaching();
}

// Combined with from()
cacheNav = nav.from( "cache" );
if ( cacheNav.has( ["redis", "host"] ) ) {
    connectToRedis( cacheNav.get( ["redis", "host"] ) );
}
```

### `isEmpty():boolean` / `isPresent():boolean`

Check if the current segment is empty or has data.

```js
nav = dataNavigate( config );

// Check current segment
userNav = nav.from( "users" );
if ( userNav.isPresent() ) {
    println( "Users section exists and has data" );
}

if ( userNav.isEmpty() ) {
    println( "Users section is empty or doesn't exist" );
    useDefaultUsers();
}

// Common pattern: Check before processing
cacheNav = nav.from( "cache" );
if ( cacheNav.isPresent() ) {
    configureCaching( cacheNav );
} else {
    disableCaching();
}
```

## 🎯 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.

```js
nav = dataNavigate( appConfig );

// Single key
appName = nav.get( "name" );                    // Raw value or null

// Nested keys (variadic path navigation)
dbHost = nav.get( ["database", "host"] );         // Traverse nested structure
sslEnabled = nav.get( ["database", "ssl", "enabled"] );

// JSONPath-style path expressions (single string with dots/brackets)
dotPath = nav.get( "boxlang.settings.hello" );  // "luis"
arrayItem = nav.get( "keywords[1]" );           // 1-based index
recursive = nav.get( "..hello" );               // Deep search, first match

// With default value
timeout = nav.get( ["application", "timeout"], 30 );
retries = nav.get( ["application", "retries"], 3 );
apiUrl = nav.get( ["api", "baseUrl"], "https://api.example.com" );

// Complex types work too (automatic casting)
dbConfig = nav.get( "database" );               // Returns struct
serverList = nav.get( "servers" );              // Returns array
```

{% hint style="success" %}
**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.
{% endhint %}

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

{% hint style="info" %}
**Since BoxLang 1.14.0**. Complements `getOrThrow()` for safe navigation with explicit fallbacks.
{% endhint %}

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.

```js
nav = dataNavigate( appConfig );

// Single key with default
timeout = nav.getOrDefault( "timeout", 30 );                          // 30 if missing
retries = nav.getOrDefault( "retries", 3 );                           // 3 if missing

// Nested paths with default (variadic keys)
dbHost = nav.getOrDefault( ["database", "host"], "localhost" );       // "localhost" if missing
maxPool = nav.getOrDefault( ["database", "pool", "max"], 10 );        // 10 if missing

// JSONPath-style path expressions also work
sslEnabled = nav.getOrDefault( "database.ssl.enabled", true );        // true if missing
firstUser  = nav.getOrDefault( "users[1].name", "Unknown" );          // "Unknown" if missing
```

{% hint style="success" %}
**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.
{% endhint %}

```js
// Comparison of retrieval styles
nav = dataNavigate( config );

// get() — returns null, you handle the check
port = nav.get( "port" );
if ( port == null ) { port = 8080; }

// getOrDefault() — one-liner, guaranteed non-null
port = nav.getOrDefault( "port", 8080 );

// getOrThrow() — fail fast for required config
port = nav.getOrThrow( "port" );
```

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

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

```js
nav = dataNavigate( config );

// Required configuration - throws if missing
apiKey = nav.getOrThrow( "api", "key" );
dbHost = nav.getOrThrow( "database", "host" );
secretToken = nav.getOrThrow( "security", "token" );

// Use in initialization where missing config should fail fast
function initializeApp( configPath ) {
    nav = dataNavigate( configPath );

    // These MUST exist or app won't start
    appName = nav.getOrThrow( "app", "name" );
    version = nav.getOrThrow( "app", "version" );
    environment = nav.getOrThrow( "app", "environment" );

    return { appName, version, environment };
}
```

{% hint style="danger" %}
**Fail Fast**: Use `getOrThrow()` for critical configuration that must exist. This prevents runtime errors later and makes configuration requirements explicit.
{% endhint %}

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

```js
nav = dataNavigate( serverConfig );

// Explicit type casting
port = nav.getAsInteger( "server", "port", 8080 );           // 8080 (integer)
timeout = nav.getAsLong( "server", "timeout", 5000 );        // 5000L (long)
loadFactor = nav.getAsDouble( "server", "loadFactor", 0.75 ); // 0.75 (double)
sslEnabled = nav.getAsBoolean( "server", "ssl", false );     // true/false
lastUpdated = nav.getAsDate( "server", "lastUpdated" );      // DateTime

// Complex types
dbConfig = nav.getAsStruct( "database" );                    // IStruct
serverList = nav.getAsArray( "servers" );                    // Array
keyName = nav.getAsKey( "keyField", "defaultKey" );          // Key object

// Useful when parsing configuration strings
maxConnections = nav.getAsInteger( "pool", "max", "10" );    // Parses "10" to 10
retryEnabled = nav.getAsBoolean( "retry", "enabled", "yes" ); // Parses "yes" to true
```

{% hint style="info" %}
**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.
{% endhint %}

### `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.

```js
nav = dataNavigate( config );

// Single match is wrapped in an array
nav.query( "name" );                     // [ "BoxLang Test Module" ]

// Recursive descent collects all matches
nav.query( "..hello" );                  // [ "luis" ]

// Wildcard — all struct values or array elements
nav.query( "keywords[*]" );              // [ "test", "example" ]
nav.query( "boxlang.settings.*" );       // [ "luis" ]

// Slice — 1-based inclusive range
nav.query( "keywords[1:2]" );            // [ "test", "example" ]

// Filter — only elements matching the condition
nav.query( "items[?(@.active == true)]" );
// Result:
// [
//   { "name": "alpha", "active": true },
//   { "name": "gamma", "active": true }
// ]

// Null values are preserved
nav.query( "settings.nullable" );        // [ null ]
```

### `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"`.

```js
nav = dataNavigate( {
    "value.sep": "root",
    "settings": { "value.sep": "nested" }
} );

nav.getByKey( "value.sep" );                    // "root"
nav.from( "settings" ).getByKey( "value.sep" ); // "nested"
nav.getByKey( "settings.value.sep" );           // null (exact key lookup only)

// Compare with get() which interprets dots as path navigation:
nav.get( "settings.value.sep" );                // "nested" (path navigation)
```

### `hasByKey( key ):boolean`

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

```js
nav = dataNavigate( {
    "value.sep": "root",
    "settings": { "value.sep": "nested" }
} );

nav.hasByKey( "value.sep" );                    // true
nav.from( "settings" ).hasByKey( "value.sep" ); // true
nav.hasByKey( "settings.value.sep" );           // false (exact key doesn't exist)
```

## 🎭 Conditional Processing

### `ifPresent( key, consumer ):Navigator`

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

```js
nav = dataNavigate( userProfile );

// Execute if key exists
nav.ifPresent( "email", ( email ) -> {
    println( "User email: #email#" );
    sendWelcomeEmail( email );
} );

// Process optional configuration
nav.ifPresent( "analytics", ( analyticsConfig ) -> {
    enableAnalytics( analyticsConfig );
    println( "Analytics enabled" );
} );

// Chain multiple conditional checks
nav
    .ifPresent( "email", ( email ) -> sendEmail( email ) )
    .ifPresent( "phone", ( phone ) -> sendSMS( phone ) )
    .ifPresent( "pushToken", ( token ) -> sendPush( token ) );
```

### `ifPresentOrElse( key, consumer, orElseAction ):Navigator`

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

```js
nav = dataNavigate( userProfile );

// Execute with fallback
nav.ifPresentOrElse(
    "phone",
    ( phone ) -> {
        println( "Calling: #phone#" );
        makePhoneCall( phone );
    },
    () -> {
        println( "No phone number available" );
        sendEmailInstead();
    }
);

// Configuration with fallback
nav.ifPresentOrElse(
    "cache",
    ( cacheConfig ) -> setupCaching( dataNavigate( cacheConfig ) ),
    () -> {
        println( "No cache configuration, using defaults" );
        setupDefaultCaching();
    }
);

// Notification strategy
nav.ifPresentOrElse(
    "preferredContact",
    ( method ) -> contactVia( method ),
    () -> useDefaultContactMethod()
);
```

{% hint style="success" %}
**Fluent Conditionals**: Both methods return the navigator, allowing you to chain multiple conditional operations together for clean, readable code.
{% endhint %}

### Practical Examples

#### Configuration Management

```javascript
// Load application configuration
function loadAppConfig( configPath ) {
    var nav = dataNavigate( configPath )

    return {
        "appName": nav.get( ["app", "name"], "Unknown App" ),
        "version": nav.get( ["app", "version"], "1.0.0" ),
        "debug": nav.get( ["app", "debug"], false ),
        "database": {
            "host": nav.get( ["database", "host"], "localhost" ),
            "port": nav.get( ["database", "port"], 5432 ),
            "ssl": nav.get( ["database", "ssl"], true )
        },
        "cache": {
            "enabled": nav.get( ["cache", "enabled"], true ),
            "provider": nav.get( ["cache", "provider"], "memory" ),
            "ttl": nav.get( ["cache", "ttl"], 3600 )
        }
    }
}

// Usage
appConfig = loadAppConfig( "/app/config.json" )
println( "Starting " & appConfig.appName & " v" & appConfig.version )
```

#### API Response Processing

```javascript
// Process API response data
function processUserData( apiResponse ) {
    var nav = dataNavigate( apiResponse )
    var users = [ ]

    // Check if response is successful
    if ( nav.getAsBoolean( "success", false ) ) {
        // Navigate to user data
        var userNav = nav.from( ["data", "users"] )

        if ( userNav.isPresent() ) {
            var userArray = userNav.getAsArray( "items", [ ] )

            for ( var userData in userArray ) {
                var userDataNav = dataNavigate( userData )

                users.append( {
                    "id": userDataNav.get( "id" ),
                    "name": userDataNav.get( ["profile", "fullName"], "Unknown" ),
                    "email": userDataNav.get( ["contact", "email"] ),
                    "active": userDataNav.get( ["status", "active"], false ),
                    "lastLogin": userDataNav.get( ["activity", "lastLogin"] )
                } )
            }
        }
    }

    return users
}

// Usage with API response
apiResponse = '
{
    "success": true,
    "data": {
        "users": {
            "items": [
                {
                    "id": "123",
                    "profile": {"fullName": "Alice Johnson"},
                    "contact": {"email": "alice@example.com"},
                    "status": {"active": true},
                    "activity": {"lastLogin": "2024-01-15T10:30:00Z"}
                }
            ]
        }
    }
}
'

users = processUserData( apiResponse )
```

#### Feature Flag Management

```javascript
// Feature flag configuration
function createFeatureManager( configData ) {
    var nav = dataNavigate( configData )

    return {
        "isEnabled": ( feature ) -> {
            return nav.get( ["features", feature, "enabled"], false )
        },

        "getConfig": ( feature ) -> {
            var featureNav = nav.from( ["features", feature] )
            if ( featureNav.isEmpty() ) {
                return { }
            }

            return {
                "enabled": featureNav.get( "enabled", false ),
                "rolloutPercent": featureNav.get( "rollout", 100 ),
                "environments": featureNav.get( "environments", [ ] ),
                "config": featureNav.get( "config", { } )
            }
        },

        "shouldShowForUser": ( feature, userID ) -> {
            var config = this.getConfig( feature )
            if ( !config.enabled ) return false

            // Simple hash-based rollout
            var userHash = hash( userID ).left( 2 ).parseInt( 16 )
            return ( userHash % 100 ) < config.rolloutPercent
        }
    }
}

// Usage
featureConfig = '
{
    "features": {
        "newDashboard": {
            "enabled": true,
            "rollout": 50,
            "environments": ["staging", "production"]
        },
        "betaFeature": {
            "enabled": false
        }
    }
}
'

features = createFeatureManager( featureConfig )

if ( features.isEnabled( "newDashboard" ) ) {
    println( "New dashboard is enabled" )
}
```

#### Database Configuration

```javascript
// Multi-environment database configuration
function getDatabaseConfig( environment = "development" ) {
    var nav = dataNavigate( "/config/database.json" )
    var envNav = nav.from( ["environments", environment] )

    // Fallback to default if environment not found
    if ( envNav.isEmpty() ) {
        envNav = nav.from( "environments", "default" )
    }

    var config = {
        "driver": envNav.get( "driver", "mysql" ),
        "host": envNav.get( "host", "localhost" ),
        "port": envNav.get( "port", 3306 ),
        "database": envNav.get( "database", "myapp" ),
        "username": envNav.get( "username", "root" ),
        "password": envNav.get( "password", "" ),
        "ssl": envNav.get( "ssl", false ),
        "pooling": {
            "enabled": envNav.get( ["pool", "enabled"], true ),
            "maxConnections": envNav.get( ["pool", "max"], 10 ),
            "timeout": envNav.get( ["pool", "timeout"], 30 )
        }
    }

    // Apply global settings
    var globalNav = nav.from( "global" )
    if ( globalNav.isPresent() ) {
        config.charset = globalNav.get( "charset", "utf8" )
        config.timezone = globalNav.get( "timezone", "UTC" )
    }

    return config
}

// Usage
dbConfig = getDatabaseConfig( "production" )
```

#### Log Configuration Processing

```javascript
// Parse logging configuration
function setupLogging( logConfigPath ) {
    var nav = dataNavigate( logConfigPath )
    var loggers = [ ]

    // Get global log level
    var globalLevel = nav.get( ["logging", "level"], "INFO" )

    // Process appenders
    var appendersNav = nav.from( ["logging", "appenders"] )
    if ( appendersNav.isPresent() ) {

        // Console appender
        appendersNav.ifPresent( "console", consoleConfig -> {
            var consoleNav = dataNavigate( consoleConfig )
            loggers.append( {
                "type": "console",
                "level": consoleNav.get( "level", globalLevel ),
                "pattern": consoleNav.get( "pattern", "%d{yyyy-MM-dd HH:mm:ss} - %m%n" ),
                "colored": consoleNav.get( "colored", true )
            } )
        } )

        // File appender
        appendersNav.ifPresent( "file", fileConfig -> {
            var fileNav = dataNavigate( fileConfig )
            loggers.append( {
                "type": "file",
                "level": fileNav.get( "level", globalLevel ),
                "path": fileNav.get( "path", "/logs/app.log" ),
                "maxSize": fileNav.get( "maxSize", "10MB" ),
                "maxFiles": fileNav.get( "maxFiles", 5 )
            } )
        } )
    }

    return loggers
}
```

### Error Handling and Validation

#### Safe Navigation Patterns

```javascript
// Safe navigation with validation
function getSecureConfig( configPath ) {
    try {
        var nav = dataNavigate( configPath )

        // Validate required sections exist
        if ( !nav.has( "security" ) ) {
            throw( type: "ConfigurationError", message: "Security configuration missing" )
        }

        var secNav = nav.from( "security" )

        return {
            "encryption": {
                "algorithm": secNav.getOrThrow( "encryption", "algorithm" ),
                "keyLength": secNav.get( ["encryption", "keyLength"], 256 ) // Keep as number
            },
            "authentication": {
                "provider": secNav.get( ["auth", "provider"], "local" ),
                "timeout": secNav.get( ["auth", "timeout"], 3600 ) // Keep as number
            }
        }

    } catch ( any e ) {
        writeLog( "Failed to load security config: " & e.message, "error" )
        return getDefaultSecurityConfig()
    }
}

function getDefaultSecurityConfig() {
    return {
        "encryption": {
            "algorithm": "AES",
            "keyLength": 256
        },
        "authentication": {
            "provider": "local",
            "timeout": 3600
        }
    }
}
```

#### Graceful Degradation

```javascript
// Load configuration with graceful fallbacks
function loadConfigWithFallbacks( primaryPath, fallbackPath ) {
    var config = { }

    // Try primary configuration
    try {
        var primaryNav = dataNavigate( primaryPath )
        config = extractConfig( primaryNav )
        println( "Loaded primary configuration from: " & primaryPath )
    } catch ( any e ) {
        writeLog( "Primary config failed: " & e.message, "warn" )

        // Try fallback configuration
        try {
            var fallbackNav = dataNavigate( fallbackPath )
            config = extractConfig( fallbackNav )
            println( "Loaded fallback configuration from: " & fallbackPath )
        } catch ( any e2 ) {
            writeLog( "Fallback config failed: " & e2.message, "error" )
            config = getHardcodedDefaults()
            println( "Using hardcoded default configuration" )
        }
    }

    return config
}

function extractConfig( nav ) {
    return {
        "app": nav.get( "name", "DefaultApp" ),
        "port": nav.get( ["server", "port"], 8080 ),
        "debug": nav.get( "debug", false )
    }
}
```

<details>

<summary>Advanced Patterns</summary>

#### Configuration Inheritance

```javascript
// Implement configuration inheritance
function buildInheritedConfig( environment ) {
    var nav = dataNavigate( "/config/app.json" )
    var config = { }

    // Start with base configuration
    var baseNav = nav.from( "base" )
    if ( baseNav.isPresent() ) {
        config = extractAllSettings( baseNav )
    }

    // Apply environment-specific overrides
    var envNav = nav.from( ["environments", environment] )
    if ( envNav.isPresent() ) {
        var envConfig = extractAllSettings( envNav )
        config = mergeConfigs( config, envConfig )
    }

    return config
}

function mergeConfigs( base, override ) {
    // Simple merge - in practice you'd want deep merging
    var merged = duplicate( base )
    for ( var key in override ) {
        merged[ key ] = override[ key ]
    }
    return merged
}
```

#### Dynamic Configuration Validation

```javascript
// Validate configuration structure
function validateConfig( configData ) {
    var nav = dataNavigate( configData )
    var errors = [ ]

    // Required fields
    var required = [
        [ "app", "name" ],
        [ "app", "version" ],
        [ "database", "host" ]
    ]

    for ( var path in required ) {
        if ( !nav.has( path ) ) {
            errors.append( "Missing required field: " & path.toList( "." ) )
        }
    }

    // Type validations
    nav.ifPresent( "server", serverConfig -> {
        var serverNav = dataNavigate( serverConfig )
        var port = serverNav.get( "port" )

        if ( port != null && ( !isNumeric( port ) || port <= 0 || port > 65535 ) ) {
            errors.append( "Invalid port number: " & port )
        }
    } )

    return {
        "valid": errors.isEmpty(),
        "errors": errors
    }
}
```

</details>

<details open>

<summary>💡 Best Practices</summary>

### 1. Use Meaningful Defaults

Always provide sensible defaults for optional configuration values.

```js
// ✅ Good: Provides complete default configuration
serverConfig = nav.get( "server", {
    "host": "localhost",
    "port": 8080,
    "ssl": false,
    "timeout": 30,
    "maxConnections": 100
} );

// ✅ Good: Individual defaults for each value
host = nav.get( ["server", "host"], "localhost" );
port = nav.get( ["server", "port"], 8080 );
ssl = nav.get( ["server", "ssl"], false );
```

### 2. Validate Critical Configuration

Use `getOrThrow()` for required configuration that must exist.

```js
// ✅ Good: Fail fast on missing critical config
apiKey = nav.getOrThrow( "api", "key" );
dbHost = nav.getOrThrow( "database", "host" );
secretToken = nav.getOrThrow( "security", "token" );

// ❌ Avoid: Silent failures on critical config
apiKey = nav.get( ["api", "key"] ); // Returns null, error happens later
```

### 3. Leverage Dynamic Typing

BoxLang automatically handles type conversions with `get()` - use typed getters only when needed.

```js
// ✅ Good: Simple get() for most cases (dynamic typing handles it)
enabled = nav.get( ["feature", "enabled"], false );      // boolean
maxRetries = nav.get( ["http", "maxRetries"], 3 );       // number
serverName = nav.get( ["server", "name"], "default" );   // string
dbConfig = nav.get( "database" );                       // struct

// ⚠️ Only use typed getters when you need explicit casting
port = nav.getAsInteger( "server", "port", "8080" );   // Parses string "8080" to 8080
```

### 4. Check Sections Before Processing

Use `isPresent()` / `isEmpty()` to check if a section exists before working with it.

```js
// ✅ Good: Check before processing
cacheNav = nav.from( "cache" );
if ( cacheNav.isPresent() ) {
    setupCaching( cacheNav );
} else {
    println( "No cache configuration, using defaults" );
    useDefaultCaching();
}

// ✅ Good: Use ifPresent for conditional setup
nav.ifPresent( "analytics", ( config ) -> {
    enableAnalytics( dataNavigate( config ) );
} );
```

### 5. Use Conditional Methods for Optional Features

Leverage `ifPresent()` and `ifPresentOrElse()` for clean conditional logic.

```js
// ✅ Good: Clean conditional processing
nav
    .ifPresent( "email", ( email ) -> sendEmail( email ) )
    .ifPresent( "sms", ( phone ) -> sendSMS( phone ) )
    .ifPresent( "push", ( token ) -> sendPush( token ) );

// ✅ Good: With fallback
nav.ifPresentOrElse(
    "customLogger",
    ( config ) -> setupCustomLogger( config ),
    () -> setupDefaultLogger()
);
```

### 6. Structure Navigation Logically

Navigate to sections once and reuse the navigator.

```js
// ✅ Good: Navigate once, reuse
dbNav = nav.from( ["database", "connection"] );
host = dbNav.get( "host", "localhost" );
port = dbNav.get( "port", 5432 );
maxPool = dbNav.get( ["pool", "max"], 10 );

// ❌ Avoid: Repeated navigation
host = nav.get( ["database", "connection", "host"], "localhost" );
port = nav.get( ["database", "connection", "port"], 5432 );
maxPool = nav.get( ["database", "connection", "pool", "max"], 10 );
```

### 7. Log Configuration Decisions

Log when using fallbacks or defaults for troubleshooting.

```js
// ✅ Good: Log configuration decisions
timeout = nav.get( "api", "timeout" );
if ( isNull( timeout ) ) {
    timeout = 30;
    println( "No API timeout configured, using default: 30s" );
}

// ✅ Good: Use ifPresentOrElse with logging
nav.ifPresentOrElse(
    "redis",
    ( config ) -> {
        println( "Using Redis configuration" );
        setupRedis( dataNavigate( config ) );
    },
    () -> {
        println( "No Redis config, using in-memory cache" );
        setupMemoryCache();
    }
);
```

### 8. Handle Multiple Data Sources

Use try/catch for graceful fallbacks when loading from multiple sources.

```js
// ✅ Good: Graceful fallback chain
function loadConfig() {
    try {
        return dataNavigate( "/etc/app/config.json" );
    } catch ( any e ) {
        try {
            return dataNavigate( "/app/config/default.json" );
        } catch ( any e2 ) {
            return dataNavigate( getHardcodedDefaults() );
        }
    }
}
```

### 9. Validate Before Use

Combine navigators with validation for robust configuration loading.

```js
// ✅ Good: Validate required fields
function loadValidatedConfig( configPath ) {
    nav = dataNavigate( configPath );

    // Validate required sections exist
    if ( !nav.has( ["app", "name"] ) ) {
        throw( type="ConfigError", message="Missing app.name" );
    }
    if ( !nav.has( ["database", "host"] ) ) {
        throw( type="ConfigError", message="Missing database.host" );
    }

    return nav;
}
```

### 10. Keep Navigation Chains Readable

Break long navigation chains into logical steps.

```js
// ✅ Good: Readable steps
appNav = nav.from( "application" );
featuresNav = appNav.from( "features" );
cacheConfig = featuresNav.get( "caching" );

// ❌ Avoid: Long, hard-to-read chains
cacheConfig = nav.from( "application" ).from( "features" ).get( "caching" );
```

</details>

***

## 📋 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.

{% hint style="success" %}
**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.
{% endhint %}

***

## 🔗 Related Documentation

* [Structures](https://github.com/ortus-boxlang/boxlang-docs/blob/v1.x/structures.md) - Working with BoxLang structs
* [JSON](https://github.com/ortus-boxlang/boxlang-docs/blob/v1.x/json.md) - JSON parsing and serialization
* [Null and Nothingness](https://github.com/ortus-boxlang/boxlang-docs/blob/v1.x/null-and-nothingness.md) - Understanding null values
* [Attempts](/boxlang-language/syntax/attempts.md) - Another pattern for safe value handling
* [Operators](https://github.com/ortus-boxlang/boxlang-docs/blob/v1.x/operators.md) - Elvis operator and safe navigation


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://boxlang.ortusbooks.com/boxlang-language/syntax/data-navigators.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
