Haskell Aeson with HTTP Request - haskell

I'm a complete newbie to Haskell and having some trouble parsing JSON from a url. I have managed to code the parsing sides of things and have tested with Strings and they work fine. The trouble I'm getting is when I start working with IO.
I have the following code :
data Movie = Movie
{ adult :: Bool,
backdrop_path :: Maybe String,
id :: Int,
original_title :: String,
release_date :: String,
poster_path :: Maybe String,
popularity :: Int,
title :: String,
vote_average :: Double,
vote_count :: Int
} deriving (Show)
data MovieList = MovieList {getMovies ::[Movie]} deriving (Show)
instance FromJSON Movie where
parseJSON (Object v) = Movie <$>
(v .: "adult") <*>
(v .:? "backdrop_path") <*> -- Optional
(v .: "id") <*>
(v .: "original_title") <*>
(v .: "release_date") <*>
(v .:? "poster_path") <*> -- Optional
(v .: "popularity") <*>
(v .: "title") <*>
(v .: "vote_average") <*>
(v .: "vote_count")
instance FromJSON MovieList where
parseJSON (Object o) = MovieList <$> o .: "results"
parseJSON _ = mzero
movieAPIRequest :: String -> IO String
movieAPIRequest movieURI =
do resp <- simpleHTTP request
case resp of
Left x -> return $ "Error connecting: " ++ show x
Right r ->
case rspCode r of
(2,_,_) -> return $ rspBody r -- Request Fulfilled
_ -> return $ show r -- Request Failed
where request = Request {rqURI = uri, rqMethod = GET, rqHeaders = [], rqBody = ""}
uri = fromJust $ parseURI movieURI
convertToByteString s = BS.pack s
main = do
response <- movieAPIRequest url
decoded <- decode (convertToByteString response):: Maybe MovieList
return $ decoded
I can't get the main to work. I want to automatically retrieve JSON from a url. The movieAPIRequest gives me the body of the request (JSON) as IO String. convertToByteString takes a String and converts to Data.ByteString.Lazy.Char8.ByteString as the decode function in Aeson takes a bytestring as an argument. With the above code I'm getting the following error:
[1 of 1] Compiling MovieDataType ( MovieDataType.hs, interpreted )
MovieDataType.hs:62:20:
Couldn't match type `Maybe' with `IO'
Expected type: IO MovieList
Actual type: Maybe MovieList
In a stmt of a 'do' block:
decoded <- decode (convertToByteString response) :: Maybe MovieList
In the expression:
do { response <- movieAPIRequest url;
decoded <- decode (convertToByteString response) ::
Maybe MovieList;
return $ decoded }
In an equation for `main':
main
= do { response <- movieAPIRequest url;
decoded <- decode (convertToByteString response) ::
Maybe MovieList;
return $ decoded }
Failed, modules loaded: none.
I've tried fixing it but keep getting different that I can't get my head around. For example this one is telling its expecting IO MovieList but decode should return a Maybe MovieList.

I see a couple of errors in your main function
main = do
response <- movieAPIRequest url
decoded <- decode (convertToByteString response):: Maybe MovieList
return $ decoded
First of all, if you look up "decode" at http://hackage.haskell.org/package/aeson-0.6.1.0/docs/Data-Aeson.html, you will see that it is of type
decode :: FromJSON a => ByteString -> Maybe a
Note that no IO is involved in this definition, so you can not extract any data from it using "<-". Instead, use "let decoded = ....".
You can read up on the theory behind this distinction as you learn more Haskell, but for now, just note that if a function returns type IO a, you extract data from it using "value <- function x y" (this is why the movieAPIRequest is fine), whereas if the function has no side effects, you extract the value using "let value = function x y". (it is more complicated than this, but this is the 90% answer that will keep you going until you learn about monads).
Second, you are returning a value from main, which is of type IO(). This is the hardcoded type of main in any Haskell program, so you can't return any value except "()". Instead, you have to do something with the value "decoded" (like print it) before the program ends.

Related

How to parse values distributed across an array with Aeson?

I have an json value of:
{
"name": "xyz1",
"extra": [
{
"this_string_A": "Hello"
},
{
"this_string_B": "World"
}
]
}
And a data type of:
data Abc = Abc
{ name :: String
, a :: Maybe String
, b :: Maybe String
} deriving (Generic, Show)
In the above case I would want it to parse with a result of Abc "xyz1" (Just "Hello") (Just "World").
I can't figure out how to conditionally parse the values within extra (which is a JSON array) within the aeson Parser context. How can I get extra[0].this_string_a for example? I
What I tried:
I thought I could create my own Parser (Maybe String) function but ran into confusing errors:
instance FromJSON Abc where
parseJSON = withObject "Abc" $ \v -> Abc
<$> v .: "name"
<*> myParse v
<*> myParse v
myParse :: Object -> Parser (Maybe String)
myParse x = withArray "extra" myParse2 (x)
myParse2 :: Array -> Parser (Maybe String)
myParse2 = undefined
typecheck fails with:
• Couldn't match type ‘unordered-containers-0.2.10.0:Data.HashMap.Base.HashMap
text-1.2.3.1:Data.Text.Internal.Text Value’
with ‘Value’
Expected type: Value
Actual type: Object
• In the third argument of ‘withArray’, namely ‘(x)’
And if I replace x with Object x then I get parse error of:
Left "Error in $: parsing extra failed, expected Array, but encountered Object"
Full example (run test function to test):
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
module Example where
import GHC.Generics
import Data.Aeson
import Data.Aeson.Types
data Abc = Abc
{ name :: String
, a :: Maybe String
, b :: Maybe String
} deriving (Generic, Show)
instance FromJSON Abc where
parseJSON = withObject "Abc" $ \v -> Abc
<$> v .: "name"
<*> (v.: "extra") -- find where object has key of this_string_a ??
<*> (v.: "extra") -- find where object has key of this_string_b ??
test :: Either String Abc
test = eitherDecode exampleJson
exampleJson = "{ \"name\": \"xyz1\", \"extra\": [ { \"this_string_A\": \"Hello\" }, { \"this_string_B\": \"World\" } ] }"
The withXXX "helpers" make everything kind of awkward, but here goes.
The Aeson Parser type is misnamed, and this causes confusion.
The idea with Aeson Parser objects is that they represent a monadic parse result. (This is different from the Parser objects you find in Parsec, etc., which represent actual monadic parsers.) So, you should think of a Parser a as isomorphic to an Either ParseError a -- a monadic result with the possibility of failure.
These parse results are usually combined applicatively. So if you have a parser like:
data Xyz = Xyz { x :: String, y :: String }
instance FromJSON Xyz where
parseJSON = withObject "Xyz" $ \v ->
Xyz <$> v .: "x" <*> v .: "y"
the parse results v .: "x" and v .: "y" have type Parser String which is really like Either ParseError a, and the last line of that instance is the usual method of combining successful and unsuccessful results in an applicative manner, along the lines of:
Xyz <$> Right "value_x" <*> Left "while parsing Xyz: key y was missing"
Now, the function parseJSON has type Value -> Parser a. This is what should properly be called a parser, but to avoid confusion, let's call it a "parse function". A parse function takes a JSON representation (a Value, or an Object or some other JSON thingy) and returns a parse result. The withXXX family of functions are used to adapt parse functions between JSON thingies. If you have a parse function that expects an Object, like:
\v -> Xyz <$> v .: "x" <*> v .: "y" :: Object -> Parser Xyz
and you want to adapt it to parseJSON :: Value -> Parser Xyz, you use withObject "str" :: (Object -> Parser Xyz) -> (Value -> Parser Xyz) to do it.
Getting back to your problem, if you'd like to write a core parser that looks like:
\v -> Abc <$> v .: "name" <*> extra .:? "this_string_A"
<*> extra .:? "this_string_B"
you want extra to be an Object, and you want to extract it monadically from the overall JSON object v :: Object, using appropriate withXXX helpers to adapt parse functions from one input JSON thingy type to another. So, let's write a monadic function (a parse function, in fact) to do that:
getExtra :: Object -> Parser Object
getExtra v = do
First, we monadically extract the optional "extra" component from v. We use the conditional form here, so mextra :: Maybe Value.
mextra <- v .:? "extra"
Second, let's monadically create our final Object out of "mextra". This will be the JSON Object whose keys are "this_string_A" and "this_string_B" with the array layer removed. Note the type of this case expression will be Parser Object, a parse result of type Object = HashMap key value. For the Just case, we have a Value that we expect to be an array, so let's use the withArray helper to ensure that. Note that the withArray "str" helper function takes our parse function of type \arr -> do ... :: Array -> Parser Object and adapts it to Value -> Parser Object so it can be applied to vv :: Value.
case mextra of
Just vv -> vv & withArray "Abc.extra" (\arr -> do
Now, arr is an Array = Vector Value. We hope it's an array of Objects. Let's pull the Values out as a list:
let vallst = toList arr
and then monadically traverse the list with the help of withObject to ensure they're all Objects as expected. Note the use of pure here, since we want to extract the Objects as-is without any additional processing:
objlst <- traverse (withObject "Abc.extra[..]" pure) vallst
Now, we have an objlst :: [Object]. They're a set of singleton hashmaps with disjoint keys, and the Object / hashmap we want is their union, so let's return that. The parenthesis here ends the withArray expression that's being applied to vv:
return $ HashMap.unions objlst)
For the Nothing case ("extra" not found), we merely return an empty hashmap:
Nothing -> return HashMap.empty
The full function looks like this:
getExtra :: Object -> Parser Object
getExtra v = do
mextra <- v .:? "extra"
case mextra of
Just vv -> vv & withArray "Abc.extra" (\arr -> do
let vallst = toList arr
objlst <- traverse (withObject "Abc.extra[..]" pure) vallst
return $ HashMap.unions objlst)
Nothing -> return HashMap.empty
and you use it in your parser instance like so:
instance FromJSON Abc where
parseJSON =
withObject "Abc" $ \v -> do
extra <- getExtra v
Abc <$> v .: "name" <*> extra .:? "this_string_A" <*> extra .:? "this_string_B"
With a test case:
example :: BL.ByteString
example = "{\"name\": \"xyz1\", \"extra\": [{\"this_string_A\": \"Hello\"}, {\"this_string_B\": \"World\"}]}"
main = print (eitherDecode example :: Either String Abc)
it works like so:
λ> main
Right (Abc {name = "xyz1", a = Just "Hello", b = Just "World"})
The full code:
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson (eitherDecode, FromJSON, Object, parseJSON, withArray, withObject, (.:), (.:?))
import Data.Aeson.Types (Parser)
import GHC.Generics (Generic)
import qualified Data.ByteString.Lazy as BL (ByteString)
import qualified Data.HashMap.Strict as HashMap (empty, unions)
import Data.Function ((&))
import Data.Foldable (toList)
data Abc = Abc
{ name :: String
, a :: Maybe String
, b :: Maybe String
} deriving (Generic, Show)
instance FromJSON Abc where
parseJSON =
withObject "Abc" $ \v -> do
extra <- getExtra v
Abc <$> v .: "name" <*> extra .:? "this_string_A" <*> extra .:? "this_string_B"
getExtra :: Object -> Parser Object
getExtra v = do
mextra <- v .:? "extra"
case mextra of
Just vv -> vv & withArray "Abc.extra" (\arr -> do
let vallst = toList arr
objlst <- traverse (withObject "Abc.extra[..]" pure) vallst
return $ HashMap.unions objlst)
Nothing -> return HashMap.empty
example :: BL.ByteString
example = "{\"name\": \"xyz1\", \"extra\": [{\"this_string_A\": \"Hello\"}, {\"this_string_B\": \"World\"}]}"
main = print (eitherDecode example :: Either String Abc)
Partial answer...
instance FromJSON Abc where
parseJSON = withObject "Abc" $ \v -> Abc
<$> v .: "name"
<*> (v .: "extra" >>= myParse)
<*> (v .: "extra" >>= myParse)
myParse :: Array -> Parser (Maybe String)
myParse x = withArray "extra" (lookupDictArray "this_string_a") (Array x)
lookupDictArray :: Text -> Array -> Parser (Maybe String)
lookupDictArray k a = do
let v = Vector.find (maybe False (HashMap.member k) . parseMaybe parseJSON) a
case v of
Just v' -> withObject "grrrrrrrrrrr" (\v -> v .: k) v'
Nothing -> pure Nothing
Fails to typecheck with:
src/Example.hs:32:69-77: error:
• Ambiguous type variable ‘a0’ arising from a use of
‘parseJSON’
prevents the constraint ‘(FromJSON a0)’ from being
solved.
Probable fix: use a type annotation to specify
what ‘a0’ should be.
These potential instances exist:
instance FromJSON DotNetTime
-- Defined in ‘aeson-1.4.4.0:Data.Aeson.Types.FromJSON’
instance FromJSON Value
-- Defined in ‘aeson-1.4.4.0:Data.Aeson.Types.FromJSON’
instance (FromJSON a, FromJSON b) => FromJSON
(Either a b)
-- Defined in ‘aeson-1.4.4.0:Data.Aeson.Types.FromJSON’
...plus 29 others
...plus 60 instances involving out-of-scope types
(use -fprint-potential-instances to see them all)
• In the first argument of ‘parseMaybe’, namely
‘parseJSON’
In the second argument of ‘(.)’, namely
‘parseMaybe parseJSON’
In the first argument of ‘Vector.find’, namely
‘(maybe False (member k) . parseMaybe
parseJSON)’
|
32 | let v = (Vector.find (maybe False (HashMap.member
k) . parseMaybe parseJSON) a)

How can I parse an Aeson Object into my own custom type?

I am trying to write a JSON parser with Aeson.
The JSON I'm working with
The way I am calling the JSON in my code:
testReq :: Request
testReq = parseRequest_ "https://api.openweathermap.org/data/2.5/onecall?lat=41.63526&lon=-70.92701&exclude=minutely&appid=93120a85abf28f8fb1cdae14ffd7435d&units=metric"
First I define my custom type
type Celsius = Double
type HPA = Int --Hectopascal Pressure Unit
type Percent = Int
type Meter = Int
type MeterPerSec = Double
type CompassDegree = Int
data WeatherObj =
WeatherObj
{ time :: UTCTime
, temp :: Celsius
, feels_like :: Celsius
, pressure :: HPA
, humidity :: Percent
, visibility :: Meter
, wind_speed :: MeterPerSec
, wind_deg :: CompassDegree
}
deriving (Eq, Show, Generic)
Next I write my FromJSON instance, which I know works because If I run parseCurrentWeather testReq I get back WeatherObj {time = 2020-07-19 16:54:43 UTC, temp = 25.51, feels_like = 29.49, pressure = 1012, humidity = 83, visibility = 10000, wind_speed = 1.34, wind_deg = 247} Which is perfect!
instance FromJSON WeatherObj where
parseJSON = withObject "weatherObj" $ \obj -> do
timeOffset <- obj .: "timezone_offset"
currentO <- obj .: "current"
dt <- currentO .: "dt"
temp <- currentO .: "temp"
feels_like <- currentO .: "feels_like"
pressure <- currentO .: "pressure"
humidity <- currentO .: "humidity"
visibility <- currentO .: "visibility"
wind_speed <- currentO .: "wind_speed"
wind_deg <- currentO .: "wind_deg"
pure $ WeatherObj (makeLocalTime dt timeOffset)
temp feels_like
pressure humidity
visibility wind_speed
wind_deg
parseCurrentWeather :: Request -> IO WeatherObj
parseCurrentWeather req = do
current <- fetchCurrentWeather req
pure $ getResponseBody current
Now I need to figure out how to parse the hourly weather which should give me back 48 objects. This code works as when I run parseHourly testReq I get back a long string of JSON with no exceptions. This JSON definitely matches the JSON from the link. I am looking great up to this point.
fetchHourly :: Request -> IO (Response HourlyWeathers) --Could also be IO (Response Object)
fetchHourly = httpJSON
data HourlyWeathers =
HourlyWeathers
{ getHours :: [Object] }
deriving (Eq, Show, Generic)
instance FromJSON HourlyWeathers where
parseJSON = withObject "hourlyWeather" $ \obj -> do
allHours <- obj .: "hourly"
pure $ HourlyWeathers allHours
parseHourly :: Request -> IO HourlyWeathers
parseHourly req = do
hours <- fetchHourly req
pure $ getResponseBody hours
Now we are at the problematic code. I would like to map objToWeatherObj onto the list of objects that I generate with parseHourly. The problem that I cannot seem to overcome is that when I run parseHourlyObjects I get back a list of all Nothings.
parseHourlyObjects :: Request -> IO [Maybe WeatherObj]
parseHourlyObjects req = do
hourly <- fetchHourly req
let x = getHours $ getResponseBody hourly
y = fmap objToWeatherObj x
pure y
objToWeatherObj :: Object -> Maybe WeatherObj
objToWeatherObj = (decode . encode)
I have been able to write a ToJSON instance for WeatherObj but that turned out to be irrelevant because I need to parse a generic Object into a WeatherObj. I believe that the function I need here is decode, though I could be wrong.
Given:
data WeatherObj =
WeatherObj
{ time :: UTCTime
, temp :: Celsius
, feels_like :: Celsius
, pressure :: HPA
, humidity :: Percent
, visibility :: Meter
, wind_speed :: MeterPerSec
, wind_deg :: CompassDegree
}
deriving (Eq, Show, Generic, FromJSON)
Note that it is now deriving FromJSON as well.
You can:
decode "{\"time\":\"...\",...}" :: Maybe WeatherObj
And get a Maybe WeatherObj. By writing your own instance of FromJSON, I think you may have made your life a bit more challenging than needed.

How to parse JSON with Aeson without declaring individual types

(Haskell newbie alert)
Here's a snippet of the code that I'm struggling with. Basically, I'm taking a JSON coming in from a websocket and I want to parse it using Aeson without defining individual data types for each response.
import Data.Aeson
import qualified Network.WebSockets as WS
aria2WebsocketReceiver :: WS.Connection -> IO ()
aria2WebsocketReceiver conn = do
msg <- WS.receiveData conn
let res = decode msg
let v = flip parseMaybe res $ \o -> do
r <- o .: "result"
version <- r .: "version"
enabledFeatures <- r .: "enabledFeatures"
id_ <- r .: "id"
return $ "version=" ++ version
putStrLn (show v)
aria2WebsocketReceiver conn
Here are the compilation errors that I'm running into:
Nightwatch/Telegram.hs:244:13:
No instance for (FromJSON a0) arising from a use of ‘decode’
The type variable ‘a0’ is ambiguous
Relevant bindings include
res :: Maybe a0 (bound at Nightwatch/Telegram.hs:244:7)
Note: there are several potential instances:
instance FromJSON Chat -- Defined at Nightwatch/Telegram.hs:90:10
instance FromJSON Message
-- Defined at Nightwatch/Telegram.hs:106:10
instance FromJSON TelegramResponse
-- Defined at Nightwatch/Telegram.hs:122:10
...plus two others
In the expression: decode msg
In an equation for ‘res’: res = decode msg
In the expression:
do { msg <- WS.receiveData conn;
let res = decode msg;
let v = flip parseMaybe res $ ...;
putStrLn (show v);
.... }
Nightwatch/Telegram.hs:246:44:
Couldn't match type ‘Maybe a0’
with ‘unordered-containers-0.2.5.1:Data.HashMap.Base.HashMap
Text Value’
Expected type: Object
Actual type: Maybe a0
Relevant bindings include
o :: Maybe a0 (bound at Nightwatch/Telegram.hs:245:34)
res :: Maybe a0 (bound at Nightwatch/Telegram.hs:244:7)
In the first argument of ‘(.:)’, namely ‘o’
In a stmt of a 'do' block: r <- o .: "result"
I'm basically trying to replicate the "Working with the AST" example given at https://hackage.haskell.org/package/aeson-0.10.0.0/docs/Data-Aeson.html
Thanks to Cale at #haskell here's the working code:
aria2WebsocketReceiver :: WS.Connection -> IO ()
aria2WebsocketReceiver conn = do
msg <- WS.receiveData conn
let v = do res <- decode msg
flip parseMaybe res $ \o -> do
r <- o .: "result"
version <- r .: "version"
return $ "version=" ++ (version :: String)
putStrLn (show v)
aria2WebsocketReceiver conn
There were three problems in the earlier code:
The type of decode msg is a Maybe which needs to be inside a separate do block.
Because of {-# LANGUAGE OverloadedStrings #-} the compiler wasn't being able to infer the type for version, hence the (version :: String) hint.
Similarly, enabledFeatures and id_ were being assigned but not being used anywhere, which was causing more problem in type-inference.

yaml library on linux doesn't decode what it's encoded

decode / encode:
yDecode :: FromJSON iFromJSONable ⇒ FilePath → IO iFromJSONable
yDecode fnm = do
ymlData ← BS.readFile fnm
return $ fromMaybe (error "Can't parse from YAML") (decode ymlData)
yEncode :: ToJSON iToJSONable ⇒ FilePath → iToJSONable → IO()
yEncode fnm dat = BS.writeFile fnm $ encode dat
I create config with this encode and it creates just fine but when I'm reading it - I'm getting this error: Can't parse from YAML - on windows same code works fine and there I just can't understand what is possibly wrong?
In cases of Nothing, it's best to grab more information by using decodeEither/decodeEither'. The left side of the either value will contain an error message telling you where the failure occurs. If you switch over, you'll see that the parsing is failing due to the error "Can't parse Repository from YAML" line (see attempt1 below). It's encountering something besides an Object!
It's best then to see what the heck the YAML package is decoding to then, by decoding to the type we know that has to succeed — Value. Decoding, we get this (see attempt2 below):
Right (Array (fromList [Object (fromList [("group",Null),("branches",Array (fromList [String "master"])),("hash",Null),("clean",Null),("location",String "/home/gentoo-haskell"),("enabled",Null),("root",Null),("postRebuild",Null),("upstream",String "upstream master"),("task",String "rebase"),("positive",Null)])]))
It appears the root data structure is an Array and not an Object. There are lots of ways to fix this, and I chose a hacky one.
parseJSON (Array array) = parseJSON (array ! 0)
This makes the program work! I pasted my code below. (Apologies for the use of lens; I use it to convert between strings and bytestrings for quick scripts like these. Your program will of course work perfectly fine without it.)
{-# LANGUAGE OverloadedStrings #-}
module Lib where
import Control.Lens
import Data.ByteString
import Data.ByteString.Lens
import Data.Vector
import Data.Yaml
data Repository = Repository
{ location :: String
, task :: String
, branches :: [String]
, upstream :: String
, enabled :: Maybe Bool
, root :: Maybe Bool
, positive :: Maybe Bool
, clean :: Maybe Bool
, postRebuild :: Maybe [String]
, syncGroup :: Maybe String
, hash :: Maybe String
} deriving (Show, Eq)
instance FromJSON Repository where
parseJSON (Object v) = Repository <$>
v .: "location" <*>
v .: "task" <*>
v .: "branches" <*>
v .: "upstream" <*>
v .:? "enabled" <*>
v .:? "root" <*>
v .:? "positive" <*>
v .:? "clean" <*>
v .:? "postRebuild" <*>
v .:? "group" <*>
v .:? "hash"
parseJSON (Array array) = parseJSON (array ! 0)
raw :: String
raw = unlines [
"- group: null",
" branches:",
" - master",
" hash: null",
" clean: null",
" location: /home/gentoo-haskell",
" enabled: null",
" root: null",
" postRebuild: null",
" upstream: upstream master",
" task: rebase",
" positive: null"]
attempt1 :: Either ParseException Repository
attempt1 = decodeEither' (raw ^. packedChars)
attempt2 :: Either ParseException Value
attempt2 = decodeEither' (raw ^. packedChars)

Flatten MonadPlus inside an Aeson Parser

I'm not sure if I'm barking up the wrong tree here, but I have an Aeson FromJSON definition that looks rather bulky and I was wondering if it could be turned into something more concise. I want to short-circuit the parsing of the entire object if the nested parsing of the URI fails.
data Link = Link { link :: URI
, tags :: [String]
} deriving (Show, Typeable, Eq)
instance FromJSON Link where
parseJSON :: Value -> Parser Link
parseJSON (Object o) = do
linkStr <- o .: "link"
tags' <- o .: "tags"
case parseURI linkStr of
Just l -> return $ Link l tags'
Nothing -> mzero
parseJSON _ = mzero
The type of parseURI is parseURI :: String -> Maybe URI and both Maybe and Parser have MonadPlus instances. Is there a way to compose the two directly and remove the ugly case statement at the end?
Applicative parsers are usually more concise and you can compose the result of parseURI using maybe mzero return which converts a Nothing into an mzero.
instance FromJSON Link where
parseJSON :: Value -> Parser Link
parseJSON (Object o) = Link
<$> (maybe mzero return . parseURI =<< o .: "link")
<*> o .: "tags"
parseJSON _ = mzero
Pattern matching works, but this only works inside do notation not explicit >>= due to the extra desugaring that goes on:
instance FromJSON Link where
parseJSON (Object o) = do
Just link' <- o .: "link"
tags' <- o .: "tags"
return $ Link link' tags'
parseJSON _ = mzero
> -- Note that I used link :: String for my testing instead
> decode "{\"link\": \"test\", \"tags\": []}" :: Maybe Link
Just (Link {link = "test", tags=[]})
> decode "{\"tags\": []}" :: Maybe Link
Nothing
What's going on here is that a failed pattern match on the left hand side of a <- is calling fail. Looking at the source for Parser tells me that fail is calling out to failDesc, which is also used by the implementation of mzero, so in this case you're safe. In general it just calls fail, which can do any number of things depending on the monad, but for Parser I'd say it makes sense.
However, #shang's answer is definitely better since it doesn't rely on implicit behavior.

Resources