我一直在试图用Aeson来解析Bitly的反应。 有人可以给我一个关于应该定义什么Haskell类型的提示 以及如何使用Aeson将以下内容解析为这些类型?:
// BITLY EXPAND RESPONSE
{
"data": {
"expand": [
{
"global_hash": "900913",
"long_url": "http://google.com/",
"short_url": "http://bit.ly/ze6poY",
"user_hash": "ze6poY"
}
]
},
"status_code": 200,
"status_txt": "OK"
}
// BITLY SHORTEN RESPONSE
{
"data": {
"global_hash": "900913",
"hash": "ze6poY",
"long_url": "http://google.com/",
"new_hash": 0,
"url": "http://bit.ly/ze6poY"
},
"status_code": 200,
"status_txt": "OK"
}
这是我到目前为止所尝试的内容:
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
module BitlyClientResponses where
import Control.Applicative
import Data.Aeson
import qualified Data.ByteString.Lazy.Char8 as L (pack)
import qualified Data.HashMap.Strict as M
data DataStatusCodeStatusTxt =
DSCST { ddata :: ResponseData
, status_code :: Integer
, status_txt :: String
}
deriving (Eq, Show)
data ResponseData
= ExpandResponseData { expand :: [Response]
}
deriving (Eq, Show)
data Response = ExpandResponse { long_url :: String -- URI
, global_hash :: String
, short_url :: String -- URI
, user_hash :: String
-- , hash :: [String]
-- , error :: String
}
| J String
| N String
deriving (Eq, Show)
instance FromJSON DataStatusCodeStatusTxt where
parseJSON (Object o) = DSCST <$>
o .: "data" <*>
o .: "status_code" <*>
o .: "status_txt"
parseJSON x = fail $ "FAIL: DataStatusCodeStatusTxt: " ++ (show x)
instance FromJSON ResponseData where
parseJSON (Object o) =
case M.lookup "expand" o of
-- LOST RIGHT HERE
Just v -> return $ ExpandResponseData [J ((show o) ++ " $$$ " ++ (show v))]
Nothing -> return $ ExpandResponseData [N "N"]
parseJSON x = fail $ "FAIL: ResponseData: " ++ (show x)
instance FromJSON Response where
parseJSON (Object o) = ExpandResponse <$>
o .: "long_url" <*>
o .: "global_hash" <*>
o .: "short_url" <*>
o .: "user_hash"
-- o .: "hash" <*>
-- o .: "error" <*>
parseJSON x = fail $ "FAIL: Response: " ++ (show x)
parseResponse :: String -> Either String DataStatusCodeStatusTxt
parseResponse x = eitherDecode $ L.pack x
当我输入(为便于阅读而编辑的手):
"{ \"status_code\": 200,
\"status_txt\": \"OK\",
\"data\": { \"expand\": [
{ \"short_url\": \"http:\\/\\/bit.ly\\/LCJq0b\",
\"long_url\": \"http:\\/\\/blog.swisstech.net\\/2012\\/06\\/local-postfix-as-relay-to-amazon-ses.html\",
\"user_hash\": \"LCJq0b\",
\"global_hash\": \"LCJsVy\" }, ...
我回来了(手工编辑):
Right
(Right
(DSCST
{ddata = ExpandResponseData {expand = [J "fromList [(\"expand\",Array (fromList [Object fromList [(\"long_url\",String \"http://blog.swisstech.net/2012/06/local-postfix-as-relay-to-amazon-ses.html\"),(\"global_hash\",String \"LCJsVy\"),(\"short_url\",String \"http://bit.ly/LCJq0b\"),(\"user_hash\",String \"LCJq0b\")], ...
$$$
Array (fromList [Object fromList [(\"long_url\",String \"http://blog.swisstech.net/2012/06/local-postfix-as-relay-to-amazon-ses.html\"),(\"global_hash\",String \"LCJsVy\"),(\"short_url\",String \"http://bit.ly/LCJq0b\"),(\"user_hash\",String \"LCJq0b\")], ...
在代码中,查找-- LOST RIGHT HERE
。我无法弄清楚如何解析"expand"
的数组。
很高兴看到如何取得进步。也许我走错了路,有人可以直截了当(例如,我到目前为止定义的数据类型可能已关闭)。
答案 0 :(得分:4)
有效使用Aeson
的诀窍是递归调用parseJSON
。当您使用(.:)
运算符时,这是隐式执行的,因此查看类似M.lookup
的内容通常是一个不好的信号。我将提供一个简化示例:(纬度,经度)对的路径,由JSON对象的JSON数组表示。
data Path = Path { points :: [Point] }
data Point = Point { lat :: Double, lon :: Double }
-- JSON format looks a bit like this
--
-- { "points": [ {"latitude": 86, "longitude": 23} ,
-- {"latitude": 0, "longitude": 16} ,
-- {"latitude": 43, "longitude": 87} ] }
instance FromJSON Path where
parseJSON = withObject "path" $ \o ->
Path <$> o .: "points"
instance FromJSON Point where
parseJSON = withObject "point" $ \o ->
Point <$> o .: "latitude"
<*> o .: "longitude"
要从这个片段中删除两个要点。首先,请注意使用withObject
快速约束传递给Value
的{{1}}被标记为parseJSON
- 它与使用模式匹配没有显着差异,但它会产生自动,统一的错误消息,所以值得考虑。
其次,更重要的是,请注意我只定义描述每个对象的高级轮廓的Object
实例。特别是,检查FromJSON
FromJSON Path
所有这些都说明我需要查看名为Path <$> o .: "points"
的条目,并尝试将其解析为构建"points"
所需的任何类型 - 在这种情况下,{{1}列表}},Path
。此用法取决于递归定义的Point
实例。我们需要解析一个数组,但幸运的是已经存在[Point]
实例
FromJSON
被解释为JSON类型FromJSON
可以解析为的JSON数组。在我们的案例instance FromJSON a => FromJSON [a] where ...
中,我们只定义该实例
a
然后递归依赖
a ~ Point
这是非常标准的。
您可以使用的另一个重要技巧是使用instance FromJSON Point where ...
连接多个解析。我将简化instance FromJSON Double where ...
数据类型,它将其解析为特定的(<|>)
或失败,并生成一个普通的动态类型Response
作为默认值。首先,我们将独立编写每个解析器。
Object
现在我们将它们组合在实际的Value
实例
data Obj = Obj { foo :: String, bar :: String }
| Dyn Value
okParse :: Value -> Parser Obj
okParse = withObject "obj" (\o -> Obj <$> o .: "foo" <*> o .: "bar")
elseParse :: Value -> Parser Obj
elseParse v = pure (Dyn v)
在这种情况下,FromJSON
会首先尝试使用instance FromJSON Obj where
parseJSON v = okParse v <|> elseParse v
,如果失败,则会回到aeson
。由于okParse
只是一个elseParse
值,因此它永远不会失败,从而提供“默认”回退。