无Haskell经验者求助:用Aeson解析JSON时存储文件名到数据结构
解决Aeson解析时添加文件名到数据结构的问题
看起来你遇到的核心问题是:Aeson的FromJSON实例无法直接获取外部的文件名信息,因为parseJSON函数只能访问JSON本身的内容,没法拿到你读取的文件路径。不过有两种简单的方法可以解决这个问题,我给你一步步讲清楚:
方法一:先解析核心数据,再补全文件名
这种方法最直观,先定义一个只包含JSON里字段的中间类型,解析完成后再把文件名加进去构造最终的Observation:
import Data.Aeson -- 第一步:定义不带filename的中间数据类型 data ObservationCore = ObservationCore { coreId :: Integer , coreValue :: Integer } deriving (Show) -- 第二步:为中间类型实现FromJSON instance FromJSON ObservationCore where parseJSON (Object v) = ObservationCore <$> (read <$> v .: "id") -- 这里的read是因为你原来的代码假设JSON里id是字符串,要是JSON里id是数字,直接写v .: "id"就行 <*> v .: "value" parseJSON _ = fail "Expected an object for ObservationCore" -- 第三步:你的原始数据类型 data Observation = Observation { id :: Integer , value :: Integer , filename :: String } deriving (Show) -- 第四步:写一个函数把中间类型和文件名组合成最终类型 attachFilename :: String -> ObservationCore -> Observation attachFilename fname core = Observation { id = coreId core , value = coreValue core , filename = fname }
使用的时候,你可以先读取文件解析成ObservationCore,再调用attachFilename:
import qualified Data.ByteString.Lazy as BL loadObservation :: FilePath -> IO (Maybe Observation) loadObservation fname = do jsonContent <- BL.readFile fname case decode jsonContent of Just core -> return $ Just (attachFilename fname core) Nothing -> return Nothing
方法二:自定义带文件名参数的解析函数
如果你不想多定义一个中间类型,可以直接写一个接收文件名的解析函数,代替默认的FromJSON实例:
import Data.Aeson import Data.Aeson.Types (Parser) data Observation = Observation { id :: Integer , value :: Integer , filename :: String } deriving (Show) -- 自定义解析函数,把文件名作为参数传入 parseObservation :: String -> Value -> Parser Observation parseObservation fname (Object v) = Observation <$> (read <$> v .: "id") -- 同样,根据JSON里id的类型调整是否需要read <*> v .: "value" <*> pure fname -- 直接把文件名作为纯值传入Parser parseObservation _ _ = fail "Expected an object for Observation"
读取文件的时候用parseMaybe来调用这个自定义解析函数:
import qualified Data.ByteString.Lazy as BL loadObservation :: FilePath -> IO (Maybe Observation) loadObservation fname = do jsonContent <- BL.readFile fname return $ parseMaybe (parseObservation fname) jsonContent
为什么原来的方法行不通?
你原来尝试在FromJSON实例里直接处理文件名,但FromJSON的parseJSON方法签名是固定的:
parseJSON :: Value -> Parser a
它只能接收JSON解析后的Value,没有办法获取外部的文件名变量——所以必须从外部把文件名传递进去,要么通过中间类型补全,要么通过自定义解析函数传入参数。
另外提醒一下:如果你的JSON里id字段本身就是数字类型(比如"id": 123),那完全不需要read,直接写v .: "id"就能得到Integer,用read反而会报错哦,记得根据实际的JSON结构调整这部分代码。
内容的提问来源于stack exchange,提问作者Vitorqb
相关产品推荐
相关产品推荐

