You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 07:29:05