class mrpt::containers::yaml

Powerful YAML-like container for possibly-nested blocks of parameters or any arbitrary structured data contents, including documentation in the form of comments attached to each node.

Supports parsing from YAML or JSON streams, files, or text strings.

This class holds the root “node” in a YAML-like tree structure. Each tree node can be of one of these types:

  • Scalar values (“leaf nodes”): Can hold any type, stored as C++17 std::any.

  • Sequence container.

  • Map (“dictionary”): pairs of name: value.

  • Null, empty nodes: yaml ~ or null.

Sequences and dictionaries can hold, in turn, any of the four types above, leading to arbitrarialy-complex nested structures.

This class was designed as a lightweight, while structured, way to pass arbitrarialy-complex parameter blocks but can be used to load and save YAML files or as a database.

yaml can be used to parse YAML (v1.2) or JSON streams, and to emit YAML. It does not support event-based parsing. The parser uses Pantelis Antoniou’s awesome libfyaml, which passes the full YAML testsuite.

Known limitations:

  • Parsing comments is limited to right-hand comments for sequence or map entries.

See examples below (containers_yaml_example/test.cpp):

#include <mrpt/containers/yaml.h>
#include <mrpt/core/demangle.h>  // demangle() utility

#include <iostream>

void YamlTest_1()
{
    std::cout << "==== YamlTest_1 ====\n";

    // Load from file:
    // const auto p = mrpt::containers::yaml::FromFile("xxx.yaml");
    // or load from text block:
    // const auto p = mrpt::containers::yaml::FromText(txt);

    // or build a document programatically:
    mrpt::containers::yaml p;
    p["K"] = 2.0;
    p["N"] = 10;
    p["name"] = "Foo";
    p["enabled"] = true;
    p["books"] = mrpt::containers::yaml::Sequence();
    p["books"].push_back("The Hobbit");
    p["books"].push_back(10.0);

    // You can use {}-initializers as well:
    p["movies"] = mrpt::containers::yaml::Sequence(
        {mrpt::containers::yaml::Map({{"title", "xxx"}, {"year", 2001}}),
         mrpt::containers::yaml::Map({{"title", "yyy"}, {"year", 1986}})});

    std::cout << "K=" << p["K"] << " N=" << p["N"] << "\n";
    std::cout << "name=" << p["name"] << "\n";
    std::cout << "Movie year=" << p["movies"](1)["year"] << "\n";

    // Get a value, or default if not found.
    // YAMLCPP equivalent: p["bar"].as<std::string>("none")
    std::cout << "bar=" << p.getOrDefault<std::string>("bar", "none") << "\n";

    // Iterate a dictionary:
    for (const auto& kv : p.asMap())
    {
        const std::string key = kv.first.as<std::string>();
        const auto& valueNode = kv.second;
        std::cout << "`" << key << "`: " << mrpt::demangle(valueNode.typeName())
                  << "\n";
    }

    // Iterate a dictionary bis:
    mrpt::containers::yaml p2;
    p2["a"] = 1.0;
    p2["b"] = 10;
    p2["c"] = -1;

    for (const auto& kv : p2.asMap())
    {
        // This will raise an exception if stored type cannot be converted to
        // double:
        std::cout << "key: `" << kv.first << "` val: " << kv.second.as<double>()
                  << "\n";
    }

    // Iterate sequence:
    for (const auto& item : p["books"].asSequence())
    {
        std::cout << "sequence item type: " << mrpt::demangle(item.typeName())
                  << "\n";
        // Access value: kv.second.as<std::string>(), etc.
    }

    // Print:
    std::cout << "\n\nPrint as YAML:\n";
    p.printAsYAML();
}

void YamlTest_2()
{
    std::cout << "\n\n==== YamlTest_2 ====\n";

    // You can use {} to initialize mappings (dictionaries):
    using mrpt::containers::CommentPosition;
    using mrpt::containers::vcp;
    using mrpt::containers::vkcp;

    mrpt::containers::yaml p;

    // Insert a key in a map with a "comment" block.
    p << vkcp("L", 5.5, "Arm length [meters]")
      << vkcp("D", 1.0, "Distance [meters]") << vkcp("Y", -5, "Comment for Y");

    ASSERT_(p.isMap());
    ASSERT_(p.has("L"));
    ASSERT_(p["L"].isScalar());
    ASSERT_(p.keyHasComment("D"));

    // Add comment associated to value (not key):
    p["X"] = vcp(1.0, "Default value");

    std::cout << "D key comment: " << p.keyComment("D") << "\n";
    std::cout << "X value comment: " << p["X"].comment() << "\n";

    // Print:
    std::cout << "\n\nPrint as YAML:\n";
    p.printAsYAML(std::cout);
}

