2014-03-03 124 views
7

我試圖從RESTful API中解析JSON。返回的JSON高度嵌套,可能/可能不包含某些字段。下面是一些例子返回的數據:用Aeson解析Haskell中的嵌套JSON

{ 
    resultSet : { 
     location : [{ 
       desc : "Tuality Hospital/SE 8th Ave MAX Station", 
       locid : 9843, 
       dir : "Eastbound", 
       lng : -122.978016886765, 
       lat : 45.5212880911494 
      } 
     ], 
     arrival : [{ 
       detour : false, 
       status : "estimated", 
       locid : 9843, 
       block : 9024, 
       scheduled : "2014-03-02T16:48:15.000-0800", 
       shortSign : "Blue to Gresham", 
       dir : 0, 
       estimated : "2014-03-02T16:48:15.000-0800", 
       route : 100, 
       departed : false, 
       blockPosition : { 
        at : "2014-03-02T16:16:43.579-0800", 
        feet : 3821, 
        lng : -122.9909514, 
        trip : [{ 
          progress : 171494, 
          desc : "Hatfield Government Center", 
          pattern : 140, 
          dir : 1, 
          route : 100, 
          tripNum : "4365647", 
          destDist : 171739 
         }, { 
          progress : 0, 
          desc : "Cleveland Ave", 
          pattern : 10, 
          dir : 0, 
          route : 100, 
          tripNum : "4365248", 
          destDist : 3577 
         } 
        ], 
        lat : 45.5215368, 
        heading : 328 
       }, 
       fullSign : "MAX Blue Line to Gresham", 
       piece : "1" 
      }, { 
       detour : false, 
       status : "estimated", 
       locid : 9843, 
       block : 9003, 
       scheduled : "2014-03-02T17:05:45.000-0800", 
       shortSign : "Blue to Gresham", 
       dir : 0, 
       estimated : "2014-03-02T17:05:45.000-0800", 
       route : 100, 
       departed : false, 
       blockPosition : { 
        at : "2014-03-02T16:34:33.787-0800", 
        feet : 3794, 
        lng : -122.9909918, 
        trip : [{ 
          progress : 171521, 
          desc : "Hatfield Government Center", 
          pattern : 140, 
          dir : 1, 
          route : 100, 
          tripNum : "4365648", 
          destDist : 171739 
         }, { 
          progress : 0, 
          desc : "Cleveland Ave", 
          pattern : 10, 
          dir : 0, 
          route : 100, 
          tripNum : "4365250", 
          destDist : 3577 
         } 
        ], 
        lat : 45.5216054, 
        heading : 345 
       }, 
       fullSign : "MAX Blue Line to Gresham", 
       piece : "1" 
      } 
     ], 
     queryTime : "2014-03-02T16:35:21.039-0800" 
    } 
} 

正如你所看到的,JSON模式與含有locationarrival,並queryTime一個resultSet開始。 location依次包含位置列表,arrival包含到達列表,queryTime只是UTC時間。然後,一個arrival可以包含一個blockPosition,它可以包含一個trip等等很多的嵌套。大量的可選字段。

爲了保持這一切,我創建了一組新的數據類型。數據類型嵌套相似。對於每種數據類型,我都有一個FromJSON實例(來自Aeson庫)。

-- Data Type Definitions and FromJSON Instance Definitions --------------------- 


data ResultSet 
    = ResultSet  { locations :: LocationList 
         ,arrivals  :: ArrivalList 
         ,queryTime :: String 
        } deriving Show 

instance FromJSON ResultSet where 
    parseJSON (Object o) = 
    ResultSet <$> ((o .: "resultSet") >>= (.: "location")) 
       <*> ((o .: "resultSet") >>= (.: "arrival")) 
       <*> ((o .: "resultSet") >>= (.: "queryTime")) 
    parseJSON _ = mzero 

data TripList  = TripList  {triplist  :: [Trip]}  deriving Show 

instance FromJSON TripList where 
    parseJSON (Object o) = 
    TripList <$> (o .: "trip") 
    parseJSON _ = mzero 

data LocationList = LocationList {locationList :: [Location]} deriving Show 

instance FromJSON LocationList where 
    parseJSON (Object o) = 
    LocationList <$> (o .: "location") 
    parseJSON _ = mzero 

data Location 
    = Location  { loc_desc   :: String 
         ,loc_locid   :: Int 
         ,loc_dir   :: String 
         ,loc_lng   :: Double 
         ,loc_lat   :: Double 
        } deriving Show 

instance FromJSON Location where 
    parseJSON (Object o) = 
    Location <$> (o .: "desc") 
       <*> (o .: "locid") 
       <*> (o .: "dir") 
       <*> (o .: "lng") 
       <*> (o .: "lat") 
    parseJSON _ = mzero 

data ArrivalList  = ArrivalList {arrivalList :: [Arrival]} deriving Show 

instance FromJSON ArrivalList where 
    parseJSON (Object o) = 
    ArrivalList <$> (o .: "arrival") 
    parseJSON _ = mzero 

data Arrival 
    = Arrival  { arr_detour   :: Bool 
         ,arr_status   :: String 
         ,arr_locid   :: Int 
         ,arr_block   :: Int 
         ,arr_scheduled  :: String 
         ,arr_shortSign  :: String 
         ,arr_dir   :: Int 
         ,estimated  :: Maybe String 
         ,route   :: Int 
         ,departed  :: Bool 
         ,blockPosition :: Maybe BlockPosition 
         ,fullSign  :: String 
         ,piece   :: String 
        } deriving Show 

