A high-performance, Codable-compatible XML encoder and decoder for Swift, backed by libxml2.
Encode and decode any Codable type to XML with streaming support, element vs. attribute mapping, namespace declarations, date strategies, XPath queries, and deterministic canonicalization — all with zero reflection, full Sendable safety, and constant-memory streaming for large documents.
XMLEncoder/XMLDecoder— Codable-compatible, zero-reflection encoding and decoding- Three-tier field mapping —
@XMLAttribute/@XMLChildproperty wrappers,@XMLCodablemacros (Swift 5.9+), or runtimeXMLFieldCodingOverrides - Streaming —
XMLStreamParser(push/SAX),XMLStreamWriter,XMLItemDecoder(item-by-item Codable decode) - XPath 1.0 — query parsed documents with namespace-aware expressions
- Namespace support — declare, resolve, and validate XML namespace prefixes; per-field namespace override via
XMLFieldNamespaceProvider/@XMLFieldNamespace - Canonicalization — deterministic XML output via
XMLCanonicalizer(XML-DSig ready) - Parser security — configurable depth, node-count, and text-size limits; network and DTD access disabled by default
- Structured diagnostics —
XMLParsingError.decodeFailedwith coding path andXMLSourceLocationfor precise error reporting - Immutable tree model — value-semantic
XMLTreeDocumentfor transform pipelines; full fidelity for processing instructions, doctypes, and comments - Swift 5.6 – 6.1 — multi-manifest compatibility; macros on 5.9+;
~Copyableownership on 6.0+ - macOS, iOS, tvOS, watchOS, Linux — SPM-only, no Objective-C, no Foundation XML APIs
// Package.swift
dependencies: [
.package(url: "https://github.com/MFranceschi6/swift-xml-coder.git", from: "2.0.0")
],
targets: [
.target(
name: "MyTarget",
dependencies: [
.product(name: "SwiftXMLCoder", package: "swift-xml-coder"),
// Optional: macro support (Swift 5.9+)
.product(name: "SwiftXMLCoderMacros", package: "swift-xml-coder"),
]
)
]import SwiftXMLCoder
struct Book: Codable {
var title: String
var author: String
var year: Int
}
let book = Book(title: "Swift in Practice", author: "Apple", year: 2024)
let data = try XMLEncoder().encode(book)
// <Book><title>Swift in Practice</title><author>Apple</author><year>2024</year></Book>let xml = Data("<Book><title>Swift in Practice</title><author>Apple</author><year>2024</year></Book>".utf8)
let book = try XMLDecoder().decode(Book.self, from: xml)import SwiftXMLCoderMacros
@XMLCodable
struct Product: Codable {
@XMLAttribute var id: String // → attribute
var name: String // → element (default)
var price: Double // → element
}
// <Product id="SKU-1"><name>Widget</name><price>9.99</price></Product>let doc = try XMLDocument(data: xmlData)
let node = try doc.xpathFirstNode("/catalog/book[@lang='en']/title")
print(node?.content ?? "not found")let parser = XMLTreeParser(configuration: .init(
limits: .untrustedInputDefault() // caps depth, nodes, text size
))
let tree = try parser.parse(data: untrustedInput)struct Product: Decodable { let sku: String; let price: Double }
let products = try XMLItemDecoder().decode(Product.self, itemElement: "Product", from: catalogData)
// Or async, one item at a time (macOS 12+):
for try await product in XMLItemDecoder().items(Product.self, itemElement: "Product", from: catalogData) {
await persist(product)
}Guides:
- Getting Started
- Field Mapping
- Namespaces
- Streaming
- Canonicalization
- XPath
- Security
- Swift Version Compatibility
- Test Support
SwiftXMLCoder ships with a comprehensive benchmark suite covering tree parsing, streaming (SAX push, pull cursor, item-by-item decode), Codable encode/decode, and comparisons against Foundation XMLParser and CoreOffice/XMLCoder.
| Document Size | Recommended Approach | Why |
|---|---|---|
| < 1 MB | XMLDecoder (tree) |
Simplest API, minimal overhead |
| 1 - 10 MB | Tree or XMLItemDecoder |
Tree works but uses more memory |
| > 10 MB | XMLItemDecoder / XMLStreamParser |
Constant memory vs linear; tree does not scale |
All figures are p50 wall-clock times. Measured on Apple M1 (arm64, 8 GB), macOS 15.3, release build with jemalloc.
| Operation | 10 KB | 100 KB | 1 MB | 10 MB |
|---|---|---|---|---|
Tree parse (XMLTreeParser) |
237 µs | 2.3 ms | 24 ms | 232 ms |
SAX push (XMLStreamParser) |
225 µs | 2.2 ms | 20 ms | 208 ms |
Codable decode (XMLDecoder) |
554 µs | 5.6 ms | 55 ms | 545 ms |
Codable encode (XMLEncoder) |
872 µs | 8.2 ms | 81 ms | 829 ms |
Stream write (XMLStreamWriter) |
221 µs | 2.3 ms | 21 ms | 215 ms |
Item decode (XMLItemDecoder, rich model) |
— | — | 53 ms | — |
All figures are p50 wall-clock times from the manual GitHub Actions benchmark run on March 22, 2026, using the macos-15 hosted runner, Swift 6.1, and jemalloc. Run: actions/runs/23411043764.
| Operation | 10 KB | 100 KB | 1 MB | 10 MB |
|---|---|---|---|---|
Tree parse (XMLTreeParser) |
268 µs | 2.7 ms | 29 ms | 241 ms |
SAX push (XMLStreamParser) |
230 µs | 2.2 ms | 28 ms | 267 ms |
Codable decode (XMLDecoder) |
591 µs | 5.7 ms | 59 ms | 588 ms |
Codable encode (XMLEncoder) |
837 µs | 8.1 ms | 108 ms | 962 ms |
Stream write (XMLStreamWriter) |
243 µs | 2.5 ms | 24 ms | 230 ms |
Item decode (XMLItemDecoder, rich model) |
— | — | 74 ms | — |
The GitHub-hosted runner preserves the same overall ranking as the local M1 run, but most internal benchmarks are modestly slower in CI. Small-payload encode stays effectively flat, while larger streaming workloads drift more: rich XMLItemDecoder moves from 53 ms to 74 ms at 1 MB. No benchmark job failed, and there were no correctness warnings or crashes in the run.
One important documentation fix came out of the CI comparison: the previous Foundation XMLParser note was inverted. In both local and GitHub Actions measurements, Foundation SAX parsing is faster than SwiftXMLCoder's SAX parser at 1 MB, while SwiftXMLCoder's tree parser remains faster than Foundation XMLDocument.
vs Foundation XMLParser (SAX): SwiftXMLCoder is about 2.0-2.3x slower at 1 MB in both local and GitHub Actions runs (~21-22 ms vs ~9-11 ms). This is consistent across 10 KB and 100 KB as well.
vs Foundation XMLDocument (tree): SwiftXMLCoder is about 18-25% faster at 1 MB (23-24 ms vs 28-32 ms) across local and GitHub Actions runs.
vs CoreOffice/XMLCoder decode: SwiftXMLCoder remains faster on both machines, from about 1.3-1.5x at 100 KB to about 1.5-2.6x at 10 MB depending on the runner.
vs CoreOffice/XMLCoder encode: SwiftXMLCoder remains about 1.5-1.7x faster across all scales on both local and GitHub Actions runs.
| Area | Scales | What It Measures |
|---|---|---|
| Tree parse / decode / encode | 10KB - 10MB | Full DOM materialization + Codable round-trip |
SAX push (XMLStreamParser) |
10KB - 100MB | Event-driven parsing, no tree allocation |
Item-by-item (XMLItemDecoder) |
10KB - 100MB | Streaming Codable decode, constant memory |
Stream writer (XMLStreamWriter) |
10KB - 10MB | Event sequence to XML serialization |
| Rich model (nested + attributes) | 10KB - 100MB | Real-world payload with 3-level nesting, namespaces |
Foundation XMLParser comparison |
10KB - 100MB | SAX + tree parse vs Apple's built-in parser |
| CoreOffice/XMLCoder comparison | 10KB - 10MB | Codable decode/encode head-to-head |
cd Benchmarks
# Internal benchmarks (parse, stream, encode, decode, Foundation comparison)
swift package --disable-sandbox benchmark
# Comparative benchmarks (SwiftXMLCoder vs CoreOffice/XMLCoder)
swift package --disable-sandbox benchmark --target ComparisonBenchmarksBenchmarks use ordo-one/package-benchmark and require macOS 13+ with jemalloc (brew install jemalloc).
The repository also runs benchmark regression checks in GitHub Actions via .github/workflows/benchmarks.yml. Every PR to main is compared against a main baseline on a macOS runner, so we can track regressions with a shared CI reference instead of relying only on local machine measurements.
| Feature | SwiftXMLCoder | CoreOffice/XMLCoder | Foundation XMLParser |
|---|---|---|---|
Codable encode/decode |
Yes | Yes | No |
| Streaming (constant memory) | Yes | No | Yes (SAX only) |
| Item-by-item Codable decode | Yes | No | No |
async/await streaming |
Yes | No | No |
| XML namespaces | Full | Partial | Yes |
| XPath 1.0 queries | Yes | No | No |
| Canonicalization (C14N) | Yes | No | No |
@XMLAttribute / @XMLChild macros |
Yes (5.9+) | No | No |
Sendable types |
All public types | No | No |
| Configurable parser limits | Yes | No | No |
| Linux support | Yes | Yes | Limited |
| Codable encode speed (1 MB) | 81 ms | 133 ms | N/A |
| Codable decode speed (1 MB) | 55 ms | 83 ms | N/A |
| Component | Requirement |
|---|---|
| Swift | 5.6+ (macros require 5.9+) |
| macOS | 10.15+ |
| iOS | 15.0+ |
| tvOS | 15.0+ |
| watchOS | 8.0+ |
| Linux | Ubuntu 20.04+ with libxml2-dev |
| Package manager | Swift Package Manager only |
MIT — see LICENSE.