2

このコードは、再帰的な JSON 構造を、私が作成した haskell オブジェクトに解析します。Aeson ライブラリを使用しています。私が直面している問題は、再帰呼び出しでも簡単にエラー チェックを実行できるようにしたいということです。現在、エラーが発生するたびにダミー値 (ayyLmao) を使用しています。しかし、私は Parser モナドから取得したエラー チェックを活用したいと考えています。どうすればこれを行うことができ、プロセス内のコードをクリーンアップできますか? 必要に応じて、サンプル JSON を投稿することもできます。

編集: "ayyLmao" (したがって、愚かな名前) を取り除き、代わりにエラー チェック用の Parser モナドに '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"
4

1 に答える 1

3

「または、パーサーのリストを取得して、それを 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"

これにより、JSON から属性CommentListをフェッチするために使用されるラッパー タイプが導入されます。obj.data.childrenこれは既存のFromJSONインスタンスをVector利用するため、返信を手動でループして個別に解析する必要はありません。

表現

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

repliesは、JSON の属性にnull値または有効な値が含まれていると想定しているCommentListため、値を解析しようとしMaybe CommentList(nullに解析されます)、を使用して値を空のベクトルNothingに置き換えます。NothingfromMaybe

于 2014-09-11T04:44:01.487 に答える