Skip to content

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.

  • Template Haskell deriving via deriveCBOR for records, enums, and sum types, with wireform-derive annotations; 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

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 CV
import CBOR.Class (toCBOR)
canonicalBytes :: Config -> ByteString
canonicalBytes cfg = encodeDeterministic (toCBOR cfg)

When debugging wire format issues, render values as diagnostic notation:

import CBOR.Diagnostic (toDiagnostic)
import CBOR.Class (toCBOR)
debugConfig :: Config -> Text
debugConfig 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 leftover
wireform-cbor vs cborg wireform-cbor vs cborg lower is better · ns · ghc-9.8.4 on darwin-aarch64, criterion 1.6.5 0 500 1000 1500 2000 302 281 462 1289 encode decode wireform-cbor cborg wireform-cbor vs cborg lower is better · ns · ghc-9.8.4 on darwin-aarch64, criterion 1.6.5 0 500 1000 1500 2000 302 281 462 1289 encode decode wireform-cbor cborg
Operationwireform-cborcborgratio
encode302 ns281 ns0.93x
decode462 ns1289 ns2.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.

ModulePurpose
CBOR.ClassToCBOR / FromCBOR typeclasses, encodeCBOR, decodeCBOR
CBOR.Encode / CBOR.DecodeLow-level wire primitives and encodeDeterministic
CBOR.ValueDynamic untyped Value ADT for schema-less processing
CBOR.DiagnosticDiagnostic notation rendering and parsing
CBOR.CDDL / CBOR.CDDLCodeGenCDDL parser and Haskell stub generator
CBOR.JSONCBOR ↔ JSON conversion
CBOR.StreamIncremental decode for framed input
CBOR.TagRegistryApplication tag registration and lookup
CBOR.DeriveTemplate Haskell deriver with wireform-derive annotations

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.