Skip to content

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.

CapabilityWhy it matters
deriveYAML Template Haskell deriverDerive config types with wireform-derive annotations; Generic defaults work for simple cases
Block and flow stylesHuman-readable block output; compact flow for inline structures
Anchors and aliasesPreserve shared references and cyclic graphs in the value layer
TagsExplicit scalar typing (!!int, application-specific tags)
Literal and folded scalarsRound-trip multiline strings (| and >)
Multi-document streams--- separated documents for kubectl-style files
YAML 1.2 core schemaPlain scalars that look like bools or numbers stay quoted on encode

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 Server
loadConfig = decodeYAML

For 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).

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 YD
import qualified YAML.Encode as YE
roundtripAnchors :: Text -> Either String Text
roundtripAnchors doc = do
val <- YD.decode doc
pure (YE.encode val)

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 V
import YAML.Encode (encodeStream)
import YAML.Value (Document(..), Stream(..), mapping, string)
writeStream :: Stream -> Text
writeStream = encodeStream
exampleStream :: Stream
exampleStream =
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.

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)”
wireform-yaml decode vs libyaml across input sizes wireform-yaml decode vs libyaml across input sizes lower is better · µs · ghc-9.8.4 on darwin-aarch64, criterion 1.6.5 0 50.0 100 150 200 0.250 7.68 3.87 39.6 4.11 60.4 4.18 13.8 15.0 137 tiny small flow literal big wireform-yaml yaml (libyaml) wireform-yaml decode vs libyaml across input sizes lower is better · µs · ghc-9.8.4 on darwin-aarch64, criterion 1.6.5 0 50.0 100 150 200 0.250 7.68 3.87 39.6 4.11 60.4 4.18 13.8 15.0 137 tiny small flow literal big wireform-yaml yaml (libyaml)
Operationwireform-yamlyaml (libyaml)ratio
tiny0.25 µs7.68 µs30.72x
small3.87 µs39.6 µs10.22x
flow4.11 µs60.4 µs14.70x
literal4.18 µs13.8 µs3.29x
big15.0 µs137 µs9.12x

Last run 2026-06-27 11:56:42 UTC. ghc-9.8.4 on darwin-aarch64, criterion 1.6.5.

wireform-yaml decode vs HsYAML (pure Haskell) across input sizes wireform-yaml decode vs HsYAML (pure Haskell) across input sizes lower is better · µs · ghc-9.8.4 on darwin-aarch64, criterion 1.6.5 0 1250 2500 3750 5000 0.250 108 3.87 1044 4.11 1286 4.18 1175 15.0 3961 tiny small flow literal big wireform-yaml HsYAML wireform-yaml decode vs HsYAML (pure Haskell) across input sizes lower is better · µs · ghc-9.8.4 on darwin-aarch64, criterion 1.6.5 0 1250 2500 3750 5000 0.250 108 3.87 1044 4.11 1286 4.18 1175 15.0 3961 tiny small flow literal big wireform-yaml HsYAML
Operationwireform-yamlHsYAMLratio
tiny0.25 µs108 µs432.84x
small3.87 µs1044 µs269.82x
flow4.11 µs1286 µs312.92x
literal4.18 µs1175 µs281.21x
big15.0 µs3961 µs263.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.

ModuleRole
YAML.ClassToYAML / FromYAML, encodeYAML, decodeYAML
YAML.ValueAST for mappings, sequences, scalars, anchors, and streams
YAML.EncodeBlock and flow emitter with YAML 1.2 quoting rules
YAML.DecodeParser for documents and multi-doc streams
YAML.DeriveTemplate Haskell deriver wired to Wireform.Derive annotations
YAML.JSONBridge between YAML values and JSON