Swiftpack.co - Package - MaxDesiatov/XMLCoder


Encoder & Decoder for XML using Swift's Codable protocols.

Build Status Version License Platform Coverage

This package is a fork of the original ShawnMoore/XMLParsing with more features and improved test coverage.


import XMLCoder

let xmlStr = """
    <body>Don't forget to use XMLCoder!</body>

struct Note: Codable {
    let to: String
    let from: String
    let heading: String
    let body: String

guard let data = xmlStr.data(using: .utf8) else { return }

let note = try? XMLDecoder().decode(Note.self, from: data)

let returnData = try? XMLEncoder().encode(note, withRootKey: "note")

Advanced features

The following features are available in 0.4.0 release or later (unless stated otherwise):

Stripping namespace prefix

Sometimes you need to handle an XML namespace prefix, like in the XML below:

<h:table xmlns:h="http://www.w3.org/TR/html4/">

Stripping the prefix from element names is enabled with shouldProcessNamespaces property:

struct Table: Codable, Equatable {
    struct TR: Codable, Equatable {
        let td: [String]

    let tr: [TR]

let decoder = XMLDecoder()

// Setting this property to `true` for the namespace prefix to be stripped
// during decoding so that key names could match.
decoder.shouldProcessNamespaces = true

let decoded = try decoder.decode(Table.self, from: xmlData)

Dynamic node coding

XMLCoder provides two helper protocols that allow you to customize whether nodes are encoded and decoded as attributes or elements: DynamicNodeEncoding and DynamicNodeDecoding.

The declarations of the protocols are very simple:

protocol DynamicNodeEncoding: Encodable {
    static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding

protocol DynamicNodeDecoding: Decodable {
    static func nodeDecoding(for key: CodingKey) -> XMLDecoder.NodeDecoding

The values returned by corresponding static functions look like this:

enum NodeDecoding {
    // decodes a value from an attribute
    case attribute

    // decodes a value from an element
    case element

    // the default, attempts to decode as an element first,
    // otherwise reads from an attribute
    case elementOrAttribute

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

Add conformance to an appropriate protocol for types you'd like to customize. Accordingly, this example code:

struct Book: Codable, Equatable, DynamicNodeEncoding {
    let id: UInt
    let title: String
    let categories: [Category]

    enum CodingKeys: String, CodingKey {
        case id
        case title
        case categories = "category"

    static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding {
        switch key {
        case Book.CodingKeys.id: return .both
        default: return .element

works for this XML:

<book id="123">
    <title>Cat in the Hat</title>

Please refer to PR #70 by @JoeMatt for more details.

Coding key value intrinsic

Suppose that you need to decode an XML that looks similar to this:

<?xml version="1.0" encoding="UTF-8"?>
<foo id="123">456</foo>

By default you'd be able to decode foo as an element, but then it's not possible to decode the id attribute. XMLCoder handles certain CodingKey values in a special way to allow proper coding for this XML. Just add a coding key with stringValue that equals "" (empty string). What follows is an example type declaration that encodes the XML above, but special handling of coding keys with those values works for both encoding and decoding.

struct Foo: Codable, DynamicNodeEncoding {
    let id: String
    let value: String

    enum CodingKeys: String, CodingKey {
        case id
        case value = ""

    static func nodeEncoding(forKey key: CodingKey)
    -> XMLEncoder.NodeEncoding {
        switch key {
        case CodingKeys.id:
            return .attribute
            return .element

Thanks to @JoeMatt for implementing this in in PR #73.

Preserving whitespaces in element content

By default whitespaces are trimmed in element content during decoding. This includes string values decoded with value intrinsic keys. Starting with version 0.5 you can now set a property trimValueWhitespaces to false (the default value is true) on XMLDecoder instance to preserve all whitespaces in decoded strings.

Choice element coding

Starting with version 0.8, you can encode and decode enums with associated values by conforming your CodingKey type additionally to XMLChoiceCodingKey. This allows decoding XML elements similar in structure to this example:


To decode these elements you can use this type:

enum IntOrString: Equatable {
    case int(Int)
    case string(String)

extension IntOrString: Codable {
    enum CodingKeys: String, XMLChoiceCodingKey {
        case int
        case string

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        switch self {
        case let .int(value):
            try container.encode(value, forKey: .int)
        case let .string(value):
            try container.encode(value, forKey: .string)

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        do {
            self = .int(try container.decode(Int.self, forKey: .int))
        } catch {
            self = .string(try container.decode(String.self, forKey: .string))

This is described in more details in PR #119 by @jsbean and @bwetherfield.

Integrating with Combine

Starting with XMLCoder version 0.9, when Apple's Combine framework is available, XMLDecoder conforms to the TopLevelDecoder protocol, which allows it to be used with the decode(type:decoder:) operator:

import Combine
import Foundation
import XMLCoder

func fetchBook(from url: URL) -> AnyPublisher<Book, Error> {
    return URLSession.shared.dataTaskPublisher(for: url)
        .decode(type: Book.self, decoder: XMLDecoder())

This was implemented in PR #132 by @sharplet.

Additionally, starting with XMLCoder 0.11 XMLEncoder conforms to the TopLevelEncoder protocol:

import Combine
import XMLCoder

func encode(book: Book) -> AnyPublisher<Data, Error> {
    return Just(book)
        .encode(encoder: XMLEncoder())

The resulting XML in the example above will start with <book, to customize capitalization of the root element (e.g. <Book) you'll need to set an appropriate keyEncoding strategy on the encoder. To change the element name altogether you'll have to change the name of the type, which is an unfortunate limitation of the TopLevelEncoder API.

Root element attributes

Sometimes you need to set attributes on the root element, which aren't directly related to your model type. Starting with XMLCoder 0.11 the encode function on XMLEncoder accepts a new rootAttributes argument to help with this:

struct Policy: Encodable {
    var name: String

let encoder = XMLEncoder()
let data = try encoder.encode(Policy(name: "test"), rootAttributes: [
    "xmlns": "http://www.nrf-arts.org/IXRetail/namespace",
    "xmlns:xsd": "http://www.w3.org/2001/XMLSchema",
    "xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",

The resulting XML will look like this:

<policy xmlns="http://www.nrf-arts.org/IXRetail/namespace"

This was implemented in PR #160 by @portellaa.



Apple Platforms

  • Xcode 10.0 or later
    • IMPORTANT: compiling XMLCoder with Xcode 11.2.0 (11B52) and 11.2.1 (11B500) is not recommended due to crashes with EXC_BAD_ACCESS caused by a compiler bug, please use Xcode 11.3 or later instead. Please refer to #150 for more details.
  • Swift 4.2 or later
  • iOS 9.0 / watchOS 2.0 / tvOS 9.0 / macOS 10.10 or later deployment targets


  • Ubuntu 14.04 or later
  • Swift 5.0.1 or later

Swift Package Manager

Swift Package Manager is a tool for managing the distribution of Swift code. It’s integrated with the Swift build system to automate the process of downloading, compiling, and linking dependencies on all platforms.

Once you have your Swift package set up, adding XMLCoder as a dependency is as easy as adding it to the dependencies value of your Package.swift.

dependencies: [
    .package(url: "https://github.com/MaxDesiatov/XMLCoder.git", from: "0.11.1")

If you're using XMLCoder in an app built with Xcode, you can also add it as a direct dependency using Xcode's GUI.


CocoaPods is a dependency manager for Swift and Objective-C Cocoa projects for Apple's platfoms. You can install it with the following command:

$ gem install cocoapods

Navigate to the project directory and create Podfile with the following command:

$ pod install

Inside of your Podfile, specify the XMLCoder pod:

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'YourApp' do
  # Comment the next line if you're not using Swift or don't want
  # to use dynamic frameworks

  # Pods for YourApp
  pod 'XMLCoder', '~> 0.11.1'

Then, run the following command:

$ pod install

Open the the YourApp.xcworkspace file that was created. This should be the file you use everyday to create your app, instead of the YourApp.xcodeproj file.


Carthage is a dependency manager for Apple's platfoms that builds your dependencies and provides you with binary frameworks.

Carthage can be installed with Homebrew using the following command:

$ brew update
$ brew install carthage

Inside of your Cartfile, add GitHub path to XMLCoder:

github "MaxDesiatov/XMLCoder" ~> 0.11.1

Then, run the following command to build the framework:

$ carthage update

Drag the built framework into your Xcode project.


This project adheres to the Contributor Covenant Code of Conduct. By participating, you are expected to uphold this code. Please report unacceptable behavior to xmlcoder@desiatov.com.


If this library saved you any amount of time or money, please consider sponsoring the work of its maintainer. While some of the sponsorship tiers give you priority support or even consulting time, any amount is appreciated and helps in maintaining the project.

Coding Style

This project uses SwiftFormat and SwiftLint to enforce formatting and coding style. We encourage you to run SwiftFormat within a local clone of the repository in whatever way works best for you either manually or automatically via an Xcode extension, build phase or git pre-commit hook etc.

To guarantee that these tools run before you commit your changes on macOS, you're encouraged to run this once to set up the pre-commit hook:

brew bundle # installs SwiftLint, SwiftFormat and pre-commit
pre-commit install # installs pre-commit hook to run checks before you commit

Refer to the pre-commit documentation page for more details and installation instructions for other platforms.

SwiftFormat and SwiftLint also run on CI for every PR and thus a CI build can fail with incosistent formatting or style. We require CI builds to pass for all PRs before merging.

Test Coverage

Our goal is to keep XMLCoder stable and to serialize any XML correctly according to XML 1.0 standard. All of this can be easily tested automatically and we're slowly improving test coverage of XMLCoder and don't expect it to decrease. PRs that decrease the test coverage have a much lower chance of being merged. If you add any new features, please make sure to add tests, likewise for changes and any refactoring in existing code.


Stars: 285


Used By

Total: 0


0.11.1 - 2020-05-03 11:48:23

This release fixes an issue, where non-string values used CDATA encoding. Thanks to @ksoftllc for reporting it!

Closed issues:

  • Non-string values are being encoded as CData when stringEncodingStrategy = .cdata (#178)
  • How to encode as an empty element (#177)

Merged pull requests:

0.11.0 - 2020-04-13 17:33:42

This is a bugfix and feature release, which fixes an issue with CDATA decoding and adds TopLevelEncoder conformance to XMLEncoder. New rootAttributes argument has been added to the encode function on XMLEncoder that allows adding attributes on root elements without adding them to your model types. Thanks to @portellaa, @Kirow and others for their contributions and bug reports!

Closed issues:

  • CDATA Decoding not working (#168)
  • Decode special XML Structure (#156)
  • Root level attributes don't get encoded back to attribute when converting back to XML file from Plist (#127)
  • Bad access error when running on device (#100)

Merged pull requests:

0.10.0 - 2020-04-04 13:22:06

This is a bugfix release, which improves encoding and decoding of enums with associated values (also known as "choice coding") with the XMLChoiceCodingKey protocol. This release is also tested on Xcode 11.4 and Swift 5.2.1 on Linux. A few breaking changes were introduced, which were needed to simplify and improve internals of the library. Please refer to the corresponding section below for more details.

Thanks to @bwetherfield and @ultramiraculous for their contributions!

Breaking changes:

This change was needed to accommodate for multiple edges cases with how arrays of empty elements and empty strings are decoded.

The value intrinsic now only accepts the empty string key "", as the previous "value" key caused naming collisions with attributes and elemenents that had the same name.

Closed issues:

  • Bundle identifier in wrong format (#164)
  • Can inheritance be implemented? (#159)
  • EXC_BAD_ACCESS when running tests (#153)
  • EXC_BAD_ACCESS on XCode 11.2 and iOS13.2 (#150)
  • Date formatting on 24h region with display set to 12h (#148)
  • Decoding containers with (potentially)-empty elements (#123)

Merged pull requests:

- 2019-10-19 11:46:20

This release fixes a few bugs with Float type parsing and Swift 5.1 support on Linux. It also adds a helper extension to improve compatibility with Combine and adds a few tests to confirm that a few edges cases are working well. Thanks to @bwetherfield, @DJBen, @jsbean, @mxcl, @marcblanchet and @sharplet for bug reports and pull requests!

Implemented enhancements:

  • Conditionally conform to Combine.TopLevelDecoder #132 (sharplet)

Fixed bugs:

  • Value with copyright symbol © has its preceding whitespace trimmed off even trimValueWhitespaces is set to false #141
  • Float vs Float64=Double not parsing 3.14 #130

Closed issues:

  • TrackPoint position parameter is ignored #125
  • TCX file need an XML root node #124
  • [Swift 5.1] Import FoundationXML rather than Foundation #121

Merged pull requests:

- 2019-08-04 12:26:25

This release adds support for decoding and encoding ordered sequences of different elements as enums with associated values. In addition, XMLCoder now supports Linux. Many thanks to @jsbean, @bwetherfield and @drewag for implementing this!

Breaking changes:

  • Fixed typo in XMLDecoder property: errorContextLenght has been renamed to errorContextLength in #114.

Closed issues:

  • XML with autoclosed tags #116
  • Arrays of enums #91
  • Array of enums with associated values #25

Merged pull requests:

- 2019-07-02 12:07:13

This release changes the behavior of attributes coding: now order of XML attributes is fully preserved. One of the benefits is that it improves unit testing for users of XMLCoder, which allows testing against specific encoded attributes without accounting for their randomized order. Also a small coding style fix is included. In addition, XMLCoder now uses Azure Pipelines instead of Travis for CI with great improvements to overall CI stability, speed, and parallel builds. Thanks to Andrés Cecilia Luque and Jay Hickey for the contributions!

Merged pull requests

- 2019-06-17 16:38:24

An improvement release that introduces convertFromKebabCase and convertToKebabCase key decoding strategies. There were a few changes that aren't visible to end-users: the way that keys and values are stored internally has changed and a few more tests added. Thanks to Andrés Cecilia Luque and Vincent Esche for the contributions!

Merged pull requests

- 2019-05-02 13:47:02

Bugfix release that restores decoding of empty sequences, which became broken in 0.5.0.

Merged pull requests

- 2019-05-02 10:54:30

A small improvement release tagged early to resolve blocking issues in CoreXLSX.

Notable changes

  • Empty value strings are no longer decoded as nil when a String is expected, but are decoded as empty strings, which represents the actual value.
  • trimValueWhitespaces property was added on XMLDecoder, which allows overriding the default behaviour, where starting and trailing whitespaces are trimmed from string values.

Closed issues

  • Trimmed whitespace on decoding String #94

Merged pull requests

  • Fixed a bug when decoding a key with one character only #96 (TheFlow95)
  • Add more cases to AttributedIntrinsicTest #95 (MaxDesiatov)
  • Use map instead of mapValues/shuffle in XMLCoderElement.flatten #93 (jsbean)
  • Fix decoding empty element as optional #92 (MaxDesiatov)

- 2019-04-12 07:46:16

A bugfix release removing unused Xcode project scheme to improve build time for Carthage users.

Notable changes

- 2019-04-08 09:46:54

This is a release with plenty of new features that allow you to parse many more XML variations than previously. Compatibility with Xcode 10.2 and Swift 5.0 is also improved. A huge thank you to @JoeMatt and @regexident for their contributions, to @hodovani for maintaining the project, and to @Inukinator, @qmoya, @Ma-He, @khoogheem and @thecb4 for reporting issues during development!

Notable changes

  • Ordered encoding: this was one of the most requested changes and it's finally here! 🎉 Now both keyed and unkeyed elements are encoded in the exactly same order that was used in original containers. This is applicable to both compiler-generated encoding implementations (just reorder properties or cases in your CodingKeys enum if you have it) and manually implemented func encode(to: Encoder).
  • Stripping namespace prefix: now if your coding key string values contain an XML namespace prefix (e.g. prefix h in <h:td>Apples</h:td>), you can set shouldProcessNamespaces property to true on your XMLDecoder instance for the prefix to be stripped before decoding keys in your Decodable types.
  • Previously it was possible to customize encoding with NodeEncodingStrategy, but no such type existed for decoding. A corresponding NodeDecodingStrategy type was added with nodeDecodingStrategy property on XMLDecoder.
  • Thanks to the previous change, XMLCoder now provides two helper protocols that allow you to easily customize whether nodes are encoded and decoded as attributes or elements for conforming types: DynamicNodeEncoding and DynamicNodeDecoding.
  • Previously if you needed to decode or encode an XML element with both attributes and values, this was impossible to do with XMLCoder. Now with the addition of coding key value intrinsic, this is as easy as adding one coding key with a specific string raw value ("value" or empty string "" if you already have an XML attribute named "value").

Closed issues

  • Crash: Range invalid bounds in XMLStackParser.swift #83
  • Document DynamicNodeEncoding and attributed intrinsic #80
  • Fix nested attributed intrinsic #78
  • nodeEncodingStrategy #49
  • XmlEncoder: ordering of elements #17
  • Can’t reach an XML value #12

Merged pull requests

0.3.1 - 2019-02-06 11:05:11

A bugfix release that adds missing CFBundleVersion in generated framework's Info.plist (#72 reported by @stonedauwg).


- 2019-01-22 10:18:23

A maintenance release focused on fixing bugs, improving error reporting and overall internal architecture of the library. For this release we've started tracking test coverage and were able to increase it from 11.8% to 75.6%. 🎉 Thanks to @hodovani and @regexident for their work on improving test coverage in this release.


You can now set errorContextLength: UInt property on XMLDecoder instance, which will make it add a snippet of XML of at most this length from parser state when a parsing error occurs. This change was provided by @hodovani and can greatly help with attempts to parse invalid XML, where previously only a line and column number were reported.


NodeEncodingStrategies was renamed to NodeEncodingStrategy for consistency. NodeEncodingStrategies is still available as a deprecated typealias, which will be removed in future versions. Thanks to @regexident for cleaning this up and providing many more changes in this release that make XMLCoder better and easier to use.


- 2018-11-18 17:34:56

  • watchOS deployment target set to 2.0 for Carthage (@MaxDesiatov)

- 2018-11-18 17:21:42

  • Add watchOS 2.0 deployment target (@MaxDesiatov)

- 2018-11-18 17:06:48

  • Set iOS deployment target to 9.0 (@MaxDesiatov)

- 2018-11-08 10:46:16

  • Add support for decoupled, type-dependent node-encoding strategies (@regexident)
  • Add missing visibility declarations (@regexident)
  • Improve .gitignore and remove tracked *.xcuserdata files (@regexident)
  • Make XMLEncoder.OutputFormatting.prettyPrinted actually do something (@regexident)
  • Add tvOS deployment target to podspec (@edc1591)
  • Fix Carthage command (@salavert)
  • Set deployment versions to allow older SDKs (@Lutzifer)
  • Add Info.plist to allow Framework use in App Store Connect via Carthage (@Lutzifer)
  • Add convertFromCapitalized strategy, simple test (@MaxDesiatov)
  • Allow older iOS/tvOS deployment targets in podspec (@MaxDesiatov)