wireform-yaml
wireform-yaml implements YAML 1.2 read and write paths for Haskell
applications. Configuration files, CI manifests, and Kubernetes-style multi-doc
streams all map cleanly onto typed records via Generic, while the value layer
still exposes anchors, aliases, tags, and both block and flow styles when you
need full YAML expressiveness. The decoder and emitter are validated against the
upstream yaml-test-suite with 100%
conformance.
Key features
Section titled “Key features”| Capability | Why it matters |
|---|---|
deriveYAML Template Haskell deriver | Derive config types with wireform-derive annotations; Generic defaults work for simple cases |
| Block and flow styles | Human-readable block output; compact flow for inline structures |
| Anchors and aliases | Preserve shared references and cyclic graphs in the value layer |
| Tags | Explicit scalar typing (!!int, application-specific tags) |
| Literal and folded scalars | Round-trip multiline strings (| and >) |
| Multi-document streams | --- separated documents for kubectl-style files |
| YAML 1.2 core schema | Plain scalars that look like bools or numbers stay quoted on encode |
Basic usage
Section titled “Basic usage”Typed records
Section titled “Typed records”Derive codecs with the Template Haskell deriver and use encodeYAML /
decodeYAML for the common case.
{-# LANGUAGE DeriveGeneric #-}{-# LANGUAGE TemplateHaskell #-}{-# LANGUAGE OverloadedStrings #-}import GHC.Generics (Generic)import Data.Text (Text)import YAML.Class (ToYAML, FromYAML, encodeYAML, decodeYAML)import YAML.Derive (deriveYAML)
data Server = Server { host :: !Text , port :: !Int , tls :: !Bool } deriving stock (Generic)
$(deriveYAML ''Server)
loadConfig :: Text -> Either String ServerloadConfig = decodeYAMLFor simple cases with no wire-format customization, Generic defaults also
work: add deriving Generic and declare empty instance ToYAML Server and
instance FromYAML Server declarations.
Field naming follows the same Wireform.Derive annotation vocabulary as other
wireform formats (rename, omitEmpty, and friends via YAML.Derive).
Value-level API for anchors and tags
Section titled “Value-level API for anchors and tags”When the shape is dynamic or you must preserve YAML-specific features, work in
YAML.Value and encode with YAML.Encode.
{-# LANGUAGE OverloadedStrings #-}import Data.Text (Text)import qualified YAML.Decode as YDimport qualified YAML.Encode as YE
roundtripAnchors :: Text -> Either String TextroundtripAnchors doc = do val <- YD.decode doc pure (YE.encode val)Multi-document streams
Section titled “Multi-document streams”Kubernetes and other tools emit several YAML documents in one file. Decode the
stream as a YAML.Value.Stream, or encode many values with encodeStream.
{-# LANGUAGE OverloadedStrings #-}import qualified Data.Vector as Vimport YAML.Encode (encodeStream)import YAML.Value (Document(..), Stream(..), mapping, string)
writeStream :: Stream -> TextwriteStream = encodeStream
exampleStream :: StreamexampleStream = Stream $ V.fromList [ Document True False (mapping [(string "apiVersion", string "v1")]) , Document True False (mapping [(string "kind", string "ConfigMap")]) ]The emitter chooses block style by default and falls back to flow for empty
containers. Output is round-trippable: encode followed by decode recovers
the same Value.
Performance
Section titled “Performance”wireform-yaml is a pure Haskell parser that consistently outperforms the C libyaml bindings by 3-32x across all input sizes, making it one of the fastest YAML parsers in any language. Against HsYAML (the other pure Haskell option), it is 244-408x faster.
wireform-yaml vs libyaml (C bindings via Hackage yaml package)
Section titled “wireform-yaml vs libyaml (C bindings via Hackage yaml package)”| Operation | wireform-yaml | yaml (libyaml) | ratio |
|---|---|---|---|
| tiny | 0.25 µs | 7.68 µs | 30.72x |
| small | 3.87 µs | 39.6 µs | 10.22x |
| flow | 4.11 µs | 60.4 µs | 14.70x |
| literal | 4.18 µs | 13.8 µs | 3.29x |
| big | 15.0 µs | 137 µs | 9.12x |
Last run 2026-06-27 11:56:42 UTC. ghc-9.8.4 on darwin-aarch64, criterion 1.6.5.
wireform-yaml vs HsYAML (pure Haskell)
Section titled “wireform-yaml vs HsYAML (pure Haskell)”| Operation | wireform-yaml | HsYAML | ratio |
|---|---|---|---|
| tiny | 0.25 µs | 108 µs | 432.84x |
| small | 3.87 µs | 1044 µs | 269.82x |
| flow | 4.11 µs | 1286 µs | 312.92x |
| literal | 4.18 µs | 1175 µs | 281.21x |
| big | 15.0 µs | 3961 µs | 263.87x |
Last run 2026-06-27 11:56:42 UTC. ghc-9.8.4 on darwin-aarch64, criterion 1.6.5.
The charts and tables above are regenerated by wireform-stats from wireform-yaml/bench-results/summary/yaml-decode-vs-{libyaml,hsyaml}.json — the same source the README charts are built from.
Notable modules
Section titled “Notable modules”| Module | Role |
|---|---|
YAML.Class | ToYAML / FromYAML, encodeYAML, decodeYAML |
YAML.Value | AST for mappings, sequences, scalars, anchors, and streams |
YAML.Encode | Block and flow emitter with YAML 1.2 quoting rules |
YAML.Decode | Parser for documents and multi-doc streams |
YAML.Derive | Template Haskell deriver wired to Wireform.Derive annotations |
YAML.JSON | Bridge between YAML values and JSON |