我正在尝试在 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)
那么代码有什么问题呢?
如果您查看pack
from的类型签名Data.ByteString
pack :: [Word8] -> ByteString
你看,它不需要一个String
或某种类型的实现IsString
类型类,而是一个字节列表。让我们通过使用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
某个 type 的 typea
值,而是type的值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
fromData.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
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句