Skip to content

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.

  • Template Haskell deriving via deriveEDN from EDN.Derive, with wireform-derive annotations; 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 Value ADT for schema-less parsing
  • Direct encoding for writing into pre-allocated buffers

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 -> ByteString
toText pt = encodeEDN pt
fromText :: ByteString -> Either String Point
fromText bs = decodeEDN bs

For 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.Value
uuidTag 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 E
import Data.Aeson (Value)
bridgeToJson :: E.Value -> Value
bridgeToJson edn = toJSON edn
bridgeFromJson :: Value -> E.Value
bridgeFromJson json = fromJSON json
wireform-edn encode + decode (Person record, text format) wireform-edn encode + decode (Person record, text format) lower is better · ns · ghc-9.8.4 on darwin-aarch64, criterion 1.6.5 0 62500 125000 187500 250000 787 2010 84916 244287 single Person [Person] x 100 encode decode wireform-edn encode + decode (Person record, text format) lower is better · ns · ghc-9.8.4 on darwin-aarch64, criterion 1.6.5 0 62500 125000 187500 250000 787 2010 84916 244287 single Person [Person] x 100 encode decode
Operationencodedecoderatio
single Person787 ns2010 ns2.55x
[Person] x 10084916 ns244287 ns2.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.

ModulePurpose
EDN.ClassToEDN / FromEDN, encodeEDN, decodeEDN
EDN.Encode / EDN.DecodeLow-level text encode and decode
EDN.ValueDynamic Value ADT (keywords, symbols, sets, tags, …)
EDN.JSONEDN ↔ JSON conversion
EDN.DeriveTemplate Haskell deriver with wireform-derive annotations