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.
Key features
Section titled “Key features”- Template Haskell deriving via
deriveIonfromIon.Derive, withwireform-deriveannotations; 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
ValueADT for exploratory processing
Binary compatibility
Section titled “Binary compatibility”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.
Basic usage
Section titled “Basic usage”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 -> ByteStringpublish m = encodeIon m
consume :: ByteString -> Either String Metricconsume bs = decodeIon bsFor 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.
Performance
Section titled “Performance”Encode/decode (binary Ion)
Section titled “Encode/decode (binary Ion)”| Operation | encode | decode | ratio |
|---|---|---|---|
| single Person | 554 ns | 450 ns | 0.81x |
| [Person] x 100 | 10608 ns | 17260 ns | 1.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.
Notable modules
Section titled “Notable modules”| Module | Purpose |
|---|---|
Ion.Class | ToIon / FromIon, encodeIon, decodeIon |
Ion.Encode / Ion.Decode | Low-level binary Ion encode and decode |
Ion.Value | Dynamic untyped Value ADT |
Ion.SchemaLang | Ion Schema Language parser |
Ion.ISLSchema / Ion.ISLCodeGen | ISL AST and Haskell code generator |
Ion.QQ | QuasiQuoter for Ion text literals |
Ion.Derive | Template Haskell deriver with wireform-derive annotations |
| Operation | wireform-ion | ion-rs | ratio |
|---|---|---|---|
| encode Person | 515 ns | 957 ns | 0.54x |
| decode Person | 387 ns | 1729 ns | 0.22x |
| encode [Person] x 100 | 5719 ns | 6413 ns | 0.89x |
| decode [Person] x 100 | 15559 ns | 38016 ns | 0.41x |
| encode symbols x 100 | 16061 ns | 17538 ns | 0.92x |
| decode symbols x 100 | 30877 ns | 89302 ns | 0.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.
Generated typed records
Section titled “Generated typed records”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.
| Operation | wireform-ion | ion-rs serde | ratio |
|---|---|---|---|
| encode Person | 554 ns | 1153 ns | 0.48x |
| decode Person | 450 ns | 1731 ns | 0.26x |
| encode [Person] x 100 | 10608 ns | 17498 ns | 0.61x |
| decode [Person] x 100 | 17260 ns | 32871 ns | 0.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.