A pure Swift YAML 1.2.2 parser with zero dependencies. Parses YAML strings into a typed Node tree with full spec compliance, verified by 569 tests.
Add to your Package.swift:
dependencies: [
.package(url: "https://github.com/1amageek/swift-yaml.git", from: "0.3.0")
]Then add "YAML" to your target's dependencies:
.target(name: "YourTarget", dependencies: ["YAML"])import YAML
let node = try compose(yaml: """
server:
host: localhost
port: 8080
features: [auth, logging]
""")
if case .mapping(let root) = node,
case .mapping(let server) = root["server"] {
print(server["host"]) // Optional(.scalar("localhost"))
print(server["port"]) // Optional(.scalar("8080"))
print(server["features"]) // Optional(.sequence([...]))
}The parser produces a recursive Node tree with three cases:
| Case | Type | Description |
|---|---|---|
.scalar(Scalar) |
Node.Scalar |
String value with optional source position |
.mapping(Mapping) |
Node.Mapping |
Ordered key-value pairs (RandomAccessCollection) |
.sequence(Sequence) |
Node.Sequence |
Ordered list of nodes (RandomAccessCollection) |
Access values using computed properties:
node.scalar?.string // "value" or nil
node.mapping?["key"] // Node? or nil
node.sequence?[0] // Node or nil| Feature | Example |
|---|---|
| Block mapping | key: value |
| Block sequence | - item |
| Flow sequence | [a, b, c] |
| Flow mapping | {key: value} |
| Literal block scalar | |, |-, |+ |
| Folded block scalar | >, >-, >+ |
| Single-quoted string | 'it''s escaped' |
| Double-quoted string | "hello\nworld" |
| Anchors & aliases | &anchor / *anchor |
| Tags | !!str, !custom, !<verbatim> |
| Multi-document | --- / ... |
| Directives | %YAML 1.2, %TAG |
| Comments | # comment |
| Complex mapping keys | {flow: map}: value, [seq]: value |
| Escape sequences | All 18 YAML escapes + \xNN, \uNNNN, \UNNNNNNNN |
| Nested structures | Arbitrary depth |
All parse errors are reported as YAMLError with source position:
do {
let node = try compose(yaml: invalidYAML)
} catch let error as YAMLError {
print(error) // "3:12: scanner error: unterminated double-quoted string"
}All public types conform to Sendable, Hashable, and Equatable:
// Use nodes as dictionary keys or set elements
var seen: Set<Node> = []
seen.insert(node)
// Compare nodes
if node == .scalar(Node.Scalar("expected")) { ... }The parser uses a classic three-stage pipeline:
YAML String → Scanner → Parser → Node Tree
(tokens) (recursive descent)
- Scanner — Tokenizes YAML input with indentation tracking, flow/block context switching, and block scalar processing
- Parser — Recursive descent parser that consumes tokens and builds the
Nodetree with anchor resolution - Compose — Public entry point (
compose(yaml:)) that wires Scanner and Parser together
public func compose(yaml: String) throws -> Node?Returns nil for empty documents. Throws YAMLError on invalid input.
public enum Node: Sendable, Hashable {
case scalar(Scalar)
case mapping(Mapping)
case sequence(Sequence)
var scalar: Scalar? { get }
var mapping: Mapping? { get }
var sequence: Sequence? { get }
}public struct Scalar: Sendable, Hashable {
public let string: String
public let mark: Mark?
public init(_ string: String, mark: Mark? = nil)
}public struct Mapping: Sendable, Hashable, RandomAccessCollection {
// Element = (key: Node, value: Node)
public subscript(key: String) -> Node? // lookup by string key
public subscript(position: Int) -> (Node, Node) // access by index
public var first: (key: Node, value: Node)?
public var isEmpty: Bool
}public struct Sequence: Sendable, Hashable, RandomAccessCollection {
// Element = Node
public subscript(position: Int) -> Node
}public struct Mark: Sendable, Hashable, CustomStringConvertible {
public let line: Int // 1-based
public let column: Int // 1-based
}public enum YAMLError: Error, Sendable, CustomStringConvertible {
case scanner(message: String, mark: Mark)
case parser(message: String, mark: Mark)
case unexpectedEndOfInput(mark: Mark)
}- Swift 6.2+
MIT License. See LICENSE for details.