2013-05-14 21 views
9

Ich versuche eine FromJSON Funktion für Aeson schreiben.Parse Array in geschachtelten JSON mit Aeson

Die 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" 
     } 
    } 
    ] 
} 

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

Mein Versuch:

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 

Dies gibt Couldn't match expected type 'Parser t0' with actual type 'Maybe a0' In the first argument of 'head'.

Wie Sie sehen können, versuche ich, den ersten der Filme in der Array auszuwählen, aber ich hätte nichts dagegen, eine Liste von Filmen zu bekommen (falls es mehrere im Array gibt).

Antwort

10

Wenn Sie wirklich ein einzelnes Movie aus einer JSON-Array von Filmen analysieren möchten, können Sie etwas tun:

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

Aber die sicherere Route eine [Movie] über newtype Wrapper zu analysieren wäre :

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

Es ist normalerweise am einfachsten, die Struktur Ihrer ADTs und Instanzen an die Struktur Ihres JSON anzupassen.

Hier habe ich einen neuen Typ MovieList hinzugefügt, um mit dem äußersten Objekt umzugehen, so dass die Instanz für Movie nur mit einem einzelnen Film zu tun hat. Dies gibt Ihnen auch mehrere Filme kostenlos über die FromJSON Instanz für Listen.

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

danke! Ich dachte nicht daran, einen anderen Typ einzuführen. Darf ich kurz nachfragen? Wenn ich den 'Movie'-Typ erweitern möchte, um weitere Felder wie' filePath' oder 'myRating' einzufügen, empfehlen wir, einen neuen Typ' myMovie' hinzuzufügen oder einige 'Maybe'-Felder in den' Film' einzufügen tippen und füllen sie nach dem 'decode'? (Ich denke, das Auffüllen würde bedeuten, eine neue Instanz mit allen Feldern zu erstellen, da ADT unveränderlich ist.) – mb21

+1

@ mb21: Beide Ansätze funktionieren gut. Es hängt vom Rest Ihrer Anwendung ab. Wenn diese Felder immer unmittelbar nach der Decodierung hinzugefügt werden, kann es sinnvoll sein, einen neuen Typ zu erstellen, so dass der Rest Ihrer Funktionen nicht mit einem 'Maybe' zu ​​tun hat, das immer' Just' sein sollte. Auf der anderen Seite, wenn diese Felder optional sind, macht es Sinn, sie in einem 'Maybe' zu ​​behalten. – hammar

+0

@hammar: Okay, vielen Dank! – mb21