2013-05-14 14 views
9

Próbuję napisać funkcję Aeson dla FromJSON.Parse Array w zagnieżdżonym JSONie z Aesonem

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

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

Moja próba:

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 

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

Jak widać, próbuję wybrać pierwszy z filmów w Array, ale nie miałbym nic przeciwko otrzymaniu listy filmów (w przypadku, gdy jest ich kilka w Tablicy).

Odpowiedz

10

Jeśli naprawdę chcą analizować pojedynczy Movie z tablicy JSON filmów, można zrobić coś takiego:

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

Ale bezpieczniejsza trasa będzie analizować [Movie] poprzez newtype owijki :

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

Zazwyczaj najłatwiej dopasować strukturę swoich ADT i instancji do struktury JSON.

Tutaj dodałem nowy typ MovieList, aby poradzić sobie z najbardziej zewnętrznym obiektem, aby instancja dla Movie zajmowała się tylko jednym filmem. Daje to również wiele filmów za darmo za pośrednictwem instancji FromJSON dla list.

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

dziękuję! Nie myślałem o wprowadzeniu innego typu. Czy mogę prosić o szybką kontynuację? Jeśli chciałbym rozszerzyć typ 'Film' o kilka dodatkowych pól, takich jak' filePath' lub 'myRating', czy poleciłbyś dodanie nowego typu' myMovie' lub wprowadzenie kilku pól 'Maybe' do' filmu' wpisz i wypełnij je po "dekodowaniu"? (Przypuszczam, że zapełnienie będzie oznaczało utworzenie nowej instancji ze wszystkimi polami, ponieważ ADT są niezmienne ..) – mb21

+1

@ mb21: Obie metody będą działać poprawnie. To zależy od reszty twojej aplikacji. Jeśli te pola są zawsze dodawane natychmiast po dekodowaniu, może być sens tworzenia nowego typu, aby pozostałe funkcje nie musiały zajmować się "Może", które powinno zawsze być "Po prostu". Z drugiej strony, jeśli te pola są opcjonalne, warto je zachować w "Być może". – hammar

+0

@hammar: ok, wielkie dzięki! – mb21

Powiązane problemy