.. _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.