| Copyright | (c) 2026 The Polytropos Authors |
|---|---|
| License | EUPL-1.2 OR Apache-2.0 |
| Maintainer | nadia.yvette.chambers@ik.me |
| Stability | experimental |
| Portability | non-portable (GHC extensions) |
| Safe Haskell | None |
| Language | GHC2021 |
Polytropos.Aeson
Description
Linear \(O(N)\) ToJSON and FromJSON instances for heterogeneous and
homogeneous Record and Variant types without standard GHC.Generics
compile slowdowns: the codecs unroll the flat Rep payload directly into
a JSON object and back, with no generic-representation tree anywhere.
Architectural Overview & Rationale
Encoding Structure
- A record encodes as an anonymous JSON object with one key per field symbol, in row order.
- A variant encodes as a single-member tagged object
{"ctor": fields}, where the member key is the active constructor symbol and the value is the JSON object of that constructor's payload fields (or empty object for a nullary constructor).
Truly Heterogeneous Row Support
Unlike satellites restricted to homogeneous types, polytropos-aeson provides genuine
heterogeneous serialization across open rows via AllToJSON and AllFromJSON:
every field requires ToJSON / FromJSON at its own concrete type, so rows
mixing String, Int, Bool, custom types, or nested rows serialize and deserialize
directly without nominal intermediate wrappers.
Why Avoid GHC.Generics Trees for JSON
Standard aeson deriving walks a nested binary tree of M1, (:*:), and (:+:) constructors.
On large schemas (30+ fields or constructors), this causes significant compile-time memory
blow-up and runtime intermediate allocation overhead. polytropos-aeson walks the flat array of
payload slots in a single tight loop directly into aeson's KeyMap.
When to Use This Module
- When serializing or deserializing
polytroposopen records and variants to and from JSON.
Worked Example
Records are most conveniently constructed using the REC { ... } syntax enabled
by the polytropos source plugin (via -fplugin=Polytropos.Plugin):
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
{-# OPTIONS_GHC -fplugin=Polytropos.Plugin #-}
import Data.Aeson (encode, decode)
import Polytropos
import Polytropos.Aeson
-- A genuinely heterogeneous record mixing String, Int, and Bool:
type Config = Record '["host" ':= String, "port" ':= Int, "active" ':= Bool]
cfg :: Config
cfg = REC { host = "localhost", port = 8080, active = True }
-- Encodes to JSON: {"active":true,"host":"localhost","port":8080}
jsonBytes = encode cfg
-- Decodes back to Config:
Just cfg' = decode @Config jsonBytesVariants with heterogeneous constructors serialize into single-member tagged objects:
type Response = Variant '["Ok" ':= '["body" ':= String, "status" ':= Int], "Err" ':= '["reason" ':= String]]
resp :: Response
resp = VAR { Ok = REC { body = "server ready", status = 200 } }
-- Encodes to JSON: {"Ok":{"body":"server ready","status":200}}
respBytes = encode respSynopsis
- class AllToJSON (r :: Row) where
- encRecordFields :: Proxy r -> [Identity (Any :: Type)] -> [(Key, Value)]
- class AllFromJSON (r :: Row) where
- decRecordFields :: Proxy r -> Object -> Parser [Identity (Any :: Type)]
- class AllCtorsToJSON (s :: SumRow) where
- encVariantTag :: Proxy s -> Word -> [Identity (Any :: Type)] -> Value
- class AllCtorsFromJSON (s :: SumRow) where
- decVariantVal :: Proxy s -> Word -> Text -> Value -> Parser (Word, [Identity (Any :: Type)])
- type family AllFieldsAre a (r :: Row) where ...
- class AllCtorsHom a (s :: SumRow) where
- ctorSpecs :: Proxy a -> Proxy s -> [(String, [String])]
- class FieldNames (r :: Row) where
- fieldNames :: Proxy r -> [String]
- type family RowField (r :: Row) where ...
- type family SumField (s :: SumRow) where ...
Heterogeneous Row Codecs
class AllToJSON (r :: Row) where Source #
Evidence that every field of the row r has a ToJSON instance
at that field's own type. Serializes the erased payload slots in row order.
class AllFromJSON (r :: Row) where Source #
Evidence that every field of the row r has a FromJSON instance
at that field's own type. Decodes each field by name from the JSON object.
Instances
| AllFromJSON ('[] :: [Pair Symbol Type]) Source # | |
Defined in Polytropos.Aeson | |
| (KnownSymbol n, FromJSON a, AllFromJSON r) => AllFromJSON ((n ':= a) ': r) Source # | |
Defined in Polytropos.Aeson | |
class AllCtorsToJSON (s :: SumRow) where Source #
Evidence that every constructor of the sum row s has a heterogeneous
field row satisfying AllToJSON, paired with its constructor tag index.
Methods
encVariantTag :: Proxy s -> Word -> [Identity (Any :: Type)] -> Value Source #
Instances
| AllCtorsToJSON ('[] :: [Pair Symbol Row]) Source # | |
Defined in Polytropos.Aeson | |
| (KnownSymbol c, AllToJSON fs, AllCtorsToJSON s) => AllCtorsToJSON ((c ':= fs) ': s) Source # | |
Defined in Polytropos.Aeson | |
class AllCtorsFromJSON (s :: SumRow) where Source #
Evidence that every constructor of the sum row s has a heterogeneous
field row satisfying AllFromJSON, paired with its constructor tag index.
Methods
decVariantVal :: Proxy s -> Word -> Text -> Value -> Parser (Word, [Identity (Any :: Type)]) Source #
Instances
| AllCtorsFromJSON ('[] :: [Pair Symbol Row]) Source # | |
Defined in Polytropos.Aeson | |
| (KnownSymbol c, AllFromJSON fs, AllCtorsFromJSON s) => AllCtorsFromJSON ((c ':= fs) ': s) Source # | |
Defined in Polytropos.Aeson | |
Homogeneity Evidence and Row Introspection
Homogeneity Evidence and Introspection Helpers
In addition to the primary heterogeneous codecs (AllToJSON, AllFromJSON),
this module provides closed type families and reflection classes for
inspecting row metadata and enforcing uniform payload types.
AllFieldsAreis a closed type family asserting that every field in a row has the uniform typea.RowFieldandSumFieldcompute the uniform payload type directly from the row or sum row, eliminating type ambiguity for custom instances.FieldNamesreifies type-levelSymbolfield labels into runtime[keys in exact row order.String]AllCtorsHomreifies the constructor topology of a homogeneous sum row into[(String, [String])](mapping each constructor name to its list of field names).
type family AllFieldsAre a (r :: Row) where ... Source #
Evidence that every field of the row r has type a.
Equations
| AllFieldsAre _1 ('[] :: [Pair Symbol Type]) = () | |
| AllFieldsAre a ((n ':= a) ': rest) = AllFieldsAre a rest |
class AllCtorsHom a (s :: SumRow) where Source #
Reflection dictionary for homogeneous sum rows (variants).
Reifies the full constructor topology into a runtime list pairing each
constructor symbol with its ordered field symbols: [(String, [String])].
Instances
| AllCtorsHom a ('[] :: [Pair Symbol Row]) Source # | |
| (KnownSymbol c, FieldNames fs, AllFieldsAre a fs, AllCtorsHom a s) => AllCtorsHom a ((c ':= fs) ': s) Source # | |
Defined in Polytropos.Aeson | |
class FieldNames (r :: Row) where Source #
The field names of a row, in exact row order, reified from type-level
Symbol labels into runtime [ keys.String]
Methods
fieldNames :: Proxy r -> [String] Source #
Instances
| FieldNames ('[] :: [Pair Symbol Type]) Source # | |
Defined in Polytropos.Aeson Methods fieldNames :: Proxy ('[] :: [Pair Symbol Type]) -> [String] Source # | |
| (KnownSymbol n, FieldNames r) => FieldNames ((n ':= a) ': r) Source # | |
Defined in Polytropos.Aeson Methods fieldNames :: Proxy ((n ':= a) ': r) -> [String] Source # | |
type family RowField (r :: Row) where ... Source #
Extracts the uniform field type of a homogeneous row: the type of every
field, or Void for the empty row '[].
type family SumField (s :: SumRow) where ... Source #
Extracts the uniform field type shared by every constructor of a homogeneous
sum row, or Void for the empty sum '[].