const auto sData = std::string(R"xxx(
myMap:
  K: 10.0
  P: -5.0
  Q: ~
  nestedMap:
    a: 1  # comment for a
    b: 2
    c: 3
)xxx");

void YamlTest_3()
{
    std::cout << "\n\n==== YamlTest_3 ====\n";

    // Parse a YAML or JSON text:
    mrpt::containers::yaml p = mrpt::containers::yaml::FromText(sData);

    // Get comments:
    std::cout << "Comment: '" << p["myMap"]["nestedMap"]["a"].comment()
              << "'\n";

    // Manipulate comments:
    p["myMap"]["nestedMap"]["b"].comment("This is a comment for b");

    // Add values and comments at once:
    p["myMap"]["foo"] = mrpt::containers::vcp(1.0, "Another constant");

    std::cout << "\n\nPrint as YAML:\n";
    p.printAsYAML(std::cout);
}

Output:

==== YamlTest_1 ====
K=2 N=10
name=Foo
Movie year=1986
bar=none
`K`: scalar(double)
`N`: scalar(int)
`books`: sequence
`enabled`: scalar(bool)
`movies`: sequence
`name`: scalar(std::string)
key: `a` val: 1
key: `b` val: 10
key: `c` val: -1
sequence item type: scalar(bool)
sequence item type: scalar(double)


Print as YAML:
%YAML 1.2
---
K: 2
N: 10
books:
  - true
  - 10
enabled: true
movies:
  -
    title: xxx
    year: 2001
  -
    title: yyy
    year: 1986
name: Foo


==== YamlTest_2 ====
D key comment: Distance [meters]
X value comment: Default value


Print as YAML:
%YAML 1.2
---
#  Distance [meters]
D: 1
#  Arm length [meters]
L: 5.5
X: 1  # Default value
#  Comment for Y
Y: -5


==== YamlTest_3 ====
Comment: 'comment for a'


Print as YAML:
%YAML 1.2
---
myMap:
  K: 10.0
  P: -5.0
  Q: ~
  foo: 1  # Another constant
  nestedMap:
    a: 1  # comment for a
    b: 2  # This is a comment for b
    c: 3

[New in MRPT 2.1.0]

#include <mrpt/containers/yaml.h>

class yaml
{
public:
    // typedefs

    typedef std::any scalar_t;
    typedef std::vector<node_t> sequence_t;
    typedef std::map<node_t, node_t, std::less<>> map_t;
    typedef std::array<std::optional<std::string>, static_cast<size_t>(CommentPosition::MAX)> comments_t;

    // structs

    struct InternalPrintState;
    struct node_t;

    // construction

    yaml();
    yaml(std::initializer_list<map_t::value_type> init);
    yaml(std::initializer_list<sequence_t::value_type> init);
    yaml(const yaml& v);
    yaml(const node_t& s);

    //
methods

    void loadFromText(const std::string& yamlTextBlock);
    void loadFromFile(const std::string& fileName);
    void loadFromStream(std::istream& i);

    template <typename YAML_NODE>
    void loadFromYAMLCPP(const YAML_NODE& n);

    static node_t Sequence(std::initializer_list<sequence_t::value_type> init);
    static node_t Sequence();
    static node_t Map(std::initializer_list<map_t::value_type> init);
    static node_t Map();
    static yaml FromText(const std::string& yamlTextBlock);
    static yaml FromStream(std::istream& i);
    static yaml FromFile(const std::string& fileName);

    template <typename YAML_NODE>
    static yaml FromYAMLCPP(const YAML_NODE& n);

    bool has(const std::string& key) const;
    bool empty() const;
    void clear();
    bool isNullNode() const;
    bool isScalar() const;
    bool isSequence() const;
    bool isMap() const;
    const std::type_info& scalarType() const;
    sequence_t& asSequence();
    const sequence_t& asSequence() const;
    map_t& asMap();
    const map_t& asMap() const;
    scalar_t& asScalar();
    const scalar_t& asScalar() const;
    size_t size() const;
    node_t& node();
    const node_t& node() const;
    const node_t& keyNode(const std::string& keyName) const;
    node_t& keyNode(const std::string& keyName);
    void printAsYAML(std::ostream& o, const YamlEmitOptions& eo = {}) const;
    void printAsYAML() const;
    void printDebugStructure(std::ostream& o) const;
    yaml operator [] (const std::string& key);
    yaml operator [] (const char* key);
    const yaml operator [] (const std::string& key) const;
    const yaml operator [] (const char* key) const;

