2014-09-11 49 views
1

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

编辑:我想指出,我想摆脱“ayyLmao”(因此愚蠢的名字),并以某种方式使用'mzero'的解析器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

使用占位符值不是非常习惯的Haskell,你有没有考虑返回一个'Maybe Comments'值来表示失败的概念,然后在可能使用'Data.Maybe.fromMaybe( ayyLmao)'而不是?或者,您可以使用Aeson解析器具有失败概念并以非常相似的方式使用它的事实。 – bheklilr 2014-09-11 01:53:29

+0

@bheklilr是的,使用占位符值并不是很习惯任何东西,这就是为什么我给它一个这样一个愚蠢的名字。使用Maybe可能会起作用,但我宁愿使用Aeson已有的错误检查(mrsero对于Parser将被解释为错误)。但是,我不太清楚如何让这些类型按照我想要的方式排列。这样做的任何提示? – mrobinson7627 2014-09-11 02:14:47

+0

那么,通常解析类型使用'mzero'来表示失败。你见过像[这一个]的教程吗(https:// www。fpcomplete.com/school/starting-with-haskell/libraries-and-frameworks/text-manipulation/json)? – bheklilr 2014-09-11 02:22:42

回答

2

您点击“或者我可以有一个解析器列表,然后将其折叠成一个更大的解析器”。这正是你如何从嵌套解析器传播错误。你的代码的最小变化,除去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),然后替换一个Nothing值为空的向量使用fromMaybe

+0

非常感谢!直到现在我还没有听说过序列,这是非常有用的。我也在考虑使用FromJSON实例。我开始这样做,但并没有真正理解所有类型都做了什么,所以我开始手动做所有事情。我认为这真的帮助我学习了一大堆。 另外我会喜欢它,如果答复属性包含null时没有更多的答复(或者可能是一个空的列表)。但是API设计者认为空字符串表示“没有更多评论”是个好主意。 – mrobinson7627 2014-09-11 04:58:52