Search code examples
haskellparsecaeson

How to use Parsers from Aeson with IO


I have data types with many fields that, if not being manually specified by a JSON configuration file, should be randomly set. I am using Aeson to parse the config file. What is the best way to do this?

Currently, I am setting values equal to some impossible value, and then later checking for said value to edit.

data Example = Example { a :: Int, b :: Int }
default = Example 1 2
instance FromJSON Example where
    parseJSON = withObject "Example" $ \v -> Example
      <$> (v .: "a" <|> return (a default)) 
      <*> (v .: "b" <|> return (b default))

initExample :: Range -> Example -> IO Example
initExample range (Example x y) = do
   a' <- if x == (a default) then randomIO range else return x
   b' <- if y == (b default) then randomIO range else return y
   return $ Example a' b'

What I would like is something along the lines of:

parseJSON = withObject "Example" $ \v -> Example
      <$> (v .: "a" <|> return (randomRIO (1,10))

Is it possible to define Parsers in the IO Monad or thread along some random generator, ideally using Aeson?


Solution

  • Well, I don't know if it's a good idea, and juggling the extra IO layer will certainly get frustrating as heck for larger developments, but the following type-checks:

    {-# LANGUAGE FlexibleContexts #-}
    {-# LANGUAGE FlexibleInstances #-}
    {-# LANGUAGE OverloadedStrings #-}
    import Control.Applicative
    import Data.Aeson
    import System.Random
    
    data Example = Example { a :: Int, b :: Int } deriving (Eq, Ord, Read, Show)
    
    instance FromJSON (IO Example) where
        parseJSON = withObject "Example" $ \v -> liftA2 Example
            <$> ((pure <$> (v .: "a")) <|> pure (randomRIO (3, 4)))
            <*> ((pure <$> (v .: "b")) <|> pure (randomRIO (5, 6)))
    

    In each of the last two lines, the first pure is Int -> IO Int, and the second is IO Int -> Parser (IO Int). In ghci:

    > sequence (decode "{}") :: IO (Maybe Example)
    Just (Example {a = 4, b = 6})