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

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 static setLogger() function. For example,

auto logger = std::make_shared<MyLogger>();
internal::Logger::setLogger(logger);
{ /* do magic */ }
logger->doSomethingWithCollectedState();

✅ Supports
To log to the current config_utilities logger, 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