title: How can I improve the ease of working with JSON in Haskell?
body: <p>Haskell has become useful as a web language (thanks <code>Servant</code>!), and yet JSON is still so painful for me so I must be doing something wrong (?)</p>
  
  <p>I hear JSON mentioned as a pain point enough, and the responses I've heard revolve around "use PureScript", "wait for Sub/Row Typing", "use esoterica, like Vinyl", "Aeson + just deal with the explosion of boiler plate data types".</p>
  
  <p>As an (unfair) reference point, I really enjoy the ease of Clojure's JSON "story" (of course, it's a dynamic language, and has it's tradeoffs for which I still prefer Haskell).</p>
  
  <p>Here's an example I've been staring at for an hour.</p>
  
  <pre><code>{
      "access_token": "xxx",
      "batch": [
          {"method":"GET", "name":"oldmsg", "relative_url": "&lt;MESSAGE-ID&gt;?fields=from,message,id"},
          {"method":"GET", "name":"imp", "relative_url": "{result=oldmsg:$.from.id}?fields=impersonate_token"},
          {"method":"POST", "name":"newmsg", "relative_url": "&lt;GROUP-ID&gt;/feed?access_token={result=imp:$.impersonate_token}", "body":"message={result=oldmsg:$.message}"},
          {"method":"POST", "name":"oldcomment", "relative_url": "{result=oldmsg:$.id}/comments", "body":"message=Post moved to https://workplace.facebook.com/{result=newmsg:$.id}"},
          {"method":"POST", "name":"newcomment", "relative_url": "{result=newmsg:$.id}/comments", "body":"message=Post moved from https://workplace.facebook.com/{result=oldmsg:$.id}"},
      ]
  }
  </code></pre>
  
  <p>I need to POST this to FB workplace, which will copy a message to a new group, and comment a link on both, linking to each other.</p>
  
  <p>My first attempt looked something like: </p>
  
  <pre><code>data BatchReq = BatchReq {
    method :: Text
    , name :: Text
    , relativeUrl :: Text
    , body :: Maybe Text
    }
  
  data BatchReqs = BatchReqs {
    accessToken :: Text
    , batch :: [BatchReq]
    }
  
  softMove tok msgId= BatchReqs tok [
    BatchReq "GET" "oldmsg" (msgId `append` "?fields=from,message,id") Nothing
    ...
    ]
  </code></pre>
  
  <p>That's painfully rigid, and dealing with <code>Maybe</code>s all over is uncomfortable. Is <code>Nothing</code> a JSON <code>null</code>? Or should the field be absent? Then I worried about deriving the Aeson instances, and had to figure out how to convert eg <code>relativeUrl</code> to <code>relative_url</code>. Then I added an endpoint, and now I have name clashes. <code>DuplicateRecordFields</code>! But wait, that causes so many problems elsewhere. So update the data type to use eg <code>batchReqRelativeUrl</code>, and peel that off when deriving instances using <code>Typeable</code>s and <code>Proxy</code>s. Then I needed to add endpoints, and or massage the shape of that rigid data type for which I added more datapoints, trying to not let the "tyranny of small differences" bloat my data types too much.</p>
  
  <p>At this point, I was largely <em>consuming</em> JSON, so decided a "dynamic" thing would be to use <code>lens</code>es. So, to drill into a JSON field holding a group id I did:</p>
  
  <pre><code>filteredBy :: (Choice p, Applicative f) =&gt;  (a -&gt; Bool) -&gt; Getting (Data.Monoid.First a) s a -&gt; Optic' p f s s
  filteredBy cond lens = filtered (\x -&gt; maybe False cond (x ^? lens))
  
  -- the group to which to move the message
  groupId :: AsValue s =&gt; s -&gt; AppM Text
  groupId json  = maybe (error500 "couldn't find group id in json.")
                  pure (json ^? l)
    where l = changeValue . key "message_tags" . values . filteredBy (== "group") (key "type") . key "id" . _String
  </code></pre>
  
  <p>That's rather heavy to access fields. But I also need to generate payloads, and I'm not skilled enough to see how lenses will be nice for that. Circling around to the motivating batch request, I've come up with a "dynamic" way of writing these payloads. It could be simplified with helper fns, but, I'm not even sure how much nicer it'll get with that.</p>
  
  <pre><code>softMove :: Text -&gt; Text -&gt; Text -&gt; Value
  softMove accessToken msgId groupId = object [
    "access_token" .= accessToken
    , "batch" .= [
          object ["method" .= String "GET", "name" .= String "oldmsg", "relative_url" .= String (msgId `append` "?fields=from,message,id")]
        , object ["method" .= String "GET", "name" .= String "imp", "relative_url" .= String "{result=oldmsg:$.from.id}?fields=impersonate_token"]
        , object ["method" .= String "POST", "name" .= String "newmsg", "relative_url" .= String (groupId `append` "/feed?access_token={result=imp:$.impersonate_token}"), "body" .= String "message={result=oldmsg:$.message}"]
        , object ["method" .= String "POST", "name" .= String "oldcomment", "relative_url" .= String "{result=oldmsg:$.id}/comments", "body" .= String "message=Post moved to https://workplace.facebook.com/{result=newmsg:$.id}"]
        , object ["method" .= String "POST", "name" .= String "newcomment", "relative_url" .= String "{result=newmsg:$.id}/comments", "body" .= String "message=Post moved from https://workplace.facebook.com/{result=oldmsg:$.id}"]
        ]
    ]
  </code></pre>
  
  <p>I'm considering having JSON blobs in code or reading them in as files and using <code>Text.Printf</code> to splice in variables... </p>
  
  <p>I mean, I can do it all like this, but would sure appreciate finding an alternative. FB's API is a bit unique in that it can't be represented as a rigid data structure like a lot of REST APIs; they call it their Graph API which is quite a bit more dynamic in use, and treating it like a rigid API has been painful thus far.</p>
  
  <p>(Also, thanks to all the community help getting me this far with Haskell!)</p>