【问题标题】:Error checking with Aeson使用 Aeson 进行错误检查
【发布时间】:2014-09-11 01:24:51
【问题描述】:

此代码将递归 JSON 结构解析为我制作的 haskell 对象。我正在使用 Aeson 库。我遇到的问题是我希望能够轻松地进行错误检查,即使使用递归调用也是如此。现在,每当发生错误时,我都会使用一个虚拟值 (ayyLmao)。但是,我想利用从 Parser monad 获得的错误检查。我该如何做到这一点并可能在此过程中清理我的代码?如有必要,我还可以发布一些示例 JSON。

编辑:我想指出我想摆脱“ayyLmao”(因此是愚蠢的名字),并以某种方式使用 Parser monad 的“mzero”来代替我的错误检查。

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"

【问题讨论】:

  • 使用占位符值不是非常惯用的 Haskell,您是否考虑过返回一个 Maybe Comments 值来表示失败的概念,然后在您可能会使用 Data.Maybe.fromMaybe (ayyLmao) 得到 Nothing 时反而?或者,您可以利用 Aeson 解析器具有失败概念的事实,并以几乎相同的方式使用它。
  • @bheklilr 是的,使用占位符值不是很惯用的任何东西,这就是为什么我给它起了这么愚蠢的名字。使用 Maybe 会起作用,但我更愿意只使用 Aeson 已经拥有的错误检查(Parser 的 mzero 将被解释为错误)。但是,我不太清楚如何让类型按照我想要的方式排列。这样做有什么技巧吗?
  • 嗯,通常解析类型使用mzero来表示失败。你看过this one这样的教程吗?
  • 是的,我知道。从我的回复中:“解析器的 mzero 将被解释为错误”。我想知道在这种情况下我该怎么做。我正在处理 cmets 向量,所以我基本上必须识别 fmap 中间的错误,并以某种方式返回整个事情的 mzero。或者我可以有一个解析器列表,然后将它折叠到一个更大的解析器中(这甚至可以工作吗?)。不完全确定在那里做的最好的事情是什么。

标签: json haskell aeson


【解决方案1】:

您用“或者我可以有一个解析器列表,然后将其折叠成一个更大的解析器”来达到目标​​。这正是您从嵌套解析器传播错误的方式。删除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 属性。这利用了 Vector 的现有 FromJSON 实例,因此您不必手动循环浏览回复并单独解析它们。

表达式

fromMaybe V.empty . fmap getComments <$> dat .: "replies"

假设 JSON 中的 replies 属性包含 null 值或有效的 CommentList,因此它尝试解析 Maybe CommentList 值(null 被解析为 Nothing)然后替换使用 fromMaybe 的带有空向量的 Nothing 值。

【讨论】:

  • 哇,非常感谢!直到现在我才听说过序列,这非常有用。我也在考虑使用 FromJSON 实例。我开始这样做,但并没有真正理解所有类型的作用,所以我开始手动做所有事情。我认为这真的帮助我学到了很多东西。如果没有更多回复(或者可能是一个空列表),如果回复属性包含 null,我也会喜欢它。但 API 设计者认为空字符串表示“不再有 cmets”是个好主意。
猜你喜欢
  • 2011-09-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-09
  • 1970-01-01
  • 1970-01-01
  • 2013-08-01
相关资源
最近更新 更多