Yams: a YAML parser for Swift built on LibYAML
A Sweet and Swifty YAML parser.
At a glance
- What is it?
- Yams is a YAML parser for Swift that wraps LibYAML and exposes three conversion APIs with different costs, from Codable types through to a native Node model where you control the output style. It builds with Swift Package Manager, CMake or Bazel, and both Yams and LibYAML are MIT licensed.
- Who is it for?
- Adopt Yams if you are in Swift and need YAML, and you want to choose the API that fits the cost you can afford, since the Codable path is the cheapest and the standard library path is the most expensive. Do not adopt it if your project is not Swift, since it is a Swift package with an Apple-centric toolchain, and do not reach for the standard library API on a large document without thinking about the up-front type inference it performs.
- 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 12 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 October 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A Swift wrapper around a C YAML library
Yams is a YAML parser for Swift, described as sweet and swifty, and it is built on LibYAML. The primary language is Swift, the licence is MIT, and the homepage is jpsim.com/Yams.
The reason to use a C library underneath is that the YAML specification is enormous and the corner cases are the kind that take years to get right. Yams is the Swift-shaped layer over it, and it is a mature one, with releases 6.2.0 on 2025-10-08, 6.2.1 on 2026-02-05 and 6.2.2 on 2026-05-26, and a last push on 2026-10-13.
The repository carries the build configuration for three ecosystems, which is a good sign of how widely it is used. There is a Package.swift and a [email protected] for Swift Package Manager, a CMakeLists.txt with a cmake/ directory for CMake, and a WORKSPACE file with a BUILD file, a MODULE.bazel and a.bcr/ directory for Bazel, including the Bazel Central Registry metadata.
The rest of the tree shows a project that takes its tooling seriously. There is a.swiftlint.yml, a.jazzy.yaml for the generated API documentation, a.spi.yml, a.codecov.yml, a Yams.xcodeproj directory, a Gemfile and Gemfile.lock for the documentation tooling, Sources/ and Tests/, a CHANGELOG.md and a Docs.md. The.github/ workflows are visible in the badges at the top of the README, with separate jobs for SwiftPM, for xcodebuild and a Nightly workflow, plus a codecov badge for the coverage report.
Three APIs, and the price of each
Yams has three groups of conversion APIs, and the documentation is unusually honest about what each one costs.
The first is for Codable types. Codable is the encoding and decoding strategy introduced in Swift 4, which lets the same type be read from YAML and from other encoders such as JSONEncoder and PropertyListEncoder. This path has the lowest computational overhead, and the documentation says it is equivalent to Yams.Node. Encoding is YAMLEncoder.encode, which produces a YAML String from an instance of a type conforming to Encodable, and decoding is YAMLDecoder.decode, which decodes a Decodable instance from a YAML String or Data.
The second group is for Swift Standard Library types. The type is inferred from the contents of the internal Yams.Node representation by matching regular expressions, and this method has the largest computational overhead when decoding, because the type inference of all objects is done up-front. The trade is convenience: it may be easier to use when handling objects created from JSONSerialization, or when the input is already standard library types such as Any, Dictionary and Array. The functions are Yams.dump for encoding and Yams.load for decoding.
The third is the Yams-native representation. Yams.Node models the Nodes of the YAML specification and provides all the functions for detection and customization of the YAML format, and depending on how it is used the computational overhead can be minimized. That last clause is the interesting one, because it means the same API can be cheap or expensive depending on how you drive it, and the Node API is the one that gives you control over the output rather than just the content.
Choosing between them is mostly a question about whether your types are known at compile time.
The Codable path is the shortest
The Codable example is short, and it shows that a YAML round trip works the way a JSON round trip does.
import Foundation
import Yams
struct S: Codable {
var p: String
}
let s = S(p: "test")
let encoder = YAMLEncoder()
let encodedYAML = try encoder.encode(s)
encodedYAML == """
p: test
"""
let decoder = YAMLDecoder()
let decoded = try decoder.decode(S.self, from: encodedYAML)
s.p == decoded.pThe encoder produces a YAML String from the Encodable instance, the decoder reads a Decodable instance back from that String or from Data, and the round trip preserves the value. The trailing blank line in the expected output is worth noticing, because it is part of what the encoder emits and the kind of detail that matters when you compare generated YAML in a test.
The design decision that makes this usable is that YAMLDecoder and YAMLEncoder are familiar shapes. The same type can be fed from either format, which is what the documentation means when it says Codable enables easy conversion between YAML and other encoders.
The Combine integration follows the same principle. When Apple's Combine framework is available, YAMLDecoder conforms to the TopLevelDecoder protocol, so it works with the decode(type:decoder:) operator:
func fetchBook(from url: URL) -> AnyPublisher<Book, Error> {
URLSession.shared.dataTaskPublisher(for: url)
.map(\.data)
.decode(type: Book.self, decoder: YAMLDecoder())
.eraseToAnyPublisher()
}That conformance is the detail that makes Yams usable in a modern Swift codebase, where Combine or its Swift Concurrency equivalents are how data arrives.
The standard library path, and NSMutable compatibility
The standard library API dumps and loads Any values, and the example shows the shapes you get.
// [String: Any]
let dictionary: [String: Any] = ["key": "value"]
let mapYAML: String = try Yams.dump(object: dictionary)
mapYAML == """
key: value
"""
let loadedDictionary = try Yams.load(yaml: mapYAML) as? [String: Any]The same two functions handle arrays and scalars, with a [1, 2, 3] array dumping as a block sequence and a plain string dumping as a bare scalar. The load function returns Any, so the caller does the cast, which is the price of the dynamic typing.
There is a separate mechanism for the Cocoa-style mutable collections. Yams supports deep conversion of YAML into NSMutableDictionary and NSMutableArray, which is useful when working with mutable Cocoa-style collections, and to produce those results you pass a custom constructor:
let constructor = Constructor(Constructor.defaultScalarMap,
Constructor.nsMutableMappingMap,
Constructor.nsMutableSequenceMap)
let result = try Yams.load(yaml: yaml, .default, constructor) as? NSMutableDictionaryThe constructor takes three maps, one for scalars, one for mappings and one for sequences, and each decides which Swift type a given YAML node becomes. That is a clean design: the type mapping is data, not a switch statement, so you can add your own types for your own needs.
The caveat on this path is the one from the previous section, and it is worth repeating at the point of use. Because the type of every object is inferred up-front by matching regular expressions against the Node representation, decoding a large document into standard library types does more work than the same document decoded into a Codable type.
Node gives you control over the output style
The Node API is the one that lets you decide what the YAML looks like, and the example shows the style flags in action.
var map: Yams.Node = [
"array": [
1, 2, 3
]
]
map.mapping?.style = .flow
map["array"]?.sequence?.style = .flow
let yaml = try Yams.serialize(node: map)
yaml == """
{array: [1, 2, 3]}
"""
let node = try Yams.compose(yaml: yaml)
map == nodeSetting the style on the mapping and on the nested sequence changes the serialisation from the block form to the flow form, which is the YAML equivalent of writing {array: [1, 2, 3]} on one line. That is not a formatting convenience, it is a real use of the format, since flow style is what you want in a configuration file that people read and edit.
The serialize function produces a YAML String from a Node, and compose goes the other way, producing a Node from a YAML String. The equality check at the end is the round trip, and it holds because Node is the native model, so there is no lossy conversion in between.
The reason to reach for Node rather than Codable is visible in the API. With Codable you get your types; with Node you get the document, and every style, formatting and detection option in the library is exposed on it. The documentation also notes that the overhead of the Node path depends on how it is used, so it is not automatically the expensive choice.
Three build systems and the version you pin
Building Yams requires Xcode 14.0 or later, or a Swift 5.7 or later toolchain, with either Swift Package Manager or CMake and Ninja.
For Swift Package Manager the instruction is to add the dependency to your Package.swift file's dependencies array:
.package(url: "https://github.com/jpsim/Yams.git", from: "6.2.2")For CMake the requirement is CMake 3.17.2 or newer with Ninja 1.9.0 or newer. On Apple platforms, macOS, iOS, tvOS and watchOS, there is no need to build Foundation separately because it is included as part of the SDK:
cmake -B /path/to/build -G Ninja -S /path/to/yams -DCMAKE_BUILD_TYPE=Release
cmake --build /path/to/buildOn non-Apple platforms you point the build at a Foundation you have built yourself, with the Foundation_DIR argument:
cmake -B /path/to/build -G Ninja -S /path/to/yams -DCMAKE_BUILD_TYPE=Release -DFoundation_DIR=/path/to/foundation/build/cmake/modules
cmake --build /path/to/buildThe Bazel route is a WORKSPACE snippet with an http_archive rule:
YAMS_GIT_SHA = "SOME_SHA"
http_archive(
name = "com_github_jpsim_yams",
urls = [
"https://github.com/jpsim/Yams/archive/%s.zip" % YAMS_GIT_SHA,
],
strip_prefix = "Yams-%s" % YAMS_GIT_SHA,
)Three build systems for one library is unusual, and it is worth understanding why: the project is used from projects built with SwiftPM, from Linux projects that use CMake because Foundation is not in the SDK, and from Bazel-based monorepos, and the maintainer supports all three rather than pushing people onto one.
The licence situation is the simplest thing in the README: both Yams and libYAML are MIT licensed, so there is no copyleft obligation riding along with the C library.
Editorial conclusion
Adopt Yams if you are in Swift and need YAML, and you want to choose the API that fits the cost you can afford, since the Codable path is the cheapest and the standard library path is the most expensive. Do not adopt it if your project is not Swift, since it is a Swift package with an Apple-centric toolchain, and do not reach for the standard library API on a large document without thinking about the up-front type inference it performs. Before you start, pick the build system your project already uses, since the package supports SwiftPM, CMake and Bazel, and remember that the release you depend on is the one your Package.swift pins, with 6.2.2 as the version the README names.
Frequently asked questions
What is Yams and what is it built on?
Yams is a YAML parser for Swift built on LibYAML. It is MIT licensed, as is libYAML, and it runs on macOS, iOS, tvOS, watchOS and Linux.
Which Yams API should I use?
Yams has three: Codable types through YAMLEncoder and YAMLDecoder with the lowest overhead, the Swift Standard Library path through Yams.dump and Yams.load which infers types up-front and has the largest overhead, and the native Yams.Node representation through Yams.serialize and Yams.compose, which gives you control over the output style and whose cost depends on how you use it.
How do I add Yams to a Swift project?
Add .package(url: "https://github.com/jpsim/Yams.git", from: "6.2.2") to the dependencies array in your Package.swift file. The project can also be built with CMake, which needs CMake 3.17.2 or newer and Ninja 1.9.0 or newer, or with Bazel through a WORKSPACE http_archive rule.
Can Yams produce NSMutableDictionary and NSMutableArray results?
Yes. Yams supports deep conversion of YAML into NSMutableDictionary and NSMutableArray, and you get those results by passing a custom constructor built from Constructor.defaultScalarMap, Constructor.nsMutableMappingMap and Constructor.nsMutableSequenceMap to Yams.load.
Can I use Yams with Combine?
When Apple's Combine framework is available, YAMLDecoder conforms to the TopLevelDecoder protocol, so it can be used with the decode(type:decoder:) operator, for example on the output of URLSession.shared.dataTaskPublisher.
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/jpsim-yams)Was this useful?