尝试使用 aeson 从 json 中取出一个对象

Trying to get an object out of json using aeson

我正在尝试从以下代码中获取配置数据记录:

data Connections = Connections { cfgProperty  :: !Object
                   , connectionName :: String
                   } deriving (Show, Generic)

data Config = Config {connections :: [Connections]} deriving (Show, Generic)

data Cfg = Cfg { config :: Config } deriving (Show, Generic)



instance FromJSON Cfg
instance FromJSON Config
instance FromJSON Connections
instance ToJSON Cfg
instance ToJSON Config
instance ToJSON Connections

jsonFile :: FilePath
jsonFile = "config/config.json"

getCfg :: IO B.ByteString
getCfg = B.readFile jsonFile


parseCfg = do
      j <- (A.eitherDecode <$> getCfg) :: IO (Either String Cfg)
      case j of
        Left err ->  liftIO $ putStrLn err
        Right j -> config j

我收到以下错误:

/apps/workspace/hade/src/Actor/MasterActor.hs: 56, 20
• Couldn't match expected type ‘IO ()’ with actual type ‘Config’
• In the expression: config j
  In a case alternative: Right j -> config j
  In a stmt of a 'do' block:
    case j of {
      Left err -> liftIO $ putStrLn err
      Right j -> config j }

这里是config.json

{
  "config":
  {
    "connections": [
    {
      "cfgProperty":
      {
        "Env": "local",
        "Host": "localhost",
        "Port": "8001",
        "Directory": "/apps/workspace/hade/maps/src01_cvs"
      },
      "connectionName": "src01_cvs"
    },
    {
        "cfgProperty":
        {
          "Env": "local",
          "Host": "localhost",
          "Port": "8001",
          "Directory": "/apps/workspace/hade/maps/trg01_cvs"
        },
        "connectionName": "trg01_cvs"
      }
    ]
  }
}{
  "config":
  {
    "connections": [
    {
      "cfgProperty":
      {
        "Env": "local",
        "Host": "localhost",
        "Port": "8001",
        "Directory": "/apps/workspace/hade/maps/src01_cvs"
      },
      "connectionName": "src01_cvs"
    },
    {
        "cfgProperty":
        {
          "Env": "local",
          "Host": "localhost",
          "Port": "8001",
          "Directory": "/apps/workspace/hade/maps/trg01_cvs"
        },
        "connectionName": "trg01_cvs"
      }
    ]
  }
}

我尝试了很多不同的配置,同时使用解码和解码,但我每次都 运行 遇到障碍。如果我将大小写更改为:

,我可以获得打印配置记录的代码
      case j of
        Left err ->  putStrLn err
        Right j -> print $ config j

(以及其他一些更改),但我无法简单地 return 配置记录本身。如有任何帮助,我们将不胜感激。

您 运行 遇到的问题的根源在于 parseCfg 中对 getCfg 的调用。 getCfg 的类型是 IO ByteString ,它是一个 IO monad,你可以用 IO monad 做的唯一事情就是调用必须为所有定义的函数monads (bind, fmap, ap, ...) 所有这些 return 另一个 IO monad。这意味着如果 parseCfg 要调用 getCfg 那么它 必须 return 一个 IO monad.

引用 haskell wiki : "Because you can't escape from the IO monad, it is impossible to write a function that does a computation in the IO monad but whose result type does not include the IO type constructor."

解决此问题的一种方法是在 parseCfg 之外调用 getCfg 并将结果传递给 parseCfg。然后 parseCfg 可以 return 一个 Config 但是 return Either String Config 更有意义,这样来自的任何解析错误eitherDecode 被保留。这让您可以将 parseCfg 定义为 configfmap 超过 eitherDecode return 值。

生成的函数如下所示:

parseCfg :: ByteString -> Either String Config
parseCfg json = config <$> eitherDecode json 

这是你的程序,用上面的parseCfg修改为运行。

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE TemplateHaskell #-}

import Data.ByteString.Lazy as B
import Data.Aeson 
import Data.Aeson.TH
import GHC.Generics
import Control.Monad.IO.Class

data CfgProperty = CfgProperty { 
                       env             :: String,
                       host            :: String,
                       port            :: String,
                       directory       :: String
                   } deriving (Show, Generic)

-- Instead of FromJSON, use deriveJSON for CfgProperty 
-- which allows changing of field labels from lower 
-- case to upper case.

$(deriveJSON 

      defaultOptions { fieldLabelModifier = let f "env" = "Env"
                                                f "host" = "Host"
                                                f "port" = "Port"
                                                f "directory" = "Directory"
                                                f other = other
                                             in f
                     } 

      ''CfgProperty
 )

data Connections = Connections { cfgProperty  :: CfgProperty
                   , connectionName :: String
                   } deriving (Show, Generic)

data Config = Config {connections :: [Connections]} deriving (Show, Generic)

data Cfg = Cfg { config :: Config } deriving (Show, Generic)

instance FromJSON Cfg
instance FromJSON Config
instance FromJSON Connections

jsonFile :: FilePath
jsonFile = "config/config.json"

getCfg :: IO ByteString
getCfg = B.readFile jsonFile

parseCfg :: ByteString -> Either String Config
parseCfg json = config <$> eitherDecode json 

main :: IO()
main = do
    json <- getCfg
    case parseCfg json of
        Left err ->  Prelude.putStrLn err
        Right cfg -> print cfg  -- do whatever you want with cfg here.

这是修改后的 config.json。原件包含两条 config JSON 记录,它们之间没有任何内容,这是无效的 JSON,并且与问题无关。

{
  "config":
  {
    "connections": [
    {
      "cfgProperty":
      {
        "Env": "local",
        "Host": "localhost",
        "Port": "8001",
        "Directory": "/apps/workspace/hade/maps/src01_cvs"
      },
      "connectionName": "src01_cvs"
    },
    {
        "cfgProperty":
        {
          "Env": "local",
          "Host": "localhost",
          "Port": "8001",
          "Directory": "/apps/workspace/hade/maps/trg01_cvs"
        },
        "connectionName": "trg01_cvs"
      }
    ]
  }
}