.. _xmlio-custom-types:
User-Defined Types
==================
Serialization by ordinary functions
-----------------------------------
An application extends ``xmlio`` by defining ``read`` and ``write`` beside its
own type. No base class, registration macro, or modification of ``xmlio`` is
required.
.. literalinclude:: examples/custom_types.cpp
:language: cpp
:linenos:
The functions live in namespace ``example`` beside ``Task``. Argument-dependent
lookup finds them when the generic vector serializer encounters a ``Task``.
The resulting XML is:
.. code-block:: xml
prepare
2
finish
1
Required and optional fields
----------------------------
Use ordinary ``read`` for required fields. Use ``read_optional`` or
``std::optional`` only when absence is valid according to the type's schema:
.. code-block:: cpp
struct Task {
std::string name;
int priority{};
std::optional note;
};
void read(xmlio::XMLReader& xml, std::string_view path, Task& task) {
xmlio::XMLReader node(xml, path);
xmlio::read(node, "name", task.name);
xmlio::read(node, "priority", task.priority);
xmlio::read(node, "note", task.note);
}
A malformed present value remains an error. Optional reading should not be used
to hide conversion failures.
Enums and readable XML
----------------------
Enums are best represented by stable names rather than underlying integers:
.. code-block:: cpp
enum class Mode { fast, careful };
void write(xmlio::XMLWriter& xml, std::string_view path, Mode mode) {
xmlio::write(xml, path, mode == Mode::fast ? "fast" : "careful");
}
void read(xmlio::XMLReader& xml, std::string_view path, Mode& mode) {
const auto text = xmlio::read(xml, path);
if (text == "fast") {
mode = Mode::fast;
} else if (text == "careful") {
mode = Mode::careful;
} else {
throw xmlio::ConversionError(path, text, "Mode");
}
}
This keeps hand-written files understandable and prevents enum reordering from
changing the file format.
Validation belongs to the type
------------------------------
``xmlio`` verifies XML structure and conversion. Domain validation belongs in
the owning type or in a dedicated validation function after deserialization:
.. code-block:: cpp
if (task.priority < 0) {
throw xmlio::QueryError(path, "priority must be non-negative");
}
Keeping this boundary explicit lets the same serialization machinery serve
many packages without teaching ``xmlio`` their domain rules.
Nested and associative records
------------------------------
Once a type has ``read`` and ``write``, it can appear recursively inside
``vector``, ``list``, ``array``, ``pair``, and ``map`` values. Map keys must
also satisfy the ordering requirements of ``std::map``. Deeply nested formats
remain readable when each owning type gives its children meaningful tag names.