如何使用Aeson解析位响应JSON?

如何使用Aeson解析位响应JSON?,json,haskell,aeson,Json,Haskell,Aeson,我一直在绞尽脑汁试图用Aeson来解析那些刻薄的回答。 有人能给我一个关于Haskell类型应该定义的提示吗 以及如何使用Aeson将以下内容解析为这些类型: // BITLY EXPAND RESPONSE { "data": { "expand": [ { "global_hash": "900913", "long_url": "http://google.com/", "short_url": "http://bit

我一直在绞尽脑汁试图用Aeson来解析那些刻薄的回答。 有人能给我一个关于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
我回来了(也手工编辑):

在代码中,查找
--LOST RIGHT HERE
。我不知道如何解析
“expand”
的数组


如果能看到如何取得进展,那就太好了。也许我走错了路,有人可以纠正我的错误(例如,也许到目前为止我定义的数据类型不正确)。

有效使用
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"
这说明我需要查看名为
“points”
的条目,并尝试将其解析为构建
路径所需的任何类型,在本例中,是
点的列表,
[Point]
。这种使用依赖于递归定义的
FromJSON
实例。我们需要解析一个数组,但幸运的是已经存在
FromJSON
实例

instance FromJSON a => FromJSON [a] where ...
instance FromJSON Obj where
  parseJSON v = okParse v <|> elseParse v
它被解释为JSON类型的JSON数组
a
可以解析为什么。在我们的例子中,
a~点
,所以我们只定义该实例

instance FromJSON Point where ...
然后递归地依赖于

instance FromJSON Double where ...
这是相当标准的


您可以使用的另一个重要技巧是使用
()
连接多个解析。我将把
响应
数据类型简化一点,它要么解析为特定的
对象
,要么失败,并生成一个简单的、动态键入的
,作为默认值。首先,我们将独立编写每个解析器

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)

在这种情况下,
aeson
将首先尝试使用
okParse
,如果失败,则使用
elseParse
。由于
elseParse
只是一个
值,因此它永远不会失败,因此提供“默认”回退。

有效使用
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"
这说明我需要查看名为
“points”
的条目,并尝试将其解析为构建
路径所需的任何类型,在本例中,是
点的列表,
[Point]
。这种使用依赖于递归定义的
FromJSON
实例。我们需要解析一个数组,但幸运的是已经存在
FromJSON
实例

instance FromJSON a => FromJSON [a] where ...
instance FromJSON Obj where
  parseJSON v = okParse v <|> elseParse v
它被解释为JSON类型的JSON数组
a
可以解析为什么。在我们的例子中,
a~点
,所以我们只定义该实例

instance FromJSON Point where ...
然后递归地依赖于

instance FromJSON Double where ...
这是相当标准的


您可以使用的另一个重要技巧是使用
()
连接多个解析。我将把
响应
数据类型简化一点,它要么解析为特定的
对象
,要么失败,并生成一个简单的、动态键入的
,作为默认值。首先,我们将独立编写每个解析器

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)

在这种情况下,
aeson
将首先尝试使用
okParse
,如果失败,则使用
elseParse
。既然
elseParse
只是一个
值,它永远不会失败,因此提供了一个“默认”回退。

为什么您将长url和短url定义为[String]而不是纯字符串?感谢您捕捉到这一点。我复制了ExpandRequest构造函数,您可以在其中提供多个URL。但是,正如您所指出的,响应应该是
String
——很好!我更新了类型。也许这就是我的问题的一部分(现在将尝试)-还需要研究@abrahamson响应。为什么您将long_url和short_url定义为[String]而不是纯字符串?谢谢您的关注。我复制了ExpandRequest构造函数,您可以在其中提供多个URL。但是,正如您所指出的,响应应该是
String
——很好!我更新了类型。也许这是我的问题的一部分(我现在会试试)-还需要研究@abrahamson响应。正如你正确推断的,我迷失在两个想法的交叉点上-如何解析有时是JSON数组,有时是单个JSON对象。使用前一半的“技巧”,它现在可以工作了。但仅限于“扩展”案例。现在我需要使用您的第二个
()
技巧来处理“shorten”案例。我会带着我的最新进展回来(希望很快)。谢谢正如您正确推断的那样,我迷失在两个想法的交叉点上——如何解析有时是JSON数组,有时是单个JSON对象的东西。使用前一半的“技巧”,它现在可以工作了。但仅限于“扩展”案例。现在我需要使用您的第二个
()
技巧来处理“shorten”案例。我会带着我的最新进展回来(希望很快)。谢谢