此代码将递归JSON结构解析为我创建的haskell对象。我正在使用Aeson库。我遇到的问题是我希望能够轻松地进行错误检查,即使是递归调用也是如此。现在,每当发生错误时我都会使用虚拟值(ayyLmao)。但是,我想利用我从Parser monad获得的错误检查。我怎么能这样做并可能在此过程中清理我的代码?如有必要,我也可以发布一些示例JSON。
编辑:我想指出我想摆脱“ayyLmao”(因此是愚蠢的名字),并以某种方式使用'mzero'代替Parser monad进行错误检查。type Comments = Vector Comment
data Comment = Comment
{ author :: Text
, body :: Text
, replies :: Comments
} deriving Show
-- empty placeholder value (only should appear when errors occur)
ayyLmao :: Comment
ayyLmao = Comment "Ayy" "Lmao" V.empty
parseComment :: Object -> Maybe Comments
parseComment obj = flip parseMaybe obj $ \listing -> do
-- go through intermediate objects
comments <- listing .: "data" >>= (.: "children")
-- parse every comment in an array
return $ flip fmap comments $ \commentData -> case commentData of
-- if the data in the array is an object, parse the comment
-- (using a dummy value on error)
Object v -> fromMaybe ayyLmao (parseMaybe parseComment' v)
-- use a dummy value for errors (we should only get objects in
-- the array
_ -> ayyLmao
where
parseComment' :: Object -> Parser Comment
parseComment' v = do
-- get all data from the object
comment <- v .: "data"
authorField <- comment .: "author"
bodyField <- comment .: "body"
replyObjs <- comment .: "replies"
return $ case replyObjs of
-- if there are more objects, then parse recursively
Object more -> case parseComment more of
-- errors use the dummy value again
Just childReplies -> Comment authorField bodyField childReplies
Nothing -> ayyLmao
-- otherwise, we've reached the last comment in the
-- tree
_ -> Comment authorField bodyField V.empty
编辑:下面答案中的代码是正确的,但我想添加我修改后的解决方案。给出的解决方案假定“null”表示没有更多的回复,但由于某种原因,API设计者决定应该用空字符串表示。
instance FromJSON Comment where
parseJSON = withObject "Comment" $ \obj -> do
dat <- obj .: "data"
commReplies <- dat .: "replies"
Comment
<$> dat .: "author"
<*> dat .: "body"
<*> case commReplies of
Object _ -> getComments <$> dat .: "replies"
String "" -> return V.empty
_ -> fail "Expected more comments or a the empty string"
答案 0 :(得分:2)
你用&#34标记;或者我可以有一个解析器列表然后将其折叠成一个更大的解析器&#34;。这正是您从嵌套解析器传播错误的方式。要移除ayyLmao
的代码的最小更改是:
parseComment :: Object -> Maybe Comments
parseComment obj = flip parseMaybe obj $ \listing -> do
-- go through intermediate objects
comments <- listing .: "data" >>= (.: "children")
-- parse every comment in an array
V.sequence $ flip fmap comments $ \commentData -> case commentData of
-- if the data in the array is an object, parse the comment
-- (using a dummy value on error)
Object v -> parseComment' v
-- use a dummy value for errors (we should only get objects in
-- the array
_ -> mzero
where
parseComment' :: Object -> Parser Comment
parseComment' v = do
-- get all data from the object
comment <- v .: "data"
authorField <- comment .: "author"
bodyField <- comment .: "body"
replyObjs <- comment .: "replies"
case replyObjs of
-- if there are more objects, then parse recursively
Object more -> case parseComment more of
-- errors use the dummy value again
Just childReplies -> return $ Comment authorField bodyField childReplies
Nothing -> mzero
-- otherwise, we've reached the last comment in the
-- tree
_ -> return $ Comment authorField bodyField V.empty
这会针对错误情况使用mzero
,并使用V.sequence
传播回复列表中的错误。 sequence
正好是一个解析器列表(或者,在本例中是一个向量)并折叠成一个成功或失败的解析器。
但是,以上不是使用aeson的好方法。导出FromJSON
类型类的实例并从那里开始工作通常会更好。我会将上面的内容实现为
{-# LANGUAGE OverloadedStrings #-}
import qualified Data.Vector as V
import Data.Vector (Vector)
import Data.Text (Text)
import Data.Aeson
import Data.Maybe (fromMaybe)
import Control.Applicative
type Comments = Vector Comment
data Comment = Comment
{ author :: Text
, body :: Text
, replies :: Comments
} deriving Show
newtype CommentList = CommentList { getComments :: Comments }
instance FromJSON Comment where
parseJSON = withObject "Comment" $ \obj -> do
dat <- obj .: "data"
Comment
<$> dat .: "author"
<*> dat .: "body"
<*> (fromMaybe V.empty . fmap getComments <$> dat .: "replies")
instance FromJSON CommentList where
parseJSON = withObject "CommentList" $ \obj -> do
dat <- obj .: "data"
CommentList <$> dat .: "children"
这引入了一个包装器类型CommentList
,用于从JSON中获取obj.data.children
属性。这利用了FromJSON
的现有Vector
实例,因此您不必手动循环回复并分别解析它们。
表达式
fromMaybe V.empty . fmap getComments <$> dat .: "replies"
假设JSON中的replies
属性包含null
值或有效CommentList
,因此它会尝试解析Maybe CommentList
值(null
解析为Nothing
),然后使用Nothing
将fromMaybe
值替换为空向量。