You can adapt my solution for a similar problem. It uses scrap-your-boilerplate (SYB) generics. That answer explains the code in a fair bit of detail, so I won't explain it here. Below is sample code that works with your example. It supports data types with a mixture of Maybe String
and String
fields; other field types are ignored by getKey
.
Note that this is not going to be very efficient. If this is a core part of your application logic, rather than some debugging hack, you should consider rethinking your data types. Maybe you don't actually want a Haskell record type here, but rather some kind of bi-directional map (e.g., a Map k v
and Map v k
paired together accessed through a set of functions that keep them consistent).
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
import Data.Tuple
import Data.Generics
data HaskellRecord = HaskellRecord {
_key1 :: Maybe String
, _key2 :: Maybe String
, _key3 :: Maybe String
} deriving (Data)
-- Get field names (empty list if not record constructor)
getnames :: Data object => object -> [String]
getnames = constrFields . toConstr
-- Get field values as `Maybe String`s
getfields :: Data object => object -> [Maybe String]
getfields = gmapQ toString
-- Generic function to convert one field.
toString :: (Data a) => a -> Maybe String
toString = mkQ Nothing -- make a query with default value Nothing
id -- handle: id :: Maybe String -> Maybe String
`extQ` Just -- extend to: Just :: String -> Maybe String
-- Get field name/value pairs from any `Data` object.
getpairs :: Data object => object -> [(String, Maybe String)]
getpairs = zip <$> getnames <*> getfields
getKey :: Data record => String -> record -> Maybe String
getKey v = lookup (Just v) . map swap . getpairs
main :: IO ()
main = do
print $ getKey "value2" $
HaskellRecord {
_key1 = Just "value1",
_key2 = Just "value2",
_key3 = Just "value3"
}