# How to Validate YAML Node Structure Before Access in yaml-cpp

> Learn how to validate YAML node structure before access in yaml-cpp to prevent errors. Discover how EnsureNodeExists() protects your application from InvalidNode exceptions.

- Repository: [Jesse Beder/yaml-cpp](https://github.com/jbeder/yaml-cpp)
- Tags: how-to-guide
- Published: 2026-07-11

---

**yaml-cpp validates every node access through an internal `EnsureNodeExists()` guard that throws `InvalidNode` when you attempt to read an undefined or type-mismatched node.**

When parsing configuration files with jbeder/yaml-cpp, attempting to access a missing key or treating a scalar as a map causes immediate runtime exceptions. Validating YAML node structure before access prevents crashes by checking existence and type compatibility through built-in inspector methods.

## The Internal Guard: EnsureNodeExists()

Every public accessor in [`include/yaml-cpp/node/node.h`](https://github.com/jbeder/yaml-cpp/blob/main/include/yaml-cpp/node/node.h) delegates to `Node::EnsureNodeExists()` before dereferencing the internal `detail::node` pointer. This inline method in [`include/yaml-cpp/node/impl.h`](https://github.com/jbeder/yaml-cpp/blob/main/include/yaml-cpp/node/impl.h) performs two critical validations:

First, it verifies the node has not been **invalidated**—a state that occurs when a lookup returns a "zombie" node. Second, it ensures the underlying pointer exists, lazily initializing a null node if the object was default-constructed.

If either check fails, the method throws `InvalidNode`, defined in [`include/yaml-cpp/exceptions.h`](https://github.com/jbeder/yaml-cpp/blob/main/include/yaml-cpp/exceptions.h), which carries the specific key that caused the failure:

```cpp
// include/yaml-cpp/node/impl.h
inline void Node::EnsureNodeExists() const {
  if (!m_isValid)
    throw InvalidNode(m_invalidKey);          // Guard ①: zombie check
  if (!m_pNode) {                             // Guard ②: existence check
    m_pMemory.reset(new detail::memory_holder);
    m_pNode = &m_pMemory->create_node();
    m_pNode->set_null();
  }
}

```

## Zombie Nodes and Failed Lookups

When you index into a map using `operator[]` and the key does not exist, yaml-cpp does not throw immediately. Instead, it returns a **zombie node** marked as invalid. The actual exception is deferred until you attempt to read from that node.

As shown in [`include/yaml-cpp/node/impl.h`](https://github.com/jbeder/yaml-cpp/blob/main/include/yaml-cpp/node/impl.h), the subscript operator creates the zombie when `get()` returns nullptr:

```cpp
template <typename Key>
inline const Node Node::operator[](const Key& key) const {
  EnsureNodeExists();                          // Verify this node is valid
  detail::node* value =
      static_cast<const detail::node&>(*m_pNode).get(key, m_pMemory);
  if (!value) {
    return Node(ZombieNode, key_to_string(key)); // Create zombie on miss
  }
  return Node(*value, m_pMemory);
}

```

Attempting to call `as<T>()` or `Scalar()` on this zombie triggers `EnsureNodeExists()`, which throws `InvalidNode` with the missing key embedded in the message.

## Explicit Validation Methods

Before dereferencing, verify node state using these inspectors:

- **`IsDefined()`** — Returns `false` if the node is a zombie or was never materialized. Use this immediately after map lookups to verify existence.
- **Type queries** — `Type()` returns the `NodeType` enum (Null, Map, Sequence, Scalar). Helper methods `IsMap()`, `IsSequence()`, and `IsScalar()` provide boolean checks for specific containers.

These methods safely call `EnsureNodeExists()` internally, so they will never crash on invalid nodes.

## Safe Access Patterns

Follow this three-step pattern when validating YAML node structure before access:

1. **Check existence** with `IsDefined()` after any lookup that might miss.
2. **Verify type** using `IsMap()`, `IsSequence()`, or `IsScalar()` before container-specific operations.
3. **Wrap in exception handling** to catch `InvalidNode` or `BadConversion` for user-friendly error reporting.

```cpp
#include <yaml-cpp/yaml.h>
#include <iostream>

int main() {
    YAML::Node root = YAML::LoadFile("config.yaml");

    // Step 1: Validate existence
    if (!root["database"].IsDefined()) {
        std::cerr << "Error: missing 'database' section.\n";
        return 1;
    }

    const YAML::Node& db = root["database"];
    
    // Step 2: Validate type
    if (!db.IsMap()) {
        std::cerr << "Error: 'database' is not a map.\n";
        return 1;
    }

    // Step 3: Safe access with exception handling
    try {
        std::string host = db["host"].as<std::string>();
        int port = db["port"].as<int>();
        std::cout << "DB host: " << host << ", port: " << port << '\n';
    } catch (const YAML::InvalidNode& ex) {
        std::cerr << "Invalid node access: " << ex.msg << '\n';
    } catch (const YAML::BadConversion& ex) {
        std::cerr << "Type conversion error: " << ex.msg << '\n';
    }
}

```

## Exception Hierarchy and Error Messages

`InvalidNode` inherits from `RepresentationException`, which carries line and column marks when available. Catch it specifically when you need to distinguish between structural errors (accessing undefined nodes) and conversion errors (type mismatches during `as<T>()`).

```cpp
try {
    // Risky deep access
    auto value = root["level1"]["level2"]["level3"].as<std::string>();
} catch (const YAML::InvalidNode& ex) {
    // Specifically handles zombie node access
    std::cerr << "Missing key: " << ex.msg << '\n';
}

```

## Summary

- Every node access in yaml-cpp routes through `EnsureNodeExists()` in [`include/yaml-cpp/node/impl.h`](https://github.com/jbeder/yaml-cpp/blob/main/include/yaml-cpp/node/impl.h), which validates the node is not a zombie and the underlying pointer exists.
- Failed map lookups return **zombie nodes** that throw `InvalidNode` (defined in [`include/yaml-cpp/exceptions.h`](https://github.com/jbeder/yaml-cpp/blob/main/include/yaml-cpp/exceptions.h)) only when dereferenced.
- Use `IsDefined()` to check for existence and `IsMap()`/`IsSequence()`/`IsScalar()` to verify types before accessing data.
- Wrap YAML processing in `try/catch` blocks catching `YAML::Exception` to handle validation failures gracefully.

## Frequently Asked Questions

### What is the difference between IsDefined() and IsNull()?

`IsDefined()` returns `false` for zombie nodes created by failed lookups or invalidated nodes, while `IsNull()` returns `true` only for nodes that exist but contain a YAML null value. A node can be defined but null, or undefined (which implies it is not safe to access).

### When does yaml-cpp throw InvalidNode versus BadConversion?

`InvalidNode` is thrown when you attempt to access a node that does not exist (a zombie) or has been invalidated, typically via `EnsureNodeExists()` in [`include/yaml-cpp/node/impl.h`](https://github.com/jbeder/yaml-cpp/blob/main/include/yaml-cpp/node/impl.h). `BadConversion` is thrown by `as<T>()` when the node exists but cannot be converted to the requested C++ type (e.g., calling `as<int>()` on a string scalar).

### How do I check if a YAML node is a specific type before accessing it?

Call `node.IsMap()`, `node.IsSequence()`, or `node.IsScalar()` to verify the container type, or compare `node.Type()` against the `NodeType` enum values. These methods safely return false for undefined nodes without throwing exceptions.

### Can I disable the runtime validation in yaml-cpp for performance?

No, the `EnsureNodeExists()` guard is mandatory and embedded in every accessor in [`include/yaml-cpp/node/impl.h`](https://github.com/jbeder/yaml-cpp/blob/main/include/yaml-cpp/node/impl.h). While this adds a small branch overhead, it prevents undefined behavior from null pointer dereferences. The only way to avoid the check is to use the underlying `detail::node` API directly, which is not recommended as it breaks the library's safety guarantees.