Skip to content

wireform-ion

wireform-ion implements Amazon Ion, a superset of JSON designed for high-volume structured data in AWS services such as QLDB and Ion-based data lakes. Ion supports symbols, timestamps, decimals, and a rich type system in both text and binary forms. Use this package when you exchange Ion payloads with AWS tooling or need schema-checked Ion documents in Haskell.

  • Template Haskell deriving via deriveIon from Ion.Derive, with wireform-derive annotations; Generic defaults (empty instances) work for simple uncustomized records
  • Ion Schema Language (ISL) parser for declarative schema definitions
  • Schema-driven codegen that emits Haskell types and codec stubs from ISL
  • QuasiQuoter for embedding Ion Schema Language declarations at compile time
  • Dynamic values via the untyped Value ADT for exploratory processing

The binary codec uses system symbol IDs and local symbol tables for field names, symbols and ordered annotations. It handles local table replacement, append imports, version-marker resets, duplicate/gap slots and padding. The dynamic model supports null, bool, Int64, Double, string, symbol, blob, clob, list, struct and annotations. Decimal, timestamp, s-expression, typed-null and unknown-text symbol values return Left.

There is no shared-symbol catalog API. Imports with max_id reserve their unknown SID range so later local symbols retain their correct IDs; an unknown imported symbol or an import without max_id returns Left. Earlier nonstandard symbol-bearing bytes, which wrote text instead of symbol IDs, are not a supported interchange format.

Derive Ion codecs for a record and round-trip through binary Ion:

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE TemplateHaskell #-}
module Metrics where
import Ion.Class (ToIon, FromIon, encodeIon, decodeIon)
import Ion.Derive (deriveIon)
import GHC.Generics (Generic)
import Data.Text (Text)
data Metric = Metric
{ metricName :: !Text
, metricValue :: !Double
}
deriving stock (Show, Eq, Generic)
$(deriveIon ''Metric)
publish :: Metric -> ByteString
publish m = encodeIon m
consume :: ByteString -> Either String Metric
consume bs = decodeIon bs

For simple records with no custom wire naming, Generic defaults also work: declare empty instance ToIon Metric and instance FromIon Metric after deriving stock (Show, Eq, Generic). Field names go to the wire verbatim and annotations are not supported.

For schema-first workflows, define types in ISL and splice them at compile time with the QuasiQuoter:

{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
module MetricsSchema where
import Ion.QQ (isl)
[isl|
type::{ name: Metric, fields: { name: string, value: float } }
|]

This parses the ISL definition and generates a Haskell record with ToIon and FromIon instances that match the schema field names and types.

wireform-ion encode + decode (Person record) wireform-ion encode + decode (Person record) lower is better · ns · GHC 9.10.3 -O2 -fasm on darwin-aarch64, Criterion 1.6.5.0; TH-generated Person codecs 0 5000 10000 15000 20000 554 450 10608 17260 single Person [Person] x 100 encode decode wireform-ion encode + decode (Person record) lower is better · ns · GHC 9.10.3 -O2 -fasm on darwin-aarch64, Criterion 1.6.5.0; TH-generated Person codecs 0 5000 10000 15000 20000 554 450 10608 17260 single Person [Person] x 100 encode decode
Operationencodedecoderatio
single Person554 ns450 ns0.81x
[Person] x 10010608 ns17260 ns1.63x

Last run 2026-11-04 13:05:38 UTC. GHC 9.10.3 -O2 -fasm on darwin-aarch64, Criterion 1.6.5.0; TH-generated Person codecs.

Sub-microsecond single-record performance. Batch operations scale linearly.

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

ModulePurpose
Ion.ClassToIon / FromIon, encodeIon, decodeIon
Ion.Encode / Ion.DecodeLow-level binary Ion encode and decode
Ion.ValueDynamic untyped Value ADT
Ion.SchemaLangIon Schema Language parser
Ion.ISLSchema / Ion.ISLCodeGenISL AST and Haskell code generator
Ion.QQQuasiQuoter for Ion text literals
Ion.DeriveTemplate Haskell deriver with wireform-derive annotations
Ion 1.0 binary codecs — owned dynamic values Ion 1.0 binary codecs — owned dynamic values lower is better · ns · GHC 9.10.3 -O2 -fasm, Criterion 1.6.5.0; ion-rs 1.1.0, criterion.rs 0.7; darwin-aarch64 0 25000 50000 75000 100000 515 957 387 1729 5719 6413 15559 38016 16061 17538 30877 89302 encode Person decode Person encode [Person] x 100 decode [Person] x 100 encode symbols x 100 decode symbols x 100 wireform-ion ion-rs Ion 1.0 binary codecs — owned dynamic values lower is better · ns · GHC 9.10.3 -O2 -fasm, Criterion 1.6.5.0; ion-rs 1.1.0, criterion.rs 0.7; darwin-aarch64 0 25000 50000 75000 100000 515 957 387 1729 5719 6413 15559 38016 16061 17538 30877 89302 encode Person decode Person encode [Person] x 100 decode [Person] x 100 encode symbols x 100 decode symbols x 100 wireform-ion ion-rs
Operationwireform-ionion-rsratio
encode Person515 ns957 ns0.54x
decode Person387 ns1729 ns0.22x
encode [Person] x 1005719 ns6413 ns0.89x
decode [Person] x 10015559 ns38016 ns0.41x
encode symbols x 10016061 ns17538 ns0.92x
decode symbols x 10030877 ns89302 ns0.35x

Last run 2026-11-04 13:06:38 UTC. GHC 9.10.3 -O2 -fasm, Criterion 1.6.5.0; ion-rs 1.1.0, criterion.rs 0.7; darwin-aarch64.

The typed Rust reference uses ion-rs 1.1.0’s experimental serde bridge; this is separate from the owned dynamic-value codec comparison. Fixture construction is outside the clock on both sides.

Ion 1.0 generated typed record codecs Ion 1.0 generated typed record codecs lower is better · ns · GHC 9.10.3 -O2 -fasm, TH-derived records, Criterion 1.6.5.0; ion-rs 1.1.0 experimental-serde, serde derive, criterion.rs 0.7; darwin-aarch64 0 12500 25000 37500 50000 554 1153 450 1731 10608 17498 17260 32871 encode Person decode Person encode [Person] x 100 decode [Person] x 100 wireform-ion ion-rs serde Ion 1.0 generated typed record codecs lower is better · ns · GHC 9.10.3 -O2 -fasm, TH-derived records, Criterion 1.6.5.0; ion-rs 1.1.0 experimental-serde, serde derive, criterion.rs 0.7; darwin-aarch64 0 12500 25000 37500 50000 554 1153 450 1731 10608 17498 17260 32871 encode Person decode Person encode [Person] x 100 decode [Person] x 100 wireform-ion ion-rs serde
Operationwireform-ionion-rs serderatio
encode Person554 ns1153 ns0.48x
decode Person450 ns1731 ns0.26x
encode [Person] x 10010608 ns17498 ns0.61x
decode [Person] x 10017260 ns32871 ns0.53x

Last run 2026-11-04 13:07:17 UTC. GHC 9.10.3 -O2 -fasm, TH-derived records, Criterion 1.6.5.0; ion-rs 1.1.0 experimental-serde, serde derive, criterion.rs 0.7; darwin-aarch64.