2014-03-19 36 views
2

我一直在砸我的腦袋,試圖用艾森解析Bitly的反應。 可能有人給我一個提示,什麼類型的Haskell應該定義 以及如何使用埃宋來然後解析以下爲那些類型?:你如何解析Aeson的BitSys響應JSON?

// 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

你爲什麼將long_url和short_url定義爲[String]而不是純String? – sinelaw

+0

感謝您的支持。我已經複製了ExpandRequest構造函數,您可以在其中提供多個URL。但是,正如你所指出的那樣,這些迴應應該只是「字符串」 - 很好!我更新了類型。也許這是我的問題的一部分(現在會嘗試) - 仍然需要研究@abrahamson的反應。 – haroldcarr

回答

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來快速約束傳遞給parseJSONValue被標記爲Object-這與使用模式匹配沒有明顯不同,但它會生成自動的統一錯誤消息,因此值得考慮。

其次,更重要的是,請注意,我只定義了描述每個對象的高級輪廓的FromJSON實例。尤其是要檢查的FromJSON Path

Path <$> o .: "points" 

身體所有這說的是,我需要尋找到一個名爲"points"進入和嘗試解析它作爲任何類型有必要建立一個Path -in這種情況下,列表Point s,[Point]。這種使用取決於遞歸定義的FromJSON實例。我們需要分析一個數組,幸好已經存在的FromJSON實例

instance FromJSON a => FromJSON [a] where ... 

這被解釋爲無論什麼類型的JSON可以a作爲解析JSON陣列。在我們的例子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。由於elseParse只是一個pure值,因此它永遠不會失敗,因此提供了「默認」後退。

+0

正如您正確推斷的那樣,我在兩個想法的交集處丟失了 - 如何解析有時是JSON數組,有時是單個JSON對象的東西。使用你的「技巧」的前半部分,現在起作用。但僅限於「擴大」的情況。現在我需要用你的第二個'(<|>)'技巧來處理「縮短」的情況。隨着我最新的進展,我會回來(很快我希望)。謝謝! – haroldcarr