2017-01-10 19 views
4

我想將給定目錄中的所有json文件解析爲數據類型結果如何使用Data.Text.Lazy.IO與Aeson解析JSON文件

所以我有一個解碼功能

decodeResult :: Data.ByteString.Lazy.ByteString -> Maybe Result 

我Data.Text.Lazy.IO開始到文件加載到懶惰的字節串,

import qualified Data.Text.Lazy.IO as T 
import qualified Data.Text.Lazy.Encoding as T 

getFileContent :: FilePath -> IO B.ByteString 
getFileContent path = T.encodeUtf8 `fmap` T.readFile path 

它編譯,但是我跑進太多文件打開的問題,所以我想也許我應該使用withFile

import System.IO 
import qualified Data.ByteString.Lazy as B 
import qualified Data.Text.Lazy.IO as T 
import qualified Data.Text.Lazy.Encoding as T 

getFileContent :: FilePath -> IO (Maybe Result) 
getFileContent path = withFile path ReadMode $ \hnd -> do 
    content <- T.hGetContents hnd 
    return $ (decodeAnalytic . T.encodeUtf8) content 

loadAllResults :: FilePath -> IO [Result] 
loadAllResults path = do 
    paths <- listDirectory path 
    results <- sequence $ fmap getFileContent (fmap (path ++) $ filter (endswith ".json") paths) 
    return $ catMaybes results 

在這個版本中,懶惰的io似乎永遠不會被評估,它總是返回空列表。但如果我打印內容getFileContent函數,那麼一切似乎工作正常。

getFileContent :: FilePath -> IO (Maybe Result) 
getFileContent path = withFile path ReadMode $ \hnd -> do 
    content <- T.hGetContents hnd 
    print content 
    return $ (decodeAnalytic . T.encodeUtf8) content 

所以我不知道我錯過了什麼,我應該使用管道這種類型的東西?

+0

簡單的答案是肯定的,使用管道或類似的東西。更復雜的答案是,你的'loadAllResults'是*令人難以置信的*懶惰 - 簡單地執行'loadAllResults x'實際上並不讀取任何文件(或者確實打開它們)。當您嘗試評估結果列表時,您同時打開所有文件並嘗試閱讀它們。 'withFile'不會幫助你,因爲懶惰來自'hGetContents' - 嘗試切換到非懶惰的Text IO。 – user2407038

回答

5

一般來說我推薦使用流媒體庫來解析任意大小的數據,如JSON文件。但是,在使用aeson解析JSON的特定情況下,由於aeson庫本身將最終將內存中的整個文件表示爲Value類型,因此溢出內存的擔憂並不像IMO那麼重要。因此,您可以選擇使用嚴格的字節串I/O。我給出了一個使用導管和嚴格I/O來解析JSON值的例子。 (我認爲在一些圖書館管道版本存在了,我不知道。)

#!/usr/bin/env stack 
{- stack --resolver lts-7.14 --install-ghc runghc 
    --package aeson --package conduit-extra 
-} 
import   Control.Monad.Catch  (MonadThrow, throwM) 
import   Control.Monad.IO.Class (MonadIO, liftIO) 
import   Data.Aeson    (FromJSON, Result (..), eitherDecodeStrict', 
              fromJSON, json, Value) 
import   Data.ByteString   (ByteString) 
import qualified Data.ByteString   as B 
import   Data.Conduit   (ConduitM, runConduitRes, (.|)) 
import   Data.Conduit.Attoparsec (sinkParser) 
import   Data.Conduit.Binary  (sourceFile) 

sinkFromJSON :: (MonadThrow m, FromJSON a) => ConduitM ByteString o m a 
sinkFromJSON = do 
    value <- sinkParser json 
    case fromJSON value of 
     Error e -> throwM $ userError e 
     Success x -> return x 

readJSONFile :: (MonadIO m, FromJSON a) => FilePath -> m a 
readJSONFile fp = liftIO $ runConduitRes $ sourceFile fp .| sinkFromJSON 

-- Or using strict I/O 
readJSONFileStrict :: (MonadIO m, FromJSON a) => FilePath -> m a 
readJSONFileStrict fp = liftIO $ do 
    bs <- B.readFile fp 
    case eitherDecodeStrict' bs of 
     Left e -> throwM $ userError e 
     Right x -> return x 

main :: IO() 
main = do 
    x <- readJSONFile "test.json" 
    y <- readJSONFileStrict "test.json" 
    print (x :: Value) 
    print (y :: Value) 

編輯忘了提:我會使用文本I/O強烈建議閱讀您的JSON文件。 JSON文件應使用UTF-8編碼,而文本I/O函數將使用您的系統設置爲字符編碼指定的任何內容。依靠Data.ByteString.readFile和類似的更可靠。我進入更多細節in a recent blog post

+1

非常感謝您的詳細解答! –