polytropos-aeson
Copyright(c) 2026 The Polytropos Authors
LicenseEUPL-1.2 OR Apache-2.0
Maintainernadia.yvette.chambers@ik.me
Stabilityexperimental
Portabilitynon-portable (GHC extensions)
Safe HaskellNone
LanguageGHC2021

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 polytropos open 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 jsonBytes

Variants 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 resp
Synopsis

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.

Methods

encRecordFields :: Proxy r -> [Identity (Any :: Type)] -> [(Key, Value)] Source #

Instances

Instances details
AllToJSON ('[] :: [Pair Symbol Type]) Source # 
Instance details

Defined in Polytropos.Aeson

Methods

encRecordFields :: Proxy ('[] :: [Pair Symbol Type]) -> [Identity (Any :: Type)] -> [(Key, Value)] Source #

(KnownSymbol n, ToJSON a, AllToJSON r) => AllToJSON ((n ':= a) ': r) Source # 
Instance details

Defined in Polytropos.Aeson

Methods

encRecordFields :: Proxy ((n ':= a) ': r) -> [Identity (Any :: Type)] -> [(Key, Value)] Source #

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.

Methods

decRecordFields :: Proxy r -> Object -> Parser [Identity (Any :: Type)] Source #

Instances

Instances details
AllFromJSON ('[] :: [Pair Symbol Type]) Source # 
Instance details

Defined in Polytropos.Aeson

Methods

decRecordFields :: Proxy ('[] :: [Pair Symbol Type]) -> Object -> Parser [Identity (Any :: Type)] Source #

(KnownSymbol n, FromJSON a, AllFromJSON r) => AllFromJSON ((n ':= a) ': r) Source # 
Instance details

Defined in Polytropos.Aeson

Methods

decRecordFields :: Proxy ((n ':= a) ': r) -> Object -> Parser [Identity (Any :: Type)] Source #

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

Instances details
AllCtorsToJSON ('[] :: [Pair Symbol Row]) Source # 
Instance details

Defined in Polytropos.Aeson

Methods

encVariantTag :: Proxy ('[] :: [Pair Symbol Row]) -> Word -> [Identity (Any :: Type)] -> Value Source #

(KnownSymbol c, AllToJSON fs, AllCtorsToJSON s) => AllCtorsToJSON ((c ':= fs) ': s) Source # 
Instance details

Defined in Polytropos.Aeson

Methods

encVariantTag :: Proxy ((c ':= fs) ': s) -> Word -> [Identity (Any :: Type)] -> Value Source #

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

Instances details
AllCtorsFromJSON ('[] :: [Pair Symbol Row]) Source # 
Instance details

Defined in Polytropos.Aeson

Methods

decVariantVal :: Proxy ('[] :: [Pair Symbol Row]) -> Word -> Text -> Value -> Parser (Word, [Identity (Any :: Type)]) Source #

(KnownSymbol c, AllFromJSON fs, AllCtorsFromJSON s) => AllCtorsFromJSON ((c ':= fs) ': s) Source # 
Instance details

Defined in Polytropos.Aeson

Methods

decVariantVal :: Proxy ((c ':= fs) ': s) -> Word -> Text -> Value -> Parser (Word, [Identity (Any :: Type)]) Source #

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.

  1. AllFieldsAre is a closed type family asserting that every field in a row has the uniform type a.
  2. RowField and SumField compute the uniform payload type directly from the row or sum row, eliminating type ambiguity for custom instances.
  3. FieldNames reifies type-level Symbol field labels into runtime [String] keys in exact row order.
  4. AllCtorsHom reifies 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])].

Methods

ctorSpecs :: Proxy a -> Proxy s -> [(String, [String])] Source #

Instances

Instances details
AllCtorsHom a ('[] :: [Pair Symbol Row]) Source # 
Instance details

Defined in Polytropos.Aeson

Methods

ctorSpecs :: Proxy a -> Proxy ('[] :: [Pair Symbol Row]) -> [(String, [String])] Source #

(KnownSymbol c, FieldNames fs, AllFieldsAre a fs, AllCtorsHom a s) => AllCtorsHom a ((c ':= fs) ': s) Source # 
Instance details

Defined in Polytropos.Aeson

Methods

ctorSpecs :: Proxy a -> Proxy ((c ':= fs) ': s) -> [(String, [String])] Source #

class FieldNames (r :: Row) where Source #

The field names of a row, in exact row order, reified from type-level Symbol labels into runtime [String] keys.

Methods

fieldNames :: Proxy r -> [String] Source #

Instances

Instances details
FieldNames ('[] :: [Pair Symbol Type]) Source # 
Instance details

Defined in Polytropos.Aeson

Methods

fieldNames :: Proxy ('[] :: [Pair Symbol Type]) -> [String] Source #

(KnownSymbol n, FieldNames r) => FieldNames ((n ':= a) ': r) Source # 
Instance details

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 '[].

Equations

RowField ('[] :: [Pair Symbol Type]) = Void 
RowField ((n ':= a) ': rest) = a 

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 '[].

Equations

SumField ('[] :: [Pair Symbol Row]) = Void 
SumField ((c ':= fs) ': s) = RowField fs 

Orphan instances

AllFromJSON r => FromJSON (Record r) Source # 
Instance details

AllCtorsFromJSON s => FromJSON (Variant s) Source # 
Instance details

AllToJSON r => ToJSON (Record r) Source # 
Instance details

AllCtorsToJSON s => ToJSON (Variant s) Source # 
Instance details