xml_annotator
Annotations and runtime helpers for XML serialization in Dart. The annotations
describe how a class maps to XML; the runtime helpers back the parse/serialize
code. Pairs with the
xml_serializer generator.
Install
dart pub add xml_annotator
dependencies:
xml_annotator: ^0.1.0
Needs Dart SDK ^3.9.0.
Imports
Import both libraries:
import 'package:xml_annotator/xml_annotator.dart'; // annotations
import 'package:xml_annotator/runtime.dart'; // parse/write + helpers
Example
import 'package:xml_annotator/runtime.dart';
import 'package:xml_annotator/xml_annotator.dart';
part '../xml_annotation/reading.g.dart';
@XmlSerializer()
@XmlRoot('reading')
class Reading {
@XmlAttribute()
final String station;
@XmlElement()
final double temperature;
Reading({required this.station, required this.temperature});
factory Reading.fromXml(XmlElementNode e) => $ReadingFromXml(e);
void toXml(XmlBuilder b) => $ReadingToXml(this, b);
}
final xml = writeXmlDocument(
Reading(station: 'KSEA', temperature: 18.4).toXml,
declaration: false,
);
// <reading station="KSEA"><temperature>18.4</temperature></reading>
final reading = Reading.fromXml(parseXmlDocument(xml));
Annotations
Class and root
| Annotation | On | Purpose |
|---|---|---|
@XmlSerializer(...) |
class / enum | Marks a type as XML-serializable. Required on every mapped type. |
@XmlRoot(name, ...) |
class | The element name a type uses as a document root. Nested-only types omit it and take their name from the field that references them. |
@XmlSerializer:
fieldRename- name style for fields with no explicitname:. Defaultnone.createFromXml/createToXml- skip the read or write side whenfalse. Both defaulttrue.namespaces-prefix -> URImap for this type's fields.''is the default namespace.declares- forceprefix -> URIdeclarations on the root, for namespaces hidden inside raw passthrough.
@XmlRoot:
namespace- URI for the root element.prefix- prefix to write (null= default namespace).enforceOnRead- verify the root name/namespace on read, throw on mismatch. Defaultfalse.
Node kinds
One per field. An unannotated public field is treated as @XmlElement.
| Annotation | Maps the field to | Params |
|---|---|---|
@XmlElement |
a child element (default) | name, namespace, prefix, omitEmpty, defaultValue |
@XmlAttribute |
an attribute | name, namespace, prefix, omitEmpty, defaultValue |
@XmlText |
the element's text | omitEmpty |
@XmlInnerXml |
raw inner XML, verbatim (String) |
- |
@XmlAnyElement |
every child no other field claims | - |
@XmlIgnore |
nothing - skips the field | - |
@XmlElement / @XmlAttribute params:
name- XML name.nullderives it from the field viafieldRename.namespace- URI for this field. A read constraint, auto-declared on the root.prefix- write-time prefix override. Needs anamespace.omitEmpty- skip writing an empty/zero value. Scalars only (int/double/String/bool); a build error elsewhere, so use a nullableT?for optional fields. Derives a read-side zero fallback.defaultValue- read fallback when the node is absent and the field is non-nullable. Usually unneeded; the constructor default covers it.
Enums
@XmlEnum() // optional: unknownValue: Priority.low
enum Priority {
@XmlValue('lo') low,
@XmlValue('med') medium,
@XmlValue('hi') high,
}
@XmlEnum({unknownValue})- marks the enum.unknownValueis the fallback for an unknown literal on read;null(default) throws.@XmlValue(literal)- the serialized form. Every constant needs one.
Converters
For values the built-in coercions don't cover. A const instance of your
converter is the annotation.
class BoolZeroOne implements XmlValueConverter<bool> {
const BoolZeroOne();
@override
bool fromXmlString(String raw) => raw == '1' || raw == 'true';
@override
String toXmlString(bool value) => value ? '1' : '0';
}
class CellFormat {
@XmlAttribute(name: 'val')
@BoolZeroOne()
final bool bold;
CellFormat({required this.bold});
}
XmlValueConverter<T>-Tto/from a single attribute or text string (fromXmlString/toXmlString). Annotate the type to convert every field of that type automatically.XmlElementConverter<T>-Tto/from a whole element body (readElement/buildElementBody), for bodies that can't be described with fields.buildElementBodykeeps the runtimewriteElement/writeTexthelpers callable inside it.
A value converter on an @XmlSerializer type is a build error.
Defaults and optionality
When a non-nullable field is absent on read, the fallback resolves in order:
defaultValue:, else- the constructor parameter's default, else
- zero, for an
omitEmptyscalar, else - throw
XmlDeserializationException.
Namespaces
Bind each prefix <-> URI once on the class; fields reference the URI:
const dcUri = 'http://purl.org/dc/elements/1.1/';
@XmlSerializer(namespaces: {'dc': dcUri})
@XmlRoot('record')
class Record {
@XmlElement(name: 'id') final String id;
@XmlElement(name: 'title', namespace: dcUri) final String title;
}
// <record xmlns:dc="..."><id>...</id><dc:title>...</dc:title></record>
Every namespace is hoisted to one declaration on the root. Reads match by URI,
not prefix. A field prefix: overrides the class map; a URI bound by neither is a
build error.
License
MIT © Kawaljeet Singh