JSON, Files and Command-Line Apps
28.1 JSON with aeson
{-# LANGUAGE DeriveGeneric, DeriveAnyClass, OverloadedStrings #-}
import Data.Aeson
import GHC.Generics (Generic)
import qualified Data.ByteString.Lazy.Char8 as BL
data Person = Person
{ name :: String
, age :: Int
, emails :: [String]
}
deriving (Show, Generic, ToJSON, FromJSON) -- automatic instances via Generics
main :: IO ()
main = do
let ana = Person "Ana" 30 ["ana@example.com"]
BL.putStrLn (encode ana) -- {"name":"Ana","age":30,"emails":["ana@example.com"]}
print (decode "{\"name\":\"Bo\",\"age\":25,\"emails\":[]}" :: Maybe Person)
print (eitherDecode "{\"name\":1}" :: Either String Person) -- a descriptive error
-- Manual instances for custom field names / validation
data Item = Item {itemId :: Int, title :: String} deriving Show
instance FromJSON Item where
parseJSON = withObject "Item" $ \o -> Item <$> o .: "id" <*> o .: "title"
instance ToJSON Item where
toJSON (Item i t) = object ["id" .= i, "title" .= t]28.2 Command-Line Parsing with optparse-applicative
Subcommands (hsubparser), environment variables, and automatic --help. Other useful CLI libraries: turtle (shell scripting), typed-process (run external processes), ansi-terminal, brick (terminal UIs), filepath/directory.
import Options.Applicative
data Options = Options
{ optInput :: FilePath
, optCount :: Int
, optVerbose :: Bool
}
optionsP :: Parser Options
optionsP =
Options
<$> strArgument (metavar "FILE" <> help "Input file")
<*> option auto (long "count" <> short 'n' <> value 10 <> showDefault <> help "Number of lines")
<*> switch (long "verbose" <> short 'v' <> help "Verbose output")
main :: IO ()
main = do
opts <- execParser (info (optionsP <**> helper) (fullDesc <> progDesc "Show the head of a file"))
contents <- readFile (optInput opts)
mapM_ putStrLn (take (optCount opts) (lines contents))