← ClaudeAtlas

haskell-jsonlisted

Serialize and deserialize Haskell types to JSON with Aeson. Covers the TaggedObject discriminator pattern for sum types, fieldLabelModifier to strip record prefixes, manual ToJSON/FromJSON for constructors without record syntax, omitNothingFields, and the generic vs manual decision. Use when writing Aeson instances, customizing JSON shape for sum types, or making serialization consistent across a codebase.
ivelten/claude-haskell-skills · ★ 0 · Data & Documents · score 60
Install: claude install-skill ivelten/claude-haskell-skills
# JSON Serialization with Aeson ## Generic vs manual — decide upfront | Situation | Approach | |---|---| | All constructors use record syntax | Generic with `Options` | | Any constructor lacks record syntax | Manual instances | | The JSON shape diverges significantly from the type structure | Manual instances | | Simple product type with no sum | Generic with `defaultOptions` + `fieldLabelModifier` | Mixing approaches in one type is a maintenance trap. Pick one and apply it consistently to `ToJSON` and `FromJSON`. --- ## 1. Sum types with record syntax — generic `TaggedObject` When every constructor uses record syntax, `genericToJSON` with `TaggedObject` produces a flat object with a discriminator and inlined fields. ```haskell import Data.Aeson (ToJSON(..), FromJSON(..), genericToJSON, genericParseJSON, defaultOptions) import Data.Aeson.Types (Options(..), SumEncoding(..), camelTo2) data Payload = TextPayload { payloadText :: Text } | ImagePayload { payloadUrl :: Text, payloadWidth :: Int, payloadHeight :: Int } | FilePayload { payloadFileId :: FileId, payloadMimeType :: Text } deriving stock (Show, Eq, Generic) payloadOptions :: Options payloadOptions = defaultOptions { sumEncoding = TaggedObject { tagFieldName = "type", contentsFieldName = "data" } , constructorTagModifier = camelTo2 '_' -- "ImagePayload" → "image_payload" , fieldLabelModifier = camelTo2 '_' . dropPrefix "payload" } instance ToJSON Payload where toJSON = gener