Skip to content

Instantly share code, notes, and snippets.

@someodd
Created September 17, 2024 21:04
Show Gist options
  • Save someodd/b717a1b9679e631463bd262338b1d932 to your computer and use it in GitHub Desktop.
Save someodd/b717a1b9679e631463bd262338b1d932 to your computer and use it in GitHub Desktop.
Haskell: data type with record fields to text pairs
import Data.Data (Data, gmapQ, toConstr, cast)
import Data.Maybe (catMaybes)
import Data.Data (constrFields)
import qualified Data.Text as Text
{- | Convert any generic data type with record syntax to a list
of key-value `Text` pairs.
Uses some Haskell magic.
-}
genericToPairs :: (Data a) => a -> [(Text.Text, Text.Text)]
genericToPairs record = catMaybes $ zipWith extractField (map Text.pack fieldNames) fields
where
-- Get the list of field names from the data type
fieldNames = constrFields $ toConstr record
fields = gmapQ castToMaybe record
-- Attempt to cast each field to `Maybe`
castToMaybe :: (Data b) => b -> Maybe (Maybe Text.Text)
castToMaybe field = cast field :: Maybe (Maybe Text.Text)
-- Safely extract fields of type `Maybe`
extractField :: Text.Text -> Maybe (Maybe Text.Text) -> Maybe (Text.Text, Text.Text)
extractField name (Just (Just value)) = Just (name, value)
extractField _ _ = Nothing
@someodd
Copy link
Author

someodd commented Sep 17, 2024

I'm using some language extensions that I think may be required to get this to work. And the data types you convert have to derive something like deriving (Show, Data, Typeable, Generic, FromJSON).

For example:

data FrontMatter = FrontMatter {
    title :: Maybe Text.Text,
    phlog :: Maybe Bool,
    date :: Maybe Text.Text,
    tags :: Maybe [Text.Text],
    draft :: Maybe Bool,
    skipMarkdown :: Maybe Bool,
    skipTemplating :: Maybe Bool,
    parent :: Maybe Text.Text,
    -- ^ If this is set, then the document will be rendered as a partial/child of the
    -- defined parent.
    variables :: Maybe (Map.Map Text.Text Text.Text)
    } deriving (Show, Data, Typeable, Generic, FromJSON)

Here are the language extensions:

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE DeriveDataTypeable #-}

... and:

  default-extensions:
    - OverloadedStrings
    - DeriveGeneric
    - DeriveAnyClass
    - OverloadedRecordDot
    - DuplicateRecordFields

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment