Haskell - Aeson:尝试解码 JSON URL Req 时得到 "Nothing"
Haskell - Aeson : Getting "Nothing" when trying to decode JSON URL Req
我对 haskell 比较陌生,现在我正在尝试更深入地了解并尝试适应不同的流行库。
现在我正在尝试 "aeson"。
我想做的是解析来自 https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=MSFT&apikey=demo
的 MSFT 报价请求
这是它的样子
{
"Global Quote": {
"01. symbol": "MSFT",
"02. open": "105.3500",
"03. high": "108.2400",
"04. low": "105.2700",
"05. price": "107.6000",
"06. volume": "23308066",
"07. latest trading day": "2018-10-11",
"08. previous close": "106.1600",
"09. change": "1.4400",
"10. change percent": "1.3564%"
}
}
这是我目前得到的
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson
import qualified Data.ByteString.Lazy as B
import GHC.Exts
import GHC.Generics
import Network.HTTP
import Network.URI
jsonURL :: String
jsonURL = "http://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=MSFT&apikey=demo"
getRequest_ :: HStream ty => String -> Request ty
getRequest_ s = let Just u = parseURI s in defaultGETRequest_ u
jsonReq = getRequest_ jsonURL
data Quote = Quote {quote :: String,
symbol :: String,
open :: Float,
high :: Float,
low :: Float,
price :: Float,
volume :: Float,
ltd :: String,
previousClose :: Float,
change :: Float,
changePerct :: Float
} deriving (Show, Generic)
instance FromJSON Quote
instance ToJSON Quote
main :: IO ()
main = do
d <- simpleHTTP jsonReq
body <- getResponseBody d
print (decode body :: Maybe Quote)
我做错了什么?
编辑:修复了答案中的版本。
首先:Aeson 对于初学者来说不是最简单的库。当然还有更难的,但它假设你已经对这门语言有相当多的了解。您没有选择 "simplest task" 开始。我知道这可能会令人惊讶,您可能认为解析 JSON 应该很简单,但解析具有强类型保证的 JSON 实际上并不那么简单。
但是我可以告诉你一些可以帮助你的事情:
首先,使用eitherDecode
而不是decode
:你会得到一个错误信息而不是简单的Nothing
,这会对你有一点帮助。
通过 Generic
进行推导很简洁,而且经常可以节省时间,但它也不是魔术。对象键的名称和数据类型字段的名称必须完全匹配。遗憾的是,这里不是这种情况,并且由于 haskell 语法,您不能像对象的键那样命名您的字段。您最好的解决方案是手动实施 FromJSON(请参阅下面推荐的 link)。通过泛型 FromJSON 查看 "what is expect" 的一个好方法是还派生 ToJSON,创建一个虚拟 Quote
并查看 encode
的结果。
您的第一个字段(quote
)不是对象本身的键,而是该对象的名称。所以你有动态键("Global Quote" 在这里)。同样,这通常是您想要手动编写 FromJSON 实例的情况。
我建议您阅读 Aeson 上著名的 tutorial written by Artyom Kazak。这将极大地帮助您,并且可能是我能提供的最佳建议。
对于您的手动实例,假设它 完全是 您要解析的文档,而您只有 "Global Quote" 需要处理,它看起来或多或少像这样:
instance ToJSON Quote where
parseJSON = withObject "Document" $
\d -> do
glob <- d .: "Global Quote"
withObject "Quote" v (\gq ->
Quote <$> gq .: "01. symbol"
<*> pure "Global Quote"
<*> gq .: "02. open"
<*> gq .: "03. high"
-- ... and so on
) v
(这不是最漂亮的写法,也不是最好的写法,但应该是一种可能的写法)。
另请注意,正如一位精明的评论者所写,您的字段类型并不总是与示例 JSON 文档的类型一致。 "volume" 是一个 Int
(字节限制的整数),可能是一个 Integer
("mathematical" 整数,无限制),但不是 Float
。您的 "ltd" 可以解析为字符串 - 但它可能应该是一个日期(Data.Time
中的 Day
将是第一选择 - 它已经有一个 FromJSON
实例所以机会是它应该是可解析的)。更改百分比很可能无法像 Float 那样解析,您需要为此类型编写一个专用解析器(并决定您要如何存储它 - Ratio
是一个潜在的解决方案)。
@Raveline 上面的回答为我指明了正确的方向。我能够解决所有这些问题,这是最终产品!
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module Test where
import Data.Aeson
import qualified Data.ByteString.Lazy as B
import GHC.Exts
import GHC.Generics
import Network.HTTP.Conduit (simpleHttp)
jsonURL :: String
jsonURL = "https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=MSFT&apikey=demo"
getJSON :: IO B.ByteString
getJSON = simpleHttp jsonURL
data Quote = Quote {
symbol :: String,
open :: String,
high :: String,
low :: String,
price :: String,
volume :: String,
ltd :: String,
previousClose :: String,
change :: String,
changePercent :: String
} deriving (Show, Generic)
instance FromJSON Quote where
parseJSON = withObject "Global Quote" $
\o -> do
globalQuote <- o .: "Global Quote"
symbol <- globalQuote .: "01. symbol"
open <- globalQuote .: "02. open"
high <- globalQuote .: "03. high"
low <- globalQuote .: "04. low"
price <- globalQuote .: "05. price"
volume <- globalQuote .: "06. volume"
ltd <- globalQuote .: "07. latest trading day"
previousClose <- globalQuote .: "08. previous close"
change <- globalQuote .: "09. change"
changePercent <- globalQuote .: "10. change percent"
return Quote {..}
main :: IO ()
main = do
d <- (eitherDecode <$> getJSON) :: IO (Either String Quote)
case d of
Left e -> print e
Right qt -> print (read (price qt) :: Float)
我对 haskell 比较陌生,现在我正在尝试更深入地了解并尝试适应不同的流行库。
现在我正在尝试 "aeson"。
我想做的是解析来自 https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=MSFT&apikey=demo
的 MSFT 报价请求这是它的样子
{
"Global Quote": {
"01. symbol": "MSFT",
"02. open": "105.3500",
"03. high": "108.2400",
"04. low": "105.2700",
"05. price": "107.6000",
"06. volume": "23308066",
"07. latest trading day": "2018-10-11",
"08. previous close": "106.1600",
"09. change": "1.4400",
"10. change percent": "1.3564%"
}
}
这是我目前得到的
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson
import qualified Data.ByteString.Lazy as B
import GHC.Exts
import GHC.Generics
import Network.HTTP
import Network.URI
jsonURL :: String
jsonURL = "http://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=MSFT&apikey=demo"
getRequest_ :: HStream ty => String -> Request ty
getRequest_ s = let Just u = parseURI s in defaultGETRequest_ u
jsonReq = getRequest_ jsonURL
data Quote = Quote {quote :: String,
symbol :: String,
open :: Float,
high :: Float,
low :: Float,
price :: Float,
volume :: Float,
ltd :: String,
previousClose :: Float,
change :: Float,
changePerct :: Float
} deriving (Show, Generic)
instance FromJSON Quote
instance ToJSON Quote
main :: IO ()
main = do
d <- simpleHTTP jsonReq
body <- getResponseBody d
print (decode body :: Maybe Quote)
我做错了什么?
编辑:修复了答案中的版本。
首先:Aeson 对于初学者来说不是最简单的库。当然还有更难的,但它假设你已经对这门语言有相当多的了解。您没有选择 "simplest task" 开始。我知道这可能会令人惊讶,您可能认为解析 JSON 应该很简单,但解析具有强类型保证的 JSON 实际上并不那么简单。
但是我可以告诉你一些可以帮助你的事情:
首先,使用
eitherDecode
而不是decode
:你会得到一个错误信息而不是简单的Nothing
,这会对你有一点帮助。通过
Generic
进行推导很简洁,而且经常可以节省时间,但它也不是魔术。对象键的名称和数据类型字段的名称必须完全匹配。遗憾的是,这里不是这种情况,并且由于 haskell 语法,您不能像对象的键那样命名您的字段。您最好的解决方案是手动实施 FromJSON(请参阅下面推荐的 link)。通过泛型 FromJSON 查看 "what is expect" 的一个好方法是还派生 ToJSON,创建一个虚拟Quote
并查看encode
的结果。您的第一个字段(
quote
)不是对象本身的键,而是该对象的名称。所以你有动态键("Global Quote" 在这里)。同样,这通常是您想要手动编写 FromJSON 实例的情况。
我建议您阅读 Aeson 上著名的 tutorial written by Artyom Kazak。这将极大地帮助您,并且可能是我能提供的最佳建议。
对于您的手动实例,假设它 完全是 您要解析的文档,而您只有 "Global Quote" 需要处理,它看起来或多或少像这样:
instance ToJSON Quote where
parseJSON = withObject "Document" $
\d -> do
glob <- d .: "Global Quote"
withObject "Quote" v (\gq ->
Quote <$> gq .: "01. symbol"
<*> pure "Global Quote"
<*> gq .: "02. open"
<*> gq .: "03. high"
-- ... and so on
) v
(这不是最漂亮的写法,也不是最好的写法,但应该是一种可能的写法)。
另请注意,正如一位精明的评论者所写,您的字段类型并不总是与示例 JSON 文档的类型一致。 "volume" 是一个 Int
(字节限制的整数),可能是一个 Integer
("mathematical" 整数,无限制),但不是 Float
。您的 "ltd" 可以解析为字符串 - 但它可能应该是一个日期(Data.Time
中的 Day
将是第一选择 - 它已经有一个 FromJSON
实例所以机会是它应该是可解析的)。更改百分比很可能无法像 Float 那样解析,您需要为此类型编写一个专用解析器(并决定您要如何存储它 - Ratio
是一个潜在的解决方案)。
@Raveline 上面的回答为我指明了正确的方向。我能够解决所有这些问题,这是最终产品!
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module Test where
import Data.Aeson
import qualified Data.ByteString.Lazy as B
import GHC.Exts
import GHC.Generics
import Network.HTTP.Conduit (simpleHttp)
jsonURL :: String
jsonURL = "https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=MSFT&apikey=demo"
getJSON :: IO B.ByteString
getJSON = simpleHttp jsonURL
data Quote = Quote {
symbol :: String,
open :: String,
high :: String,
low :: String,
price :: String,
volume :: String,
ltd :: String,
previousClose :: String,
change :: String,
changePercent :: String
} deriving (Show, Generic)
instance FromJSON Quote where
parseJSON = withObject "Global Quote" $
\o -> do
globalQuote <- o .: "Global Quote"
symbol <- globalQuote .: "01. symbol"
open <- globalQuote .: "02. open"
high <- globalQuote .: "03. high"
low <- globalQuote .: "04. low"
price <- globalQuote .: "05. price"
volume <- globalQuote .: "06. volume"
ltd <- globalQuote .: "07. latest trading day"
previousClose <- globalQuote .: "08. previous close"
change <- globalQuote .: "09. change"
changePercent <- globalQuote .: "10. change percent"
return Quote {..}
main :: IO ()
main = do
d <- (eitherDecode <$> getJSON) :: IO (Either String Quote)
case d of
Left e -> print e
Right qt -> print (read (price qt) :: Float)