instance FromJSON Arrival where 
    parseJSON (Object o) = 
    Arrival <$> (o .: "detour") 
      <*> (o .: "status") 
      <*> (o .: "locid") 
      <*> (o .: "block") 
      <*> (o .: "scheduled") 
      <*> (o .: "shortSign") 
      <*> (o .: "dir") 
      <*> (o .:? "estimated") 
      <*> (o .: "route") 
      <*> (o .: "departed") 
      <*> (o .:? "blockPosition") 
      <*> (o .: "fullSign") 
      <*> (o .: "piece") 
    parseJSON _ = mzero 

data BlockPosition 
    = BlockPosition { bp_at     :: String 
         ,bp_feet    :: Int 
         ,bp_lng    :: Double 
         ,bp_trip    :: Trip 
         ,bp_lat    :: Double 
         ,bp_heading   :: Int 
         } deriving Show 

instance FromJSON BlockPosition where 
    parseJSON (Object o) = 
    BlockPosition <$> (o .: "at") 
       <*> (o .: "feet") 
       <*> (o .: "lng") 
       <*> (o .: "trip") 
       <*> (o .: "lat") 
       <*> (o .: "heading") 
    parseJSON _ = mzero 

data Trip   
    = Trip   { trip_progress  :: Int 
         ,trip_desc   :: String 
         ,trip_pattern  :: Int 
         ,trip_dir   :: Int 
         ,trip_route   :: Int 
         ,trip_tripNum  :: Int 
         ,trip_destDist  :: Int 
        } deriving Show 

instance FromJSON Trip where 
    parseJSON (Object o) = 
    Trip <$> (o .: "progress") 
     <*> (o .: "desc") 
     <*> (o .: "pattern") 
     <*> (o .: "dir") 
     <*> (o .: "route") 
     <*> (o .: "tripNum") 
     <*> (o .: "destDist") 
    parseJSON _ = mzero 

現在,問題:檢索數據很容易。我可以

json <- getJSON stopID 
putStrLn (show (decode json :: (Maybe Value))) 

顯示原始JSON但是,當我試圖讓ResultSet中的數據,它失敗Nothing

putStrLn (show (decode json :: Maybe ResultSet)) 

但是,如果我刪除嵌套數據,而只是試圖讓queryString場(由FromJSON的數據類型,例如刪除字段,它成功並返回查詢字符串場。

data ResultSet 
    = ResultSet  { 
         queryTime :: String 
        } deriving Show 

instance FromJSON ResultSet where 
    parseJSON (Object o) 
    = ResultSet <$> ((o .: "resultSet") >>= (.: "queryTime")) 
    parseJSON _ = mzero 

我在做什麼錯?這是在Haskell中解析JSON最簡單的方法嗎?我是這個(學生)的總noob,所以請保持溫柔

+0

這個問題可能是,要麼你的解析器有一些錯誤或輸入並不像你想象的那樣。您應該嘗試使用anyDecode函數而不是解碼來獲取錯誤消息並將其粘貼到此處。 – Reite

+0

樣式點:在ResultSet的parseJSON中,您應該提取ResultSet一次,然後詢問它。您正在爲相同的值進行三次單獨查找。 –

+0

@Reite:使用任一代碼確實揭示了一個mzero錯誤。這告訴我,我的FromJSON實例是不正確的。它看起來像我的模式匹配正在落入'parseJSON _ = mzero'模式。但爲什麼?我已經檢查了傳入的JSON。位置字段在那裏。 –

回答

8

我解決了我的問題。爲返回的JSON對象列表創建數據類型PLE,用於位置數據,這是返回的位置的列表:

resultSet : { 
    location : [{ 
     desc : "Tuality Hospital/SE 8th Ave MAX Station", 
     locid : 9843, 
     dir : "Eastbound", 
     lng : -122.978016886765, 
     lat : 45.5212880911494 
    } 
    ], 

我被設置含有[Arrival]列表的Arrivals數據類型:

data ArrivalList  = ArrivalList {arrivalList :: [Arrival]} deriving Show 

然後,當我試圖解析JSON,我試圖將一個ArrivalList填充到我的ResultSet中,後者用於解析其中的JSON數據。但是因爲ArrivalList不是JSON對象,所以它失敗了。

解決方法是不使用自定義數據類型的列表。相反,將一個列表分配給一個JSON!Array對象,該對象稍後可以被解析爲它自己的對象和子對象。

data ResultSet 
     = ResultSet  { 
         locations :: !Array 
         ,arrivals  :: !Array 
         ,queryTime :: String 
         } deriving Show 

全部放在一起:

data ResultSet 
    = ResultSet  { 
         locations :: !Array 
         ,arrivals  :: !Array 
         ,queryTime :: String 
        } deriving Show 

instance FromJSON ResultSet where 
    parseJSON (Object o) = ResultSet <$> 
         ((o .: "resultSet") >>= (.: "location")) 
        <*> ((o .: "resultSet") >>= (.: "arrival")) 
        <*> ((o .: "resultSet") >>= (.: "queryTime")) 
    parseJSON _ = mzero 

data Location 
    = Location  { loc_desc   :: String 
         ,loc_locid   :: Int 
         ,loc_dir   :: String 
         ,loc_lng   :: Double 
         ,loc_lat   :: Double 
        } deriving Show 

instance FromJSON Location where 
    parseJSON (Object o) = 
    Location <$> (o .: "desc") 
       <*> (o .: "locid") 
       <*> (o .: "dir") 
       <*> (o .: "lng") 
       <*> (o .: "lat") 
    parseJSON _ = mzero