Implementing Custom Type Conversion for User-Defined Types in yaml-cpp: A Complete Guide
You enable seamless serialization of custom C++ classes in yaml-cpp by specializing the YAML::convert<T> template with static encode() and decode() methods, allowing your types to work natively with Node::as<T>() and the stream operator.
The yaml-cpp library by jbeder represents every YAML document element as a YAML::Node, but converting between these nodes and your own C++ structs requires explicit type conversion logic. By implementing custom type conversion for user-defined types in yaml-cpp, you extend the library's template-based architecture to support domain-specific objects—from 3D vectors to configuration structs—without modifying the core parser.
How yaml-cpp Handles Type Conversion
According to the yaml-cpp source code in include/yaml-cpp/node/convert.h, the library declares a primary template YAML::convert<T> that defines the interface for all type conversions. This header provides built-in specializations for primitives, STL containers, and strings (lines 79–511), while user-defined types require you to add your own specialization in the YAML namespace.
Each specialization must implement two static members:
static Node encode(const T& rhs)– Constructs aYAML::Nodefrom your C++ object.static bool decode(const Node& node, T& rhs)– Populates your C++ object from aYAML::Node, returningtrueon success.
The decode implementation for bool in src/convert.cpp demonstrates the validation pattern used throughout the library, showing how scalar strings are mapped to Boolean values with appropriate error checking.
Step-by-Step Implementation
Defining Your Custom Type
First, create your user-defined type with any necessary comparison operators to facilitate testing.
struct Vec3 {
double x, y, z;
bool operator==(const Vec3& other) const {
return x == other.x && y == other.y && z == other.z;
}
};
Creating the YAML::convert Specialization
Place your specialization inside the YAML namespace to ensure argument-dependent lookup finds it. The specialization must live at global scope within this namespace.
namespace YAML {
template<>
struct convert<Vec3> {
// Encode: Convert Vec3 to YAML sequence [x, y, z]
static Node encode(const Vec3& rhs) {
Node node;
node.push_back(rhs.x);
node.push_back(rhs.y);
node.push_back(rhs.z);
return node;
}
// Decode: Extract from sequence, validating size and type
static bool decode(const Node& node, Vec3& rhs) {
if (!node.IsSequence() || node.size() != 3)
return false;
rhs.x = node[0].as<double>();
rhs.y = node[1].as<double>();
rhs.z = node[2].as<double>();
return true;
}
};
} // namespace YAML
Encoding Maps Instead of Sequences
For objects that map naturally to YAML mappings, use NodeType::Map as demonstrated in the official docs/Tutorial.md.
struct Person {
std::string name;
int age;
};
namespace YAML {
template<> struct convert<Person> {
static Node encode(const Person& p) {
Node n(NodeType::Map);
n["name"] = p.name;
n["age"] = p.age;
return n;
}
static bool decode(const Node& n, Person& p) {
if (!n.IsMap()) return false;
p.name = n["name"].as<std::string>();
p.age = n["age"].as<int>();
return true;
}
};
} // namespace YAML
Using Custom Types with the Node API
When you assign a value to a node (node["key"] = myObj;), the compiler instantiates YAML::convert<decltype(myObj)> and calls the encode method. The Node::as<T>() method defined in src/node.cpp forwards to your decode implementation when extracting values.
int main() {
// Encoding
Vec3 p{1.0, 2.0, 3.0};
YAML::Node doc;
doc["position"] = p; // encode called here
std::cout << doc << "\n"; // Emits: position: [1.0, 2.0, 3.0]
// Decoding
YAML::Node loaded = YAML::Load(doc);
Vec3 q = loaded["position"].as<Vec3>(); // decode called here
assert(p == q);
}
Working with Collections of Custom Types
The conversion system composes automatically with STL containers. When storing a std::vector<Person>, yaml-cpp applies your convert<Person> specialization to each element recursively.
std::vector<Person> team = { {"Alice", 30}, {"Bob", 25} };
YAML::Node root;
root["staff"] = team; // encode called for each Person
std::cout << root << "\n";
Summary
- Specialize
YAML::convert<T>in theYAMLnamespace to register your type with the conversion system defined ininclude/yaml-cpp/node/convert.h. - Implement
encode()to serialize your object into aYAML::Node(scalar, sequence, or map). - Implement
decode()to validate the node structure (checkingIsSequence(),IsMap(), or size) and populate your object, returningfalseon validation failure to trigger exceptions inNode::as<T>(). - The mechanism integrates automatically with
YAML::Loadand the emitter (operator<<), requiring no changes to the core parser orsrc/convert.cpp.
Frequently Asked Questions
Why must the specialization reside in the YAML namespace?
The library locates conversion specializations through argument-dependent lookup. By placing your template<> struct convert<YourType> inside namespace YAML, you ensure the compiler finds your specialization when resolving Node::as<YourType>() or assignment operations that internally call convert<T>::encode.
What happens if the decode function returns false?
When decode returns false, Node::as<T>() throws a YAML::TypedBadConversion<T> exception. You should validate node types (e.g., checking IsSequence() or IsMap()) and sizes before attempting extraction to ensure robust error handling, following the pattern used by convert<bool>::decode in src/convert.cpp.
Can I use custom types inside standard containers?
Yes. The built-in specializations for std::vector, std::map, and other STL containers in include/yaml-cpp/node/convert.h recursively call convert<T>::encode and convert<T>::decode for their element types. Once you define conversion for a custom type, it works automatically inside containers without additional code.
Where is the primary convert template defined?
The primary template declaration and all built-in specializations reside in include/yaml-cpp/node/convert.h (lines 79–511), while src/convert.cpp implements non-template helpers like the Boolean conversion logic. The docs/Tutorial.md file provides the official reference example for implementing user-defined conversions.
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 →