在 json 解析中使用文件名 (Haskell Aeson)

Using file name in json parsing (Haskell Aeson)

我没有Haskell方面的经验。我正在尝试使用 aeson 将许多 .json 文件解析为 Haskell 中的数据结构。但是,由于我无法控制的原因,我需要将解析数据的文件的名称存储为我的数据中的字段之一。到目前为止,我所拥有的一个简单示例是:

data Observation = Observation { id :: Integer
                               , value :: Integer
                               , filename :: String}

instance FromJSON Observation where
  parseJson (Object v) =
    Observation <$> (read <$> v .: "id")
                <*> v .: "value"
                <*> ????

我的问题是:在解析具有文件名访问权限的 json 文件时,能够序列化我的数据的聪明方法是什么?

我想到的是定义另一个 data,例如 NotNamedObservation,对其进行初始化,然后使用一个函数来转换 NotNamedObservation -> String -> Observation(其中 String 是文件名),但这听起来就像一个非常糟糕的方法。

谢谢。

当您不控制 data 定义并且对要解析的格式有严格要求时,最好显式编写(反)序列化器。

如果需要外部信息来完全构造值,请避免使用 FromJSON/ToJSON 类型 类,只需编写独立的解析器。

aeson 的派生机制更适合与自己对话的应用程序(因此只关心 parseJSONtoJSON 之间的往返),或者可以灵活定义 JSON 格式或 Haskell 类型。


如果出于某种原因您仍然必须使用这些 类,当然,一种选择是将 undefined 放在那些缺失的字段中。要更多地依赖类型系统,您还可以通过 "phase" 参数化类型(假设您可以再次调整数据类型),这是一个包装一些字段的类型构造函数。


data Observation' p = Observation
  { id :: Integer
  , value :: Integer
  , filename :: p String }

-- This is isomorphic to the original Observation data type
type Observation = Observation Identity

-- When we don't have the filename available, we keep the field empty with Proxy
instance FromJSON (Observation' Proxy) where
  ...

mkObservation :: FileName -> Observation' Proxy -> Observation

只需让您的实例成为从文件路径到观察的函数:

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson
import qualified Data.ByteString.Lazy as LBS
import System.Environment

data Observation = Observation { ident    :: Integer
                               , value    :: Integer
                               , filename :: FilePath
                               } deriving (Show)

instance FromJSON (FilePath -> Observation) where
  parseJSON (Object v) =
    do i <- read <$> v .: "id"
       l <- v .: "value"
       pure $ Observation i l

main :: IO ()
main = do
  files <- getArgs
  fileContents <- traverse LBS.readFile files
  print fileContents
  let fs = map (maybe (error "Invalid json") id . decode) fileContents
      jsons :: [Observation]
      jsons = zipWith ($) fs files
  print jsons