我有许多字段的数据类型,如果不是由JSON配置文件手动指定,则应该随机设置。我正在使用Aeson来解析配置文件。这样做的最佳方式是什么?
目前,我设置的值等于某个不可能的值,然后检查所述值以进行编辑。
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'
我想要的是:
parseJSON = withObject "Example" $ \v -> Example
<$> (v .: "a" <|> return (randomRIO (1,10))
是否可以在IO Monad中定义Parsers或者使用某个随机生成器进行线程,最好是使用Aeson?
答案 0 :(得分:5)
好吧,我不知道这是不是一个好主意,而且对于更大的开发来说,处理额外的IO
层肯定会令人沮丧,但是下面的类型检查:
{-# 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)))
在最后两行中,第一行pure
为Int -> IO Int
,第二行为IO Int -> Parser (IO Int)
。在ghci:
> sequence (decode "{}") :: IO (Maybe Example)
Just (Example {a = 4, b = 6})
答案 1 :(得分:2)
由于ParseJSON monad不是变换器或基于IO,我不知道有什么好的策略可以达到你想要的位置。您可以更轻松地做到的是解码为一种类型然后转换为第二种类型,就像之前的问题“Give a default value for fields not available in json using aeson”中所做的那样。
由于大型结构的重现繁琐,您可以对结构进行参数化,并使用IO Int
或Int
对其进行实例化。例如,假设您希望来自网络的字段a
,但{mon}中的b
是随机的:
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveTraversable #-}
import Data.Aeson
import System.Random
import Data.ByteString.Lazy (ByteString)
data Example' a =
Example { a :: Int
, b :: a
} deriving (Show,Functor,Foldable,Traversable)
type Partial = Example' (IO Int)
type Example = Example' Int
instance FromJSON Partial where
parseJSON (Object o) =
Example <$> o .: "a"
<*> pure (randomRIO (1,10))
loadExample :: Partial -> IO Example
loadExample = mapM id
parseExample :: ByteString -> IO (Maybe Example)
parseExample = maybe (pure Nothing) (fmap Just . loadExample) . decode
注意loadExample
如何使用我们的traverse
实例在结构内执行IO操作。以下是一个使用示例:
Main> parseExample "{ \"a\" : 1111 }"
Just (Example {a = 1111, b = 5})
高级强>
如果您有多种类型的字段需要IO操作,则可以
为所有这些数据生成一种数据类型。您可以将b
替换为IO Int
,而不是IO MyComplexRecord
。这是一个简单的解决方案。
更复杂和有趣的解决方案是使用更高类型的参数。
对于选项2,请考虑:
data Example' f = Example { a :: Int
, b :: f Int
, c :: f String }
然后,您可以使用Proxy
和Control.Monad.Identity
代替之前使用的IO Int
和Int
等值。您需要编写自己的遍历,因为您无法为此类派生Traverse
(这就是我们上面使用的mapM
)。我们可以使用类(* -> *) -> *
使用一些扩展(其中包含RankNTypes)创建一个遍历类,但除非经常这样做,并且我们得到某种派生支持或TH,我认为这不值得。< / p>
答案 2 :(得分:1)
这是另一种解决方案,它涉及更多的手工劳动,但这种方法非常简单 - 生成一个随机IO Example
使用它来生成一个随机的#34;解析器&#34;。使用通常的decode
函数解码为JSON。
{-# LANGUAGE OverloadedStrings #-}
module Test where
import Data.Aeson
import Data.Aeson.Types
import System.Random
data Example = Example {_a :: Int, _b :: Int} deriving (Show, Ord, Eq)
getExample :: IO (Value -> Maybe Example)
getExample = do
ex <- randomRIO (Example 1 1, Example 10 100)
let ex' = withObject "Example" $ \o ->
do a <- o .:? "a" .!= _a ex
b <- o .:? "b" .!= _b ex
return $ Example a b
return (parseMaybe ex')
instance Random Example where
randomRIO (low,hi) = Example <$> randomRIO (_a low,_a hi)
<*> randomRIO (_b low,_b hi)
...
main :: IO ()
main = do
getExample' <- getExample
let example = getExample' =<< decode "{\"a\": 20}"
print example
我不确定,但我相信这是@ DanielWagner解决方案的更详细的实现。