使用Aeson进行错误检查

时间:2014-09-11 01:24:52

标签: json haskell aeson

此代码将递归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"

1 个答案:

答案 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),然后使用NothingfromMaybe值替换为空向量。