    template <typename T>
    const T getOrDefault(const std::string& key, const T& defaultValue) const;

    template <typename T>
    T as() const;

    template <typename T>
    T& asRef();

    template <typename T>
    const T& asRef() const;

    yaml& operator = (bool v);
    yaml& operator = (float v);
    yaml& operator = (double v);
    yaml& operator = (int8_t v);
    yaml& operator = (uint8_t v);
    yaml& operator = (int16_t v);
    yaml& operator = (uint16_t v);
    yaml& operator = (int32_t v);
    yaml& operator = (uint32_t v);
    yaml& operator = (int64_t v);
    yaml& operator = (uint64_t v);
    yaml& operator = (const std::string& v);
    yaml& operator = (const char* v);
    yaml& operator = (const std::string_view& v);
    yaml& operator = (const yaml& v);

    template <typename T>
    yaml& operator = (const ValueCommentPair<T>& vc);

    template <typename T>
    yaml& operator << (const ValueKeyCommentPair<T>& vc);

    operator bool () const;
    operator double () const;
    operator float () const;
    operator int8_t () const;
    operator uint8_t () const;
    operator int16_t () const;
    operator uint16_t () const;
    operator int32_t () const;
    operator uint32_t () const;
    operator int64_t () const;
    operator uint64_t () const;
    operator std::string () const;
    bool hasComment() const;
    bool hasComment(CommentPosition pos) const;
    const std::string& comment() const;
    const std::string& comment(CommentPosition pos) const;
    void comment(const std::string& c, CommentPosition position = CommentPosition::RIGHT);
    bool keyHasComment(const std::string& key) const;
    bool keyHasComment(const std::string& key, CommentPosition pos) const;
    const std::string& keyComment(const std::string& key) const;
    const std::string& keyComment(const std::string& key, CommentPosition pos) const;
    void keyComment(const std::string& key, const std::string& c, CommentPosition position = CommentPosition::TOP);
    yaml operator () (int index);
    const yaml operator () (int index) const;
    void push_back(const double v);
    void push_back(const std::string v);
    void push_back(const uint64_t v);
    void push_back(const bool v);
    void push_back(const yaml& v);
};

Construction

yaml(std::initializer_list<map_t::value_type> init)

Constructor for maps, from list of pairs of values.

See examples in yaml above.

yaml(std::initializer_list<sequence_t::value_type> init)

Constructor for sequences, from list of values.

See examples in yaml above.

Methods

void loadFromText(const std::string& yamlTextBlock)

Parses a text as YAML or JSON (autodetected) and stores the contents into this document.

Parameters:

std::exception

Upon format errors

void loadFromFile(const std::string& fileName)

Parses a text as YAML or JSON (autodetected) and stores the contents into this document.

Parameters:

std::exception

Upon I/O or format errors

void loadFromStream(std::istream& i)

Parses the stream as YAML or JSON (autodetected) and stores the contents into this document.

Parameters:

std::exception

Upon format errors

template <typename YAML_NODE>
void loadFromYAMLCPP(const YAML_NODE& n)

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

static yaml FromText(const std::string& yamlTextBlock)

Parses a text as YAML or JSON (autodetected) and returns a document.

Parameters:

std::exception

Upon format errors

static yaml FromStream(std::istream& i)

Parses the stream as YAML or JSON (autodetected) and returns a document.

Parameters:

std::exception

Upon format errors

static yaml FromFile(const std::string& fileName)

Parses the filename as YAML or JSON (autodetected) and returns a document.

Parameters:

std::exception

Upon I/O or format errors.

template <typename YAML_NODE>
static yaml FromYAMLCPP(const YAML_NODE& n)

Builds an object copying the structure and contents from an existing YAMLCPP Node.

Requires user to #include yamlcpp from your calling program (does NOT requires yamlcpp while compiling mrpt itself).

Parameters:

YAML_NODE

Must be YAML::Node. Made a template just to avoid build-time depedencies.

bool has(const std::string& key) const

For map nodes, checks if the given key name exists.

bool empty() const

For map or sequence nodes, checks if the container is empty.

Also returns true for null(empty) nodes.

void clear()

Resets to empty (can be called on a root node or any other node to clear that subtree only).

const std::type_info& scalarType() const

For scalar nodes, returns its type, or typeid(void) if an empty node.

Parameters:

std::exception

If called on a map or sequence.

sequence_t& asSequence()

Use: for (auto &kv: n.asSequence()) {...}

Parameters:

std::exception

If called on a non-sequence node.

map_t& asMap()

Use: for (auto &kv: n.asMap()) {...}

Parameters:

std::exception

