wireform-edn
wireform-edn implements Extensible Data Notation (EDN), the text-based data
format used by Clojure and many ClojureScript tools. EDN is human-readable
like JSON but adds keywords, symbols, sets, tagged literals, and richer
numeric types. Use this package when you exchange data with Clojure services,
read EDN configuration files, or need a text format that maps naturally to
Clojure’s data model.
EDN is a text format, not a binary codec. Payloads are UTF-8 encoded documents rather than compact byte streams.
Key features
Section titled “Key features”- Template Haskell deriving via
deriveEDNfromEDN.Derive, withwireform-deriveannotations; Generic defaults (empty instances) work for simple uncustomized records - Clojure literals including keywords, symbols, sets, and tagged values
- JSON bridge for converting between EDN and Aeson
Value - Dynamic values via the untyped
ValueADT for schema-less parsing - Direct encoding for writing into pre-allocated buffers
Basic usage
Section titled “Basic usage”Define a record and derive EDN codecs. Records encode as EDN maps with keyword keys:
{-# LANGUAGE DeriveGeneric #-}{-# LANGUAGE TemplateHaskell #-}module Point where
import EDN.Class (ToEDN, FromEDN, encodeEDN, decodeEDN)import EDN.Derive (deriveEDN)import GHC.Generics (Generic)
data Point = Point { pointX :: !Double , pointY :: !Double } deriving stock (Show, Eq, Generic)
$(deriveEDN ''Point)
toText :: Point -> ByteStringtoText pt = encodeEDN pt
fromText :: ByteString -> Either String PointfromText bs = decodeEDN bsFor simple records with no custom wire naming, Generic defaults also work:
declare empty instance ToEDN Point and instance FromEDN Point after
deriving stock (Show, Eq, Generic). Field names go to the wire verbatim and
annotations are not supported.
Tagged literals use EDN’s #tag reader syntax. Build them with the dynamic
ADT when you need custom tags:
import EDN.Value qualified as E
uuidTag :: Text -> E.ValueuuidTag s = E.Tagged "" "uuid" (E.String s)Convert between EDN and JSON when bridging to HTTP APIs or Aeson-based tools:
import EDN.JSON (toJSON, fromJSON)import EDN.Value qualified as Eimport Data.Aeson (Value)
bridgeToJson :: E.Value -> ValuebridgeToJson edn = toJSON edn
bridgeFromJson :: Value -> E.ValuebridgeFromJson json = fromJSON jsonPerformance
Section titled “Performance”Encode/decode (text format)
Section titled “Encode/decode (text format)”| Operation | encode | decode | ratio |
|---|---|---|---|
| single Person | 787 ns | 2010 ns | 2.55x |
| [Person] x 100 | 84916 ns | 244287 ns | 2.88x |
Last run 2026-06-27 11:35:54 UTC. ghc-9.8.4 on darwin-aarch64, criterion 1.6.5.
EDN is a text format, so encode/decode is naturally slower than binary formats. Single-record encode is still sub-microsecond; decode is under 2 µs.
The chart and table above are regenerated by wireform-stats from wireform-edn/bench-results/summary/edn-encode-decode.json — the same source the README chart is built from.
Notable modules
Section titled “Notable modules”| Module | Purpose |
|---|---|
EDN.Class | ToEDN / FromEDN, encodeEDN, decodeEDN |
EDN.Encode / EDN.Decode | Low-level text encode and decode |
EDN.Value | Dynamic Value ADT (keywords, symbols, sets, tags, …) |
EDN.JSON | EDN ↔ JSON conversion |
EDN.Derive | Template Haskell deriver with wireform-derive annotations |