Skip to content

wireform-msgpack

wireform-msgpack implements the MessagePack binary serialization format. MessagePack is widely used for RPC, caching, and inter-service communication because it is compact, fast to parse, and supported by libraries in most languages. Use this package when you want a lightweight alternative to JSON with similar flexibility but smaller payloads.

  • Template Haskell deriving via deriveMsgPack for records, enums, and sum types, with wireform-derive annotations; Generic defaults (empty instances) work for simple cases
  • Streaming decode for concatenated or length-prefixed MessagePack frames
  • msgpack-RPC message encoding for request/response/notification patterns
  • JSON bridge for converting between MessagePack and Aeson Value
  • Dynamic values via the untyped Value ADT when schemas are unknown at compile time

Define a type and derive codec instances with Template Haskell:

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE TemplateHaskell #-}
module Person where
import MsgPack.Class (ToMsgPack, FromMsgPack, encodeMsgPack, decodeMsgPack)
import MsgPack.Derive (deriveMsgPack)
import GHC.Generics (Generic)
import Data.Text (Text)
data Person = Person
{ personName :: !Text
, personAge :: !Int
}
deriving stock (Show, Eq, Generic)
$(deriveMsgPack ''Person)
roundTrip :: Person -> Either String Person
roundTrip p =
case decodeMsgPack (encodeMsgPack p) of
Left err -> Left err
Right val -> Right val

For simple cases with no wire-format customization, Generic defaults also work: add deriving Generic and declare empty instance ToMsgPack Person and instance FromMsgPack Person declarations.

For RPC-style messaging, use the msgpack-RPC envelope helpers:

import MsgPack.RPC (RPCMessage(..), encodeRPC, decodeRPC)
import Data.Vector (Vector)
import qualified Data.Vector as V
import MsgPack.Value qualified as MV
call :: Text -> Vector MV.Value -> ByteString
call method params =
encodeRPC (RPCRequest 1 method params)
handle :: ByteString -> Either String RPCMessage
handle = decodeRPC

When processing a buffer that may contain multiple MessagePack values, decode one at a time and advance the cursor:

import MsgPack.Stream (decodeOneWithLeftover)
takeNext :: ByteString -> Either String (MV.Value, ByteString)
takeNext = decodeOneWithLeftover

To inspect or transform values without generated types, round-trip through the dynamic ADT:

import MsgPack.Value qualified as MV
import MsgPack.Encode (encode)
import MsgPack.Decode (decode)
dynamicRoundTrip :: MV.Value -> Either String MV.Value
dynamicRoundTrip val =
case decode (encode val) of
Left err -> Left err
Right out -> Right out

wireform-msgpack is ~4x faster than the Hackage msgpack package on both encode and decode for a typical record payload.

wireform-msgpack vs Hackage msgpack wireform-msgpack vs Hackage msgpack lower is better · ns · ghc-9.8.4 on darwin-aarch64, criterion 1.6.5 0 500 1000 1500 2000 297 1232 388 1775 encode decode wireform-msgpack msgpack wireform-msgpack vs Hackage msgpack lower is better · ns · ghc-9.8.4 on darwin-aarch64, criterion 1.6.5 0 500 1000 1500 2000 297 1232 388 1775 encode decode wireform-msgpack msgpack
Operationwireform-msgpackmsgpackratio
encode297 ns1232 ns4.14x
decode388 ns1775 ns4.58x

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

The chart and table above are regenerated by wireform-stats from wireform-msgpack/bench-results/summary/msgpack-vs-msgpack-haskell.json — the same source the README chart is built from.

ModulePurpose
MsgPack.ClassToMsgPack / FromMsgPack, encodeMsgPack, decodeMsgPack
MsgPack.Encode / MsgPack.DecodeLow-level wire encode and decode
MsgPack.ValueDynamic untyped Value ADT
MsgPack.StreamIncremental decode for framed input
MsgPack.RPCmsgpack-RPC request, response, and notification messages
MsgPack.JSONMessagePack ↔ JSON conversion
MsgPack.DeriveTemplate Haskell deriver with wireform-derive annotations