6

I'm writing a data record to marshall a JIRA JSON object. The problem is, multiple objects have the same labels for name/value pairs. For example :

(returned from curl and formatted)

{"expand":"schema,names"
,"startAt":0
,"maxResults":2
,"total":74
,"issues":[
           {"expand":"editmeta,renderedFields,transitions,changelog,operations"
           ,"id":"183614"
           ,"self":"https://10.64.16.44/rest/api/latest/issue/183614"
           ,"key":"BNAP-339"
           ,"fields":{"versions":[
                                  {"self":"https://10.64.16.44/rest/api/2/version/28240"  
                                  ,"id":"28240"
                                  ,"name":"2012-12-07"
                                  ,"archived":false
                                  ,"released":false
                                  }
                                 ]
                     ,"status":{"self":"https://10.64.16.44/rest/api/2/status/1"
                               ,"description":"The issue is open and ready for the assignee to start work on it."
                               ,"iconUrl":"https://10.64.16.44/images/icons/status_open.gif"
                               ,"name":"Open"
                               ,"id":"1"
                               }
                     ,"description":"Do Re Mi Fa"
                     ,"resolution":null
                     }
           }
          ]

when I construct the problematic corresponding Haskell data records I get:

data Issue = Issue {expand :: String
                   ,id :: String
                   ,self :: String
                   ,key :: String
                   ,fields :: Fields
                   } deriving Generic


data Version = Version {self :: String
                       ,id :: String
                       ,name :: String
                       ,archived :: Bool
                       ,released :: Bool
                       } deriving Generic

and 'id' and 'self' will clash. It occured to me I could solve this by just changing the names in the records and fixing it with a manually-created FromJSON instance. Any alternative solutions would be welcome.

2 Answers2

9

I solve this in protocol buffers by putting things like Issue and Version in separate files in the same hierarchy.

Haskell only uses separate modules to control namespaces, so this is the orthodox solution.

Much much fancier: use type classes to define available name:

class Has'self a b | a -> bwhere
   get'self :: a -> b
   set'self :: b -> a -> b

instance Has'self Issue String where ...
instance Has'self Version String where ....

EDIT: The comments below remind me to give more verbose advice. Do not use Has'self like solutions -- those that have gone that road report it gets ugly. I can vouch for the path of separate modules.

PS: Perhaps you can use the lens library for your fields!

Chris Kuklewicz
  • 8,123
  • 22
  • 33
  • 7
    Note that `HasFoobar` style typeclasses are almost always a terrible idea when it comes to writing clean, well-structured Haskell code. But when trying to match the structure of non-Haskell code for interop purposes, if the other side relies heavily on overloaded functions and/or subtype hierarchies, there may not be any better approach. – C. A. McCann Dec 12 '12 at 22:05
  • I'm upvoting the part where you recommended separate files. Type classes make for a poor namespacing solution since they are very difficult for users to reason about the types and will silently fail and do the wrong thing when applied to the wrong type. – Gabriella Gonzalez Dec 12 '12 at 22:41
2

Another alternative that should work is to use a single data type that contains the distinct records. For example, the following data type is capable of representing your value without field conflicts:

import Prelude hiding (id)

data JIRA = JIRA
  { expand :: String
  , startAt :: Int
  , maxResults :: Int
  , total :: Int
  , issues :: [JIRA]
  } | Issue 
  { expand :: String
  , id :: Int
  , self :: String
  , key :: String
  , fields :: JIRA
  } | Field
  { versions :: [JIRA]
  , status :: JIRA
  , description :: String
  , resolution :: Maybe String
  } | Version
  { self :: String
  , id :: Int
  , name :: String
  , archived :: Bool
  , released :: Bool
  } | Status
  { self :: String
  , description :: String
  , iconUrl :: String
  , name :: String
  , id :: Int
  }


yourExample = JIRA
  { expand = "schema, names"
  , startAt = 0
  , maxResults = 2
  , total = 74
  , issues = [ Issue
               { expand = "editmeta, etc..."
               , id = 12345
               , self = "https://xyz"
               , key = "BLAH"
               , fields = Field
                          { versions = [ Version
                                          { self = "https://foobar"
                                          , id = 1234
                                          , name = "quux"
                                          , archived = False
                                          , released = False
                                          }
                                       ]
                          , status = Status
                                     { self = "https://etc"
                                     , description = "issue"
                                     , iconUrl = "https://iconurl"
                                     , name = "open"
                                     , id = 1
                                     }
                          , description = "another description"
                          , resolution = Nothing
                          }
                 }
               ]
  }
user1891025
  • 131
  • 3