How to Parse YAML to C++ Objects Using the yaml-cpp Node API

The yaml-cpp library represents YAML documents as a hierarchy of YAML::Node objects that you load from files or strings and convert to native C++ types using template helpers like as<T>() and operator[].

The yaml-cpp library (available at jbeder/yaml-cpp) provides a high-level Node API that transforms YAML parsing into a declarative process. Instead of writing event handlers, you load documents into a tree of YAML::Node instances and navigate them using container-like syntax. This approach lets you parse YAML to C++ objects with minimal boilerplate while maintaining type safety through template-based conversion specializations.

Loading YAML Documents into Node Objects

The entry points for parsing are the free functions declared in [include/yaml-cpp/node/parse.h](https://github.com/jbeder/yaml-cpp/blob/master/include/yaml-cpp/node/parse.h). These functions instantiate a YAML::Parser (defined in [include/yaml-cpp/parser.h](https://github.com/jbeder/yaml-cpp/blob/master/include/yaml-cpp/parser.h)) to tokenize the input and build an in-memory node tree.

  • YAML::LoadFile(const std::string& filename) reads a file from disk and returns the root node.
  • YAML::Load(const std::string& input) parses a YAML string directly.
  • YAML::LoadAll returns a std::vector<YAML::Node> for multi-document streams.
#include <yaml-cpp/yaml.h>

// Parse a file
YAML::Node config = YAML::LoadFile("settings.yaml");

// Parse a string literal
YAML::Node data = YAML::Load("{host: localhost, port: 8080}");

Internally, these functions construct a YAML::Parser object that emits parsing events and constructs the node hierarchy stored in detail::node (see [include/yaml-cpp/node/impl.h](https://github.com/jbeder/yaml-cpp/blob/master/include/yaml-cpp/node/impl.h)).

The YAML::Node class (defined in [include/yaml-cpp/node/node.h](https://github.com/jbeder/yaml-cpp/blob/master/include/yaml-cpp/node/node.h)) behaves like a lightweight smart pointer to an internal representation. It provides familiar container semantics for traversing scalars, sequences, and maps.

Scalar Access

Use Scalar() to retrieve the raw string representation, or as<T>() to convert to a specific C++ type:

std::string name = config["user"].as<std::string>();
int id = config["id"].as<int>();

Sequence Access

Access sequences by index using operator[] or iterate with range-based for loops:

// Index-based access
int first = config["items"][0].as<int>();

// Iteration
for (const auto& element : config["items"]) {
    std::cout << element.as<std::string>() << '\n';
}

// Container operations
config["items"].push_back("new_item");
size_t count = config["items"].size();

Map Access

Treat nodes as associative arrays using operator[] and check for key existence with contains():

std::string value = config["database"]["host"].as<std::string>();

// Insert or overwrite
config["database"]["ssl"] = true;

// Check existence before access
if (config["optional_key"].IsDefined()) {
    // ...
}

Converting Nodes to C++ Types with convert<T>

The magic behind Node::as<T>() resides in the template specializations of convert<T> declared in [include/yaml-cpp/node/convert.h](https://github.com/jbeder/yaml-cpp/blob/master/include/yaml-cpp/node/convert.h). These specializations handle standard library containers, arithmetic types, strings, booleans, and std::pair.

For example, converting a YAML sequence to std::vector<int> works because of this specialization:

template <typename T, typename A>
struct convert<std::vector<T, A>> {
    static Node encode(const std::vector<T, A>& rhs) { /* ... */ }
    static bool decode(const Node& node, std::vector<T, A>& rhs) {
        if (!node.IsSequence()) return false;
        rhs.clear();
        for (const auto& element : node)
            rhs.push_back(element.as<T>());
        return true;
    }
};

This mechanism enables direct conversion to complex types:

std::vector<int> primes = root["primes"].as<std::vector<int>>();
std::map<std::string, std::string> env = root["environment"].as<std::map<std::string, std::string>>();

Handling Conversion Errors and Type Checking

Before converting, verify node types using IsScalar(), IsSequence(), or IsMap(). If as<T>() encounters a mismatch, it throws YAML::BadConversion. You can also use the fallback overload as<T>(default_value) to provide a default instead of catching exceptions.

// Safe conversion with fallback
int timeout = config["timeout"].as<int>(30); // Uses 30 if key missing or not an int

// Explicit type checking
if (config["data"].IsSequence()) {
    auto list = config["data"].as<std::vector<std::string>>();
}

Complete Example: Parsing a Configuration File

The following example demonstrates loading a YAML file, accessing nested structures, converting to standard containers, and emitting modifications back to YAML:

#include <yaml-cpp/yaml.h>
#include <iostream>
#include <vector>
#include <map>

int main() {
    // 1. Load YAML from file
    YAML::Node config = YAML::LoadFile("example.yaml");

    // 2. Access scalar values
    std::string title = config["title"].as<std::string>();
    int version       = config["version"].as<int>();

    // 3. Convert sequences to std::vector
    std::vector<int> primes = config["primes"].as<std::vector<int>>();
    std::cout << "First prime: " << primes.front() << '\n';

    // 4. Convert maps to std::map
    std::map<std::string, std::string> settings =
        config["settings"].as<std::map<std::string, std::string>>();
    for (const auto& kv : settings)
        std::cout << kv.first << " = " << kv.second << '\n';

    // 5. Navigate nested structures
    double threshold = config["filters"]["threshold"].as<double>();
    std::cout << "Threshold: " << threshold << '\n';

    // 6. Safe access with fallback
    int retries = config["retries"].as<int>(3);

    // 7. Modify and emit back to YAML
    config["new_key"] = "added_value";
    std::cout << YAML::Dump(config) << '\n';
}

Summary

  • Load documents using YAML::LoadFile or YAML::Load from include/yaml-cpp/node/parse.h, which internally utilizes the YAML::Parser engine.
  • Navigate hierarchies with operator[] for both sequences and maps, using size(), push_back(), and contains() for container operations.
  • Convert to C++ types via Node::as<T>(), powered by convert<T> specializations in include/yaml-cpp/node/convert.h that support standard containers and primitives.
  • Handle errors by checking IsScalar(), IsSequence(), or IsMap() before conversion, or catch YAML::BadConversion exceptions; use as<T>(fallback) for safe defaults.

Frequently Asked Questions

What is the difference between YAML::Load and YAML::LoadFile?

YAML::Load accepts a std::string containing YAML text and parses it immediately into a YAML::Node, while YAML::LoadFile opens the specified file path and streams its contents through the parser. Both functions return a root node and use the same underlying parser implementation in include/yaml-cpp/parser.h, but LoadFile handles the file stream management automatically.

How do I check if a key exists before accessing it?

Use the IsDefined() method or the contains(key) function on the parent node. IsDefined() returns false for uninitialized nodes (such as those returned by operator[] when the key is missing), while contains() explicitly checks for key existence in map nodes.

Can I parse YAML into custom C++ structs?

Yes, by specializing YAML::convert<YourStruct> in the YAML namespace. You must implement a static decode(const Node& node, YourStruct& rhs) method that extracts values from the node and populates your struct, returning true on success. Once defined, you can call root["key"].as<YourStruct>() just like with built-in types.

What exception does yaml-cpp throw for type mismatches?

The library throws YAML::BadConversion (derived from YAML::Exception) when as<T>() cannot convert the node's content to the requested type. The exception carries information about the line and column where the conversion failed, allowing you to diagnose schema errors in the input YAML.

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 →