0

I have an application that gets JSON response from an API and I'm trying to get a specific key from that response. The working code looks as below:

{-# LANGUAGE OverloadedStrings #-}

module Main (main) where
import Data.Aeson
import Network.HTTP.Req
   
tinyUIDRequest :: String -> Req (JsonResponse Object)
tinyUIDRequest url = let payload = object [ "url" .= url ] 
                 in req 
                    POST 
                    (https "tinyuid.com" /: "api" /: "v1" /: "shorten") (
                    ReqBodyJson payload) 
                    jsonResponse
                    mempty           

main :: IO ()
main = do 
    response <- runReq defaultHttpConfig (tinyUIDRequest "http://google.com")
    let body = responseBody response
    print body

And the function to process the response should look similar to:

tinyUIDResponseHandler :: HttpResponseBody (JsonResponse Object) -> String
tinyUIDResponseHandler r = case lookup "result_url" r of 
                            Just url -> url
                            Nothing -> ""

unfortunately I'm not able to figure out how to transform HttpResponseBody (JsonResponse Object) to something like hashmap/list and find a proper key. I was checking the documentation and req source code but maybe, as a beginner in Haskell, I don't know exactly what to look for to understand the internals of the response type.

trivelt
  • 1,913
  • 3
  • 22
  • 44

1 Answers1

4

HttpResponseBody is a red herring. It's an associated type, and HttpResponseBody (JsonResponse a) is the same as just a, so HttpResponseBody (JsonResponse Object) is really just Object. As such, once you have body, req's job is done, and it's now just aeson that you need to be concerned with.

Since body is an Object and not a list of tuples, you can't use Prelude's lookup on it. If your Aeson version is older than 2, then Object is just HashMap Text Value, so do this instead:

import Data.Text
import qualified Data.HashMap.Strict as HM

tinyUIDResponseHandler :: Object -> Text
tinyUIDResponseHandler r = case HM.lookup "result_url" r of 
                            Just (String url) -> url
                            _ -> ""

If your Aeson version is 2 or newer, then Object is KeyMap Value, so do this instead:

import Data.Text
import qualified Data.Aeson.KeyMap as AKM

tinyUIDResponseHandler :: Object -> Text
tinyUIDResponseHandler r = case AKM.lookup "result_url" r of 
                            Just (String url) -> url
                            _ -> ""

In either case, tinyUIDResponseHandler body will then be the value you want, so for example, you could do print (tinyUIDResponseHandler body).

  • "`(JsonResponse a)` is the same as just `a`" - I'm not sure why you say this, it can't be pattern matched as `a`. – Chris Stryczynski Nov 25 '21 at 18:37
  • @ChrisStryczynski That's not what I said. I said "`HttpResponseBody (JsonResponse a)` is the same as just `a`". – Joseph Sible-Reinstate Monica Nov 25 '21 at 18:39
  • @ChrisStryczynski I suspect your actual problem is that my answer was written for Aeson 1.x, and in Aeson 2, `Object` is no longer a `HashMap Text Value`. I'll update my answer to explain that. – Joseph Sible-Reinstate Monica Nov 25 '21 at 18:43
  • Ah sorry my bad, in which case it does actually pattern match (though it seems strange to me that it would). Is this a feature of "associated types" with type families? – Chris Stryczynski Nov 25 '21 at 18:44
  • 1
    @ChrisStryczynski That's exactly what type families are. For a minimal example, do `type family UnMaybe t where UnMaybe (Maybe a) = a`, then you can write `one = 1 :: UnMaybe (Maybe Int)` and it'll be exactly the same as `one = 1 :: Int`. – Joseph Sible-Reinstate Monica Nov 25 '21 at 18:54