If called on a non-map node.

scalar_t& asScalar()

Parameters:

std::exception

If called on a non-scalar node.

size_t size() const

Returns 1 for null or scalar nodes, the number of children for sequence or map nodes.

node_t& node()

For a master yaml document, returns the root node; otherwise, the referenced node.

const node_t& node() const

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

const node_t& keyNode(const std::string& keyName) const

Maps only: returns a reference to the key node of a key-value pair.

Parameters:

std::exception

If called on a non-map node or key does not exist.

void printAsYAML(std::ostream& o, const YamlEmitOptions& eo = {}) const

Prints the document in YAML format to the given stream.

void printAsYAML() const

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

void printDebugStructure(std::ostream& o) const

Prints a tree-like representation of all nodes in the document in a custom format (nor YAML neither JSON).

yaml operator [] (const std::string& key)

Write access for maps.

yaml operator [] (const char* key)

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

const yaml operator [] (const std::string& key) const

Read access for maps.

Parameters:

std::runtime_error

if key does not exist.

const yaml operator [] (const char* key) const

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

template <typename T>
const T getOrDefault(
    const std::string& key,
    const T& defaultValue
    ) const

Scalar read access for maps, with default value if key does not exist.

template <typename T>
T as() const

Returns a copy of the existing value of the given type, or tries to convert it between easily-compatible types (e.g.

double<->int, string<->int).

Parameters:

std::exception

If the contained type does not match and there is no obvious conversion.

template <typename T>
T& asRef()

Returns a ref to the existing or new value of the given type.

If types do not match, the old content will be discarded and a new variable created into this scalar node.

Parameters:

std::exception

If accessing to a non-scalar node.

template <typename T>
const T& asRef() const

const version of asRef().

Unlike as<T>(), this version will NOT try to convert between types if T does not match exactly the stored type, and will raise an exception instead.

template <typename T>
yaml& operator = (const ValueCommentPair<T>& vc)

vcp (value-comment) wrapper

template <typename T>
yaml& operator << (const ValueKeyCommentPair<T>& vc)

vkcp (value-keyComment) wrapper

bool hasComment() const

Returns true if the proxied node has an associated comment block, at any location.

bool hasComment(CommentPosition pos) const

Returns true if the proxied node has an associated comment block at a particular position.

const std::string& comment() const

Gets the comment associated to the proxied node.

This version returns the first comment, of all possible (top, right).

Parameters:

std::exception

If there is no comment attached.

See also:

hasComment()

const std::string& comment(CommentPosition pos) const

Gets the comment associated to the proxied node, at the particular position.

See code examples in mrpt::containers::yaml.

Parameters:

std::exception

If there is no comment attached.

See also:

hasComment()

void comment(const std::string& c, CommentPosition position = CommentPosition::RIGHT)

Sets the comment attached to a given proxied node.

See code examples in mrpt::containers::yaml

See also:

hasComment()

bool keyHasComment(const std::string& key) const

Maps only: returns true if the given key node has an associated comment block, at any location.

Parameters:

std::exception

If called on a non-map or key does not exist.

bool keyHasComment(const std::string& key, CommentPosition pos) const

Maps only: Returns true if the given key has an associated comment block at a particular position.

Parameters:

std::exception

If called on a non-map or key does not exist.

const std::string& keyComment(const std::string& key) const

Maps only: Gets the comment associated to the given key.

This version returns the first comment, of all possible (top, right).

Parameters:

std::exception

If called on a non-map or key does not exist.

std::exception

If there is no comment attached.

See also:

hasComment()

const std::string& keyComment(const std::string& key, CommentPosition pos) const

Maps only: Gets the comment associated to the given key, at the particular position.

See code examples in mrpt::containers::yaml.

Parameters:

std::exception

If called on a non-map or key does not exist.

std::exception

If there is no comment attached.

See also:

hasComment()

void keyComment(const std::string& key, const std::string& c, CommentPosition position = CommentPosition::TOP)

Maps only: Sets the comment attached to a given key.

See code examples in mrpt::containers::yaml

Parameters:

std::exception

If called on a non-map or key does not exist.

See also:

hasComment()

yaml operator () (int index)

Write into an existing index of a sequence.

Parameters:

std::out_of_range

if index is out of range.

const yaml operator () (int index) const

Read from an existing index of a sequence.

Parameters:

std::out_of_range

if index is out of range.

void push_back(const double v)

Append a new value to a sequence.

Parameters:

std::exception

If this is not a sequence

void push_back(const std::string v)

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

void push_back(const uint64_t v)

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

void push_back(const bool v)

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

void push_back(const yaml& v)

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.