2

I'm writing a JSON service for JIRA, and I've come across a requirement that conflicts with Haskell's namespace. I have this record

data Assignee = Assignee {name :: Text} deriving Generic
instance ToJSON Assignee

This is dictated by what JIRA wants, unfortunately it wants the same field for a different object.

data Reporter = Reporter {name :: Text} deriving Generic
instance ToJSON Reporter

I see a few options:

  1. Maybe I can circumvent the compiler's complaining with template Haskell, but how?
  2. I could simply not have a Reporter record, and change the reporter field with a seperate service after the ticket has been created. That I know how to do, but is it the best way?
  3. Create the JSON object by hand, but I form it from this record:

      data Fields = Fields 
              { project     :: HashMap Key Project
              , summary     :: Text
              , issuetype   :: HashMap Name Task
              , versions    :: [HashMap Name Text]
              , description :: Text
              , assignee    :: Assignee
              } deriving (Generic)
    

The thought of making this by hand gives me the wiggins. If I must I will.

So, my question now is, if there is no other better way than the ones I've presented, which of these is the best course of action?

Johannes Kuhn
  • 14,778
  • 4
  • 49
  • 73

2 Answers2

6

The most straightforward way is to enable the -XDisambiguateRecordFields extension.

leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
5

If DisambiguateRecordFields and/or keeping the records in separate modules works for you, that's excellent.

If not, then a common way to work around this problem is to prefix record field labels in some way, to disambiguate:

data Assignee = Assignee {assigneeName :: Text} deriving Generic
data Reporter = Reporter {reporterName :: Text} deriving Generic

You can still use GHC Generics to derive the JSON translation functions, but you have to configure it so that the field labels are changed, for example like this:

stripPrefix :: Eq a => [a] -> [a] -> [a]
stripPrefix p x = case splitAt (length p) x of
  (y, z)
    | y == p    -> z
    | otherwise -> x

lower :: String -> String
lower []       = []
lower (x : xs) = toLower x : xs

stripPrefixOptions :: String -> Options
stripPrefixOptions p = defaultOptions {
  fieldLabelModifier = lower . stripPrefix p
}

Then you can say:

data Assignee = Assignee {assigneeName :: Text} deriving Generic
instance ToJSON Assignee where
  toJSON = genericToJSON (stripPrefixOptions "assignee")

data Reporter = Reporter {reporterName :: Text} deriving Generic
instance ToJSON Reporter where
  toJSON = genericToJSON (stripPrefixOptions "reporter")

Testing in GHCi:

GHCi> > encode (Assignee { assigneeName = "foo" })
"{\"name\":\"foo\"}"
kosmikus
  • 19,549
  • 3
  • 51
  • 66