2013-05-14 15 views
9

Sto provando a scrivere una funzione FromJSON per Aeson.Array Pare in JSON annidato con Aeson

Il JSON:

{ 
    "total": 1, 
    "movies": [ 
    { 
     "id": "771315522", 
     "title": "Harry Potter and the Philosophers Stone (Wizard's Collection)", 
     "posters": { 
     "thumbnail": "http://content7.flixster.com/movie/11/16/66/11166609_mob.jpg", 
     "profile": "http://content7.flixster.com/movie/11/16/66/11166609_pro.jpg", 
     "detailed": "http://content7.flixster.com/movie/11/16/66/11166609_det.jpg", 
     "original": "http://content7.flixster.com/movie/11/16/66/11166609_ori.jpg" 
     } 
    } 
    ] 
} 

L'ADT: data Movie = Movie {id::String, title::String}

Il mio tentativo:

instance FromJSON Movie where 
    parseJSON (Object o) = do 
     movies <- parseJSON =<< (o .: "movies") :: Parser Array 
     v <- head $ decode movies 
     return $ Movie <$> 
      (v .: "movies" >>= (.: "id")) <*> 
      (v .: "movies" >>= (.: "title")) 
    parseJSON _ = mzero 

Questo dà Couldn't match expected type 'Parser t0' with actual type 'Maybe a0' In the first argument of 'head'.

Come potete vedere, sto cercando di selezionare il primo dei film nel numero Array, ma non mi dispiacerebbe ottenere un elenco di film (nel caso ce ne siano diversi nell'array).

risposta

10

Se davvero si vuole analizzare un singolo Movie da un array JSON di film, si può fare qualcosa di simile:

instance FromJSON Movie where 
    parseJSON (Object o) = do 
     movieValue <- head <$> o .: "movies" 
     Movie <$> movieValue .: "id" <*> movieValue .: "title" 
    parseJSON _ = mzero 

Ma il percorso più sicuro sarebbe quello di analizzare un [Movie] via newtype involucro :

main = print $ movieList <$> decode "{\"total\":1,\"movies\":[ {\"id\":\"771315522\",\"title\":\"Harry Potter and the Philosophers Stone (Wizard's Collection)\",\"posters\":{\"thumbnail\":\"http://content7.flixster.com/movie/11/16/66/11166609_mob.jpg\",\"profile\":\"http://content7.flixster.com/movie/11/16/66/11166609_pro.jpg\",\"detailed\":\"http://content7.flixster.com/movie/11/16/66/11166609_det.jpg\",\"original\":\"http://content7.flixster.com/movie/11/16/66/11166609_ori.jpg\"}}]}" 

newtype MovieList = MovieList {movieList :: [Movie]} 

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

data Movie = Movie {id :: String, title :: String} 

instance FromJSON Movie where 
    parseJSON (Object o) = Movie <$> o .: "id" <*> o .: "title" 
    parseJSON _ = mzero 
8

In genere è più semplice abbinare la struttura dei vostri ADT e delle istanze alla struttura del vostro JSON.

Qui, ho aggiunto un nuovo tipo MovieList per gestire l'oggetto più esterno in modo che l'istanza per Movie abbia a che fare solo con un singolo film. Questo ti dà anche più film gratis tramite l'istanza FromJSON per gli elenchi.

data Movie = Movie { id :: String, title :: String } 

newtype MovieList = MovieList [Movie] 

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

instance FromJSON Movie where 
    parseJSON (Object o) = 
    Movie <$> (o .: "id") 
      <*> (o .: "title") 
    parseJSON _ = mzero 
+0

grazie! Non pensavo di introdurre un altro tipo. Posso chiedere un rapido follow-up? Se mi piacerebbe estendere il tipo 'Film' per includere altri campi come' filePath' o 'myRating', consiglieresti di aggiungere un nuovo tipo' myMovie', o introdurre alcuni campi 'Maybe' nel' Film' digita e riempi dopo la 'decodifica'? (Immagino che riempire significherebbe davvero creare una nuova istanza con tutti i campi, dal momento che ADT è immutabile ..) – mb21

+1

@ mb21: Entrambi gli approcci funzioneranno correttamente. Dipende dal resto della tua applicazione. Se quei campi vengono sempre aggiunti immediatamente dopo la decodifica, potrebbe essere sensato fare un nuovo tipo in modo che il resto delle funzioni non abbia a che fare con un 'Maybe' che dovrebbe sempre essere' Giusto'. D'altra parte, se questi campi sono opzionali, ha senso tenerli in un 'Maybe'. – hammar

+0

@hammar: ok, grazie mille! – mb21