我正在尝试在Haskell中解析JSON,但是找不到有关此任务的任何有用的文档。我无法执行以下小程序:
{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson
import Data.Word (Word16)
import Data.ByteString.Lazy (pack, ByteString)
import Data.ByteString.Char8 ()
data AppConfig = AppConfig {
database :: DatabaseConfig
} deriving (Show)
data DatabaseConfig = DatabaseConfig {
host :: String,
port :: Word,
username :: String,
password :: String,
databaseName :: String
} deriving (Show)
instance FromJSON DatabaseConfig where
parseJSON (Object obj) = DatabaseConfig
<$> obj .: "host"
<*> obj .: "port"
<*> obj .: "username"
<*> obj .: "password"
<*> obj .: "databaseName"
parseJSON obj = fail $ show obj
instance FromJSON AppConfig where
parseJSON (Object obj) = AppConfig <$> obj .: "database"
parseJSON obj = fail $ show obj
config = "{ \"database\": { \"host\": \"db\", \"port\": 1234, \"username\": \"ledger\", \"password\": \"ledger\", \"database_name\": \"ledger\" } }\""
main :: IO ()
main = do
let cfg = eitherDecode config
case cfg of
Left err -> fail $ "error: " ++ err
Right ps -> putStrLn $ ps
该错误消息完全没有意义:
用户错误(错误:$中的错误:endOfInput)
那么代码有什么问题?
答案 0 :(得分:2)
如果您查看pack
中Data.ByteString
的类型签名
pack :: [Word8] -> ByteString
您会看到,它不需要String
或实现IsString
typeclass的某种类型,而是一个字节列表。让我们通过使用Data.ByteString.Char8
代替Data.ByteString
来解决此问题。
现在,出现以下错误:
test.hs:36:12: error:
• Couldn't match type ‘Either String’ with ‘IO’
Expected type: IO (Either [Char] String)
Actual type: Either String (Either [Char] String)
• In a stmt of a 'do' block: cfg <- (eitherDecode config)
In the expression:
do cfg <- (eitherDecode config)
case cfg of
Left err -> fail $ "error: " ++ err
Right ps -> putStrLn $ ps
In an equation for ‘main’:
main
= do cfg <- (eitherDecode config)
case cfg of
Left err -> fail $ "error: " ++ err
Right ps -> putStrLn $ ps
|
36 | cfg <- (eitherDecode config)
| ^^^^^^^^^^^^^^^^^^^
那是因为对于某些类型eitherDecode config
,IO a
不会导致类型为a
的值,而是会导致类型为Either String a
的值。因此,我们修复它。
现在出现以下错误:
test.hs:36:27: error:
• Couldn't match expected type ‘Data.ByteString.Lazy.Internal.ByteString’
with actual type ‘ByteString’
NB: ‘ByteString’ is defined in ‘Data.ByteString.Internal’
‘Data.ByteString.Lazy.Internal.ByteString’
is defined in ‘Data.ByteString.Lazy.Internal’
• In the first argument of ‘eitherDecode’, namely ‘config’
In the expression: (eitherDecode config)
In an equation for ‘cfg’: cfg = (eitherDecode config)
|
36 | let cfg = (eitherDecode config)
| ^^^^^^
很显然,我们选择了错误的ByteString
类型。 ByteString
中的Data.ByteString.Char8
很严格,但是aeson希望使用惰性字节串。因此,让我们使用Data.ByteString.Lazy.Char8
来解决此问题。
现在它编译并运行该程序会出现以下错误:
[nix-shell:~/tmp]$ ./test
test: user error (error: Error in $: endOfInput)
这是因为字符串文字中的JSON错误。读
"{ \"database\": { \"host\": \"db\", \"port\": 1234, \"username\": \"ledger\", \"password\": \"ledger\", \"database_name\": \"ledger\" } }\""
它应显示为:
"{ \"database\": { \"host\": \"db\", \"port\": 1234, \"username\": \"ledger\", \"password\": \"ledger\", \"database_name\": \"ledger\" } }"
重新编译并运行测试现在会导致以下错误:
[nix-shell:~/tmp]$ ./test
test: user error (error: Error in $: expected String, encountered Object)
因此,显然,aeson认为它应该解码json字符串值,但是遇到了一个对象。如果您看以下几行
let cfg = (eitherDecode config)
case cfg of
Left err -> fail $ "error: " ++ err
Right ps -> putStrLn $ ps
您会看到ps
被键入为String
,因为它被用作putStrLn
的自变量,这解释了观察到的行为。如果我们只是将putStrLn $ ps
更改为putStrLn $ show ps
,则编译器根本不知道ps
的类型是什么,所以让我们为他提供类型注释。
重新编译并运行测试现在会导致以下错误:
[nix-shell:~/tmp]$ ./test
test: user error (error: Error in $.database: key "databaseName" not present)
因此您的程序希望databaseName
作为json对象中的键,而不是database_name
。修复FromJSON
实例以解决此问题。
现在它输出:
[nix-shell:~/tmp]$ ./test
AppConfig {database = DatabaseConfig {host = "db", port = 1234, username = "ledger", password = "ledger", databaseName = "ledger"}}
最终程序显示为:
{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson
import Data.Word (Word16)
import Data.ByteString.Lazy.Char8 (pack,ByteString)
data AppConfig = AppConfig {
database :: DatabaseConfig
} deriving (Show)
data DatabaseConfig = DatabaseConfig {
host :: String,
port :: Word,
username :: String,
password :: String,
databaseName :: String
} deriving (Show)
instance FromJSON DatabaseConfig where
parseJSON (Object obj) = DatabaseConfig
<$> obj .: "host"
<*> obj .: "port"
<*> obj .: "username"
<*> obj .: "password"
<*> obj .: "database_name"
parseJSON obj = fail $ show obj
instance FromJSON AppConfig where
parseJSON (Object obj) = AppConfig <$> obj .: "database"
parseJSON obj = fail $ show obj
config = pack "{ \"database\": { \"host\": \"db\", \"port\": 1234, \"username\": \"ledger\", \"password\": \"ledger\", \"database_name\": \"ledger\" } }"
main :: IO ()
main = do
let cfg = (eitherDecode config) :: Either String AppConfig
case cfg of
Left err -> fail $ "error: " ++ err
Right ps -> putStrLn $ show ps
答案 1 :(得分:1)
看起来像您输入中的错误,而不是代码中的错误。字符串末尾的\"
看起来可疑,该字符串的内容如下:
{ "database": { "host": "db", ... } }"
结尾"
引起解析错误的地方。
答案 2 :(得分:0)
hnefati和Krom已经提供了答案(您有一个额外的\“字符,需要在解码函数中添加类型注释,并且需要将database_name与databaseName匹配)。对于此特定示例,如果您满意,使用几种语言扩展,您的代码可以这样简化:
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
module SO where
import Data.Aeson
import Data.ByteString.Char8 ()
import GHC.Generics (Generic)
data AppConfig = AppConfig {
database :: DatabaseConfig
} deriving (Show, Generic, FromJSON)
data DatabaseConfig = DatabaseConfig {
host :: String,
port :: Word,
username :: String,
password :: String,
databaseName :: String
} deriving (Show, Generic, FromJSON)
config = "{ \"database\": { \"host\": \"db\", \"port\": 1234, \"username\": \"ledger\", \"password\": \"ledger\", \"databaseName\": \"ledger\" } }"
main :: IO ()
main = do
let cfg = eitherDecode config
case cfg of
Left err -> fail $ "error: " ++ err
Right ps -> putStrLn $ show $ (ps :: AppConfig)