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

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 delegates to Node::EnsureNodeExists() before dereferencing the internal detail::node pointer. This inline method in 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, which carries the specific key that caused the failure:

// 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, the subscript operator creates the zombie when get() returns nullptr:

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

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, 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) 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. 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. 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →