Parsing configs from data sources
January 28, 2026 · View on GitHub
This tutorial explains how add custom extensions of the functionalities provided in config_utilities.
Contents:
- Adding custom types
- Adding custom conversions
- Adding custom checks
- Adding custom loggers
- Adding custom formatters
- Adding custom parsers
Adding custom types
For a type to be supported as a config_utilities param, it only needs to be serializable to/from yaml as explained in their tutorial.
In short, you can specialize the template struct YAML::convert for your type. An example implementation of this is given in types/eigen_matrix.h.
namespace YAML {
template<>
struct convert<MyType> {
// Implement these two functions.
static Node encode(const MyType& rhs);
static bool decode(const Node& node, MyType& rhs);
};
} // namespace YAML
Adding custom conversions
To implement custom field conversions, you can create a conversion struct. The struct must implement two static conversion functions toIntermediate and fromIntermediate, where intermediate is a yaml-castable type. Examples of this are given in types/conversions.h.
Conversions can also optionally implement a getFieldInputInfo() function to return additional field info constraints when getting config infos. If this function is not implemented, no additional field info will be issued.
struct MyConversion {
// If conversion fails, set 'error' to the failure message.
static IntermediateType toIntermediate(MyType value, std::string& error);
static void fromIntermediate(const IntermediateType& intermediate, MyType& value, std::string& error);
// Optionally provide more field info. Must have exactly this signature.
static config::internal::FieldInputInfo::Ptr getFieldInputInfo();
};
The conversion can now be called on any field definitions by specifying the converter template:
void declare_config(Config& config) {
// 'field' will now be read as the intermediate type and will be converted to the underlying config type
field<MyConversion>(config.field, "field");
}
Adding custom checks
To implement custom checks, inherit from CheckBase and implement all virtual functions. Several examples of this are gievn in checks.h.
class MyCheck : public CheckBase {
public:
// Constructor takes the arguments of the check call as input to execute the check.
Check(Arguments... to_check);
// Return true if the check passed, false if it failed.
bool valid() const override;
// The error message for failed checks.
std::string message() const override;
// The name of the param (input) for which the check was executed.
std::string name() const override;
// Create a copy of this check instance.
std::unique_ptr<CheckBase> clone() const override ;
};
Custom checks can be called using the templated check function:
void declare_config(Config& config) {
// The arguments are the input to the check constructor.
check<MyCheck>(config.arguments_to_check);
}
Adding custom loggers
To implement custom loggers, inherit from Logger and implement all desired virtual functions. Examples of this are given in logging/.
class MyLogger : public Logger {
public:
// Implement this function to do the logging work.
void logImpl(const Severity severity, const std::string& message) override;
// Factory registration to allow setting of formatters via Settings::setLogger().
inline static const auto registration_ = Registration<Logger, MyLogger>("my_logger");
// Optionally use a static registration struct to set your logger automatically if included.
inline static const struct Initializer {
Initializer() { Logger::setLogger(std::make_shared<MyLogger>()); }
} initializer_;
};
✅ Supports
Loggers can be set using the base-logger's staticsetLogger()function. For example,auto logger = std::make_shared<MyLogger>(); internal::Logger::setLogger(logger); { /* do magic */ } logger->doSomethingWithCollectedState();
✅ Supports
To log to the currentconfig_utilitieslogger, you can use:internal::Logger::log(severity, my_message);
Adding custom formatters
Formatters work exactly like the loggers above. To implement your custom formatter, inherit from Formatter and implement the virtual functions. An example is given in formatters/asl.h.
Adding custom parsers
config_utilities uses yaml-cpp as internal data representation. To parse configs from other sources of data, convert them to yaml compatible data structures.
Adding custom substitutions
To define a custom substitution, you need to first implement a parser (see here for examples).
This may look like
#include <config_utilities/substitutions.h>
struct CustomSubstitution : public Substitution {
inline static const std::string NAME = "custom";
std::string process(const ParserContext& context, const std::string& contents) const override {
if (contents == "what is the meaning of life?") {
return "42";
}
if (contents == "invalid") {
context.error();
}
return contents;
}
};
// ideally this lives in a source file
static const auto custom_reg = RegisteredSubstitutions::Registration<CustomSubstitution>();
that would result in the following substitutions
test: $<custom | what is the meaning of life>
# maps to
test: 42
test: $<custom | something else>
# maps to
test: something else
test: $<custom | invalid> # will error out in strict parsing mode