wireform-cbor
wireform-cbor implements Concise Binary Object Representation (CBOR) per
RFC 8949. CBOR is a compact, self-describing binary format used in IoT
protocols, COSE/JOSE, WebAuthn, and many other standards. Use this package
when you need a schema-flexible binary codec with strong tooling for
debugging, schema definition, and cross-language interoperability.
Key features
Section titled “Key features”- Template Haskell deriving via
deriveCBORfor records, enums, and sum types, withwireform-deriveannotations; Generic defaults (empty instances) work for simple cases - Streaming decode for framed or concatenated CBOR values without loading the entire input into memory
- CDDL schema language (RFC 8610) with a parser and Haskell code generator
- Diagnostic notation for human-readable debug output (RFC 8949 Section 8)
- JSON bridge for converting between CBOR and Aeson
Value - Deterministic encoding per RFC 8949 Section 4.2 for canonical byte sequences suitable for hashing and signing
- Tag registry for application-specific CBOR tags
Basic usage
Section titled “Basic usage”Derive instances with the Template Haskell deriver, then encode and decode in one call:
{-# LANGUAGE DeriveGeneric #-}{-# LANGUAGE TemplateHaskell #-}module Config where
import CBOR.Class (ToCBOR, FromCBOR, encodeCBOR, decodeCBOR)import CBOR.Derive (deriveCBOR)import GHC.Generics (Generic)import Data.Text (Text)
data Config = Config { cfgHost :: !Text , cfgPort :: !Int } deriving stock (Show, Eq, Generic)
$(deriveCBOR ''Config)
save :: Config -> IO ()save cfg = do let bytes = encodeCBOR cfg writeFileBinary "config.cbor" bytes
load :: IO (Either String Config)load = do bytes <- readFileBinary "config.cbor" pure (decodeCBOR bytes)For simple cases with no wire-format customization, Generic defaults also
work: add deriving Generic and declare empty instance ToCBOR Config and
instance FromCBOR Config declarations.
For signed payloads or content-addressed storage, use deterministic encoding so the same value always produces the same bytes:
import CBOR.Encode (encodeDeterministic)import CBOR.Value qualified as CVimport CBOR.Class (toCBOR)
canonicalBytes :: Config -> ByteStringcanonicalBytes cfg = encodeDeterministic (toCBOR cfg)When debugging wire format issues, render values as diagnostic notation:
import CBOR.Diagnostic (toDiagnostic)import CBOR.Class (toCBOR)
debugConfig :: Config -> TextdebugConfig cfg = toDiagnostic (toCBOR cfg)For streams of CBOR items (logs, multiplexed channels), decode one value at a time and keep the leftover bytes:
import CBOR.Stream (decodeOneWithLeftover)
decodeStream :: ByteString -> [(Either String CV.Value, ByteString)]decodeStream bs = go bs where go rest | BS.null rest = [] | otherwise = case decodeOneWithLeftover rest of Left err -> [(Left err, BS.empty)] Right (val, leftover) -> (Right val, leftover) : go leftoverPerformance
Section titled “Performance”wireform-cbor vs cborg
Section titled “wireform-cbor vs cborg”| Operation | wireform-cbor | cborg | ratio |
|---|---|---|---|
| encode | 302 ns | 281 ns | 0.93x |
| decode | 462 ns | 1289 ns | 2.79x |
Last run 2026-06-27 11:56:42 UTC. ghc-9.8.4 on darwin-aarch64, criterion 1.6.5.
Encode performance is roughly even with cborg (the established Haskell CBOR library). Decode is 2.6x faster due to wireform’s unboxed-sum decoder architecture.
The chart and table above are regenerated by wireform-stats from wireform-cbor/bench-results/summary/cbor-vs-cborg-encode.json — the same source the README chart is built from.
Notable modules
Section titled “Notable modules”| Module | Purpose |
|---|---|
CBOR.Class | ToCBOR / FromCBOR typeclasses, encodeCBOR, decodeCBOR |
CBOR.Encode / CBOR.Decode | Low-level wire primitives and encodeDeterministic |
CBOR.Value | Dynamic untyped Value ADT for schema-less processing |
CBOR.Diagnostic | Diagnostic notation rendering and parsing |
CBOR.CDDL / CBOR.CDDLCodeGen | CDDL parser and Haskell stub generator |
CBOR.JSON | CBOR ↔ JSON conversion |
CBOR.Stream | Incremental decode for framed input |
CBOR.TagRegistry | Application tag registration and lookup |
CBOR.Derive | Template Haskell deriver with wireform-derive annotations |
Conformance
Section titled “Conformance”Deterministic encoding follows RFC 8949 Section 4.2: shortest integer forms,
definite-length containers, and canonical map key ordering. Use
encodeDeterministic when byte-for-byte reproducibility matters for signatures
or content hashes.