【问题标题】:How to use Data.Text.Lazy.IO to parse JSON files with Aeson如何使用 Data.Text.Lazy.IO 通过 Aeson 解析 JSON 文件
【发布时间】:2017-01-10 04:33:14
【问题描述】:

我想把给定目录下的所有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 似乎从未被评估过,它总是返回空列表。但是如果我在 getFileContent 函数中打印内容,那么一切似乎都正常工作。

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

所以我不确定我错过了什么,我应该使用管道来处理这类事情吗?

【问题讨论】:

  • 简单的答案是肯定的,使用导管之类的。更复杂的答案是,您的 loadAllResults难以置信 懒惰的 - 简单地执行 loadAllResults x 实际上并没有读取任何文件(或者实际上,打开它们)。当您尝试评估结果列表时,您会同时打开所有文件并尝试读取它们。 withFile 对你没有帮助,因为懒惰来自hGetContents - 尝试切换到非懒惰的文本 IO。

标签: json haskell io lazy-evaluation aeson


【解决方案1】:

一般来说,我建议使用流式库来解析任意大小的数据,例如 JSON 文件。但是,在使用 aeson 解析 JSON 的特定情况下,内存溢出的问题并不像 IMO 那样重要,因为 aeson 库本身最终会将内存中的整个文件表示为 Value 类型。因此,鉴于此,您可以选择简单地使用严格的字节串 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

【讨论】:

  • 非常感谢您的详细解答!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多