XMLCoder: XML for Swift Codable, and where it fights back
Easy XML parsing using Codable protocols in Swift
At a glance
- What is it?
- XMLCoder gives Swift Codable types an XML encoder and decoder, so the same structs that talk to a JSON API can talk to an XML service. The friction is in the details: namespaces, attributes and whitespace each need a deliberate switch.
- Who is it for?
- XMLCoder is a good fit for Swift codebases that must talk XML to an existing service while keeping the same Codable models used elsewhere, and for anyone who wants attribute versus element placement decided per key. It is the wrong choice when whitespace inside text nodes must survive untouched, when namespace prefixes should be handled without an explicit opt-in, or when you want a stable, fully specified XML conformance beyond what the README covers.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 179 days ago.
- What is it written in?
- Mainly Swift, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 7, 2026, and from our analysis. They are not legal advice.
Star history
- CoreOffice/XMLCoder878 ★ · +0
| Repository | 2026-10-21 | 2026-11-07 |
|---|---|---|
| CoreOffice/XMLCoder | 878 | 878 |
0 Stars in the last 17 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
One Codable model, two wire formats
XMLCoder is an encoder and decoder for XML built on Swift's Codable protocols. The problem it addresses is narrow and real: Swift has first class Codable support for JSON, PropertyListEncoder and PropertyListDecoder, but nothing equivalent for XML, so a project that has to talk to a legacy or enterprise XML service ends up with a second, hand written model layer that drifts away from the first. With XMLCoder the same struct that encodes to JSON encodes to XML, and the decoding side works the same way, since both directions are expressed through the standard Codable machinery. The audience is server and app developers on Apple platforms and Linux who already have Codable types and only need the XML transport. The package is a fork of ShawnMoore/XMLParsing with more features and improved test coverage, and the repository keeps a Benchmarks directory, a Swift Package Manager manifest, a CocoaPods podspec and separate scripts for testing under SwiftPM and Xcode.
XMLDecoder and XMLEncoder wrap the Codable entry points
The API surface mirrors the Foundation JSON types, with XML specific names. Decoding takes a type and Data, encoding takes a value and can be given a root element name. The short example in the README decodes a note document into a plain Codable struct and encodes it back out again:
import XMLCoder
import Foundationstruct Note: Codable {
let to: String
let from: String
let heading: String
let body: String
}let note = try! XMLDecoder().decode(Note.self, from: Data(sourceXML.utf8))let encodedXML = try! XMLEncoder().encode(note, withRootKey: "note")The withRootKey argument is what names the wrapping element, so the same struct can produce a document with or without a root without changing the type. Internally the library works through XMLCoderElement, its own node representation, which is the type you will meet when you write custom Decodable logic. The elements in that representation are where attributes and child elements are distinguished, and that distinction is the source of most of the configuration the library exposes.
Namespace prefixes are stripped only when you ask
Namespaced XML is the first place where a default expectation and XMLCoder's behaviour diverge. A document like an HTML table with an h: prefix will not decode into a plain struct whose keys are td and tr, because the element names still carry the prefix. XMLCoder handles this with an opt-in property on the decoder, shouldProcessNamespaces. Setting it to true strips the prefix during decoding so that key names match, which is what a hand written parser would have to do by hand:
decoder.shouldProcessNamespaces = truelet decoded = try decoder.decode(Table.self, from: xmlData)The default is off, and that default is a reasonable choice rather than an oversight. If a document uses the same local name in two different namespaces, stripping prefixes merges them, and a caller who silently lost that distinction would get wrong data rather than a decoding error. The cost is that any real world feed with prefixes will fail until you flip one flag, and the failure appears as a key decoding error rather than as a message about namespaces.
Attribute versus element is decided per key, not per type
XML puts values in two different places, and Codable has no concept of either. XMLCoder bridges that gap with two helper protocols, DynamicNodeEncoding and DynamicNodeDecoding, that let a type state where each of its keys belongs. The declarations are small:
protocol DynamicNodeEncoding: Encodable {
static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding
}
protocol DynamicNodeDecoding: Decodable {
static func nodeDecoding(for key: CodingKey) -> XMLDecoder.NodeDecoding
}What those functions return is the actual policy. On the encoding side the default is an element, with attribute and both available:
enum NodeEncoding {
// encodes a value in an attribute
case attribute
// the default, encodes a value in an element
case element
// encodes a value in both attribute and element
case both
}On the decoding side there is a third option that has no encoding counterpart, elementOrAttribute, which attempts an element first and otherwise reads from an attribute. That is the pragmatic choice when the same feed sometimes sends a value inline and sometimes as an attribute, and it is the one that makes mixed documents decode without a custom parser. A type opts in by conforming and returning the right case for each key, which is what this one does to write the id into both places:
static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding {
switch key {
case Book.CodingKeys.id: return .both
default: return .element
}
}The cost of this flexibility is that the attribute placement lives in the type rather than in a separate mapping layer, so a type is no longer a plain data description, and a struct that maps to a different schema needs a different type.
Whitespace is trimmed by default, and the fix is two switches
Whitespace handling is the most likely source of a bug report against any XML library, and XMLCoder is explicit about it. By default whitespaces are trimmed in element content during decoding, and that includes string values decoded with value intrinsic keys. If the text matters, for example in a mixed content node, set trimValueWhitespaces to false on the XMLDecoder instance. The default value is true, and the switch exists since version 0.5. Turning trimming off has a side effect that the project documents separately: when you decode pretty printed XML with trimming disabled, whitespace elements can be added as child elements on an instance of XMLCoderElement, and those extra children make it impossible to decode data structures that need custom Decodable logic. Since version 0.13.0 the decoder has a second property, removeWhitespaceElements, which you set to true to remove them. Its default is false. The two settings interact, and getting the order wrong is easy: the sensible sequence is to turn trimming off first, and only then decide whether whitespace elements need to be stripped.
Adding the package and running the first decode
The project does not spell out an install command in its documentation, so take the dependency coordinates from the package page rather than from a snippet you found on a forum. The repository root contains Package.swift for Swift Package Manager, and a CocoaPods podspec named XMLCoder.podspec, and Carthage appears in the topic list, so all three distribution paths are represented. The package targets watchOS, iOS, tvOS, macOS, Linux and Windows, and the generated API documentation is published on GitHub Pages under the CoreOffice organisation. Once the dependency resolves, the first real use is the decode and encode pair shown earlier, and the value it produces is an XML document whose root element name comes from withRootKey. Two extra capabilities sit on top of the plain round trip. Choice element coding, available since version 0.8, encodes and decodes enums with associated values by conforming the CodingKey type additionally to XMLChoiceCodingKey, which produces XML like a container holding int and string children in sequence. Separately, an element whose content is the value itself, as in a foo tag with an id attribute, needs a special coding key whose stringValue is the empty string, and the special handling of such keys works for both encoding and decoding.
Where XMLCoder is the wrong tool
The library is good at the common case and honest about its edges, but there are situations where another approach is less work. If a document needs exact preservation of whitespace inside text, the default trimming has to be disabled and the whitespace element problem handled, and any custom Decodable logic will then compete with those extra child nodes. If two namespaces reuse the same local element name, prefix stripping collapses them, so a namespace aware model would be needed and Codable alone cannot express it. If you need schema validation, DTD or entity expansion control, the documentation says nothing about any of that, and a general purpose XML tool with explicit security settings would be the safer choice. For distribution, the library is a fork of ShawnMoore/XMLParsing, so the practical question is whether the extra features and the improved test coverage justify taking the fork rather than the original. The licence is MIT, which is the permissive end of the scale and raises no obstacle to shipping XMLCoder inside a commercial product, and the release history shows steady but slow movement: 0.18.2 on 2026-05-13, 0.18.1 on 2026-04-09 and 0.18.0 on 2025-12-29, with the last push on 2026-05-13.
Editorial conclusion
XMLCoder is a good fit for Swift codebases that must talk XML to an existing service while keeping the same Codable models used elsewhere, and for anyone who wants attribute versus element placement decided per key. It is the wrong choice when whitespace inside text nodes must survive untouched, when namespace prefixes should be handled without an explicit opt-in, or when you want a stable, fully specified XML conformance beyond what the README covers. Before adopting, check the generated documentation on GitHub Pages for the exact behaviour of the version you pin, confirm that the version boundary you need is in the release notes (0.5 for whitespace trimming, 0.8 for choice coding, 0.13.0 for whitespace element removal, 0.18.2 published on 2026-05-13), and decide whether the fork's extra features matter to you against the original ShawnMoore/XMLParsing project it grew from. The MIT licence lets you ship it inside a product, and the last push was on 2026-05-13.
Frequently asked questions
How do I add XMLCoder to a Swift project?
The repository ships Package.swift for Swift Package Manager and a CocoaPods podspec named XMLCoder.podspec, and the package page is on Swift Package Index. The README does not include a dependency snippet, so take the exact coordinates from that package page.
How do I decode XML that uses namespace prefixes?
Set shouldProcessNamespaces to true on the XMLDecoder instance. The prefix is then stripped during decoding so that key names match, which is needed for documents such as an HTML table using an h: prefix.
Why are spaces missing from the strings I decode?
Whitespaces are trimmed in element content during decoding by default. Set trimValueWhitespaces to false on the decoder to keep them, and if you then decode pretty printed XML, set removeWhitespaceElements to true so that whitespace only child elements do not interfere with custom Decodable logic.
How do I encode a value as an XML attribute instead of an element?
Conform the type to DynamicNodeEncoding and return .attribute from its nodeEncoding(for:) function, or .both to write the value into an attribute and an element. For decoding, conform to DynamicNodeDecoding and return .attribute, or .elementOrAttribute to try an element first and fall back to an attribute.
Which platforms does XMLCoder support?
The package is published for watchOS, iOS, tvOS, macOS, Linux and Windows, and it is written in Swift.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/coreoffice-xmlcoder)Star history chart for your README
[](https://hysenlabs.com/projects/coreoffice-xmlcoder)Was this useful?