Aeson如何使用Data.Text.Lazy.IO解析JSON文件
How to use Data.Text.Lazy.IO to parse JSON files with Aeson
我想将给定目录中的所有 json 文件解析为数据类型 Result.
所以我有解码功能
decodeResult :: Data.ByteString.Lazy.ByteString -> Maybe Result
我从 Data.Text.Lazy.IO 开始将文件加载到 Lazy ByteString,
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 似乎从未被评估过,它总是 return 空列表。但是如果我在 getFileContent 函数中打印内容,那么一切似乎都正常工作。
getFileContent :: FilePath -> IO (Maybe Result)
getFileContent path = withFile path ReadMode $ \hnd -> do
content <- T.hGetContents hnd
print content
return $ (decodeAnalytic . T.encodeUtf8) content
所以我不确定我错过了什么,我应该为这类事情使用导管吗?
一般来说,我 会 推荐使用流媒体库来解析任意大小的数据,例如 JSON 文件。但是,在使用 aeson 解析 JSON 的特定情况下,内存溢出的问题在 IMO 中并不重要,因为 aeson 库本身最终会将内存中的整个文件表示为 Value
类型。因此,鉴于此,您可以选择简单地使用严格的字节串 I/O。我给出了一个使用 conduit 和 strict 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。
我想将给定目录中的所有 json 文件解析为数据类型 Result.
所以我有解码功能
decodeResult :: Data.ByteString.Lazy.ByteString -> Maybe Result
我从 Data.Text.Lazy.IO 开始将文件加载到 Lazy ByteString,
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 似乎从未被评估过,它总是 return 空列表。但是如果我在 getFileContent 函数中打印内容,那么一切似乎都正常工作。
getFileContent :: FilePath -> IO (Maybe Result)
getFileContent path = withFile path ReadMode $ \hnd -> do
content <- T.hGetContents hnd
print content
return $ (decodeAnalytic . T.encodeUtf8) content
所以我不确定我错过了什么,我应该为这类事情使用导管吗?
一般来说,我 会 推荐使用流媒体库来解析任意大小的数据,例如 JSON 文件。但是,在使用 aeson 解析 JSON 的特定情况下,内存溢出的问题在 IMO 中并不重要,因为 aeson 库本身最终会将内存中的整个文件表示为 Value
类型。因此,鉴于此,您可以选择简单地使用严格的字节串 I/O。我给出了一个使用 conduit 和 strict 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。