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()— Returnsfalseif the node is a zombie or was never materialized. Use this immediately after map lookups to verify existence.- Type queries —
Type()returns theNodeTypeenum (Null, Map, Sequence, Scalar). Helper methodsIsMap(),IsSequence(), andIsScalar()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:
- Check existence with
IsDefined()after any lookup that might miss. - Verify type using
IsMap(),IsSequence(), orIsScalar()before container-specific operations. - Wrap in exception handling to catch
InvalidNodeorBadConversionfor 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()ininclude/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 ininclude/yaml-cpp/exceptions.h) only when dereferenced. - Use
IsDefined()to check for existence andIsMap()/IsSequence()/IsScalar()to verify types before accessing data. - Wrap YAML processing in
try/catchblocks catchingYAML::Exceptionto 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →