I am trying to understand a little more Haskell, and have followed an Aeson example with success. I'm trying to adapt it now, and am lacking and what I'm sure is a fairly basic understanding. If it matters, I am doing this in a Jupyter Lab setup using the IHaskell-aeson package.
The initial example assumed an input JSON file consisting of a single, simple object. The output parses the object and provides a small greet
message.
{
"name": "Johann Carl Freidrich Guass",
"nationality": "German",
"born": "30 April 1777",
"died": "23 February 1855"
}
Output: Johann Carl Freidrich Guass was born 30 April 1777
I want to extend this to use this JSON:
{
"mathematicians": [
{ "name".... },
{ "name".... },
{ "name".... },
{ "name".... }
]
}
Here is my code right now, I've left commented in the first version (:extension
is for IHaskell/Jupyter):
:extension OverloadedStrings
import qualified Data.ByteString.Lazy as B
import Control.Monad (mzero)
import Data.Foldable (toList)
import Data.Aeson.Types (Parser)
data Mathematicians = Mathematicians
{ mathematicians :: [Mathematician]
} deriving (Show)
data Mathematician = Mathematician
{ name :: String
, nationality :: String
, born :: String
, died :: Maybe String
} deriving (Show)
instance A.FromJSON Mathematician where
parseJSON (A.Object v) = Mathematician
<$> (v A..: "name")
<*> (v A..: "nationality")
<*> (v A..: "born")
<*> (v A..:? "died")
parseJSON _ = mzero
instance A.FromJSON Mathematicians where
parseJSON (A.Object v) = do
ms <- v A..: "mathematicians"
fmap Mathematicians $ A.parseJSON ms
parseJSON _ = mzero
input <- B.readFile "mathematicians.json"
greet m = (show.name) m ++
" was born " ++
(show.born) m
-- let mm = A.decode input :: Maybe Mathematician
-- case mm of
-- Nothing -> print "error parsing JSON"
-- Just m -> (putStrLn.greet) m
let mms = A.decode input :: Maybe Mathematicians
case mms of
Nothing -> print "error parsing JSON"
Just ms -> print ms
Printing ms
gives me the following, which leads me to believe the parsing is probably working:
Mathematicians {mathematicians = [Mathematician {name = "Johann Carl Friedrich Gauss", nationality = "German", born = "30 April 1777", died = Just "23 February 1855"},Mathematician {name = "Leonhard Euler", nationality = "German", born = "15 April 1707", died = Just "18 September 1783"},Mathematician {name = "William Rowan Hamilton", nationality = "Irish", born = "3 August 1805", died = Just "2 September 1865"},Mathematician {name = "Terence Chi-Shen Tao", nationality = "Australia", born = "17 July 1975", died = Nothing}]}
Also, intentionally inserting a typo in the Mathematician
parser leads to an error parsing JSON
message, so fingers crossed it does seem like the parsing works.
However, I do not understand how to access the individual Mathematician
elements, and then run greet
on them sequentially.
I have tried printing like this: print (fmap greet ms)
and get
<interactive>:3:34: error:
• Couldn't match expected type ‘f0 Mathematician’ with actual type ‘Mathematicians’
• In the second argument of ‘fmap’, namely ‘ms’
In the first argument of ‘print’, namely ‘(fmap greet ms)’
In the expression: print (fmap greet ms)
using print (map greet ms)
instead:
<interactive>:3:33: error:
• Couldn't match expected type ‘[Mathematician]’ with actual type ‘Mathematicians’
• In the second argument of ‘map’, namely ‘ms’
In the first argument of ‘print’, namely ‘(map greet ms)’
In the expression: print (map greet ms)
neither of which, I'm sorry to say, I understand at all.
You can make use of mapM_ :: (Monad m, Foldable f) => (a -> m b) -> f a -> m ()
to run a monadic action on all the elements of a Foldable
:
greet :: Mathematician -> String
greet Mathematician { name=n, born=b } = n ++ " was born " ++ show b
main = do
input <- B.readFile "mathematicians.json"
case A.decode input :: Maybe Mathematicians of
Nothing -> print "error parsing JSON"
Just (Mathematicians ms) -> mapM_ (putStrLn . greet) ms